Add ORB confluence factors (NR4/NR7, gap alignment, breakout volume, prior close) + session file logger
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
This commit is contained in:
@@ -32,7 +32,7 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
/// <summary>
|
||||
/// Base class for strategies that integrate NT8 SDK components.
|
||||
/// </summary>
|
||||
public abstract class NT8StrategyBase : Strategy
|
||||
public abstract class NT8StrategyBase : Strategy, INT8ExecutionBridge
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
private DateTime _lastBarTime;
|
||||
private bool _killSwitchTriggered;
|
||||
private ExecutionCircuitBreaker _circuitBreaker;
|
||||
private System.IO.StreamWriter _fileLog;
|
||||
private readonly object _fileLock = new object();
|
||||
|
||||
#region User-Configurable Properties
|
||||
|
||||
@@ -93,8 +95,59 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
[Display(Name = "Verbose Logging", GroupName = "Debug", Order = 1)]
|
||||
public bool EnableVerboseLogging { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Display(Name = "Enable File Logging", GroupName = "Diagnostics", Order = 10)]
|
||||
public bool EnableFileLogging { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Display(Name = "Log Directory", GroupName = "Diagnostics", Order = 11)]
|
||||
public string LogDirectory { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Display(Name = "Enable Long Trades", GroupName = "Trade Direction", Order = 1)]
|
||||
public bool EnableLongTrades { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Display(Name = "Enable Short Trades", GroupName = "Trade Direction", Order = 2)]
|
||||
public bool EnableShortTrades { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
// INT8ExecutionBridge implementation
|
||||
public void EnterLongManaged(int quantity, string signalName, int stopTicks, int targetTicks, double tickSize)
|
||||
{
|
||||
if (stopTicks > 0)
|
||||
SetStopLoss(signalName, CalculationMode.Ticks, stopTicks, false);
|
||||
if (targetTicks > 0)
|
||||
SetProfitTarget(signalName, CalculationMode.Ticks, targetTicks);
|
||||
EnterLong(quantity, signalName);
|
||||
}
|
||||
|
||||
public void EnterShortManaged(int quantity, string signalName, int stopTicks, int targetTicks, double tickSize)
|
||||
{
|
||||
if (stopTicks > 0)
|
||||
SetStopLoss(signalName, CalculationMode.Ticks, stopTicks, false);
|
||||
if (targetTicks > 0)
|
||||
SetProfitTarget(signalName, CalculationMode.Ticks, targetTicks);
|
||||
EnterShort(quantity, signalName);
|
||||
}
|
||||
|
||||
public void ExitLongManaged(string signalName)
|
||||
{
|
||||
ExitLong(signalName);
|
||||
}
|
||||
|
||||
public void ExitShortManaged(string signalName)
|
||||
{
|
||||
ExitShort(signalName);
|
||||
}
|
||||
|
||||
public void FlattenAll()
|
||||
{
|
||||
ExitLong("EmergencyFlatten");
|
||||
ExitShort("EmergencyFlatten");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the SDK strategy instance.
|
||||
/// </summary>
|
||||
@@ -136,6 +189,10 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
MaxContracts = 10;
|
||||
EnableKillSwitch = false;
|
||||
EnableVerboseLogging = false;
|
||||
EnableFileLogging = true;
|
||||
LogDirectory = string.Empty;
|
||||
EnableLongTrades = true;
|
||||
EnableShortTrades = true;
|
||||
_killSwitchTriggered = false;
|
||||
}
|
||||
else if (State == State.DataLoaded)
|
||||
@@ -156,11 +213,35 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (State == State.Realtime)
|
||||
{
|
||||
InitFileLog();
|
||||
WriteSessionHeader();
|
||||
}
|
||||
else if (State == State.Terminated)
|
||||
{
|
||||
WriteSessionFooter();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBarUpdate()
|
||||
{
|
||||
// Kill switch check — must be first
|
||||
if (!_sdkInitialized || _sdkStrategy == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentBar < BarsRequiredToTrade)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time[0] == _lastBarTime)
|
||||
return;
|
||||
|
||||
_lastBarTime = Time[0];
|
||||
|
||||
// Kill switch — checked AFTER bar guards so ExitLong/ExitShort are valid
|
||||
if (EnableKillSwitch)
|
||||
{
|
||||
if (!_killSwitchTriggered)
|
||||
@@ -177,29 +258,9 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
Print(string.Format("[SDK] Kill switch flatten error: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_sdkInitialized || _sdkStrategy == null)
|
||||
{
|
||||
if (CurrentBar == 0)
|
||||
Print(string.Format("[SDK] Not initialized: sdkInit={0}, strategy={1}", _sdkInitialized, _sdkStrategy != null));
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentBar < BarsRequiredToTrade)
|
||||
{
|
||||
if (CurrentBar == 0)
|
||||
Print(string.Format("[SDK] Waiting for bars: current={0}, required={1}", CurrentBar, BarsRequiredToTrade));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time[0] == _lastBarTime)
|
||||
return;
|
||||
|
||||
_lastBarTime = Time[0];
|
||||
|
||||
// Log first processable bar and every 100th bar.
|
||||
if (CurrentBar == BarsRequiredToTrade || CurrentBar % 100 == 0)
|
||||
{
|
||||
@@ -290,9 +351,98 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
if (string.IsNullOrEmpty(execution.Order.Name) || !execution.Order.Name.StartsWith("SDK_"))
|
||||
return;
|
||||
|
||||
FileLog(string.Format("FILL {0} {1} @ {2:F2} | OrderId={3}",
|
||||
execution.MarketPosition,
|
||||
execution.Quantity,
|
||||
execution.Price,
|
||||
execution.OrderId));
|
||||
|
||||
_executionAdapter.ProcessExecution(orderId, executionId, price, quantity, time);
|
||||
}
|
||||
|
||||
private void InitFileLog()
|
||||
{
|
||||
if (!EnableFileLogging)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
string dir = string.IsNullOrEmpty(LogDirectory)
|
||||
? System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
"NinjaTrader 8", "log", "nt8-sdk")
|
||||
: LogDirectory;
|
||||
|
||||
System.IO.Directory.CreateDirectory(dir);
|
||||
|
||||
string path = System.IO.Path.Combine(
|
||||
dir,
|
||||
string.Format("session_{0}.log", DateTime.Now.ToString("yyyyMMdd_HHmmss")));
|
||||
|
||||
_fileLog = new System.IO.StreamWriter(path, false);
|
||||
_fileLog.AutoFlush = true;
|
||||
Print(string.Format("[NT8-SDK] File log started: {0}", path));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Print(string.Format("[NT8-SDK] Failed to open file log: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private void FileLog(string message)
|
||||
{
|
||||
if (_fileLog == null)
|
||||
return;
|
||||
|
||||
lock (_fileLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_fileLog.WriteLine(string.Format("[{0:HH:mm:ss.fff}] {1}", DateTime.Now, message));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteSessionHeader()
|
||||
{
|
||||
FileLog("=== SESSION START " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " ===");
|
||||
FileLog(string.Format("Strategy : {0}", Name));
|
||||
FileLog(string.Format("Account : {0}", Account != null ? Account.Name : "N/A"));
|
||||
FileLog(string.Format("Symbol : {0}", Instrument != null ? Instrument.FullName : "N/A"));
|
||||
FileLog(string.Format("Risk : DailyLimit=${0} MaxTradeRisk=${1} RiskPerTrade=${2}",
|
||||
DailyLossLimit,
|
||||
MaxTradeRisk,
|
||||
RiskPerTrade));
|
||||
FileLog(string.Format("Sizing : MinContracts={0} MaxContracts={1}", MinContracts, MaxContracts));
|
||||
FileLog(string.Format("VerboseLog : {0} FileLog: {1}", EnableVerboseLogging, EnableFileLogging));
|
||||
FileLog("---");
|
||||
}
|
||||
|
||||
private void WriteSessionFooter()
|
||||
{
|
||||
FileLog("---");
|
||||
FileLog("=== SESSION END " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " ===");
|
||||
|
||||
if (_fileLog != null)
|
||||
{
|
||||
lock (_fileLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_fileLog.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_fileLog = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeSdkComponents()
|
||||
{
|
||||
_logger = new BasicLogger(Name);
|
||||
@@ -354,6 +504,17 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
|
||||
private StrategyContext BuildStrategyContext()
|
||||
{
|
||||
DateTime etTime;
|
||||
try
|
||||
{
|
||||
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
|
||||
etTime = TimeZoneInfo.ConvertTime(Time[0], easternZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
etTime = Time[0];
|
||||
}
|
||||
|
||||
var customData = new Dictionary<string, object>();
|
||||
customData.Add("CurrentBar", CurrentBar);
|
||||
customData.Add("BarsRequiredToTrade", BarsRequiredToTrade);
|
||||
@@ -361,7 +522,7 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
|
||||
return NT8DataConverter.ConvertContext(
|
||||
Instrument.MasterInstrument.Name,
|
||||
Time[0],
|
||||
etTime,
|
||||
BuildPositionInfo(),
|
||||
BuildAccountInfo(),
|
||||
BuildSessionInfo(),
|
||||
@@ -391,20 +552,42 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
|
||||
private MarketSession BuildSessionInfo()
|
||||
{
|
||||
if (_currentSession != null && _currentSession.SessionStart.Date == Time[0].Date)
|
||||
return _currentSession;
|
||||
DateTime etTime;
|
||||
try
|
||||
{
|
||||
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
|
||||
etTime = TimeZoneInfo.ConvertTime(Time[0], easternZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
etTime = Time[0];
|
||||
}
|
||||
|
||||
var sessionStart = Time[0].Date.AddHours(9).AddMinutes(30);
|
||||
var sessionEnd = Time[0].Date.AddHours(16);
|
||||
var isRth = Time[0].Hour >= 9 && Time[0].Hour < 16;
|
||||
var sessionName = isRth ? "RTH" : "ETH";
|
||||
var sessionStart = etTime.Date.AddHours(9).AddMinutes(30);
|
||||
var sessionEnd = etTime.Date.AddHours(16);
|
||||
var isRth = etTime.TimeOfDay >= TimeSpan.FromHours(9.5)
|
||||
&& etTime.TimeOfDay < TimeSpan.FromHours(16.0);
|
||||
|
||||
_currentSession = NT8DataConverter.ConvertSession(sessionStart, sessionEnd, isRth, sessionName);
|
||||
_currentSession = NT8DataConverter.ConvertSession(sessionStart, sessionEnd, isRth, isRth ? "RTH" : "ETH");
|
||||
return _currentSession;
|
||||
}
|
||||
|
||||
private void ProcessStrategyIntent(StrategyIntent intent, StrategyContext context)
|
||||
{
|
||||
// Direction filter — checked before risk to avoid unnecessary processing
|
||||
if (intent.Side == SdkOrderSide.Buy && !EnableLongTrades)
|
||||
{
|
||||
if (EnableVerboseLogging)
|
||||
Print(string.Format("[SDK] Long trade filtered by direction setting: {0}", intent.Symbol));
|
||||
return;
|
||||
}
|
||||
if (intent.Side == SdkOrderSide.Sell && !EnableShortTrades)
|
||||
{
|
||||
if (EnableVerboseLogging)
|
||||
Print(string.Format("[SDK] Short trade filtered by direction setting: {0}", intent.Symbol));
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnableVerboseLogging)
|
||||
Print(string.Format("[SDK] Validating intent: {0} {1}", intent.Side, intent.Symbol));
|
||||
|
||||
@@ -461,7 +644,11 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
private void SubmitOrderToNT8(OmsOrderRequest request, StrategyIntent intent)
|
||||
{
|
||||
// Circuit breaker gate
|
||||
if (_circuitBreaker != null && !_circuitBreaker.ShouldAllowOrder())
|
||||
if (State == State.Historical)
|
||||
{
|
||||
// Skip circuit breaker during backtest — wall-clock timeout is meaningless on historical data.
|
||||
}
|
||||
else if (_circuitBreaker != null && !_circuitBreaker.ShouldAllowOrder())
|
||||
{
|
||||
var state = _circuitBreaker.GetState();
|
||||
Print(string.Format("[SDK] Circuit breaker OPEN — order blocked: {0}", state.Reason));
|
||||
@@ -472,7 +659,39 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
|
||||
try
|
||||
{
|
||||
var orderName = string.Format("SDK_{0}_{1}", intent.Symbol, DateTime.Now.Ticks);
|
||||
var orderName = string.Format("SDK_{0}_{1}", intent.Symbol, Guid.NewGuid().ToString("N").Substring(0, 12));
|
||||
|
||||
if (EnableFileLogging)
|
||||
{
|
||||
string grade = "N/A";
|
||||
string score = "N/A";
|
||||
string factors = string.Empty;
|
||||
|
||||
if (intent.Metadata != null && intent.Metadata.ContainsKey("confluence_score"))
|
||||
{
|
||||
var cs = intent.Metadata["confluence_score"] as NT8.Core.Intelligence.ConfluenceScore;
|
||||
if (cs != null)
|
||||
{
|
||||
grade = cs.Grade.ToString();
|
||||
score = cs.WeightedScore.ToString("F3");
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var f in cs.Factors)
|
||||
sb.Append(string.Format("{0}={1:F2} ", f.Type, f.Score));
|
||||
factors = sb.ToString().TrimEnd();
|
||||
}
|
||||
}
|
||||
|
||||
FileLog(string.Format("SIGNAL {0} | Grade={1} | Score={2}", intent.Side, grade, score));
|
||||
if (!string.IsNullOrEmpty(factors))
|
||||
FileLog(string.Format(" Factors: {0}", factors));
|
||||
FileLog(string.Format("SUBMIT {0} {1} @ Market | Stop={2} Target={3}",
|
||||
intent.Side,
|
||||
request.Quantity,
|
||||
intent.StopTicks,
|
||||
intent.TargetTicks));
|
||||
}
|
||||
|
||||
_executionAdapter.SubmitOrder(request, orderName);
|
||||
|
||||
if (request.Side == OmsOrderSide.Buy)
|
||||
@@ -541,4 +760,3 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user