// ============================================================================= // AdaptiveRegimeStrategy.cpp // // Adaptive Regime-Switching Strategy for ES/MES and NQ/MNQ (ACSIL) // // SUMMARY OF LOGIC // ----------------- // 1. REGIME DETECTION (runs on every fully closed bar, on whatever timeframe // this chart is set to): // - Uses Kaufman's Efficiency Ratio (ER) over N bars. // - ER near 1 -> trending market -> use BREAKOUT logic. // - ER near 0 -> choppy market -> use MEAN-REVERSION (fade) logic // against today's session high/low. // - ER in between -> "neutral" -> no new entries this bar. // // 2. TREND STRATEGY: Donchian-style breakout. Enter long when the last // closed bar's close breaks above the highest high of the prior N bars; // enter short on a break below the lowest low. // // 3. CHOP STRATEGY: Fade the day's high/low on the CURRENT chart's own bars // (the day's actual high/low price is the same regardless of the bar // period used to compute it, so no cross-chart reference is required // here). Short when price is within a tick buffer of the session high, // long when within a tick buffer of the session low. // // 4. STOP: Based on a TRUE 1-MINUTE ATR. Because ATR magnitude genuinely // depends on the bar period it's computed from (5-min bar ranges are not // the same as 1-min bar ranges), getting a real 1-minute ATR while this // strategy runs on an arbitrary chart timeframe requires reading it from // a separate 1-minute chart in the same chartbook. See SETUP NOTES below. // If that reference isn't configured/available, the strategy falls back // to an internally computed ATR from the current chart's own bars (and // logs a warning) so it never silently fails -- but for correct behavior, // configure the 1-minute reference chart. // // 5. TARGET: Target = (StopTicks * TargetMultiplier) - TargetTickAdjustment. // Defaults: TargetMultiplier = 1.5, TargetTickAdjustment = 1 tick. // This implements "1.5x the stop, minus 1 tick" -- i.e. an asymmetric // reward:risk close to 1.5:1 so winners can outweigh losers even at a // sub-50% win rate. Both numbers are inputs -- flip the interpretation // easily if you intended something different. // // 6. RISK CONTROLS: // - Only ever 1 open position at a time (Max Contracts input, default 1, // and MaximumPositionAllowed is enforced every call). // - Stops trading for the rest of the session after N consecutive // losing trades (default 3, configurable), long or short combined. // Resets automatically at the start of each new trading day. // - Optional trading window (start/end time) and optional flatten time // to avoid holding through periods you don't want exposure. // // SETUP NOTES (read before compiling) // ------------------------------------ // A. Add this file to your Sierra Chart ACS_Source folder and build it // (Analysis >> Build Custom Studies DLL, or F7). Then add the resulting // study to your ES/MES (or NQ/MNQ) chart as a Trading Strategy study. // // B. FOR A TRUE 1-MINUTE ATR (recommended): // 1. In the SAME chartbook, open an additional chart of the SAME // symbol on a 1-minute bar period. It does not need to be visible // in a layout permanently, but it must be open so Sierra Chart // keeps it updated. // 2. Add an "Average True Range" study to that 1-minute chart. // 3. Note that chart's Chart Number (shown in the chart's title bar / // Chart Settings) and the Study ID of the ATR study (hover over the // study name in Studies list, or use "Copy Study ID" via right // click on the study). // 4. In THIS strategy's inputs, set "1-Min ATR Reference Chart Number" // and "1-Min ATR Reference Study ID" to those values, and // "1-Min ATR Reference Subgraph Index" to 0 (ATR is normally // subgraph 0 on the standard ATR study, but confirm in that study's // settings). // If you skip this setup (leave Chart Number at 0), the strategy will // compute its own ATR from the current chart's bars as a fallback and // write a warning to the Sierra Chart message log once per session. // // C. BEFORE LIVE USE: compile, then run this in Sierra Chart's Strategy // Analyzer / on the Simulation trade service across a meaningful amount // of ES/NQ history and multiple timeframes. Tune ER thresholds, // Donchian length, ATR multiplier, and target multiplier there. No // strategy is profitable purely from architecture -- the entry logic and // parameters must be validated against real data before real money. // // D. ACSIL API surface (order/position structures, enum names) can shift // slightly between Sierra Chart versions. If you hit a compile error on // a specific field/function name, tell me the exact error and I will // correct it -- this is a normal part of ACSIL development and expected // to take one or two iteration passes. // // E. TESTING THE ORDER/BRACKET MECHANICS DIRECTLY: // Two inputs, used as a deliberate two-step confirmation (no blocking // popup -- ACSIL doesn't support one safely from this thread): // Step 1: "TEST STEP 1 of 2: Arm Test Order" -> Yes. This only logs a // message; no order is sent yet. // Step 2: with Step 1 still Yes, separately set "TEST STEP 2 of 2: // Confirm Fire Test Order" -> Yes. The order fires the instant // both are Yes together: a single 1-lot SELL market order with // a Stop/Target bracket attached, completely bypassing // warmup, regime detection, the consecutive-loss halt, the // existing-position check, and any time window. // It will not fire again while both stay Yes. To fire again, set both // back to No, then repeat Step 1 and Step 2. Use this to confirm your // bracket orders behave correctly (both legs attach, one cancels when // the other fills) independent of whether your entry signal logic is // triggering. // ============================================================================= #include "sierrachart.h" SCDLLName("Adaptive Regime Strategy - ES NQ") SCSFExport scsf_AdaptiveRegimeStrategy(SCStudyInterfaceRef sc) { // ---- Subgraphs (for visual confirmation on the chart) ---- SCSubgraphRef Subgraph_Regime = sc.Subgraph[0]; // 1 = trend, -1 = chop, 0 = neutral SCSubgraphRef Subgraph_ATR = sc.Subgraph[1]; SCSubgraphRef Subgraph_StopTicks = sc.Subgraph[2]; SCSubgraphRef Subgraph_TargetTicks = sc.Subgraph[3]; SCSubgraphRef Subgraph_ER = sc.Subgraph[4]; SCSubgraphRef Subgraph_SessionHigh = sc.Subgraph[5]; SCSubgraphRef Subgraph_SessionLow = sc.Subgraph[6]; SCSubgraphRef Subgraph_HaltStatus = sc.Subgraph[7]; // ---- Inputs ---- SCInputRef Input_EnableTrading = sc.Input[0]; SCInputRef Input_MaxContracts = sc.Input[1]; SCInputRef Input_AllowLong = sc.Input[2]; SCInputRef Input_AllowShort = sc.Input[3]; SCInputRef Input_MaxConsecutiveLosses = sc.Input[4]; SCInputRef Input_ERLength = sc.Input[5]; SCInputRef Input_ERTrendThreshold = sc.Input[6]; SCInputRef Input_ERChopThreshold = sc.Input[7]; SCInputRef Input_DonchianLength = sc.Input[8]; SCInputRef Input_FadeBufferTicks = sc.Input[9]; SCInputRef Input_ATRRefChartNumber = sc.Input[10]; SCInputRef Input_ATRRefStudyID = sc.Input[11]; SCInputRef Input_ATRRefSubgraphIndex = sc.Input[12]; SCInputRef Input_ATRFallbackLength = sc.Input[13]; SCInputRef Input_ATRStopMultiplier = sc.Input[14]; SCInputRef Input_MinStopTicks = sc.Input[15]; SCInputRef Input_MaxStopTicks = sc.Input[16]; SCInputRef Input_TargetMultiplier = sc.Input[17]; SCInputRef Input_TargetTickAdjustment = sc.Input[18]; SCInputRef Input_UseTradingWindow = sc.Input[19]; SCInputRef Input_TradingStartTime = sc.Input[20]; SCInputRef Input_TradingEndTime = sc.Input[21]; SCInputRef Input_UseFlattenTime = sc.Input[22]; SCInputRef Input_FlattenTime = sc.Input[23]; SCInputRef Input_TestOrderArm = sc.Input[24]; SCInputRef Input_TestOrderConfirmFire = sc.Input[25]; SCInputRef Input_ResetHaltArm = sc.Input[26]; SCInputRef Input_ResetHaltConfirm = sc.Input[27]; SCInputRef Input_ExecuteDuringOpenWindow = sc.Input[28]; SCInputRef Input_ExecuteDuringCloseWindow = sc.Input[29]; SCInputRef Input_RequireFractalConfirmation = sc.Input[30]; SCInputRef Input_FractalSideBars = sc.Input[31]; SCInputRef Input_EnableTrendFollow = sc.Input[32]; SCInputRef Input_EnableFade = sc.Input[33]; SCInputRef Input_EnableMandatoryFlattenWindow = sc.Input[34]; if (sc.SetDefaults) { sc.GraphName = "Adaptive Regime Strategy (ES/MES/NQ/MNQ)"; sc.StudyDescription = "Adaptive trend/chop regime-switching strategy with ATR-based " "stops and asymmetric targets. See top-of-file comments for setup."; sc.AutoLoop = 1; sc.GraphRegion = 0; sc.FreeDLL = 1; sc.AllowMultipleEntriesInSameDirection = 0; sc.SupportReversals = 0; sc.CancelAllOrdersOnEntriesAndReversals = 1; sc.AllowOppositeEntryWithOpposingPositionOrOrders = 0; sc.SupportAttachedOrdersForTrading = 1; sc.MaximumPositionAllowed = 1; // overwritten from input each call below sc.SendOrdersToTradeService = 1; // overwritten every call below to match actual SIM/live mode Subgraph_Regime.Name = "Regime (1=Trend,-1=Chop,0=Neutral)"; Subgraph_Regime.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_ATR.Name = "1-Min ATR Used"; Subgraph_ATR.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_StopTicks.Name = "Stop (Ticks)"; Subgraph_StopTicks.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_TargetTicks.Name = "Target (Ticks)"; Subgraph_TargetTicks.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_ER.Name = "Efficiency Ratio"; Subgraph_ER.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_SessionHigh.Name = "Session High (Today)"; Subgraph_SessionHigh.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_SessionLow.Name = "Session Low (Today)"; Subgraph_SessionLow.DrawStyle = DRAWSTYLE_IGNORE; Subgraph_HaltStatus.Name = "Halted For Day (1=Yes, 0=No)"; Subgraph_HaltStatus.DrawStyle = DRAWSTYLE_IGNORE; Input_EnableTrading.Name = "Enable Trading"; Input_EnableTrading.SetYesNo(1); Input_MaxContracts.Name = "Max Contracts (Position Size)"; Input_MaxContracts.SetInt(1); Input_MaxContracts.SetIntLimits(1, 10); Input_AllowLong.Name = "Allow Long Entries"; Input_AllowLong.SetYesNo(1); Input_AllowShort.Name = "Allow Short Entries"; Input_AllowShort.SetYesNo(1); Input_MaxConsecutiveLosses.Name = "Max Consecutive Losses Before Daily Halt"; Input_MaxConsecutiveLosses.SetInt(3); Input_MaxConsecutiveLosses.SetIntLimits(1, 20); Input_ERLength.Name = "Efficiency Ratio Length (bars)"; Input_ERLength.SetInt(10); Input_ERLength.SetIntLimits(3, 100); Input_ERTrendThreshold.Name = "Efficiency Ratio Trend Threshold (0-1)"; Input_ERTrendThreshold.SetFloat(0.5f); Input_ERTrendThreshold.SetFloatLimits(0.1f, 0.95f); Input_ERChopThreshold.Name = "Efficiency Ratio Chop Threshold (0-1)"; Input_ERChopThreshold.SetFloat(0.25f); Input_ERChopThreshold.SetFloatLimits(0.01f, 0.5f); Input_DonchianLength.Name = "Donchian Breakout Length (bars, Trend Mode)"; Input_DonchianLength.SetInt(20); Input_DonchianLength.SetIntLimits(5, 200); Input_FadeBufferTicks.Name = "Fade Buffer From Session High/Low (Ticks, Chop Mode)"; Input_FadeBufferTicks.SetInt(4); Input_FadeBufferTicks.SetIntLimits(0, 100); Input_ATRRefChartNumber.Name = "1-Min ATR Reference Chart Number (0 = fallback to this chart)"; Input_ATRRefChartNumber.SetChartNumber(0); Input_ATRRefStudyID.Name = "1-Min ATR Reference Study ID"; Input_ATRRefStudyID.SetStudyID(1); Input_ATRRefSubgraphIndex.Name = "1-Min ATR Reference Subgraph Index"; Input_ATRRefSubgraphIndex.SetInt(0); Input_ATRFallbackLength.Name = "ATR Fallback Length (bars, used if reference unavailable)"; Input_ATRFallbackLength.SetInt(14); Input_ATRFallbackLength.SetIntLimits(2, 200); Input_ATRStopMultiplier.Name = "Stop = ATR x This Multiplier"; Input_ATRStopMultiplier.SetFloat(1.0f); Input_ATRStopMultiplier.SetFloatLimits(0.1f, 10.0f); Input_MinStopTicks.Name = "Minimum Stop (Ticks)"; Input_MinStopTicks.SetInt(6); Input_MinStopTicks.SetIntLimits(1, 500); Input_MaxStopTicks.Name = "Maximum Stop (Ticks)"; Input_MaxStopTicks.SetInt(60); Input_MaxStopTicks.SetIntLimits(1, 2000); Input_TargetMultiplier.Name = "Target Multiplier (x Stop)"; Input_TargetMultiplier.SetFloat(1.5f); Input_TargetMultiplier.SetFloatLimits(0.1f, 10.0f); Input_TargetTickAdjustment.Name = "Target Tick Adjustment (subtracted, ticks)"; Input_TargetTickAdjustment.SetInt(1); Input_TargetTickAdjustment.SetIntLimits(0, 50); Input_UseTradingWindow.Name = "Restrict Entries To Time Window"; Input_UseTradingWindow.SetYesNo(0); Input_TradingStartTime.Name = "Trading Window Start Time"; Input_TradingStartTime.SetTime(HMS_TIME(9, 30, 0)); Input_TradingEndTime.Name = "Trading Window End Time"; Input_TradingEndTime.SetTime(HMS_TIME(15, 45, 0)); Input_UseFlattenTime.Name = "Flatten Position At Time"; Input_UseFlattenTime.SetYesNo(0); Input_FlattenTime.Name = "Flatten Time"; Input_FlattenTime.SetTime(HMS_TIME(15, 55, 0)); Input_TestOrderArm.Name = "TEST STEP 1 of 2: Arm Test Order (set this first, by itself)"; Input_TestOrderArm.SetYesNo(0); Input_TestOrderConfirmFire.Name = "TEST STEP 2 of 2: Confirm Fire Test Order (only after Step 1 is Yes)"; Input_TestOrderConfirmFire.SetYesNo(0); Input_ResetHaltArm.Name = "RESET HALT STEP 1 of 2: Arm Reset (clears consecutive-loss halt)"; Input_ResetHaltArm.SetYesNo(0); Input_ResetHaltConfirm.Name = "RESET HALT STEP 2 of 2: Confirm Reset (only after Step 1 is Yes)"; Input_ResetHaltConfirm.SetYesNo(0); Input_ExecuteDuringOpenWindow.Name = "Execute Orders During 9:30-9:40 AM ET Window"; Input_ExecuteDuringOpenWindow.SetYesNo(0); Input_ExecuteDuringCloseWindow.Name = "Execute Orders During 3:50-4:00 PM ET Window"; Input_ExecuteDuringCloseWindow.SetYesNo(0); Input_RequireFractalConfirmation.Name = "Require Williams Fractal Confirmation (7-bar) Before Entry"; Input_RequireFractalConfirmation.SetYesNo(1); Input_FractalSideBars.Name = "Williams Fractal Side Bars (3 = 7-bar fractal total)"; Input_FractalSideBars.SetInt(3); Input_FractalSideBars.SetIntLimits(1, 10); Input_EnableTrendFollow.Name = "Enable Trend-Follow (Breakout) Mode"; Input_EnableTrendFollow.SetYesNo(1); Input_EnableFade.Name = "Enable Fade (Chop / Session High-Low) Mode"; Input_EnableFade.SetYesNo(1); Input_EnableMandatoryFlattenWindow.Name = "Enable Mandatory 4:50-5:00 PM ET Flatten & No New Trades"; Input_EnableMandatoryFlattenWindow.SetYesNo(1); return; } // ------------------------------------------------------------------- // Persistent variables (retained across calls) // ------------------------------------------------------------------- int& r_LastProcessedClosedBar = sc.GetPersistentInt(1); int& r_PrevPositionQuantity = sc.GetPersistentInt(2); int& r_ConsecutiveLosses = sc.GetPersistentInt(3); int& r_TradingHaltedForDay = sc.GetPersistentInt(4); float& r_SessionHigh = sc.GetPersistentFloat(2); float& r_SessionLow = sc.GetPersistentFloat(3); int& r_SessionInitialized = sc.GetPersistentInt(5); if (sc.IsFullRecalculation) return; // never submit orders during a full study recalculation (e.g. triggered by // changing any Input) -- doing so is rejected by Sierra Chart with error -8998 int& r_WarnedAboutATRFallback = sc.GetPersistentInt(6); int& r_PrevTestOrderCombined = sc.GetPersistentInt(7); int& r_TestOrderArmedLogged = sc.GetPersistentInt(8); int& r_AwaitingHighFractalOpenIdx = sc.GetPersistentInt(11); int& r_AwaitingLowFractalOpenIdx = sc.GetPersistentInt(12); float& r_HighFractal8thBarOpen = sc.GetPersistentFloat(4); float& r_LowFractal8thBarOpen = sc.GetPersistentFloat(5); if (r_LastProcessedClosedBar == 0) { r_LastProcessedClosedBar = -1; // force first-run processing r_AwaitingHighFractalOpenIdx = -1; r_AwaitingLowFractalOpenIdx = -1; } sc.MaximumPositionAllowed = Input_MaxContracts.GetInt(); // Keep SendOrdersToTradeService in sync with whatever mode Sierra Chart is // actually in right now. A mismatch here causes every order to be silently // rejected with "SendOrdersToTradeService is not consistent with Trade >> // Trade Simulation Mode On" -- this self-corrects whether you're in SIM // (as now) or later switch to live trading, so it never needs manual sync. sc.SendOrdersToTradeService = sc.GlobalTradeSimulationIsOn ? 0 : 1; // ------------------------------------------------------------------- // TEST ORDER FIRING WITH BRACKETS (two-step arm/confirm, no popup) // Runs FIRST, every call, before any warmup/regime/halt/position gate. // // Step 1: set "TEST STEP 1 of 2: Arm Test Order" to Yes by itself. // This only logs a message -- it does NOT place an order yet. // Step 2: with Step 1 still Yes, separately set "TEST STEP 2 of 2: // Confirm Fire Test Order" to Yes. The order fires the moment // BOTH are Yes at the same time. // // Fires exactly once per Arm+Confirm cycle (tracked via an edge on the // combined state), so it will not repeat-fire while both stay set to // Yes. To fire again, turn BOTH back to No, then repeat Step 1 and // Step 2. Uses your Minimum Stop Ticks input directly (not the ATR // calc), so it works even without enough historical bars loaded. // ------------------------------------------------------------------- int TestOrderArmed = Input_TestOrderArm.GetYesNo(); int TestOrderConfirm = Input_TestOrderConfirmFire.GetYesNo(); int TestOrderCombined = (TestOrderArmed == 1 && TestOrderConfirm == 1) ? 1 : 0; if (TestOrderArmed == 1 && r_TestOrderArmedLogged == 0) { sc.AddMessageToLog( "Test Order: ARMED. Set Step 2 (Confirm Fire) to Yes to actually send the order.", 0); r_TestOrderArmedLogged = 1; } else if (TestOrderArmed == 0) { r_TestOrderArmedLogged = 0; } if (TestOrderCombined == 1 && r_PrevTestOrderCombined == 0) { int TestStopTicks = Input_MinStopTicks.GetInt(); int TestTargetTicks = (int)((TestStopTicks * Input_TargetMultiplier.GetFloat()) + 0.5f) - Input_TargetTickAdjustment.GetInt(); if (TestTargetTicks < 1) TestTargetTicks = 1; s_SCNewOrder TestOrder; TestOrder.OrderQuantity = 1; TestOrder.OrderType = SCT_ORDERTYPE_MARKET; TestOrder.Target1Offset = TestTargetTicks * sc.TickSize; TestOrder.AttachedOrderTarget1Type = SCT_ORDERTYPE_LIMIT; TestOrder.Stop1Offset = TestStopTicks * sc.TickSize; TestOrder.AttachedOrderStop1Type = SCT_ORDERTYPE_STOP; int SendResult = sc.SellEntry(TestOrder); SCString TestMsg; if (SendResult > 0) { TestMsg.Format( "TEST ORDER submitted OK (result code %d): 1-lot SELL market, Stop %d ticks, Target %d ticks. " "Turn both Step 1 and Step 2 back to No before arming again.", SendResult, TestStopTicks, TestTargetTicks); } else { TestMsg.Format( "TEST ORDER FAILED to submit (result code %d -- see ACSIL SCT_ORDERERROR / " "TradeService docs for this code's meaning, or check the Trade Service Log). " "Turn both Step 1 and Step 2 back to No before retrying.", SendResult); } sc.AddMessageToLog(TestMsg, 1); } r_PrevTestOrderCombined = TestOrderCombined; // ------------------------------------------------------------------- // RESET CONSECUTIVE-LOSS HALT (same two-step arm/confirm pattern) // Lets you manually clear the daily halt and loss counter without // waiting for the next trading day or removing/re-adding the study // (which would also wipe session high/low tracking unnecessarily). // ------------------------------------------------------------------- int& r_PrevResetHaltCombined = sc.GetPersistentInt(9); int& r_ResetHaltArmedLogged = sc.GetPersistentInt(10); int ResetHaltArmed = Input_ResetHaltArm.GetYesNo(); int ResetHaltConfirm = Input_ResetHaltConfirm.GetYesNo(); int ResetHaltCombined = (ResetHaltArmed == 1 && ResetHaltConfirm == 1) ? 1 : 0; if (ResetHaltArmed == 1 && r_ResetHaltArmedLogged == 0) { sc.AddMessageToLog( "Reset Halt: ARMED. Set Step 2 (Confirm) to Yes to clear the halt and loss counter.", 0); r_ResetHaltArmedLogged = 1; } else if (ResetHaltArmed == 0) { r_ResetHaltArmedLogged = 0; } if (ResetHaltCombined == 1 && r_PrevResetHaltCombined == 0) { r_ConsecutiveLosses = 0; r_TradingHaltedForDay = 0; sc.AddMessageToLog( "Trading halt manually RESET. Consecutive loss counter cleared. " "Turn both Reset Halt inputs back to No.", 1); } r_PrevResetHaltCombined = ResetHaltCombined; int LastClosedBar = sc.Index - 1; int WarmupBars = Input_ERLength.GetInt() + Input_DonchianLength.GetInt() + 5; if (LastClosedBar < WarmupBars) return; // ------------------------------------------------------------------- // Session (trading day) high/low tracking + daily reset of loss counter // Runs off the last CLOSED bar so it is stable and not repainting. // // On the very first activation of this study instance (e.g. just added // to the chart, possibly mid-session), there is no incrementally-built // running high/low yet. Rather than starting from 0 (which would leave // Session Low stuck near 0 and Session High reflecting only a single // bar for the remainder of that day), do a one-time backward scan // across today's already-loaded bars to seed both correctly. // ------------------------------------------------------------------- if (r_SessionInitialized == 0) { float ScanHigh = sc.High[LastClosedBar]; float ScanLow = sc.Low[LastClosedBar]; int ScanIndex = LastClosedBar; while (ScanIndex > 0 && !sc.IsNewTradingDay(ScanIndex)) { ScanIndex--; if (sc.High[ScanIndex] > ScanHigh) ScanHigh = sc.High[ScanIndex]; if (sc.Low[ScanIndex] < ScanLow) ScanLow = sc.Low[ScanIndex]; } r_SessionHigh = ScanHigh; r_SessionLow = ScanLow; r_SessionInitialized = 1; SCString SeedMsg; SeedMsg.Format( "[%s] Session High/Low seeded from today's existing bars: High=%.2f Low=%.2f", sc.Symbol.GetChars(), r_SessionHigh, r_SessionLow); sc.AddMessageToLog(SeedMsg, 0); } else if (sc.IsNewTradingDay(LastClosedBar)) { r_SessionHigh = sc.High[LastClosedBar]; r_SessionLow = sc.Low[LastClosedBar]; r_ConsecutiveLosses = 0; r_TradingHaltedForDay = 0; } else { if (sc.High[LastClosedBar] > r_SessionHigh) r_SessionHigh = sc.High[LastClosedBar]; if (sc.Low[LastClosedBar] < r_SessionLow) r_SessionLow = sc.Low[LastClosedBar]; } Subgraph_SessionHigh[sc.Index] = r_SessionHigh; Subgraph_SessionLow[sc.Index] = r_SessionLow; // ------------------------------------------------------------------- // Position / trade-outcome tracking (runs every call, every tick) // Classifies a just-closed trade as win/loss using the REALIZED P&L // of the last closed trade (PositionData.LastTradeProfitLoss), which // reflects the actual fill rather than an unrealized mark taken just // before the position went flat. // ------------------------------------------------------------------- s_SCPositionData PositionData; sc.GetTradePosition(PositionData); if (r_PrevPositionQuantity != 0 && PositionData.PositionQuantity == 0) { if (PositionData.LastTradeProfitLoss > 0) r_ConsecutiveLosses = 0; else r_ConsecutiveLosses++; SCString DiagMsg; DiagMsg.Format( "[%s] Trade closed. LastTradeProfitLoss=%.2f -> ConsecutiveLosses now %d (halt threshold %d).", sc.Symbol.GetChars(), PositionData.LastTradeProfitLoss, r_ConsecutiveLosses, Input_MaxConsecutiveLosses.GetInt()); sc.AddMessageToLog(DiagMsg, 0); if (r_ConsecutiveLosses >= Input_MaxConsecutiveLosses.GetInt()) { r_TradingHaltedForDay = 1; SCString Msg; Msg.Format("[%s] Adaptive Regime Strategy: HALTED for the day after %d consecutive losses.", sc.Symbol.GetChars(), r_ConsecutiveLosses); sc.AddMessageToLog(Msg, 1); } } r_PrevPositionQuantity = PositionData.PositionQuantity; Subgraph_HaltStatus[sc.Index] = (float)r_TradingHaltedForDay; // ------------------------------------------------------------------- // Optional flatten-at-time (checked every call so it fires promptly). // // BOUNDED to a 10-minute window starting at the configured time, not // an open-ended ">= FlattenTime" check. An unbounded check here had // the same bug as the mandatory 4:50 PM flatten did before its fix: // it would keep firing for the REST OF THE DAY once the clock passed // FlattenTime, immediately closing every subsequent trade within // about a minute of it opening -- silently destroying every // evening/overnight trade's results, not just flattening positions // still open at FlattenTime as intended. // ------------------------------------------------------------------- if (Input_UseFlattenTime.GetYesNo()) { double NowTimeOfDaySeconds = sc.BaseDateTimeIn[sc.Index].GetTimeInSeconds(); double FlattenStartSeconds = (double)Input_FlattenTime.GetTime(); double FlattenEndSeconds = FlattenStartSeconds + 600.0; // +10 minutes if (NowTimeOfDaySeconds >= FlattenStartSeconds && NowTimeOfDaySeconds < FlattenEndSeconds && PositionData.PositionQuantity != 0) { sc.FlattenAndCancelAllOrders(); } } // ------------------------------------------------------------------- // MANDATORY: never hold a position overnight. Flatten AND cancel all // attached bracket orders (target/stop) during the 4:50-5:00 PM ET // window, every single day, regardless of the optional flatten-time // setting above. Controlled by "Enable Mandatory 4:50-5:00 PM ET // Flatten & No New Trades", defaulting to Yes -- made into a visible, // named setting (rather than hardcoded/hidden) specifically so it's // easy to see and confirm at a glance rather than being an invisible // rule buried in code. The actual CME daily maintenance break is // 5:00-6:00 PM ET; this gives a 10-minute safety buffer ahead of it. // sc.FlattenAndCancelAllOrders() closes any open position at market // AND cancels all working orders for this symbol, so no orphaned // bracket legs are left behind. // // IMPORTANT: this is bounded to a WINDOW (4:50-5:00 PM), not just a // ">= 4:50 PM" open-ended check. An earlier version had no upper // bound, which meant it kept firing all evening and flattened every // normal overnight/ETH trade within a minute of it opening -- not // intended, and not what "don't hold overnight" was supposed to mean. // ------------------------------------------------------------------- if (Input_EnableMandatoryFlattenWindow.GetYesNo()) { SCDateTime NowTimeOfDay = sc.BaseDateTimeIn[sc.Index].GetTimeInSeconds(); SCDateTime MandatoryFlattenStart = HMS_TIME(16, 50, 0); SCDateTime MandatoryFlattenEnd = HMS_TIME(17, 0, 0); if (NowTimeOfDay >= MandatoryFlattenStart && NowTimeOfDay < MandatoryFlattenEnd && PositionData.PositionQuantity != 0) { sc.FlattenAndCancelAllOrders(); sc.AddMessageToLog( "MANDATORY 4:50 PM ET flatten: position closed and all bracket orders cancelled.", 1); } } // ------------------------------------------------------------------- // Only evaluate entry signals once per newly closed bar. // ------------------------------------------------------------------- if (r_LastProcessedClosedBar == LastClosedBar) return; r_LastProcessedClosedBar = LastClosedBar; // ---- Efficiency Ratio (regime detection) ---- int ERLen = Input_ERLength.GetInt(); float NetChange = (float)fabs(sc.Close[LastClosedBar] - sc.Close[LastClosedBar - ERLen]); float SumAbsChange = 0.0f; for (int i = LastClosedBar - ERLen + 1; i <= LastClosedBar; i++) SumAbsChange += (float)fabs(sc.Close[i] - sc.Close[i - 1]); float ER = (SumAbsChange > 0.0f) ? (NetChange / SumAbsChange) : 0.0f; int Regime = 0; // 0 = neutral/no-trade if (ER >= Input_ERTrendThreshold.GetFloat()) Regime = 1; // trending else if (ER <= Input_ERChopThreshold.GetFloat()) Regime = -1; // choppy Subgraph_ER[sc.Index] = ER; Subgraph_Regime[sc.Index] = (float)Regime; // ------------------------------------------------------------------- // Williams Fractal (7-bar by default) confirmation tracking. // Center bar = LastClosedBar - FractalSideBars, confirmed once // FractalSideBars bars exist on both sides. The "8th bar" (one bar // past the last confirming bar) is tracked separately since its Open // isn't known until that bar itself has closed. // ------------------------------------------------------------------- int FractalSideBars = Input_FractalSideBars.GetInt(); int FractalCenter = LastClosedBar - FractalSideBars; if (FractalCenter - FractalSideBars >= 0) { bool IsHighFractal = true; bool IsLowFractal = true; for (int k = 1; k <= FractalSideBars; k++) { if (sc.High[FractalCenter - k] >= sc.High[FractalCenter] || sc.High[FractalCenter + k] >= sc.High[FractalCenter]) IsHighFractal = false; if (sc.Low[FractalCenter - k] <= sc.Low[FractalCenter] || sc.Low[FractalCenter + k] <= sc.Low[FractalCenter]) IsLowFractal = false; } if (IsHighFractal) { r_AwaitingHighFractalOpenIdx = FractalCenter + FractalSideBars + 1; // An opposite-direction fractal invalidates whatever Low Fractal // reference is currently pending or already active -- the setup // has been overtaken by an opposing structure. r_AwaitingLowFractalOpenIdx = -1; r_LowFractal8thBarOpen = 0.0f; } if (IsLowFractal) { r_AwaitingLowFractalOpenIdx = FractalCenter + FractalSideBars + 1; // Same invalidation, opposite direction. r_AwaitingHighFractalOpenIdx = -1; r_HighFractal8thBarOpen = 0.0f; } } if (r_AwaitingHighFractalOpenIdx >= 0 && LastClosedBar == r_AwaitingHighFractalOpenIdx) { r_HighFractal8thBarOpen = sc.Open[r_AwaitingHighFractalOpenIdx]; r_AwaitingHighFractalOpenIdx = -1; } if (r_AwaitingLowFractalOpenIdx >= 0 && LastClosedBar == r_AwaitingLowFractalOpenIdx) { r_LowFractal8thBarOpen = sc.Open[r_AwaitingLowFractalOpenIdx]; r_AwaitingLowFractalOpenIdx = -1; } // ---- 1-Minute ATR (for stop calculation) ---- float ATRValue = 0.0f; int ATRRefChart = Input_ATRRefChartNumber.GetChartNumber(); if (ATRRefChart != 0) { SCFloatArray ATRArray; sc.GetStudyArrayFromChartUsingID(ATRRefChart, Input_ATRRefStudyID.GetStudyID(), Input_ATRRefSubgraphIndex.GetInt(), ATRArray); int ArraySize = ATRArray.GetArraySize(); if (ArraySize >= 2) ATRValue = ATRArray[ArraySize - 2]; // last fully closed bar on that chart } if (ATRValue <= 0.0f) { if (!r_WarnedAboutATRFallback) { sc.AddMessageToLog( "Adaptive Regime Strategy: 1-min ATR reference chart not configured/available. " "Falling back to ATR computed from this chart's own bars. See setup notes.", 1); r_WarnedAboutATRFallback = 1; } int N = Input_ATRFallbackLength.GetInt(); float SumTR = 0.0f; for (int i = LastClosedBar - N + 1; i <= LastClosedBar; i++) { float TR1 = sc.High[i] - sc.Low[i]; float TR2 = (float)fabs(sc.High[i] - sc.Close[i - 1]); float TR3 = (float)fabs(sc.Low[i] - sc.Close[i - 1]); float TrueRange = TR1; if (TR2 > TrueRange) TrueRange = TR2; if (TR3 > TrueRange) TrueRange = TR3; SumTR += TrueRange; } ATRValue = SumTR / (float)N; } Subgraph_ATR[sc.Index] = ATRValue; // ---- Stop / Target sizing from ATR ---- int StopTicks = (int)(((ATRValue * Input_ATRStopMultiplier.GetFloat()) / sc.TickSize) + 0.5f); if (StopTicks < Input_MinStopTicks.GetInt()) StopTicks = Input_MinStopTicks.GetInt(); if (StopTicks > Input_MaxStopTicks.GetInt()) StopTicks = Input_MaxStopTicks.GetInt(); int TargetTicks = (int)((StopTicks * Input_TargetMultiplier.GetFloat()) + 0.5f) - Input_TargetTickAdjustment.GetInt(); if (TargetTicks < 1) TargetTicks = 1; Subgraph_StopTicks[sc.Index] = (float)StopTicks; Subgraph_TargetTicks[sc.Index] = (float)TargetTicks; // ---- Gate checks: enabled, not halted, no open position, in window ---- if (!Input_EnableTrading.GetYesNo()) return; if (r_TradingHaltedForDay) return; if (PositionData.PositionQuantity != 0) return; if (Input_UseTradingWindow.GetYesNo()) { SCDateTime NowTimeOfDay = sc.BaseDateTimeIn[LastClosedBar].GetTimeInSeconds(); SCDateTime StartTimeOfDay = Input_TradingStartTime.GetTime(); SCDateTime EndTimeOfDay = Input_TradingEndTime.GetTime(); if (NowTimeOfDay < StartTimeOfDay || NowTimeOfDay > EndTimeOfDay) return; } // Optionally exclude the 9:30-9:40 AM ET open window, when many // discretionary traders are still establishing intraday support/ // resistance and price action can be less reliable for our signals. // Assumes chart bar timestamps are in Eastern Time (matching the CME // futures convention already used for the Trading Window defaults // above) -- if your chart's display time zone differs, adjust or // flag this for a fix. if (!Input_ExecuteDuringOpenWindow.GetYesNo()) { SCDateTime NowTimeOfDay = sc.BaseDateTimeIn[LastClosedBar].GetTimeInSeconds(); SCDateTime OpenWindowStart = HMS_TIME(9, 30, 0); SCDateTime OpenWindowEnd = HMS_TIME(9, 40, 0); if (NowTimeOfDay >= OpenWindowStart && NowTimeOfDay < OpenWindowEnd) return; } // Optionally exclude the 3:50-4:00 PM ET close window, just ahead of // the 4:15 PM ET RTH close, when algorithmic/imbalance-driven flow // tends to dominate and price action can lose pattern. if (!Input_ExecuteDuringCloseWindow.GetYesNo()) { SCDateTime NowTimeOfDay = sc.BaseDateTimeIn[LastClosedBar].GetTimeInSeconds(); SCDateTime CloseWindowStart = HMS_TIME(15, 50, 0); SCDateTime CloseWindowEnd = HMS_TIME(16, 0, 0); if (NowTimeOfDay >= CloseWindowStart && NowTimeOfDay < CloseWindowEnd) return; } // Controlled by "Enable Mandatory 4:50-5:00 PM ET Flatten & No New // Trades" (same toggle as the flatten above), immediately ahead of // the CME's actual 5:00-6:00 PM ET daily maintenance break. if (Input_EnableMandatoryFlattenWindow.GetYesNo()) { SCDateTime NowTimeOfDay = sc.BaseDateTimeIn[LastClosedBar].GetTimeInSeconds(); SCDateTime PreCloseBlockStart = HMS_TIME(16, 50, 0); SCDateTime PreCloseBlockEnd = HMS_TIME(17, 0, 0); if (NowTimeOfDay >= PreCloseBlockStart && NowTimeOfDay < PreCloseBlockEnd) return; } // ---- Trend regime: Donchian breakout ---- bool BreakoutLong = false; bool BreakoutShort = false; if (Regime == 1 && Input_EnableTrendFollow.GetYesNo()) { int DLen = Input_DonchianLength.GetInt(); float HH = -FLT_MAX; float LL = FLT_MAX; for (int i = LastClosedBar - DLen; i < LastClosedBar; i++) { if (sc.High[i] > HH) HH = sc.High[i]; if (sc.Low[i] < LL) LL = sc.Low[i]; } BreakoutLong = sc.Close[LastClosedBar] > HH; BreakoutShort = sc.Close[LastClosedBar] < LL; } // ---- Chop regime: fade the day's high/low ---- bool FadeLong = false; bool FadeShort = false; if (Regime == -1 && Input_EnableFade.GetYesNo()) { float FadeBuffer = Input_FadeBufferTicks.GetInt() * sc.TickSize; FadeShort = sc.Close[LastClosedBar] >= (r_SessionHigh - FadeBuffer); FadeLong = sc.Close[LastClosedBar] <= (r_SessionLow + FadeBuffer); } // Williams Fractal confirmation gate: High Fractal (resistance-type) // confirms shorts, Low Fractal (support-type) confirms longs. If the // confirmation requirement is off, these are always true (no effect). bool FractalConfirmsShort = !Input_RequireFractalConfirmation.GetYesNo() || (r_HighFractal8thBarOpen > 0.0f && sc.Close[LastClosedBar] <= (r_HighFractal8thBarOpen - sc.TickSize)); bool FractalConfirmsLong = !Input_RequireFractalConfirmation.GetYesNo() || (r_LowFractal8thBarOpen > 0.0f && sc.Close[LastClosedBar] >= (r_LowFractal8thBarOpen + sc.TickSize)); bool WantLong = (BreakoutLong || FadeLong) && Input_AllowLong.GetYesNo() && FractalConfirmsLong; bool WantShort = (BreakoutShort || FadeShort) && Input_AllowShort.GetYesNo() && FractalConfirmsShort; // Don't take both directions off the same bar (shouldn't happen given // the logic above, but guard against it explicitly). if (WantLong && WantShort) return; if (!WantLong && !WantShort) return; s_SCNewOrder NewOrder; NewOrder.OrderQuantity = Input_MaxContracts.GetInt(); NewOrder.OrderType = SCT_ORDERTYPE_MARKET; NewOrder.Target1Offset = TargetTicks * sc.TickSize; NewOrder.AttachedOrderTarget1Type = SCT_ORDERTYPE_LIMIT; NewOrder.Stop1Offset = StopTicks * sc.TickSize; NewOrder.AttachedOrderStop1Type = SCT_ORDERTYPE_STOP; if (WantLong) sc.BuyEntry(NewOrder); else if (WantShort) sc.SellEntry(NewOrder); }