S2-B3/S3-05: PortfolioRiskManager, connection loss recovery, long-only lock
Some checks failed
Build and Test / build (push) Has been cancelled

This commit is contained in:
2026-03-19 16:17:02 -04:00
parent 3282254572
commit 2f623dc2f8
4 changed files with 452 additions and 0 deletions

View File

@@ -53,6 +53,7 @@ namespace NinjaTrader.NinjaScript.Strategies
private int _ordersSubmittedToday;
private DateTime _lastBarTime;
private bool _killSwitchTriggered;
private bool _connectionLost;
private ExecutionCircuitBreaker _circuitBreaker;
private System.IO.StreamWriter _fileLog;
private readonly object _fileLock = new object();
@@ -194,6 +195,7 @@ namespace NinjaTrader.NinjaScript.Strategies
EnableLongTrades = true;
EnableShortTrades = true;
_killSwitchTriggered = false;
_connectionLost = false;
}
else if (State == State.DataLoaded)
{
@@ -220,6 +222,7 @@ namespace NinjaTrader.NinjaScript.Strategies
}
else if (State == State.Terminated)
{
PortfolioRiskManager.Instance.UnregisterStrategy(Name);
WriteSessionFooter();
}
}
@@ -261,6 +264,14 @@ namespace NinjaTrader.NinjaScript.Strategies
return;
}
// Connection loss guard — do not submit new orders if broker is disconnected
if (_connectionLost)
{
if (EnableVerboseLogging)
Print(string.Format("[NT8-SDK] Bar skipped — connection lost: {0}", Time[0]));
return;
}
// Log first processable bar and every 100th bar.
if (CurrentBar == BarsRequiredToTrade || CurrentBar % 100 == 0)
{
@@ -357,9 +368,54 @@ namespace NinjaTrader.NinjaScript.Strategies
execution.Price,
execution.OrderId));
var fill = new NT8.Core.Common.Models.OrderFill(
orderId,
execution.Order != null ? execution.Order.Instrument.MasterInstrument.Name : string.Empty,
execution.Quantity,
execution.Price,
time,
0.0,
executionId);
PortfolioRiskManager.Instance.ReportFill(Name, fill);
_executionAdapter.ProcessExecution(orderId, executionId, price, quantity, time);
}
/// <summary>
/// Handles broker connection status changes. Halts new orders on disconnect,
/// logs reconnect, and resets the connection flag when restored.
/// </summary>
protected override void OnConnectionStatusUpdate(
Connection connection,
ConnectionStatus status,
DateTime time)
{
if (connection == null) return;
if (status == ConnectionStatus.Connected)
{
if (_connectionLost)
{
_connectionLost = false;
Print(string.Format("[NT8-SDK] Connection RESTORED at {0} — trading resumed.",
time.ToString("HH:mm:ss")));
FileLog(string.Format("CONNECTION RESTORED at {0}", time.ToString("HH:mm:ss")));
}
}
else if (status == ConnectionStatus.Disconnected ||
status == ConnectionStatus.ConnectionLost)
{
if (!_connectionLost)
{
_connectionLost = true;
Print(string.Format("[NT8-SDK] Connection LOST at {0} — halting new orders. Status={1}",
time.ToString("HH:mm:ss"),
status));
FileLog(string.Format("CONNECTION LOST at {0} Status={1}", time.ToString("HH:mm:ss"), status));
}
}
}
private void InitFileLog()
{
if (!EnableFileLogging)
@@ -418,6 +474,7 @@ namespace NinjaTrader.NinjaScript.Strategies
RiskPerTrade));
FileLog(string.Format("Sizing : MinContracts={0} MaxContracts={1}", MinContracts, MaxContracts));
FileLog(string.Format("VerboseLog : {0} FileLog: {1}", EnableVerboseLogging, EnableFileLogging));
FileLog(string.Format("ConnectionLost : {0}", _connectionLost));
FileLog("---");
}
@@ -481,6 +538,8 @@ namespace NinjaTrader.NinjaScript.Strategies
_sdkStrategy.Initialize(_strategyConfig, null, _logger);
ConfigureStrategyParameters();
PortfolioRiskManager.Instance.RegisterStrategy(Name, _riskConfig);
Print(string.Format("[NT8-SDK] Registered with PortfolioRiskManager: {0}", PortfolioRiskManager.Instance.GetStatusSnapshot()));
_ordersSubmittedToday = 0;
_lastBarTime = DateTime.MinValue;
@@ -590,6 +649,16 @@ namespace NinjaTrader.NinjaScript.Strategies
private void ProcessStrategyIntent(StrategyIntent intent, StrategyContext context)
{
// Portfolio-level risk check — runs before per-strategy risk validation
var portfolioDecision = PortfolioRiskManager.Instance.ValidatePortfolioRisk(Name, intent);
if (!portfolioDecision.Allow)
{
Print(string.Format("[SDK] Portfolio blocked: {0}", portfolioDecision.RejectReason));
if (_logger != null)
_logger.LogWarning("Portfolio risk blocked order: {0}", portfolioDecision.RejectReason);
return;
}
// Direction filter — checked before risk to avoid unnecessary processing
if (intent.Side == SdkOrderSide.Buy && !EnableLongTrades)
{

View File

@@ -70,6 +70,7 @@ namespace NinjaTrader.NinjaScript.Strategies
Calculate = Calculate.OnBarClose;
BarsRequiredToTrade = 50;
EnableLongTrades = true;
// Long-only: short trades permanently disabled pending backtest confirmation
EnableShortTrades = false;
}
else if (State == State.Configure)