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) | |
| { | |
评论
?
参与讨论