FTM Opening Range Breakout MNQ v1.8.0 RC3

// ============================================================================
// FTM_OPENING_RANGE_BREAKOUT_MNQ_v1_8_0_RC3
//
// One self-contained MNQ opening-range breakout strategy. It requires one-minute
// MNQ bars and the CME US Index Futures ETH Trading Hours template. All trading
// decisions use New York time after converting NinjaTrader timestamps through
// UTC, so the NinjaTrader display time zone may be Eastern, UTC, Madrid, or any
// other correctly configured system time zone.
//
// The strategy builds the 09:30-09:45 ET opening range, then checks completed
// 15-minute closes from 10:00 through 15:45. A breakout must clear the range by
// one tick and pass candle-shape, close-location, and touch-count admission. A
// first-decision breakout close to completed-session VWAP takes the direct route;
// other signals pass through the prior-session, nearest-neighbor, volatility,
// and continuation refinements. At most one admitted breakout is acted on per
// cash date. Orders are submitted only after the final required bar completes.
//
// Select one sizing mode:
// - FixedDollar: fixed USD risk budget with defensive volatility/trend caps.
// - ClosedEquityPercent: closed strategy-sleeve equity risk with the same caps.
// - ConfidenceScaledPercent: causal 0/50/100 allocation score mapped to the
// configured low/base/high percentages, without duplicate defensive caps.
//
// The direction model uses only already completed session observations and fixed
// per-contract execution costs, keeping its labels independent of order size.
// EstimatedRoundTurnCost is a sizing reserve, not a commission. Stored
// rollover offsets apply only to Merge back adjusted data through MNQ 09-26.
// Actual orders require complete strictly-prior volatility and trend context;
// model labels and eligible-session rolling context continue warming even when
// no order is submitted.
//
// For restart recovery, ImmediatelySubmit may continue only an exactly
// reconstructed strategy position with one full-quantity GTC stop and one
// full-quantity GTC target in the same OCO bracket. Any mismatch blocks new
// strategy actions and requires manual account/order reconciliation. The code
// never adopts or mutates arbitrary account orders. NinjaTrader's
// StopCancelClose handling and 30-second session-close exit remain enabled.
// ============================================================================
#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
#endregion
namespace NinjaTrader.NinjaScript.Strategies
{
public class FTM_OPENING_RANGE_BREAKOUT_MNQ_v1_8_0_RC3 : Strategy
{
public enum RiskSizingMode
{
FixedDollar,
ClosedEquityPercent,
ConfidenceScaledPercent
}
private enum RestartRecoveryState
{
Historical,
AuditPending,
FlatReady,
CurrentInstancePosition,
RecoveredProtected,
FailClosedExitPending,
FlatUntilNextCashDate,
ManualReconciliationRequired
}
private const string StrategyVersion = "1.8.0-rc.3";
private const string NtAdapterVersion = "0.4.1-draft";
private const string NativeValidationStatus = "UNCOMPILED_UNRECONCILED_DRAFT";
private const string RequiredTradingHoursText = "CME US Index Futures ETH";
private const int OrbStartMinuteEt = 9 * 60 + 30;
private const int OrbEndMinuteEt = 9 * 60 + 45;
private const int FirstBreakoutCloseMinuteEt = 10 * 60;
private const int FlattenMinuteEt = 16 * 60;
private const int RequiredCashCloseMinuteEt = 16 * 60;
private const int ExpectedOrbBars = 15;
private const double StopOrbMultiple = 1.25;
private const double MinimumStopPoints = 2.0;
private const double MaximumStopPoints = 100.0;
private const double BaselineTargetR = 3.0;
private const double HighOrbTargetR = 1.25;
private const int ConfidenceOrbLookback = 120;
private const double ConfidenceOrbQuantile = 0.75;
private const int DefensiveMaxContracts = 1;
private const double AdmissionBodyFraction = 0.15;
private const double AdmissionCloseLocation = 0.60;
private const int AdmissionMinimumTouches = 3;
private const int ConfirmationTicks = 1;
private const int MaxAdministrativeExitAttempts = 3;
private const int ConfidenceTrendLookback = 20;
private const int RequiredConfidenceTrendCloses = ConfidenceTrendLookback + 1;
private const double BaselineManagedStopTriggerR = 1.25;
private const double BaselineManagedStopLockR = 0.0;
private const double CountertrendManagedStopTriggerR = 0.75;
private const double CountertrendManagedStopLockR = 0.10;
private const int ConditionalExitMinuteEt = 15 * 60 + 30;
private const double ConditionalLossBoundaryR = 0.0;
private const double ConditionalProfitBoundaryR = 1.0;
private const double PriorDayContinuationThresholdBps = 300.0;
private const int ModelFeatureCount = 14;
private const int ModelNeighbors = 15;
private const int ModelMinimumTrainingRows = 100;
private const double ModelFlipProbabilityThreshold = 0.65;
private const int ModelTrainingStartDateKey = 20210101;
private const int ModelPredictionStartDateKey = 20230101;
private const double WeakSignalBodyFraction = 0.20;
private const int WeakSignalDelayBars = 1;
private const int HighVolTouchLimit = 3;
private const int HighVolVoteBars = 3;
private const double HighVolVoteThresholdOrbFraction = 0.0;
private const double IntradayContinuationThresholdBps = 25.0;
private const double IntradayContinuationMaxSignalExtensionOrb = 0.25;
private const int IntradayContinuationObservationBars = 1;
private const double PriorSessionDisagreementThresholdBps = 100.0;
private const double PriorSessionDisagreementMaxOrbBodyFraction = 0.0;
private const int PriorSessionDisagreementObservationBars = 2;
private const string EntryRefinementPriorityPolicy =
"prior_session_disagreement_then_intraday_continuation";
private const double Rc1DirectElapsedSignal15m = 1.0;
private const double Rc1DirectMaxAlignedVwapDistanceBps = 20.0;
private const int Rc1AlignedVwapFeatureIndex = 4;
private const int Rc1ElapsedSignalFeatureIndex = 7;
private const string Rc1DirectAction = "direct_first_signal_near_vwap";
private const string Rc1PriorityPolicy =
"direct_first_signal_near_vwap_else_integrated_refinement";
// The online classifier learns from causal per-contract shadow outcomes
// using fixed execution costs. Label construction stays independent of the
// selected sizing mode and order quantity.
private const int ModelEntrySlippageTicks = 1;
private const int ModelStopSlippageTicks = 1;
private const int ModelDayFlatSlippageTicks = 1;
private const double ModelRoundTurnCost = 2.50;
private static readonly string[] ModelFeatureNames = new string[]
{
"aligned_gap_bps",
"aligned_prior_session_open_to_rth_close_bps",
"aligned_prior_ret_5_bps",
"aligned_prior_ret_20_bps",
"aligned_vwap_distance_bps",
"aligned_ret_30m_bps",
"breakout_side",
"signal_elapsed_15m",
"orb_bps",
"touch_count",
"weekday_sin",
"weekday_cos",
"month_sin",
"month_cos"
};
// These cash dates preserve known rollover and degraded-data boundaries.
// They are data-quality exclusions, not discretionary market filters.
private static readonly int[] ContractRollExclusionDateKeys = new int[]
{
20200311, 20200610, 20200909, 20210609,
20210908, 20220608, 20220907, 20260611
};
private static readonly int[] DegradedDataExclusionDateKeys = new int[]
{
20200227, 20200228, 20200630, 20200701, 20200702,
20211206, 20220103, 20240918, 20240919, 20250917,
20250918, 20250924, 20250925, 20251128, 20260316,
20260317, 20260410, 20260525, 20260730, 20260731
};
private sealed class DirectionTrainingRow
{
public DateTime Session;
public double[] Features;
public bool FlipWins;
public int Sequence;
}
private sealed class NeighborMatch
{
public DirectionTrainingRow Row;
public double Distance;
}
private sealed class QuarterDirectionModel
{
public List Rows;
public double[] Means;
public double[] Scales;
}
private sealed class ShadowTradeState
{
public int Side;
public double Entry;
public double ActiveStop;
public double Target;
public double RiskPoints;
public double ManagedTriggerR;
public double ManagedLockR;
public bool PendingConditionalExit;
public bool Complete;
public double ExitPrice;
}
private sealed class ShadowPairState
{
public DateTime Session;
public DateTime ExpectedParentFillOpenEt;
public int BaselineSide;
public double[] Features;
public double PriorTrendBps;
public bool PriorTrendAvailable;
public bool Initialized;
public bool Invalid;
public bool LabelRecorded;
public ShadowTradeState Breakout;
public ShadowTradeState Fade;
}
private sealed class PendingEntryDecision
{
public DateTime ExpectedObservationOpenEt;
public int DirectionSide;
public int RequiredObservationBars;
public int ObservedBars;
public double FirstObservationOpen;
public bool SubmitActualOrder;
public string Branch;
public string DirectionSource;
public int BaselineSide;
public double[] Features;
public double SignalClose;
}
private sealed class PendingFinalEntryDecision
{
public DateTime ExpectedObservationOpenEt;
public int RefinedSide;
public int RequiredObservationBars;
public int ObservedBars;
public double FirstObservationOpen;
public bool SubmitActualOrder;
public string Branch;
public string DirectionSource;
public string RefinementPath;
}
// Captured 2026-08-19 from NinjaTrader 8 Instrument Editor > MNQ >
// Contract months. NinjaTrader's Merge back adjusted convention applies
// each incoming offset cumulatively to earlier contracts. Contract keys
// are YYYYMM and rollover-date keys are YYYYMMDD in New York cash dates.
private static readonly int[] StoredIncomingContractKeys = new int[]
{
202006, 202009, 202012, 202103, 202106, 202109, 202112,
202203, 202206, 202209, 202212, 202303, 202306, 202309,
202312, 202403, 202406, 202409, 202412, 202503, 202506,
202509, 202512, 202603, 202606, 202609
};
private static readonly int[] StoredRolloverDateKeys = new int[]
{
20200312, 20200611, 20200910, 20201210, 20210311, 20210610,
20210909, 20211209, 20220310, 20220609, 20220908, 20221212,
20230313, 20230612, 20230911, 20231211, 20240311, 20240617,
20240916, 20241216, 20250317, 20250616, 20250915, 20251215,
20260316, 20260612
};
private static readonly double[] StoredRolloverOffsets = new double[]
{
-5.25, -11.0, -18.5, 1.75, -9.0, -15.0, -9.25,
4.0, -2.0, 33.75, 75.0, 117.5, 122.25, 178.75,
195.0, 210.75, 247.75, 260.5, 235.25, 287.0, 204.25,
211.75, 237.25, 253.25, 214.25, 295.75
};
private TimeZoneInfo platformTimeZone;
private TimeZoneInfo easternTimeZone;
private SessionIterator sessionIterator;
private bool configurationValid;
private bool timeZoneContractValidated;
private DateTime portfolioStartCashDate;
private double baseRiskFraction;
private double minRiskFraction;
private double maxRiskFraction;
private bool scheduleKnown;
private DateTime actualSessionBeginPlatform;
private DateTime actualSessionEndPlatform;
private DateTime actualTradingDayExchange;
private bool barClockContextAvailable;
private DateTime lastBarOpenPlatform;
private DateTime lastBarOpenUtc;
private DateTime lastBarOpenEt;
private DateTime lastBarClosePlatform;
private DateTime lastBarCloseUtc;
private DateTime lastBarCloseEt;
private DateTime currentCashDate;
private bool sessionDateEligible;
private bool referenceSessionOpenCaptured;
private double referenceSessionOpen;
private bool dayBlocked;
private bool sessionEnding;
private bool orbFinalized;
private bool missingOrbLogged;
private bool breakoutConsumed;
private bool cashExitTriggered;
private bool cashWindowIntegrity;
private bool orbHistoryRecorded;
private int orbBarCount;
private int expectedNextOrbOpenMinute;
private double orbHigh;
private double orbLow;
private double orbOpen;
private double orbClose;
private double currentOrbBps;
private double currentPriorOrbQ75Bps;
private bool currentPriorOrbQ75Available;
private List eligibleOrbHistoryBps;
private List eligibleRthCloseHistory;
private List eligibleSessionReturnHistoryBps;
private List currentRthMinuteCloses;
private double currentRthTypicalVolumeSum;
private double currentRthVolumeSum;
private bool rthCloseHistoryRecorded;
private double currentPriorTrendBps;
private bool currentPriorTrendAvailable;
private int activeInitialStopTicks;
private double activeManagedStopTriggerR;
private double activeManagedStopLockR;
private string activeManagementRegime;
private bool managedStopActivated;
private bool conditionalExitRequested;
private DateTime lastCashBarCloseEt;
private PendingEntryDecision pendingEntryDecision;
private PendingFinalEntryDecision pendingFinalEntryDecision;
private ShadowPairState activeShadowPair;
private List directionTrainingRows;
private QuarterDirectionModel activeQuarterModel;
private int activeQuarterKey;
private int trainingSequence;
private int eligibleSessionCount;
private int admittedSignalCount;
private int geometryRejectCount;
private int touchVetoCount;
private int priorDayOverrideCount;
private int knnPredictionCount;
private int knnOverrideCount;
private int weakDelayCount;
private int highVolVoteCount;
private int highVolVoteFlipCount;
private int priorSessionConditionCount;
private int intradayConditionCount;
private int entryConditionOverlapCount;
private int priorSessionReversalCount;
private int intradayKeepCount;
private int intradayReversalCount;
private int modelLabelCount;
private int sizingSkipCount;
private int contextWarmupSkipCount;
private int failClosedCount;
private int rc1DirectDecisionCount;
private int rc1ParentDecisionCount;
private string activeEntrySignal;
private string activeExitSignal;
private Order entryOrder;
private Order stopOrder;
private Order targetOrder;
private Order administrativeExitOrder;
private int administrativeExitAttempts;
private bool administrativeExitFillAwaitingExecution;
private int administrativeExitPlatformFailureLatched;
private bool duplicateManagedStopObserved;
private bool duplicateManagedTargetObserved;
private bool managedProtectionReferenceAmbiguous;
private bool protectiveFillObserved;
private bool accountOrderSetMismatchObserved;
private bool liveManagedProtectionObserved;
private bool restartProtectionDegraded;
private int restartProtectionAuditQueued;
private bool restartFlatConfirmationPending;
private bool restartOpenFirstSnapshotMatched;
private int restartOpenFirstSnapshotBar;
private string restartOpenFirstSnapshotFingerprint;
private Order restartOpenFirstSnapshotStopOrder;
private Order restartOpenFirstSnapshotTargetOrder;
private RestartRecoveryState restartRecoveryState;
private DateTime restartRecoveryCashDate;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "FTM_OPENING_RANGE_BREAKOUT_MNQ_v1_8_0_RC3";
Description = "MNQ one-minute opening-range breakout with direct near-VWAP entries, integrated direction refinement, three risk-sizing modes, managed protection, and guarded restart recovery.";
Calculate = Calculate.OnBarClose;
EntriesPerDirection = 1;
EntryHandling = EntryHandling.UniqueEntries;
IsExitOnSessionCloseStrategy = true;
ExitOnSessionCloseSeconds = 30;
BarsRequiredToTrade = 15;
StartBehavior = StartBehavior.ImmediatelySubmit;
TimeInForce = TimeInForce.Gtc;
RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
StopTargetHandling = StopTargetHandling.ByStrategyPosition;
Slippage = 1;
DefaultQuantity = 2;
IsInstantiatedOnEachOptimizationIteration = true;
IncludeTradeHistoryInBacktest = true;
TraceOrders = false;
SizingMode = RiskSizingMode.FixedDollar;
FixedRiskDollars = 535.0;
FixedDollarMaxContracts = 2;
StartingEquity = 50000.0;
TradingStartDate = 20210101;
BaseRiskPercent = 1.0;
MinRiskPercent = 0.5;
MaxRiskPercent = 2.0;
PortfolioMaxContracts = 10;
MaxNotionalLeverage = 4.0;
EstimatedRoundTurnCost = 2.50;
StopSlippageTicks = 1;
UseStoredRolloverOffsets = true;
MergeTargetContract = 202609;
EnableDiagnostics = false;
}
else if (State == State.Configure)
{
platformTimeZone = null;
easternTimeZone = null;
try
{
platformTimeZone = Core.Globals.GeneralOptions.TimeZoneInfo;
easternTimeZone = ResolveEasternTimeZone();
}
catch (Exception ex)
{
Log("FLAT MOON SOCIETY could not initialize time-zone conversion: " + ex.Message, LogLevel.Error);
}
}
else if (State == State.DataLoaded)
{
sessionIterator = new SessionIterator(Bars);
InitializeRuntimeState();
configurationValid = ValidateConfiguration();
PrintStartupDiagnostics();
}
else if (State == State.Realtime)
{
MapHistoricalOrderReferencesToRealtime();
liveManagedProtectionObserved = IsLiveManagedProtectiveReference(stopOrder)
|| IsLiveManagedProtectiveReference(targetOrder);
BeginRealtimeRestartRecovery();
}
else if (State == State.Terminated)
{
PrintAnalyzerSummary();
}
}
protected override void OnBarUpdate()
{
if (BarsInProgress != 0 || CurrentBar < BarsRequiredToTrade || !configurationValid)
return;
if (State == State.Realtime
&& restartRecoveryState == RestartRecoveryState.ManualReconciliationRequired)
return;
if (State == State.Realtime && restartProtectionDegraded)
ProcessQueuedRestartProtectionAudit(null);
else if (State == State.Realtime
&& restartRecoveryState == RestartRecoveryState.AuditPending)
AuditRealtimeRestartRecovery(true);
if (State == State.Realtime
&& restartRecoveryState == RestartRecoveryState.ManualReconciliationRequired)
return;
if (State == State.Realtime
&& restartRecoveryState == RestartRecoveryState.AuditPending)
return;
if (State == State.Realtime
&& restartRecoveryState == RestartRecoveryState.FailClosedExitPending)
{
if (Position.MarketPosition == MarketPosition.Flat
&& PositionAccount != null
&& PositionAccount.MarketPosition == MarketPosition.Flat)
ProcessDegradedProtectionSnapshot(true);
else if (administrativeExitFillAwaitingExecution)
Diagnostic("ADMIN EXIT retry blocked until OnExecutionUpdate processes the reported fill.");
else
{
if (administrativeExitAttempts >= MaxAdministrativeExitAttempts
&& !IsActiveOrder(administrativeExitOrder))
RequireManualRestartReconciliation(
"The bounded managed administrative-exit attempts were exhausted while exposure remained open.");
else
RequestAdministrativeExit("RestartProtectionInvalidFollowup");
}
return;
}
if (State == State.Realtime
&& (restartRecoveryState == RestartRecoveryState.RecoveredProtected
|| restartRecoveryState == RestartRecoveryState.CurrentInstancePosition))
{
if (Position.MarketPosition == MarketPosition.Flat
&& PositionAccount != null
&& PositionAccount.MarketPosition == MarketPosition.Flat)
{
ProcessDegradedProtectionSnapshot(true);
return;
}
string protectedPositionFailure;
if (!TryValidateRecoveredPositionParity(out protectedPositionFailure))
{
RequireManualRestartReconciliation(protectedPositionFailure);
return;
}
}
RefreshSessionSchedule();
if (!scheduleKnown)
return;
DateTime barOpenEt;
DateTime barCloseEt;
DateTime barOpenUtc;
DateTime barCloseUtc;
DateTime barOpenPlatform;
DateTime barClosePlatform;
try
{
// NinjaTrader minute bars use the close timestamp.
// Convert the close to an absolute UTC instant before subtracting one
// real minute. Subtracting in platform wall time is unsafe at a DST fold.
barClosePlatform = DateTime.SpecifyKind(Time[0], DateTimeKind.Unspecified);
barCloseUtc = ToUtc(barClosePlatform);
barOpenUtc = barCloseUtc.AddMinutes(-1);
barOpenPlatform = DateTime.SpecifyKind(
TimeZoneInfo.ConvertTimeFromUtc(barOpenUtc, platformTimeZone),
DateTimeKind.Unspecified);
barOpenEt = FromUtcToEastern(barOpenUtc);
barCloseEt = FromUtcToEastern(barCloseUtc);
CaptureBarClockContext(
barOpenPlatform, barOpenUtc, barOpenEt,
barClosePlatform, barCloseUtc, barCloseEt);
}
catch (Exception ex)
{
configurationValid = false;
Log("FLAT MOON SOCIETY time conversion failed; no further orders will be submitted: " + ex.Message, LogLevel.Error);
return;
}
DateTime scheduleCashDate = actualTradingDayExchange.Date;
bool beginsNewCashDate = currentCashDate == DateTime.MinValue
|| scheduleCashDate != currentCashDate;
if (beginsNewCashDate)
BeginCashDate(scheduleCashDate);
if (State == State.Realtime && beginsNewCashDate
&& restartRecoveryState == RestartRecoveryState.FlatUntilNextCashDate)
ReleaseRestartEntryBlockAtNewCashDate();
CaptureReferenceSessionOpen(barOpenEt, barOpenUtc);
int openMinuteEt = MinuteOfDay(barOpenEt);
int closeMinuteEt = MinuteOfDay(barCloseEt);
bool sameCashDate = barOpenEt.Date == currentCashDate && barCloseEt.Date == currentCashDate;
bool exactOneMinuteBar = IsExactMinute(barOpenEt)
&& IsExactMinute(barCloseEt)
&& barCloseEt == barOpenEt.AddMinutes(1);
if (sessionEnding)
{
InvalidatePendingSessionState("session-ending state");
CancelWorkingEntry();
RequestAdministrativeExit("SessionEnding");
return;
}
if (sameCashDate && openMinuteEt >= OrbStartMinuteEt
&& closeMinuteEt <= RequiredCashCloseMinuteEt
&& !exactOneMinuteBar)
{
BlockCashDate("A New York cash bar was not aligned to one exact completed minute.");
InvalidatePendingSessionState("misaligned one-minute cash bar");
sessionEnding = true;
CancelWorkingEntry();
RequestAdministrativeExit("CashTimeAlignment");
return;
}
// Track the entire required 09:30-16:00 data window. Only complete
// eligible cash sessions enter subsequent context and sizing histories.
if (sameCashDate && openMinuteEt >= OrbStartMinuteEt
&& closeMinuteEt <= RequiredCashCloseMinuteEt)
{
if (lastCashBarCloseEt != DateTime.MinValue && barOpenEt != lastCashBarCloseEt)
{
BlockCashDate("A one-minute data gap was detected inside the New York cash window.");
InvalidatePendingSessionState("cash-window data gap");
sessionEnding = true;
RequestAdministrativeExit("CashDataGap");
return;
}
lastCashBarCloseEt = barCloseEt;
if (sessionDateEligible && cashWindowIntegrity && !dayBlocked
&& openMinuteEt == OrbStartMinuteEt
&& !referenceSessionOpenCaptured)
{
BlockCashDate("The required 23:00 UTC reference opening bar was not observed.");
InvalidatePendingSessionState("missing reference open");
return;
}
if (!dayBlocked)
{
double volume = (double)Volume[0];
if (!(volume >= 0) || double.IsNaN(volume) || double.IsInfinity(volume))
{
BlockCashDate("A one-minute RTH volume value is invalid.");
InvalidatePendingSessionState("invalid RTH volume");
return;
}
currentRthMinuteCloses.Add(Close[0]);
currentRthTypicalVolumeSum += ((High[0] + Low[0] + Close[0]) / 3.0) * volume;
currentRthVolumeSum += volume;
}
}
if (sameCashDate && activeShadowPair != null)
ProcessShadowPair(barOpenEt, barCloseEt, closeMinuteEt);
if (sameCashDate && pendingEntryDecision != null && !dayBlocked)
ProcessPendingEntryDecision(barOpenEt, barCloseEt);
if (sameCashDate && pendingFinalEntryDecision != null && !dayBlocked)
ProcessPendingFinalEntryDecision(barOpenEt, barCloseEt);
if (sameCashDate && closeMinuteEt >= RequiredCashCloseMinuteEt)
{
DateTime requiredCashCloseEt = currentCashDate.Date.AddMinutes(RequiredCashCloseMinuteEt);
if (lastCashBarCloseEt != requiredCashCloseEt)
{
BlockCashDate("The New York cash window is missing its final one-minute bar before 16:00 ET.");
InvalidatePendingSessionState("cash close bar missing");
sessionEnding = true;
CancelWorkingEntry();
RequestAdministrativeExit("CashDataGapAtClose");
return;
}
FinalizeShadowPairAtCashClose(Close[0]);
RecordCompletedSessionHistory(Close[0]);
if (!cashExitTriggered)
{
cashExitTriggered = true;
dayBlocked = true;
CancelWorkingEntry();
RequestAdministrativeExit("CashClose1600ET");
}
else
RequestAdministrativeExit("CashExitFollowup");
return;
}
// The native template owns an exchange early-close flatten. The session
// schedule was already checked before the ORB; this is a second backstop.
if (Bars.IsLastBarOfSession)
{
cashWindowIntegrity = false;
InvalidatePendingSessionState("native session ended before cash completion");
sessionEnding = true;
dayBlocked = true;
CancelWorkingEntry();
RequestAdministrativeExit("NativeSessionEnd");
return;
}
// The 16:00 exit is submitted in the complete-window branch above so
// causal session history is recorded before the exit request.
if (sameCashDate && closeMinuteEt >= FlattenMinuteEt && !cashExitTriggered)
{
FinalizeShadowPairAtCashClose(Close[0]);
RecordCompletedSessionHistory(Close[0]);
cashExitTriggered = true;
dayBlocked = true;
CancelWorkingEntry();
RequestAdministrativeExit("CashClose1600ET");
return;
}
if (cashExitTriggered)
{
RequestAdministrativeExit("CashExitFollowup");
return;
}
bool isQuarterHourClose = sameCashDate
&& closeMinuteEt >= FirstBreakoutCloseMinuteEt
&& closeMinuteEt < FlattenMinuteEt
&& closeMinuteEt % 15 == 0;
if (Position.MarketPosition != MarketPosition.Flat)
{
// Calculate.OnBarClose reaches this block only after NinjaTrader has
// evaluated the completed one-minute bar against the stop/target
// that was already working during that bar. A quarter-hour close can
// therefore revise protection only for the following one-minute bar.
//
// At 15:30, apply the regime-specific managed-stop rule first. Then
// request the selective market exit when closeR is outside [0R,+1R).
if (isQuarterHourClose)
ManageProtectiveStopAtQuarterHour();
if (sameCashDate && closeMinuteEt == ConditionalExitMinuteEt)
ManageConditionalExit1530();
return;
}
if (dayBlocked || !IsWeekday(currentCashDate.DayOfWeek))
return;
CaptureOpeningRange(barOpenEt, barCloseEt, openMinuteEt, closeMinuteEt);
if (dayBlocked || !orbFinalized || breakoutConsumed)
return;
// The opening-range bar closes at 09:45. The first later 15-minute
// decision therefore occurs at 10:00, then every quarter hour through
// 15:45. A 16:00 signal cannot fill before the mandated flatten.
bool isDecisionClose = isQuarterHourClose;
if (!isDecisionClose || Position.MarketPosition != MarketPosition.Flat)
return;
double confirmation = ConfirmationTicks * TickSize;
bool longBreakout = Close[0] >= RoundPrice(orbHigh + confirmation);
bool shortBreakout = Close[0] <= RoundPrice(orbLow - confirmation);
if (!longBreakout && !shortBreakout)
return;
EvaluateAdmissionAndDirection(longBreakout ? 1 : -1, barCloseEt);
}
protected override void OnOrderUpdate(Order order, double limitPrice, double stopPrice, int quantity,
int filled, double averageFillPrice, OrderState orderState, DateTime time, ErrorCode error, string comment)
{
if (order == null)
return;
if (State == State.Realtime)
MapHistoricalOrderReferencesToRealtime();
if (!string.IsNullOrEmpty(activeEntrySignal) && order.Name == activeEntrySignal)
entryOrder = order;
bool administrativeExitUpdate = !string.IsNullOrEmpty(activeExitSignal)
&& order.Name == activeExitSignal;
if (administrativeExitUpdate)
administrativeExitOrder = order;
bool managedProtection = TrackManagedProtectiveOrder(order);
bool tracked = order.Name == activeEntrySignal
|| order.Name == activeExitSignal
|| managedProtection;
if (tracked)
{
// NinjaTrader documents this callback value as the last order-state
// change time, but does not document its time zone. Preserve it exactly
// and report DateTime.Kind; do not relabel or convert it. The separately
// labeled bar clocks come from the validated strategy clock contract.
Diagnostic(string.Format(CultureInfo.InvariantCulture,
"ORDER {0}: callbackTimeRaw={1:o}, callbackTimeKind={2}, {3}, state={4}, qty={5}, filled={6}, avg={7:F2}, error={8}, comment={9}",
order.Name, time, time.Kind, BarClockContext(), orderState,
quantity, filled, averageFillPrice, error, comment));
}
if (tracked && (orderState == OrderState.Rejected || error != ErrorCode.NoError))
Log(string.Format(CultureInfo.InvariantCulture,
"FLAT MOON SOCIETY order failure: {0}, state={1}, error={2}, comment={3}",
order.Name, orderState, error, comment), LogLevel.Error);
if (State == State.Realtime && administrativeExitUpdate
&& (filled > 0 || orderState == OrderState.PartFilled
|| orderState == OrderState.Filled))
administrativeExitFillAwaitingExecution = true;
if (State == State.Realtime && administrativeExitUpdate
&& (orderState == OrderState.Rejected || error != ErrorCode.NoError))
{
System.Threading.Interlocked.Exchange(
ref administrativeExitPlatformFailureLatched, 1);
RequireManualRestartReconciliation(
"The managed administrative exit was rejected or reported an error. NinjaTrader's StopCancelClose path owns this outcome, so RC3 permanently blocks every further authored exit in this instance.");
return;
}
if (State == State.Realtime && administrativeExitUpdate
&& orderState == OrderState.Unknown)
{
RequireManualRestartReconciliation(
"The bounded managed administrative exit entered Unknown state; RC3 cannot prove whether it is live or terminal and will not submit a duplicate exit.");
return;
}
if (administrativeExitUpdate && orderState == OrderState.Cancelled
&& error == ErrorCode.NoError)
administrativeExitOrder = null;
if (State == State.Realtime && managedProtection
&& restartRecoveryState == RestartRecoveryState.AuditPending
&& orderState == OrderState.Working)
AuditRealtimeRestartRecovery(false);
if (State == State.Realtime && managedProtection
&& restartRecoveryState != RestartRecoveryState.Historical
&& restartRecoveryState != RestartRecoveryState.FlatReady
&& restartRecoveryState != RestartRecoveryState.ManualReconciliationRequired
&& RequiresPromptProtectionReaudit(orderState, error))
{
if (filled > 0 || orderState == OrderState.PartFilled
|| orderState == OrderState.Filled)
protectiveFillObserved = true;
restartProtectionDegraded = true;
Log("FLAT MOON SOCIETY managed protection entered a degraded or terminal/error state while exposure may remain. A strategy-thread audit is being queued before any further strategy action.", LogLevel.Error);
if (!ProtectionUpdateMayBePartOfFillSequence(order, orderState, filled))
QueueRestartProtectionAudit();
}
if (State == State.Realtime && managedProtection
&& restartRecoveryState != RestartRecoveryState.Historical
&& restartRecoveryState != RestartRecoveryState.FlatReady
&& restartRecoveryState != RestartRecoveryState.ManualReconciliationRequired
&& Position.MarketPosition == MarketPosition.Flat
&& PositionAccount != null
&& PositionAccount.MarketPosition == MarketPosition.Flat
&& IsPotentiallyLiveAccountOrder(order))
{
restartProtectionDegraded = true;
QueueRestartProtectionAudit();
}
if (State == State.Realtime && administrativeExitUpdate
&& restartRecoveryState == RestartRecoveryState.FailClosedExitPending
&& orderState == OrderState.Cancelled
&& error == ErrorCode.NoError)
{
if (filled > 0)
protectiveFillObserved = true;
restartProtectionDegraded = true;
if (filled == 0)
QueueRestartProtectionAudit();
}
}
protected override void OnExecutionUpdate(Execution execution, string executionId, double price,
int quantity, MarketPosition marketPosition, string orderId, DateTime time)
{
if (execution == null || quantity <= 0)
return;
// As with OnOrderUpdate, the official callback contract does not state a
// time zone. Keep the execution timestamp raw and pair it with separately
// labeled, normalized strategy-bar context for native reconciliation.
Diagnostic(string.Format(CultureInfo.InvariantCulture,
"FILL {0}: callbackTimeRaw={1:o}, callbackTimeKind={2}, {3}, executionId={4}, orderId={5}, price={6:F2}, qty={7}, position={8}.",
execution.Name, time, time.Kind, BarClockContext(), executionId,
orderId, price, quantity, marketPosition));
bool currentInstanceEntryExecution = State == State.Realtime
&& !string.IsNullOrEmpty(activeEntrySignal)
&& execution.Name == activeEntrySignal;
if (currentInstanceEntryExecution
&& restartRecoveryState == RestartRecoveryState.FlatReady)
{
restartRecoveryCashDate = currentCashDate;
restartRecoveryState = RestartRecoveryState.CurrentInstancePosition;
Diagnostic("CURRENT-INSTANCE POSITION: realtime entry execution observed; managed protection is now under lifecycle supervision.");
}
// A delayed/partial entry fill can race a cutoff cancellation. Establish
// current-instance ownership first, then fail closed by requesting an exit.
if (currentInstanceEntryExecution && (sessionEnding || cashExitTriggered))
RequestAdministrativeExit("LateEntryFillAfterCutoff");
bool administrativeExitExecution = !string.IsNullOrEmpty(activeExitSignal)
&& execution.Name == activeExitSignal;
if (State == State.Realtime && administrativeExitExecution)
administrativeExitFillAwaitingExecution = false;
if (State == State.Realtime
&& restartRecoveryState != RestartRecoveryState.Historical
&& restartRecoveryState != RestartRecoveryState.FlatReady
&& restartRecoveryState != RestartRecoveryState.ManualReconciliationRequired
&& TrackManagedProtectiveOrder(execution.Order))
{
protectiveFillObserved = true;
restartProtectionDegraded = true;
ProcessRestartProtectionAfterExecution();
}
if (State == State.Realtime
&& restartRecoveryState != RestartRecoveryState.Historical
&& restartRecoveryState != RestartRecoveryState.FlatReady
&& restartRecoveryState != RestartRecoveryState.ManualReconciliationRequired
&& administrativeExitExecution)
{
protectiveFillObserved = true;
restartProtectionDegraded = true;
ProcessRestartProtectionAfterExecution();
}
}
private void MapHistoricalOrderReferencesToRealtime()
{
if (State != State.Realtime)
return;
if (entryOrder != null && entryOrder.IsBacktestOrder)
entryOrder = GetRealtimeOrder(entryOrder);
if (stopOrder != null && stopOrder.IsBacktestOrder)
stopOrder = GetRealtimeOrder(stopOrder);
if (targetOrder != null && targetOrder.IsBacktestOrder)
targetOrder = GetRealtimeOrder(targetOrder);
if (administrativeExitOrder != null && administrativeExitOrder.IsBacktestOrder)
administrativeExitOrder = GetRealtimeOrder(administrativeExitOrder);
}
private bool TrackManagedProtectiveOrder(Order order)
{
if (order == null || string.IsNullOrEmpty(activeEntrySignal)
|| !string.Equals(order.FromEntrySignal, activeEntrySignal, StringComparison.Ordinal))
return false;
if (string.Equals(order.Name, "Stop loss", StringComparison.Ordinal))
{
UpdateManagedProtectiveReference(
ref stopOrder, order, ref duplicateManagedStopObserved);
return true;
}
if (string.Equals(order.Name, "Profit target", StringComparison.Ordinal))
{
UpdateManagedProtectiveReference(
ref targetOrder, order, ref duplicateManagedTargetObserved);
return true;
}
return false;
}
private void UpdateManagedProtectiveReference(
ref Order tracked,
Order update,
ref bool ambiguousLiveIdentityObserved)
{
if (update == null)
return;
if (State == State.Realtime && update.IsBacktestOrder
&& tracked != null && !tracked.IsBacktestOrder)
return;
bool sameIdentity = IsSameOrderIdentity(tracked, update);
if (tracked != null && !sameIdentity
&& !tracked.IsBacktestOrder && !update.IsBacktestOrder)
{
ambiguousLiveIdentityObserved = true;
managedProtectionReferenceAmbiguous = true;
}
if (tracked == null || sameIdentity || !update.IsBacktestOrder
|| tracked.IsBacktestOrder)
tracked = update;
if (State == State.Realtime && !update.IsBacktestOrder)
liveManagedProtectionObserved = true;
}
private bool IsSameOrderIdentity(Order first, Order second)
{
return first != null && second != null
&& object.ReferenceEquals(first, second);
}
private bool RequiresPromptProtectionReaudit(OrderState orderState, ErrorCode error)
{
return error != ErrorCode.NoError
|| orderState == OrderState.Cancelled
|| orderState == OrderState.Rejected
|| orderState == OrderState.PartFilled
|| orderState == OrderState.Filled
|| orderState == OrderState.Unknown;
}
private bool ProtectionUpdateMayBePartOfFillSequence(
Order order,
OrderState orderState,
int filled)
{
if (filled > 0 || orderState == OrderState.PartFilled
|| orderState == OrderState.Filled)
return true;
Order sibling = object.ReferenceEquals(order, stopOrder) ? targetOrder : stopOrder;
return sibling != null
&& (sibling.Filled > 0
|| sibling.OrderState == OrderState.PartFilled
|| sibling.OrderState == OrderState.Filled);
}
private void QueueRestartProtectionAudit()
{
if (State != State.Realtime
|| restartRecoveryState == RestartRecoveryState.Historical
|| restartRecoveryState == RestartRecoveryState.FlatReady
|| restartRecoveryState == RestartRecoveryState.ManualReconciliationRequired
|| System.Threading.Interlocked.Exchange(
ref restartProtectionAuditQueued, 1) == 1)
return;
try
{
TriggerCustomEvent(ProcessQueuedRestartProtectionAudit, null);
}
catch (Exception ex)
{
System.Threading.Interlocked.Exchange(
ref restartProtectionAuditQueued, 0);
Log("FLAT MOON SOCIETY could not queue the strategy-thread protection audit: "
+ ex.Message
+ ". The instance remains blocked and requires immediate operator reconciliation if no further strategy event arrives.", LogLevel.Error);
}
}
private void ProcessQueuedRestartProtectionAudit(object state)
{
System.Threading.Interlocked.Exchange(
ref restartProtectionAuditQueued, 0);
if (State != State.Realtime
|| restartRecoveryState == RestartRecoveryState.Historical
|| restartRecoveryState == RestartRecoveryState.FlatReady
|| restartRecoveryState == RestartRecoveryState.ManualReconciliationRequired)
{
restartProtectionDegraded = false;
return;
}
if (!restartProtectionDegraded)
return;
restartProtectionDegraded = false;
// A custom event (or the bar-driven fallback that drains the same flag)
// may establish the first exact snapshot, but it can never count as the
// distinct realtime-bar confirmation required for recovery PASS.
ProcessDegradedProtectionSnapshot(false);
}
private void ProcessRestartProtectionAfterExecution()
{
restartProtectionDegraded = true;
QueueRestartProtectionAudit();
}
private void ProcessDegradedProtectionSnapshot(bool finalAttempt)
{
if (PositionAccount != null
&& Position.MarketPosition == MarketPosition.Flat
&& PositionAccount.MarketPosition == MarketPosition.Flat)
{
string flatOrderFailure;
bool flatOrderPending;
if (!TryValidateAccountInstrumentOrderSet(
false, out flatOrderFailure, out flatOrderPending))
{
RequireManualRestartReconciliation(flatOrderFailure);
return;
}
restartFlatConfirmationPending = false;
ClearRestartOpenFirstSnapshot();
restartRecoveryState = RestartRecoveryState.FlatUntilNextCashDate;
Diagnostic("Recovered protective execution left both strategy and account flat; entries remain blocked until the next New York cash date.");
return;
}
restartRecoveryState = RestartRecoveryState.AuditPending;
AuditRealtimeRestartRecovery(finalAttempt);
}
private void BeginRealtimeRestartRecovery()
{
restartRecoveryCashDate = currentCashDate;
ClearRestartOpenFirstSnapshot();
restartRecoveryState = RestartRecoveryState.AuditPending;
if (PositionAccount == null)
{
RequireManualRestartReconciliation(
"The realtime account position is unavailable.");
return;
}
bool strategyFlat = Position.MarketPosition == MarketPosition.Flat;
bool accountFlat = PositionAccount.MarketPosition == MarketPosition.Flat;
if (strategyFlat && accountFlat)
{
if (IsLifecycleActiveOrder(stopOrder) || IsLifecycleActiveOrder(targetOrder))
{
RequireManualRestartReconciliation(
"Both positions are flat but a current-instance protective order is still active.");
return;
}
string accountOrderFailure;
bool accountOrderPending;
if (!TryValidateAccountInstrumentOrderSet(
false, out accountOrderFailure, out accountOrderPending))
{
RequireManualRestartReconciliation(accountOrderFailure);
return;
}
restartFlatConfirmationPending = true;
restartRecoveryState = RestartRecoveryState.AuditPending;
Diagnostic("Entered realtime with one flat/no-order snapshot; entries remain blocked until the first realtime strategy bar confirms it.");
return;
}
Print("FLAT MOON SOCIETY RESTART RECOVERY PENDING: validating the reconstructed strategy/account position and broker-confirmed managed bracket. No new entry can be submitted during this audit.");
AuditRealtimeRestartRecovery(false);
}
private void AuditRealtimeRestartRecovery(bool finalAttempt)
{
if (restartRecoveryState != RestartRecoveryState.AuditPending)
return;
if (restartFlatConfirmationPending)
{
if (!finalAttempt)
return;
restartFlatConfirmationPending = false;
if (PositionAccount != null
&& Position.MarketPosition == MarketPosition.Flat
&& PositionAccount.MarketPosition == MarketPosition.Flat)
{
string flatFailure;
bool flatPending;
if (TryValidateAccountInstrumentOrderSet(
false, out flatFailure, out flatPending))
{
restartRecoveryState = RestartRecoveryState.FlatReady;
Diagnostic("First realtime strategy bar confirmed flat positions and no active account order for this instrument; normal entries are enabled.");
return;
}
RequireManualRestartReconciliation(flatFailure);
return;
}
}
string failure;
bool pending;
if (TryValidateRealtimeRestartRecovery(out failure, out pending))
{
string snapshotFingerprint = BuildRestartOpenSnapshotFingerprint();
bool matchesFirstSnapshot = RestartOpenSnapshotMatchesFirst(
snapshotFingerprint);
if (!finalAttempt)
{
if (!matchesFirstSnapshot)
{
添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论