38 KiB
Phase B: NT8 Strategy Base Class - Detailed Specification
For: Kilocode AI Agent (Autonomous Implementation)
Phase: Phase B - NT8 Strategy Integration
Components: NT8StrategyBase + SimpleORBNT8 + MinimalTestStrategy
Estimated Time: 4-5 hours
Mode: Code Mode
Dependencies: Phase A must be complete
🎯 Objective
Create the NinjaTrader 8 strategy base class that inherits from NT8's Strategy class and bridges the NT8 platform to our SDK. This is the critical integration layer that makes our SDK run inside NinjaTrader 8.
Key Challenge: These files CANNOT be compiled into a DLL. They must be deployed as .cs files directly to NinjaTrader's Strategies folder so NT8 can compile them with access to NinjaTrader.NinjaScript namespaces.
📋 Architecture Overview
Layer Relationships
┌─────────────────────────────────────────────────────────┐
│ NinjaTrader 8 Platform │
│ - Strategy base class │
│ - Order, Execution, Instrument objects │
│ - OnBarUpdate, OnOrderUpdate callbacks │
└───────────────────┬─────────────────────────────────────┘
│ Inherits
↓
┌─────────────────────────────────────────────────────────┐
│ NT8StrategyBase.cs (THIS PHASE) │
│ - Inherits: NinjaTrader.NinjaScript.Strategies.Strategy│
│ - Implements: NT8 lifecycle methods │
│ - Creates: SDK components (Strategy, Risk, Sizing) │
│ - Bridges: NT8 events → SDK → NT8 actions │
└───────────────────┬─────────────────────────────────────┘
│ Uses
↓
┌─────────────────────────────────────────────────────────┐
│ NT8.Core.dll + NT8.Adapters.dll │
│ - Already built in Phase A │
│ - Strategy, Risk, Sizing, Analytics │
│ - NT8ExecutionAdapter, NT8DataConverter │
└─────────────────────────────────────────────────────────┘
Why .cs Files, Not DLLs?
NT8 Requirement:
- NinjaTrader strategies MUST inherit from
NinjaTrader.NinjaScript.Strategies.Strategy - This class is in
NinjaTrader.Custom.dllwhich is only available at NT8 compile time - We cannot reference NT8 DLLs in our SDK project (circular dependency)
- Therefore: Deploy as .cs source files to
Documents\NinjaTrader 8\bin\Custom\Strategies\
Our Approach:
- NT8StrategyBase.cs → Deployed to NT8 as source
- SimpleORBNT8.cs → Deployed to NT8 as source
- MinimalTestStrategy.cs → Deployed to NT8 as source
- These files reference NT8.Core.dll (already in Custom\bin)
📦 Component 1: NT8StrategyBase.cs
Overview
Abstract base class that all SDK-integrated strategies inherit from. Handles the NT8 lifecycle and bridges NT8 events to SDK components.
Location
Create: src/NT8.Adapters/Strategies/NT8StrategyBase.cs
Deploy To: Documents\NinjaTrader 8\bin\Custom\Strategies\NT8StrategyBase.cs
File Structure
// Required NT8 namespaces
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Strategies;
// SDK namespaces
using System;
using System.Collections.Generic;
using NT8.Core.Common.Interfaces;
using NT8.Core.Common.Models;
using NT8.Core.Risk;
using NT8.Core.Sizing;
using NT8.Core.Logging;
using NT8.Core.OMS;
using NT8.Adapters.NinjaTrader;
namespace NinjaTrader.NinjaScript.Strategies
{
/// <summary>
/// Base class for strategies that integrate NT8 SDK.
/// Handles NT8 lifecycle and bridges platform events to SDK components.
///
/// DEPLOYMENT NOTE: This file must be deployed as .cs source to:
/// Documents\NinjaTrader 8\bin\Custom\Strategies\
///
/// Do NOT compile into NT8.Adapters.dll - NT8 must compile it.
/// </summary>
public abstract class NT8StrategyBase : Strategy
{
// Implementation below...
}
}
Fields & Properties
#region Fields
private readonly object _lock = new object();
// SDK Components
protected IStrategy _sdkStrategy;
protected IRiskManager _riskManager;
protected IPositionSizer _positionSizer;
protected IOrderManager _orderManager;
protected NT8ExecutionAdapter _executionAdapter;
protected ILogger _logger;
// Configuration
protected StrategyConfig _strategyConfig;
protected RiskConfig _riskConfig;
protected SizingConfig _sizingConfig;
// State tracking
private bool _sdkInitialized;
private AccountInfo _lastAccountInfo;
private Position _lastPosition;
private MarketSession _currentSession;
private int _ordersSubmittedToday;
private DateTime _lastBarTime;
#endregion
#region User-Configurable Properties
/// <summary>
/// Enable SDK integration. Set to false to disable SDK and run NT8-only logic.
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Enable SDK", GroupName = "SDK", Order = 1)]
public bool EnableSDK { get; set; }
/// <summary>
/// Maximum daily loss limit in dollars.
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Daily Loss Limit", GroupName = "Risk", Order = 1)]
public double DailyLossLimit { get; set; }
/// <summary>
/// Maximum risk per trade in dollars.
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Max Trade Risk", GroupName = "Risk", Order = 2)]
public double MaxTradeRisk { get; set; }
/// <summary>
/// Maximum open positions allowed.
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Max Positions", GroupName = "Risk", Order = 3)]
public int MaxOpenPositions { get; set; }
/// <summary>
/// Risk per trade in dollars for position sizing.
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Risk Per Trade", GroupName = "Sizing", Order = 1)]
public double RiskPerTrade { get; set; }
/// <summary>
/// Minimum contracts (position size floor).
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Min Contracts", GroupName = "Sizing", Order = 2)]
public int MinContracts { get; set; }
/// <summary>
/// Maximum contracts (position size ceiling).
/// </summary>
[NinjaScriptProperty]
[Display(Name = "Max Contracts", GroupName = "Sizing", Order = 3)]
public int MaxContracts { get; set; }
#endregion
Abstract Methods
#region Abstract Methods - Derived Strategies Must Implement
/// <summary>
/// Create the SDK strategy instance.
/// Called during State.DataLoaded.
/// </summary>
/// <returns>SDK strategy implementation</returns>
protected abstract IStrategy CreateSdkStrategy();
/// <summary>
/// Configure strategy-specific parameters.
/// Called after SDK components are initialized.
/// Override to set custom risk/sizing parameters.
/// </summary>
protected abstract void ConfigureStrategyParameters();
#endregion
NT8 Lifecycle: OnStateChange
#region NT8 Lifecycle
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
// Set defaults for all strategies
Description = "SDK-integrated strategy base";
Name = "NT8 SDK Strategy Base";
Calculate = Calculate.OnBarClose;
EntriesPerDirection = 1;
EntryHandling = EntryHandling.AllEntries;
IsExitOnSessionCloseStrategy = true;
ExitOnSessionCloseSeconds = 30;
IsFillLimitOnTouch = false;
MaximumBarsLookBack = MaximumBarsLookBack.TwoHundredFiftySix;
OrderFillResolution = OrderFillResolution.Standard;
Slippage = 0;
StartBehavior = StartBehavior.WaitUntilFlat;
TimeInForce = TimeInForce.Gtc;
TraceOrders = false;
RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
StopTargetHandling = StopTargetHandling.PerEntryExecution;
BarsRequiredToTrade = 20;
// SDK defaults
EnableSDK = true;
DailyLossLimit = 1000.0;
MaxTradeRisk = 200.0;
MaxOpenPositions = 3;
RiskPerTrade = 100.0;
MinContracts = 1;
MaxContracts = 10;
}
else if (State == State.Configure)
{
// Add any additional data series here if needed
// Derived strategies can override and add their own
}
else if (State == State.DataLoaded)
{
// Initialize SDK components
if (EnableSDK)
{
try
{
InitializeSdkComponents();
_sdkInitialized = true;
Print(string.Format("[SDK] {0} initialized successfully", Name));
}
catch (Exception ex)
{
Print(string.Format("[SDK ERROR] Initialization failed: {0}", ex.Message));
Log(string.Format("[SDK ERROR] {0}", ex.ToString()), LogLevel.Error);
_sdkInitialized = false;
}
}
else
{
Print(string.Format("[SDK] SDK disabled for {0}", Name));
_sdkInitialized = false;
}
}
else if (State == State.Terminated)
{
// Cleanup
if (_sdkInitialized)
{
Print(string.Format("[SDK] {0} terminated", Name));
}
}
}
#endregion
SDK Initialization
#region SDK Initialization
/// <summary>
/// Initialize all SDK components.
/// Called during State.DataLoaded.
/// </summary>
private void InitializeSdkComponents()
{
// 1. Create logger
_logger = new BasicLogger(Name);
_logger.LogInformation("Initializing SDK components...");
// 2. Create risk configuration
_riskConfig = new RiskConfig(
dailyLossLimit: DailyLossLimit,
maxTradeRisk: MaxTradeRisk,
maxOpenPositions: MaxOpenPositions,
emergencyFlattenEnabled: true
);
// 3. Create sizing configuration
_sizingConfig = new SizingConfig(
method: SizingMethod.FixedDollarRisk,
minContracts: MinContracts,
maxContracts: MaxContracts,
riskPerTrade: RiskPerTrade,
methodParameters: new Dictionary<string, object>()
);
// 4. Create strategy configuration
_strategyConfig = new StrategyConfig(
name: Name,
symbol: Instrument.MasterInstrument.Name,
parameters: new Dictionary<string, object>(),
riskSettings: _riskConfig,
sizingSettings: _sizingConfig
);
// 5. Create SDK components
_riskManager = new BasicRiskManager(_logger);
_positionSizer = new BasicPositionSizer(_logger);
_orderManager = new BasicOrderManager(_logger, null); // NT8 adapter set later
_executionAdapter = new NT8ExecutionAdapter();
// 6. Create SDK strategy
_sdkStrategy = CreateSdkStrategy();
if (_sdkStrategy == null)
{
throw new InvalidOperationException("CreateSdkStrategy returned null");
}
// 7. Initialize SDK strategy
_sdkStrategy.Initialize(_strategyConfig, null, _logger);
// 8. Let derived strategy configure parameters
ConfigureStrategyParameters();
// 9. Initialize state tracking
_ordersSubmittedToday = 0;
_lastBarTime = DateTime.MinValue;
_lastAccountInfo = null;
_lastPosition = null;
_currentSession = null;
_logger.LogInformation("SDK initialization complete");
}
#endregion
NT8 Bar Update Handler
#region Bar Update Handler
protected override void OnBarUpdate()
{
// Skip if SDK not initialized
if (!_sdkInitialized || _sdkStrategy == null)
return;
// Skip if not enough bars
if (CurrentBar < BarsRequiredToTrade)
return;
// Prevent processing same bar twice
if (Time[0] == _lastBarTime)
return;
_lastBarTime = Time[0];
try
{
// 1. Convert NT8 bar to SDK format
var barData = ConvertCurrentBar();
// 2. Build strategy context
var context = BuildStrategyContext();
// 3. Call SDK strategy
StrategyIntent intent = null;
lock (_lock)
{
intent = _sdkStrategy.OnBar(barData, context);
}
// 4. Process intent if generated
if (intent != null)
{
ProcessStrategyIntent(intent, context);
}
}
catch (Exception ex)
{
_logger.LogError("OnBarUpdate failed: {0}", ex.Message);
Print(string.Format("[SDK ERROR] OnBarUpdate: {0}", ex.Message));
// Log full stack trace to NT8 log
Log(string.Format("[SDK ERROR] {0}", ex.ToString()), LogLevel.Error);
}
}
/// <summary>
/// Convert current NT8 bar to SDK BarData.
/// </summary>
private BarData ConvertCurrentBar()
{
var symbol = Instrument.MasterInstrument.Name;
var time = Time[0];
var open = Open[0];
var high = High[0];
var low = Low[0];
var close = Close[0];
var volume = Volume[0];
var barSizeMinutes = (int)BarsPeriod.Value;
return NT8DataConverter.ConvertBar(
symbol, time, open, high, low, close, volume, barSizeMinutes);
}
/// <summary>
/// Build SDK StrategyContext from current NT8 state.
/// </summary>
private StrategyContext BuildStrategyContext()
{
var symbol = Instrument.MasterInstrument.Name;
var currentTime = Time[0];
// Build account info
var account = BuildAccountInfo();
// Build position info
var position = BuildPositionInfo();
// Build session info
var session = BuildSessionInfo();
// Custom data (can be extended by derived strategies)
var customData = new Dictionary<string, object>
{
{ "CurrentBar", CurrentBar },
{ "BarsRequiredToTrade", BarsRequiredToTrade },
{ "OrdersToday", _ordersSubmittedToday }
};
return NT8DataConverter.ConvertContext(
symbol, currentTime, position, account, session, customData);
}
/// <summary>
/// Build AccountInfo from NT8 account data.
/// </summary>
private AccountInfo BuildAccountInfo()
{
if (Account == null)
{
// Return default account if not available
return new AccountInfo(100000, 250000, 0, 0, DateTime.UtcNow);
}
var equity = Account.Get(AccountItem.CashValue, Currency.UsDollar);
var buyingPower = Account.Get(AccountItem.BuyingPower, Currency.UsDollar);
var dailyPnL = Account.Get(AccountItem.RealizedProfitLoss, Currency.UsDollar);
// Calculate max drawdown from system performance
var maxDrawdown = 0.0;
if (SystemPerformance != null && SystemPerformance.AllTrades.Count > 0)
{
var perf = SystemPerformance.AllTrades.TradesPerformance;
if (perf != null)
{
maxDrawdown = perf.Currency.DrawDown.Maximum;
}
}
var accountInfo = NT8DataConverter.ConvertAccount(
equity, buyingPower, dailyPnL, maxDrawdown, DateTime.UtcNow);
_lastAccountInfo = accountInfo;
return accountInfo;
}
/// <summary>
/// Build Position from NT8 position data.
/// </summary>
private Position BuildPositionInfo()
{
var symbol = Instrument.MasterInstrument.Name;
// Get current position from NT8
var quantity = Position.Quantity;
var avgPrice = Position.AveragePrice;
var unrealizedPnL = Position.GetUnrealizedProfitLoss(PerformanceUnit.Currency);
// Get realized P&L from performance
var realizedPnL = 0.0;
if (SystemPerformance != null && SystemPerformance.AllTrades.Count > 0)
{
realizedPnL = SystemPerformance.AllTrades.TradesPerformance.Currency.CumProfit;
}
var position = NT8DataConverter.ConvertPosition(
symbol, quantity, avgPrice, unrealizedPnL, realizedPnL, DateTime.UtcNow);
_lastPosition = position;
return position;
}
/// <summary>
/// Build MarketSession from NT8 session data.
/// </summary>
private MarketSession BuildSessionInfo()
{
// Use cached session if same day
if (_currentSession != null &&
_currentSession.SessionStart.Date == Time[0].Date)
{
return _currentSession;
}
// Build new session
// Note: NT8 doesn't provide direct session access, so we estimate
var sessionStart = Time[0].Date.AddHours(9).AddMinutes(30); // 9:30 AM
var sessionEnd = Time[0].Date.AddHours(16); // 4:00 PM
var isRth = Time[0].Hour >= 9 && Time[0].Hour < 16;
var sessionName = isRth ? "RTH" : "ETH";
_currentSession = NT8DataConverter.ConvertSession(
sessionStart, sessionEnd, isRth, sessionName);
return _currentSession;
}
#endregion
Intent Processing
#region Intent Processing
/// <summary>
/// Process strategy intent through risk, sizing, and execution.
/// </summary>
private void ProcessStrategyIntent(StrategyIntent intent, StrategyContext context)
{
try
{
// 1. Risk validation
var riskDecision = _riskManager.ValidateOrder(intent, context, _riskConfig);
if (!riskDecision.Approved)
{
_logger.LogWarning("Intent rejected by risk manager: {0}", riskDecision.Reason);
Print(string.Format("[SDK] Trade rejected: {0}", riskDecision.Reason));
return;
}
// 2. Position sizing
var sizingResult = _positionSizer.CalculateSize(intent, context, _sizingConfig);
if (sizingResult.Contracts < MinContracts)
{
_logger.LogWarning("Position size too small: {0} contracts", sizingResult.Contracts);
Print(string.Format("[SDK] Position size too small: {0}", sizingResult.Contracts));
return;
}
// 3. Create order request
var orderRequest = new OrderRequest(
symbol: intent.Symbol,
side: intent.Side,
quantity: sizingResult.Contracts,
type: intent.EntryType,
limitPrice: intent.LimitPrice,
stopPrice: intent.StopPrice
);
// 4. Submit to NT8
SubmitOrderToNT8(orderRequest, intent);
_ordersSubmittedToday++;
}
catch (Exception ex)
{
_logger.LogError("Failed to process intent: {0}", ex.Message);
Print(string.Format("[SDK ERROR] Intent processing: {0}", ex.Message));
Log(string.Format("[SDK ERROR] {0}", ex.ToString()), LogLevel.Error);
}
}
/// <summary>
/// Submit order to NinjaTrader 8.
/// </summary>
private void SubmitOrderToNT8(OrderRequest request, StrategyIntent intent)
{
var orderName = string.Format("SDK_{0}_{1}", intent.Symbol, DateTime.Now.Ticks);
_logger.LogInformation("Submitting order: {0} {1} {2} @ {3}",
request.Side, request.Quantity, request.Symbol, request.Type);
Print(string.Format("[SDK] Submitting: {0} {1} {2}",
request.Side, request.Quantity, request.Symbol));
// Track order in execution adapter
var trackingInfo = _executionAdapter.SubmitOrder(request, orderName);
// Submit to NT8 based on side
if (request.Side == OrderSide.Buy)
{
if (request.Type == OrderType.Market)
{
EnterLong(request.Quantity, orderName);
}
else if (request.Type == OrderType.Limit && request.LimitPrice.HasValue)
{
EnterLongLimit(request.LimitPrice.Value, request.Quantity, orderName);
}
else if (request.Type == OrderType.Stop && request.StopPrice.HasValue)
{
EnterLongStopMarket(request.StopPrice.Value, request.Quantity, orderName);
}
}
else if (request.Side == OrderSide.Sell)
{
if (request.Type == OrderType.Market)
{
EnterShort(request.Quantity, orderName);
}
else if (request.Type == OrderType.Limit && request.LimitPrice.HasValue)
{
EnterShortLimit(request.LimitPrice.Value, request.Quantity, orderName);
}
else if (request.Type == OrderType.Stop && request.StopPrice.HasValue)
{
EnterShortStopMarket(request.StopPrice.Value, request.Quantity, orderName);
}
}
// Set stops and targets if specified
if (intent.StopTicks > 0)
{
SetStopLoss(orderName, CalculationMode.Ticks, intent.StopTicks, false);
}
if (intent.TargetTicks > 0)
{
SetProfitTarget(orderName, CalculationMode.Ticks, intent.TargetTicks);
}
}
#endregion
NT8 Callback Handlers
#region NT8 Callbacks
protected override void OnOrderUpdate(
Order order, double limitPrice, double stopPrice,
int quantity, int filled, double averageFillPrice,
OrderState orderState, DateTime time,
ErrorCode errorCode, string nativeError)
{
if (!_sdkInitialized || _executionAdapter == null)
return;
// Extract SDK order ID from order name
var sdkOrderId = order.Name;
if (string.IsNullOrEmpty(sdkOrderId) || !sdkOrderId.StartsWith("SDK_"))
return; // Not our order
try
{
// Map NT8 order state to string
var nt8StateString = orderState.ToString();
var errorCodeInt = (int)errorCode;
// Process in execution adapter
_executionAdapter.ProcessOrderUpdate(
order.OrderId,
sdkOrderId,
nt8StateString,
filled,
averageFillPrice,
errorCodeInt,
nativeError
);
// Log significant state changes
if (orderState == OrderState.Filled)
{
_logger.LogInformation("Order filled: {0} @ {1:F2}", sdkOrderId, averageFillPrice);
Print(string.Format("[SDK] Filled: {0} @ {1:F2}", sdkOrderId, averageFillPrice));
}
else if (orderState == OrderState.Rejected)
{
_logger.LogError("Order rejected: {0} - {1}", sdkOrderId, nativeError);
Print(string.Format("[SDK] Rejected: {0} - {1}", sdkOrderId, nativeError));
}
else if (orderState == OrderState.Cancelled)
{
_logger.LogInformation("Order cancelled: {0}", sdkOrderId);
Print(string.Format("[SDK] Cancelled: {0}", sdkOrderId));
}
}
catch (Exception ex)
{
_logger.LogError("OnOrderUpdate failed: {0}", ex.Message);
Print(string.Format("[SDK ERROR] OnOrderUpdate: {0}", ex.Message));
}
}
protected override void OnExecutionUpdate(
Execution execution, string executionId,
double price, int quantity,
MarketPosition marketPosition, string orderId,
DateTime time)
{
if (!_sdkInitialized || _executionAdapter == null)
return;
// Check if this is our order
if (execution.Order == null || !execution.Order.Name.StartsWith("SDK_"))
return;
try
{
// Process execution in adapter
_executionAdapter.ProcessExecution(
orderId,
executionId,
price,
quantity,
time
);
_logger.LogInformation("Execution: {0} {1} @ {2:F2}",
quantity, execution.Order.Name, price);
Print(string.Format("[SDK] Exec: {0} @ {1:F2}", quantity, price));
}
catch (Exception ex)
{
_logger.LogError("OnExecutionUpdate failed: {0}", ex.Message);
Print(string.Format("[SDK ERROR] OnExecutionUpdate: {0}", ex.Message));
}
}
#endregion
Helper Methods
#region Helper Methods
/// <summary>
/// Get current SDK order status.
/// </summary>
protected OrderStatus GetSdkOrderStatus(string orderName)
{
if (_executionAdapter == null)
return null;
return _executionAdapter.GetOrderStatus(orderName);
}
/// <summary>
/// Cancel SDK order by name.
/// </summary>
protected bool CancelSdkOrder(string orderName)
{
if (_executionAdapter == null)
return false;
var canCancel = _executionAdapter.CancelOrder(orderName);
if (canCancel)
{
// Find NT8 order and cancel it
foreach (var order in Account.Orders)
{
if (order.Name == orderName && order.OrderState == OrderState.Working)
{
CancelOrder(order);
return true;
}
}
}
return false;
}
#endregion
📦 Component 2: SimpleORBNT8.cs
Overview
Concrete implementation of SimpleORB strategy for NinjaTrader 8.
Location
Create: src/NT8.Adapters/Strategies/SimpleORBNT8.cs
Deploy To: Documents\NinjaTrader 8\bin\Custom\Strategies\SimpleORBNT8.cs
Full Implementation
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Strategies;
using System;
using System.Collections.Generic;
using NT8.Core.Common.Interfaces;
using NT8.Core.Common.Models;
using NT8.Strategies.Examples;
namespace NinjaTrader.NinjaScript.Strategies
{
/// <summary>
/// Simple Opening Range Breakout strategy integrated with NT8 SDK.
///
/// Strategy Logic:
/// 1. Calculate opening range in first N minutes
/// 2. Trade breakouts of this range
/// 3. Use SDK risk management and position sizing
/// 4. Track performance with SDK analytics
///
/// DEPLOYMENT: Copy to Documents\NinjaTrader 8\bin\Custom\Strategies\
/// </summary>
public class SimpleORBNT8 : NT8StrategyBase
{
#region User Parameters
[NinjaScriptProperty]
[Display(Name = "Opening Range Minutes", GroupName = "ORB Strategy", Order = 1)]
[Range(5, 120)]
public int OpeningRangeMinutes { get; set; }
[NinjaScriptProperty]
[Display(Name = "Std Dev Multiplier", GroupName = "ORB Strategy", Order = 2)]
[Range(0.5, 3.0)]
public double StdDevMultiplier { get; set; }
[NinjaScriptProperty]
[Display(Name = "Stop Loss Ticks", GroupName = "ORB Risk", Order = 1)]
[Range(1, 50)]
public int StopTicks { get; set; }
[NinjaScriptProperty]
[Display(Name = "Profit Target Ticks", GroupName = "ORB Risk", Order = 2)]
[Range(1, 100)]
public int TargetTicks { get; set; }
#endregion
#region NT8 Lifecycle
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
// Strategy identification
Name = "Simple ORB NT8";
Description = "Opening Range Breakout with NT8 SDK integration";
// ORB parameters
OpeningRangeMinutes = 30;
StdDevMultiplier = 1.0;
StopTicks = 8;
TargetTicks = 16;
// Risk parameters (from base class)
DailyLossLimit = 1000.0;
MaxTradeRisk = 200.0;
MaxOpenPositions = 1; // ORB is single position strategy
RiskPerTrade = 100.0;
MinContracts = 1;
MaxContracts = 3;
// NT8 settings
Calculate = Calculate.OnBarClose;
BarsRequiredToTrade = 50; // Need enough bars for ORB calculation
}
// Call base implementation
base.OnStateChange();
}
#endregion
#region SDK Integration
protected override IStrategy CreateSdkStrategy()
{
// Create SimpleORBStrategy from SDK
return new SimpleORBStrategy(OpeningRangeMinutes, StdDevMultiplier);
}
protected override void ConfigureStrategyParameters()
{
// Configure risk settings specific to ORB
_strategyConfig.RiskSettings.DailyLossLimit = DailyLossLimit;
_strategyConfig.RiskSettings.MaxTradeRisk = MaxTradeRisk;
_strategyConfig.RiskSettings.MaxOpenPositions = MaxOpenPositions;
// Calculate max trade risk from stop ticks
var tickValue = Instrument.MasterInstrument.PointValue;
var tickSize = Instrument.MasterInstrument.TickSize;
var dollarRisk = StopTicks * tickSize * tickValue;
_strategyConfig.RiskSettings.MaxTradeRisk = Math.Max(dollarRisk, MaxTradeRisk);
// Position sizing
_strategyConfig.SizingSettings.RiskPerTrade = RiskPerTrade;
_strategyConfig.SizingSettings.MinContracts = MinContracts;
_strategyConfig.SizingSettings.MaxContracts = MaxContracts;
// Add ORB-specific parameters to custom data
_strategyConfig.Parameters["StopTicks"] = StopTicks;
_strategyConfig.Parameters["TargetTicks"] = TargetTicks;
_strategyConfig.Parameters["OpeningRangeMinutes"] = OpeningRangeMinutes;
_logger.LogInformation("Simple ORB configured: OR={0}min, Stop={1}ticks, Target={2}ticks",
OpeningRangeMinutes, StopTicks, TargetTicks);
}
#endregion
}
}
📦 Component 3: MinimalTestStrategy.cs
Overview
Simplest possible NT8 strategy to validate compilation and deployment. No SDK dependencies.
Location
Create: src/NT8.Adapters/Strategies/MinimalTestStrategy.cs
Deploy To: Documents\NinjaTrader 8\bin\Custom\Strategies\MinimalTestStrategy.cs
Full Implementation
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Strategies;
using System;
namespace NinjaTrader.NinjaScript.Strategies
{
/// <summary>
/// Minimal test strategy to validate NT8 integration.
/// Does NOT use SDK - just logs bar data.
///
/// Use this to verify:
/// 1. NT8 can compile strategies
/// 2. Strategies can be enabled on charts
/// 3. Basic NT8 lifecycle works
///
/// DEPLOYMENT: Copy to Documents\NinjaTrader 8\bin\Custom\Strategies\
/// </summary>
public class MinimalTestStrategy : Strategy
{
private int _barCount;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "Minimal Test";
Description = "Simple test strategy - logs bars only";
Calculate = Calculate.OnBarClose;
BarsRequiredToTrade = 1;
}
else if (State == State.DataLoaded)
{
_barCount = 0;
Print("[MinimalTest] Strategy initialized");
}
else if (State == State.Terminated)
{
Print(string.Format("[MinimalTest] Strategy terminated. Processed {0} bars", _barCount));
}
}
protected override void OnBarUpdate()
{
if (CurrentBar < BarsRequiredToTrade)
return;
_barCount++;
// Log every 10th bar to avoid spam
if (_barCount % 10 == 0)
{
Print(string.Format("[MinimalTest] Bar {0}: {1} O={2:F2} H={3:F2} L={4:F2} C={5:F2} V={6}",
CurrentBar,
Time[0].ToString("HH:mm:ss"),
Open[0],
High[0],
Low[0],
Close[0],
Volume[0]));
}
}
}
}
✅ Validation & Testing
Compilation Test Plan
Step 1: Deploy to NT8
# Copy files to NT8
$strategies = "C:\dev\nt8-sdk\src\NT8.Adapters\Strategies"
$nt8Strat = "$env:USERPROFILE\Documents\NinjaTrader 8\bin\Custom\Strategies"
Copy-Item "$strategies\NT8StrategyBase.cs" $nt8Strat -Force
Copy-Item "$strategies\SimpleORBNT8.cs" $nt8Strat -Force
Copy-Item "$strategies\MinimalTestStrategy.cs" $nt8Strat -Force
Step 2: Compile in NT8
- Open NinjaTrader 8
- Tools → NinjaScript Editor (F5)
- Compile → Compile All (F5)
- Verify: "Compilation succeeded"
Step 3: Test MinimalTestStrategy
- New → Strategy
- Select "Minimal Test"
- Enable on ES 5-minute chart
- Verify: Bars logged in Output window
- Run for 30 minutes
- Disable strategy
- Check for errors in Log
Step 4: Test SimpleORBNT8
- New → Strategy
- Select "Simple ORB NT8"
- Configure parameters:
- OpeningRangeMinutes = 30
- StopTicks = 8
- TargetTicks = 16
- DailyLossLimit = 1000
- Enable on ES 5-minute chart (simulation account)
- Verify: SDK initialization message in Print output
- Let run until opening range complete
- Verify: Strategy generates intents
- Verify: Orders submit to simulation
- Check for errors
📋 Success Criteria
Must Have (Release Blockers)
- All 3 files compile in NT8 with zero errors
- Zero compilation warnings
- MinimalTestStrategy runs and logs bars
- SimpleORBNT8 initializes SDK components
- SimpleORBNT8 processes bars without errors
- SimpleORBNT8 generates trading intents
- SimpleORBNT8 submits orders to NT8
- Risk validation works (rejects over-risk trades)
- Position sizing calculates correctly
- Order callbacks update execution adapter
- Stops and targets place correctly
- No crashes or exceptions for 1+ hours
Should Have (Quality Targets)
- OnBarUpdate executes in <200ms
- SDK initialization in <1 second
- Clear logging of all major events
- Graceful error handling
- Memory stable over time
Nice to Have (Future)
- Parameter optimization interface
- Real-time analytics display
- Multi-timeframe support
🚨 Critical Constraints
NT8-Specific Requirements
- Namespace MUST be:
NinjaTrader.NinjaScript.Strategies - Class MUST be:
public(not internal) - Inherit from:
NinjaTrader.NinjaScript.Strategies.Strategy - Properties MUST use:
[NinjaScriptProperty]for NT8 UI - NO async/await (NT8 doesn't support it well)
- NO LINQ in OnBarUpdate (performance)
- Print() for output (not Console.WriteLine)
- Log() for errors (not throw exceptions)
C# 5.0 Constraints
- ❌ No string interpolation
$"" - ❌ No expression-bodied members
=> - ❌ No null-conditional operators
?. - ❌ No pattern matching
- ✅ Use
string.Format() - ✅ Use traditional methods
- ✅ Explicit null checks
Performance Requirements
OnBarUpdate must complete in <200ms:
- Minimize allocations
- No complex calculations
- Cache frequently-used values
- Use lock scopes minimally
SDK calls must be fast:
- Risk validation: <5ms
- Position sizing: <3ms
- Intent generation: <50ms
🔄 Implementation Workflow
Step 1: Create NT8StrategyBase.cs (3 hours)
- Create file in
src/NT8.Adapters/Strategies/ - Implement all sections per specification
- Add comprehensive XML documentation
- Verify C# 5.0 compliance
- Self-review for NT8 requirements
Step 2: Create SimpleORBNT8.cs (1 hour)
- Create file in
src/NT8.Adapters/Strategies/ - Implement per specification
- Add XML documentation
- Link to SDK SimpleORBStrategy
Step 3: Create MinimalTestStrategy.cs (15 min)
- Create file in
src/NT8.Adapters/Strategies/ - Implement simple logging
- No SDK dependencies
Step 4: Local Build Verification (15 min)
# These files won't compile in our SDK project (no NT8 refs)
# But verify syntax is valid C# 5.0
# Check with code analysis tools if available
Step 5: Deploy to NT8 (15 min)
# Run deployment commands
# Open NT8 NinjaScript Editor
# Compile All
# Fix any compilation errors
Step 6: Test in NT8 (1-2 hours)
- Test MinimalTestStrategy (30 min)
- Test SimpleORBNT8 on historical data (30 min)
- Test SimpleORBNT8 on simulation (30 min)
- Document any issues
Step 7: Git Commit
git add src/NT8.Adapters/Strategies/
git commit -m "feat: Add NT8 strategy base class and implementations
- NT8StrategyBase: Full NT8 lifecycle integration
- SimpleORBNT8: ORB strategy with SDK integration
- MinimalTestStrategy: Simple test strategy
Implements:
- NT8 bar update handling
- SDK component initialization
- Risk/sizing integration
- Order submission to NT8
- Order/execution callbacks
- Error handling and logging
Tested in NT8:
- Compilation successful
- MinimalTest runs correctly
- SimpleORB initializes and trades
Phase B complete: NT8 integration layer ready"
📊 Deliverables Checklist
src/NT8.Adapters/Strategies/NT8StrategyBase.cs(~800-1000 lines)src/NT8.Adapters/Strategies/SimpleORBNT8.cs(~150-200 lines)src/NT8.Adapters/Strategies/MinimalTestStrategy.cs(~50 lines)- All files compile in NT8 with zero errors/warnings
- MinimalTest validated (runs and logs)
- SimpleORB validated (initializes, trades)
- Documentation complete (XML comments)
- Git committed with clear message
📚 Reference Materials
NT8 Documentation
- Strategy Development: https://ninjatrader.com/support/helpGuides/nt8/?strategy.htm
- OnStateChange: https://ninjatrader.com/support/helpGuides/nt8/?onstatechange.htm
- OnBarUpdate: https://ninjatrader.com/support/helpGuides/nt8/?onbarupdate.htm
- OnOrderUpdate: https://ninjatrader.com/support/helpGuides/nt8/?onorderupdate.htm
- Order Methods: https://ninjatrader.com/support/helpGuides/nt8/?order_methods.htm
SDK References
src/NT8.Strategies/Examples/SimpleORBStrategy.cssrc/NT8.Core/Common/Interfaces/IStrategy.cssrc/NT8.Core/Risk/BasicRiskManager.cssrc/NT8.Core/Sizing/BasicPositionSizer.cs
🎯 Success Definition
Phase B is complete when:
- ✅ All 3 strategy files created
- ✅ All files compile in NT8 with zero errors
- ✅ MinimalTestStrategy runs successfully
- ✅ SimpleORBNT8 initializes SDK correctly
- ✅ SimpleORBNT8 generates trading intents
- ✅ SimpleORBNT8 submits orders to NT8
- ✅ Risk/sizing validation works
- ✅ Order callbacks process correctly
- ✅ Strategy runs 1+ hours without errors
- ✅ Code committed to Git
Time Target: 4-5 hours total
READY FOR KILOCODE EXECUTION IN CODE MODE ✅
Dependencies: Phase A must be complete before starting Phase B