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
{
///
/// Simple ORB (Opening Range Breakout) strategy wrapper for NT8
/// This demonstrates how to implement a strategy that works with the SDK
///
public class SimpleORBNT8Wrapper : BaseNT8StrategyWrapper
{
#region Strategy Parameters
///
/// Opening range period in minutes
///
public int OpeningRangeMinutes { get; set; }
///
/// Number of standard deviations for breakout threshold
///
public double StdDevMultiplier { get; set; }
#endregion
#region Constructor
///
/// Constructor for SimpleORBNT8Wrapper
///
public SimpleORBNT8Wrapper()
{
OpeningRangeMinutes = 30;
StdDevMultiplier = 1.0;
}
#endregion
#region Base Class Implementation
///
/// Exposes adapter reference for integration test assertions.
///
public NT8Adapter GetAdapterForTesting()
{
return _nt8Adapter;
}
///
/// Create the SDK strategy implementation
///
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
///
/// Simple ORB strategy implementation
///
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 GetParameters()
{
var parameters = new Dictionary();
parameters.Add("opening_range_minutes", _openingRangeMinutes);
parameters.Add("std_dev_multiplier", _stdDevMultiplier);
return parameters;
}
public void SetParameters(Dictionary 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();
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
}
}