Current state: Strategy builds and loads correctly, passes 240+ tests, backtest (Strategy Analyzer) works but zero trades execute on live/SIM. Root cause identified: NT8OrderAdapter.ExecuteInNT8() is a stub - it logs to an internal list but never calls EnterLong/EnterShort/SetStopLoss/ SetProfitTarget. Fix is ready in TASK_01_WIRE_NT8_EXECUTION.md. Task files added (ready for Kilocode): - TASK_01_WIRE_NT8_EXECUTION.md (CRITICAL - INT8ExecutionBridge + wiring) - TASK_02_EMERGENCY_KILL_SWITCH.md (CRITICAL - kill switch + verbose logging) - TASK_03_WIRE_CIRCUIT_BREAKER.md (HIGH - wire ExecutionCircuitBreaker) Build Status: All 240+ tests passing, zero errors Next: Run Kilocode against TASK_01, TASK_02, TASK_03 in order
281 lines
9.3 KiB
C#
281 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using NT8.Core.Common.Interfaces;
|
|
using NT8.Core.Common.Models;
|
|
using NT8.Core.Logging;
|
|
using NT8.Adapters.NinjaTrader;
|
|
|
|
namespace NT8.Adapters.Wrappers
|
|
{
|
|
/// <summary>
|
|
/// Simple ORB (Opening Range Breakout) strategy wrapper for NT8
|
|
/// This demonstrates how to implement a strategy that works with the SDK
|
|
/// </summary>
|
|
public class SimpleORBNT8Wrapper : BaseNT8StrategyWrapper
|
|
{
|
|
#region Strategy Parameters
|
|
|
|
/// <summary>
|
|
/// Opening range period in minutes
|
|
/// </summary>
|
|
public int OpeningRangeMinutes { get; set; }
|
|
|
|
/// <summary>
|
|
/// Number of standard deviations for breakout threshold
|
|
/// </summary>
|
|
public double StdDevMultiplier { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor for SimpleORBNT8Wrapper
|
|
/// </summary>
|
|
public SimpleORBNT8Wrapper()
|
|
{
|
|
OpeningRangeMinutes = 30;
|
|
StdDevMultiplier = 1.0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Base Class Implementation
|
|
|
|
/// <summary>
|
|
/// Exposes adapter reference for integration test assertions.
|
|
/// </summary>
|
|
public NT8Adapter GetAdapterForTesting()
|
|
{
|
|
return _nt8Adapter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create the SDK strategy implementation
|
|
/// </summary>
|
|
protected override IStrategy CreateSdkStrategy()
|
|
{
|
|
var openingRangeMinutes = OpeningRangeMinutes > 0 ? OpeningRangeMinutes : 30;
|
|
var stdDevMultiplier = StdDevMultiplier > 0.0 ? StdDevMultiplier : 1.0;
|
|
return new SimpleORBStrategy(openingRangeMinutes, stdDevMultiplier);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Strategy Logic
|
|
|
|
/// <summary>
|
|
/// Simple ORB strategy implementation
|
|
/// </summary>
|
|
private class SimpleORBStrategy : IStrategy
|
|
{
|
|
private readonly int _openingRangeMinutes;
|
|
private readonly double _stdDevMultiplier;
|
|
|
|
private ILogger _logger;
|
|
private DateTime _currentSessionDate;
|
|
private DateTime _openingRangeStart;
|
|
private DateTime _openingRangeEnd;
|
|
private double _openingRangeHigh;
|
|
private double _openingRangeLow;
|
|
private bool _openingRangeReady;
|
|
private bool _tradeTaken;
|
|
|
|
public StrategyMetadata Metadata { get; private set; }
|
|
|
|
public SimpleORBStrategy(int openingRangeMinutes, double stdDevMultiplier)
|
|
{
|
|
if (openingRangeMinutes <= 0)
|
|
{
|
|
throw new ArgumentException("openingRangeMinutes");
|
|
}
|
|
|
|
if (stdDevMultiplier <= 0.0)
|
|
{
|
|
throw new ArgumentException("stdDevMultiplier");
|
|
}
|
|
|
|
_openingRangeMinutes = openingRangeMinutes;
|
|
_stdDevMultiplier = stdDevMultiplier;
|
|
|
|
_currentSessionDate = DateTime.MinValue;
|
|
_openingRangeStart = DateTime.MinValue;
|
|
_openingRangeEnd = DateTime.MinValue;
|
|
_openingRangeHigh = Double.MinValue;
|
|
_openingRangeLow = Double.MaxValue;
|
|
_openingRangeReady = false;
|
|
_tradeTaken = false;
|
|
|
|
Metadata = new StrategyMetadata(
|
|
name: "Simple ORB",
|
|
description: "Opening Range Breakout strategy",
|
|
version: "1.0",
|
|
author: "NT8 SDK Team",
|
|
symbols: new string[] { "ES", "NQ", "YM" },
|
|
requiredBars: 20
|
|
);
|
|
}
|
|
|
|
public void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger)
|
|
{
|
|
if (logger == null)
|
|
{
|
|
throw new ArgumentNullException("logger");
|
|
}
|
|
|
|
_logger = logger;
|
|
_logger.LogInformation("SimpleORBStrategy initialized with OR period {0} minutes and multiplier {1:F2}", _openingRangeMinutes, _stdDevMultiplier);
|
|
}
|
|
|
|
public StrategyIntent OnBar(BarData bar, StrategyContext context)
|
|
{
|
|
if (bar == null)
|
|
{
|
|
throw new ArgumentNullException("bar");
|
|
}
|
|
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException("context");
|
|
}
|
|
|
|
try
|
|
{
|
|
if (_currentSessionDate != context.CurrentTime.Date)
|
|
{
|
|
ResetSession(context.Session.SessionStart);
|
|
}
|
|
|
|
if (bar.Time <= _openingRangeEnd)
|
|
{
|
|
UpdateOpeningRange(bar);
|
|
return null;
|
|
}
|
|
|
|
if (!_openingRangeReady)
|
|
{
|
|
if (_openingRangeHigh > _openingRangeLow)
|
|
{
|
|
_openingRangeReady = true;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (_tradeTaken)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var openingRange = _openingRangeHigh - _openingRangeLow;
|
|
var volatilityBuffer = openingRange * (_stdDevMultiplier - 1.0);
|
|
if (volatilityBuffer < 0)
|
|
{
|
|
volatilityBuffer = 0;
|
|
}
|
|
|
|
var longTrigger = _openingRangeHigh + volatilityBuffer;
|
|
var shortTrigger = _openingRangeLow - volatilityBuffer;
|
|
|
|
if (bar.Close > longTrigger)
|
|
{
|
|
_tradeTaken = true;
|
|
return CreateIntent(context.Symbol, OrderSide.Buy, openingRange, bar.Close);
|
|
}
|
|
|
|
if (bar.Close < shortTrigger)
|
|
{
|
|
_tradeTaken = true;
|
|
return CreateIntent(context.Symbol, OrderSide.Sell, openingRange, bar.Close);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_logger != null)
|
|
{
|
|
_logger.LogError("SimpleORBStrategy OnBar failed: {0}", ex.Message);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public StrategyIntent OnTick(TickData tick, StrategyContext context)
|
|
{
|
|
// Most strategies don't need tick-level logic
|
|
return null;
|
|
}
|
|
|
|
public Dictionary<string, object> GetParameters()
|
|
{
|
|
var parameters = new Dictionary<string, object>();
|
|
parameters.Add("opening_range_minutes", _openingRangeMinutes);
|
|
parameters.Add("std_dev_multiplier", _stdDevMultiplier);
|
|
return parameters;
|
|
}
|
|
|
|
public void SetParameters(Dictionary<string, object> parameters)
|
|
{
|
|
// Parameters are constructor-bound for deterministic behavior in this wrapper.
|
|
// Method retained for interface compatibility.
|
|
}
|
|
|
|
private void ResetSession(DateTime sessionStart)
|
|
{
|
|
_currentSessionDate = sessionStart.Date;
|
|
_openingRangeStart = sessionStart;
|
|
_openingRangeEnd = sessionStart.AddMinutes(_openingRangeMinutes);
|
|
_openingRangeHigh = Double.MinValue;
|
|
_openingRangeLow = Double.MaxValue;
|
|
_openingRangeReady = false;
|
|
_tradeTaken = false;
|
|
}
|
|
|
|
private void UpdateOpeningRange(BarData bar)
|
|
{
|
|
if (bar.High > _openingRangeHigh)
|
|
{
|
|
_openingRangeHigh = bar.High;
|
|
}
|
|
|
|
if (bar.Low < _openingRangeLow)
|
|
{
|
|
_openingRangeLow = bar.Low;
|
|
}
|
|
}
|
|
|
|
private StrategyIntent CreateIntent(string symbol, OrderSide side, double openingRange, double lastPrice)
|
|
{
|
|
var metadata = new Dictionary<string, object>();
|
|
metadata.Add("orb_high", _openingRangeHigh);
|
|
metadata.Add("orb_low", _openingRangeLow);
|
|
metadata.Add("orb_range", openingRange);
|
|
metadata.Add("trigger_price", lastPrice);
|
|
metadata.Add("multiplier", _stdDevMultiplier);
|
|
|
|
if (_logger != null)
|
|
{
|
|
_logger.LogInformation("SimpleORBStrategy generated {0} intent for {1}. OR High={2:F2}, OR Low={3:F2}, Last={4:F2}", side, symbol, _openingRangeHigh, _openingRangeLow, lastPrice);
|
|
}
|
|
|
|
return new StrategyIntent(
|
|
symbol,
|
|
side,
|
|
OrderType.Market,
|
|
null,
|
|
8,
|
|
16,
|
|
0.75,
|
|
"ORB breakout signal",
|
|
metadata);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|