using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using System.Windows.Media;
using NinjaTrader.Cbi;
using NinjaTrader.Gui.Tools;
using NinjaTrader.NinjaScript;
using NinjaTrader.Gui; // For ControlCenter
using NinjaTrader.Core; // <-- Make sure this reference is added
namespace NinjaTrader.NinjaScript.AddOns
{
public class RiskManagementAddOn : AddOnBase
{
region UI & Risk Setting Fields
// Risk Manager window and its UI elements.
private Window riskWindow;
private PasswordBox passwordBox;
private Label statusLabel;
// Global (default) loss limit value.
private double dailyLossLimit = 500;
// Global “apply to all” controls.
private CheckBox applyToAllCheckBox;
private TextBox globalLossLimitBox;
// For per-account daily loss limits (keyed by account name).
private Dictionary<string, TextBox> accountLossLimitTextBoxes = new Dictionary<string, TextBox>();
// Flag for whether settings are unlocked.
private bool isUnlocked = false;
// MD5 hash for the correct password ("password" in this example).
private string hashedPassword = "5f4dcc3b5aa765d61d8327deb882cf99";
// Timer to periodically check account risk.
private DispatcherTimer riskTimer;
// Dictionary to track locked accounts (by name) until the next trading day.
private Dictionary<string, DateTime> lockedAccounts = new Dictionary<string, DateTime>();
#endregion
region OnStateChange
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "Risk Management Add-On";
}
else if (State == State.Configure)
{
// Attempt to add a menu item to the Control Center (unsupported technique).
MenuItem menuItem = new MenuItem() { Header = "Risk Manager" };
menuItem.Click += (s, e) => ShowRiskManager();
AddMenuItemToControlCenter(menuItem);
}
else if (State == State.Active)
{
// Start the risk-monitoring timer (every 3 seconds).
riskTimer = new DispatcherTimer();
riskTimer.Interval = TimeSpan.FromSeconds(3);
riskTimer.Tick += RiskTimer_Tick;
riskTimer.Start();
}
else if (State == State.Terminated)
{
if (riskTimer != null)
riskTimer.Stop();
}
}
#endregion
region Risk Monitoring
/// <summary>
/// Timer tick event: For each connected account, check its daily realized PnL.
/// If the realized loss exceeds its limit, flatten positions immediately and lock the account.
/// Also, while locked, any new positions are flattened immediately.
/// </summary>
private void RiskTimer_Tick(object sender, EventArgs e)
{
DateTime now = DateTime.Now;
foreach (var account in Account.All)
{
// If the account is locked, flatten any new positions and skip further checks.
if (lockedAccounts.ContainsKey(account.Name))
{
if (now < lockedAccounts[account.Name])
{
if (account.Positions.Count > 0)
{
FlattenAccount(account);
}
continue;
}
else
{
// Lock expired.
lockedAccounts.Remove(account.Name);
}
}
// Determine which loss limit to use.
double accountLimit = dailyLossLimit;
if (applyToAllCheckBox != null && applyToAllCheckBox.IsChecked == true)
{
if (globalLossLimitBox != null && double.TryParse(globalLossLimitBox.Text, out double globalLimit))
{
accountLimit = globalLimit;
}
}
else
{
if (accountLossLimitTextBoxes.TryGetValue(account.Nam e, out TextBox tb) &&
double.TryParse(tb.Text, out double perAccountLimit))
{
accountLimit = perAccountLimit;
}
}
// Retrieve the account's gross realized profit/loss using the extension method.
double realizedPnL = Convert.ToDouble(account.GetAccountValue(Cbi.Accou ntItem.GrossRealizedProfitLoss));
// If the loss exceeds the limit (P/L is less than -limit), flatten positions and lock the account.
if (realizedPnL < -accountLimit)
{
if (account.Positions.Count > 0)
{
FlattenAccount(account);
}
lockedAccounts[account.Name] = DateTime.Today.AddDays(1);
}
}
}
/// <summary>
/// Flattens an account by closing all open positions.
/// </summary>
private void FlattenAccount(Account account)
{
try
{
foreach (var position in account.Positions)
{
account.Flatten(new List<Instrument> { position.Instrument });
}
}
catch (Exception ex)
{
MessageBox.Show($"Error flattening account {account.Name}: {ex.Message}");
}
}
#endregion
region UI Creation
/// <summary>
/// Attempts to locate the Control Center window and add a menu item.
/// (This technique is unsupported and may not work in all versions.)
/// </summary>
private void AddMenuItemToControlCenter(MenuItem menuItem)
{
try
{
var cc = Application.Current.Windows.OfType<ControlCenter>( ).FirstOrDefault();
if (cc != null)
{
var mainMenu = cc.FindName("MainMenu") as Menu;
if (mainMenu != null)
{
mainMenu.Items.Add(menuItem);
}
else
{
MessageBox.Show("Main menu not found in Control Center. Risk Manager menu item not added.");
}
}
else
{
MessageBox.Show("Control Center window not found. Risk Manager menu item not added.");
}
}
catch (Exception ex)
{
MessageBox.Show("Error adding menu item: " + ex.Message);
}
}
/// <summary>
/// Opens (or activates) the Risk Manager window.
/// </summary>
private void ShowRiskManager()
{
if (riskWindow != null)
{
riskWindow.Activate();
return;
}
riskWindow = new Window
{
Title = "Risk Manager",
Width = 800,
Height = 600,
Content = CreateRiskManagerUI(),
Topmost = true
};
riskWindow.Closed += (s, e) => riskWindow = null;
riskWindow.Show();
}
/// <summary>
/// Creates the UI for the Risk Manager window.
/// </summary>
private UIElement CreateRiskManagerUI()
{
// Create a grid for the UI.
Grid grid = new Grid { Margin = new Thickness(10) };
// Define columns: Account Name, Status, Per-Account Loss Limit, Flatten Button.
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(2, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
int row = 0;
// Header row.
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
AddToGrid(grid, new TextBlock { Text = "Account", FontWeight = FontWeights.Bold }, 0, row);
AddToGrid(grid, new TextBlock { Text = "Status", FontWeight = FontWeights.Bold }, 1, row);
AddToGrid(grid, new TextBlock { Text = "Daily Loss Limit ($)", FontWeight = FontWeights.Bold }, 2, row);
AddToGrid(grid, new TextBlock { Text = "Flatten", FontWeight = FontWeights.Bold }, 3, row);
row++;
// One row per account.
foreach (var account in Account.All)
{
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
// Account Name.
AddToGrid(grid, new TextBlock { Text = account.Name }, 0, row);
// Status toggle button (placeholder).
ToggleButton toggle = new ToggleButton { Content = "OFF", IsChecked = false };
toggle.Checked += (s, e) => EnableAccount(account);
toggle.Unchecked += (s, e) => DisableAccount(account);
AddToGrid(grid, toggle, 1, row);
// Per-account loss limit textbox.
TextBox perAccountLossLimitBox = new TextBox { Text = dailyLossLimit.ToString(), IsEnabled = false };
AddToGrid(grid, perAccountLossLimitBox, 2, row);
accountLossLimitTextBoxes[account.Name] = perAccountLossLimitBox;
// Manual Flatten button.
Button flattenButton = new Button { Content = "FLATTEN", Background = Brushes.Red };
flattenButton.Click += (s, e) => FlattenAccount(account);
AddToGrid(grid, flattenButton, 3, row);
row++;
}
// Global settings section.
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
AddToGrid(grid, new TextBlock { Text = "Apply same daily loss limit for all accounts:" }, 0, row);
applyToAllCheckBox = new CheckBox { IsChecked = false, IsEnabled = false };
applyToAllCheckBox.Checked += (s, e) =>
{
foreach (var tb in accountLossLimitTextBoxes.Values)
tb.IsEnabled = false;
if (isUnlocked && globalLossLimitBox != null)
globalLossLimitBox.IsEnabled = true;
};
applyToAllCheckBox.Unchecked += (s, e) =>
{
foreach (var tb in accountLossLimitTextBoxes.Values)
tb.IsEnabled = isUnlocked;
if (globalLossLimitBox != null)
globalLossLimitBox.IsEnabled = false;
};
AddToGrid(grid, applyToAllCheckBox, 1, row);
row++;
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
AddToGrid(grid, new TextBlock { Text = "Global Daily Loss Limit ($):" }, 0, row);
globalLossLimitBox = new TextBox { Text = dailyLossLimit.ToString(), IsEnabled = false };
AddToGrid(grid, globalLossLimitBox, 1, row);
row++;
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
AddToGrid(grid, new TextBlock { Text = "Password to Unlock Settings:" }, 0, row);
passwordBox = new PasswordBox();
passwordBox.PasswordChanged += (s, e) => UnlockSettings();
AddToGrid(grid, passwordBox, 1, row);
row++;
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
statusLabel = new Label { Content = "Settings Locked", Foreground = Brushes.Red };
AddToGrid(grid, statusLabel, 0, row);
Grid.SetColumnSpan(statusLabel, 4);
