//+------------------------------------------------------------------+ //| AF_Engine2_Setup.mqh | //| Project : Algo Forge (refactor total) | //| Sumber inspirasi: SniperGold SMC Pro+ (c) Waseem Shahrukh | //| https://www.mql5.com/en/code/75466 | //+------------------------------------------------------------------+ //| ENGINE 2 - F3 CANDIDATE SETUP LAYER (P3-S.13) | //| | //| TUJUAN: materialisasi entitas Candidate Setup (frozen contract | //| docs/SNIPERGOLD_CANONICAL_SETUP_CONTRACT_v1.md): satu setup = | //| satu identitas = satu lifecycle. LAYER INI TIDAK MENGHITUNG | //| primitif sendiri; ia MENGONSUMSI input SEMANTIK per bar: | //| F1 events : sweep/CHoCH {onset, dir} (P3-S.11) | //| F2 zones : OB/FVG {type, formation, dir, mit, invalidated} | //| (P3-S.12) | //| H4/M30 : context STATE (as-of closed bar) | //| M15 : entry CONDITION (direction-compatible) | //| M3 : OPTIONAL micro-confirmation (no veto) | //| dan MENGHASILKAN: AFCandidateSetup (setup_id, direction, state, | //| referensi sweep/choch/zone, creation, M3, reason). | //| | //| KANONIK (frozen): | //| H4 gate -> M30 gate -> fresh sweep EVENT -> CHoCH EVENT after | //| sweep -> unmitigated OB|FVG ZONE -> M15 entry condition -> | //| CANDIDATE_SETUP -> [optional M3 confirmation]. | //| | //| Lifecycle: NONE -> CONTEXT_VALID -> LIQUIDITY_TRIGGERED -> | //| STRUCTURE_CONFIRMED -> ZONE_READY -> ENTRY_ARMED -> | //| CANDIDATE_SETUP -> [M3_CONFIRMED] -> EXPIRED | INVALIDATED | | //| CONSUMED. Tidak ada skip state; tidak ada reaktivasi; maksimum | //| satu entry per setup (frozen §H). | //| | //| IDENTITAS (frozen §E): setup_id monotonic; uniqueness = (dir, | //| sweep onset, choch onset, zone formation, creation). Satu rantai| //| kausal = satu setup_id (dedup by registry, §G). | //| | //| SCORE SEPARATION (frozen §I): score TIDAK pernah menciptakan | //| setup. Layer ini mengekspos setup_exists/setup_id/state; | //| scoring boleh melekat setelahnya. | //| | //| W_setup / W_m3 = OPEN NUMERIC PARAMETERS; nilai di AF_Defines | //| adalah PLACEHOLDER TEST BOUNDARY (bukan hasil optimasi). | //| | //| BAR SEMANTICS (parity with the F3 spec oracle): decisionBar dan | //| event onsets adalah index bar MONOTONIK (bertambah seiring | //| waktu, 0 = bar tertua). Konsumen Engine-1 (reversed index, | //| 0 = bar tertutup terbaru) harus mengonversi: | //| mono = totalClosed - 1 - reversed | //| untuk memberi makan layer ini. | //+------------------------------------------------------------------+ #ifndef AF_ENGINE2_SETUP_MQH #define AF_ENGINE2_SETUP_MQH #include "AF_Engine2_Agents.mqh" //------------------------------------------------------------------ // Lifecycle state (frozen §F) //------------------------------------------------------------------ enum AFSetupState { AF_SETUP_NONE = 0, AF_SETUP_CONTEXT_VALID = 1, AF_SETUP_LIQUIDITY_TRIGGERED= 2, AF_SETUP_STRUCTURE_CONFIRMED= 3, AF_SETUP_ZONE_READY = 4, AF_SETUP_ENTRY_ARMED = 5, AF_SETUP_CANDIDATE = 6, // CANDIDATE_SETUP (terminal formation) AF_SETUP_M3_CONFIRMED = 7, // optional micro-confirmed AF_SETUP_EXPIRED = 8, // terminal AF_SETUP_INVALIDATED = 9, // terminal AF_SETUP_CONSUMED = 10 // terminal (one entry emitted) }; enum AFSetupZoneType { AF_SETUP_ZONE_NONE = 0, AF_SETUP_ZONE_OB = 1, AF_SETUP_ZONE_FVG = 2 }; //------------------------------------------------------------------ // Event input (F1 contract, P3-S.11) — monotonic onset //------------------------------------------------------------------ struct AFSetupEvent { int onset; // M15 bar index (monotonic) of onset; -1 = none int dir; // +1 / -1 / 0 }; //------------------------------------------------------------------ // Zone input (F2 contract, P3-S.12) — semantic projection of AFZoneState //------------------------------------------------------------------ struct AFSetupZone { int type; // AF_SETUP_ZONE_OB / AF_SETUP_ZONE_FVG / NONE int formation; // formation bar (monotonic); -1 = none int dir; // +1 / -1 / 0 AFZoneMitState mit; // F2 mit_state bool invalidated; // F2 terminal flag bool IsActive() const { return (mit==AF_ZONE_UNMITIGATED || mit==AF_ZONE_PARTIALLY_FILLED) && !invalidated; } }; //------------------------------------------------------------------ // Semantic inputs for ONE decision bar (what the F3 layer consumes) //------------------------------------------------------------------ struct AFSetupInputs { int h4; // H4 context STATE direction (+1/-1/0) int m30; // M30 context STATE direction (+1/-1/0) AFSetupEvent sweep; // sweep EVENT (onset + dir) AFSetupEvent choch; // CHoCH EVENT (onset + dir) AFSetupZone zone; // unmitigated OB|FVG ZONE int m15; // M15 entry CONDITION direction (+1/-1/0) int m3; // M3 micro-confirmation (+1/-1/0; 0 = absent) bool entry; // entry signal bound to the valid setup this bar double score; // informational (NEVER creates a setup, §I) }; //------------------------------------------------------------------ // Candidate Setup entity (frozen §E) //------------------------------------------------------------------ struct AFCandidateSetup { int setup_id; // monotonic, assigned at CANDIDATE_SETUP int direction; // +1 bullish / -1 bearish AFSetupState state; // lifecycle // originating liquidity (sweep) int sweep_onset; int sweep_dir; // structure event (choch) int choch_onset; int choch_dir; // zone AFSetupZoneType zone_type; int zone_formation; double zone_top; double zone_bot; int zone_dir; AFZoneMitState zone_mit_at_creation; // entry timeframe = M15 (fixed by construction) int creation_bar; // optional M3 confirmation int m3_bar; // -1 = none // provenance / lifecycle reason string reason; // one-entry bookkeeping (frozen §H) bool entry_bound; }; //------------------------------------------------------------------ // Per-bar outcome (what consumers read) //------------------------------------------------------------------ struct AFSetupOutcome { int bar; bool setup_exists; // CANDIDATE_SETUP / M3_CONFIRMED int setup_id; AFSetupState state; int dir; bool blocked; // gate-fail (OD-4) string block_reason; bool m3_confirmed; bool entry_bound; double score; AFCandidateSetup entity; // full entity (valid when setup_exists) }; //------------------------------------------------------------------ // F3 setup engine — STATEFUL runtime (frozen §F/§G/§H). // Engine-2 detectors are stateless per-bar (P3-S.7 S-ST), but a // Candidate Setup is by definition an ENTITY with identity + lifecycle // that persists across bars (contract §A/§E/§F). This engine holds the // cross-bar state: the current forming/formed setup, the identity // registry (dedup), and the one-entry bookkeeping. It is driven once // per CLOSED M15 bar with semantic inputs (consumed, never recomputed). // The algorithm is a faithful port of the F3 spec oracle // (spec_tests_candidate_setup_runtime.py, canonical_oracle). //------------------------------------------------------------------ class AFSetupEngine { private: AFCandidateSetup m_current; // forming/formed setup (id==0 = none) AFCandidateSetup m_registry[]; // identity registry (dedup by chain key) int m_nextId; int m_entryBindings; // windows (AF_Defines placeholders) int m_wSweep; int m_wChoch; int m_wSetup; int m_wM3; void ResetEntity(AFCandidateSetup &s) const; bool SameChainKey(const AFCandidateSetup &a,const AFCandidateSetup &b) const; int RegistryHas(const AFCandidateSetup &s) const; // -1 = none bool EventActive(const AFSetupEvent &ev,int r,int w) const; void StartChain(const AFSetupInputs &inp); // NONE -> CONTEXT_VALID void Advance(int r,const AFSetupInputs &inp); // forming stages void PostFormation(int r,const AFSetupInputs &inp); // terminal/M3 public: AFSetupEngine() { m_wSweep=40; // W_sweep (frozen §M) m_wChoch=40; // W_choch (frozen §M) m_wSetup=AF_F3_W_SETUP; // placeholder test boundary m_wM3=AF_F3_W_M3; // placeholder test boundary Reset(); } void Reset() { m_nextId=1; m_entryBindings=0; ResetEntity(m_current); ArrayResize(m_registry,0); } void SetWindows(int wSweep,int wChoch,int wSetup,int wM3) { m_wSweep=wSweep; m_wChoch=wChoch; m_wSetup=wSetup; m_wM3=wM3; } // Advance the runtime by ONE closed M15 decision bar. // decisionBar : monotonic M15 bar index (see header bar semantics). // inp : semantic inputs (consumed; not computed here). // out : per-bar outcome (exposed to consumers/scoring). void Step(int decisionBar,const AFSetupInputs &inp,AFSetupOutcome &out); // diagnostics int CreatedCount() const { return m_nextId-1; } int EntryBindings() const { return m_entryBindings; } }; //------------------------------------------------------------------ // Implementasi //------------------------------------------------------------------ void AFSetupEngine::ResetEntity(AFCandidateSetup &s) const { s.setup_id=0; s.direction=0; s.state=AF_SETUP_NONE; s.sweep_onset=-1; s.sweep_dir=0; s.choch_onset=-1; s.choch_dir=0; s.zone_type=AF_SETUP_ZONE_NONE; s.zone_formation=-1; s.zone_top=0.0; s.zone_bot=0.0; s.zone_dir=0; s.zone_mit_at_creation=AF_ZONE_UNMITIGATED; s.creation_bar=-1; s.m3_bar=-1; s.reason=""; s.entry_bound=false; } bool AFSetupEngine::SameChainKey(const AFCandidateSetup &a, const AFCandidateSetup &b) const { return (a.direction==b.direction && a.sweep_onset==b.sweep_onset && a.choch_onset==b.choch_onset && a.zone_formation==b.zone_formation); } int AFSetupEngine::RegistryHas(const AFCandidateSetup &s) const { for(int i=0;i0 && SameChainKey(m_registry[i],s)) return i; return -1; } bool AFSetupEngine::EventActive(const AFSetupEvent &ev,int r,int w) const { if(ev.onset<0) return false; return (ev.onset<=r && (r-ev.onset)<=w); } // NONE -> CONTEXT_VALID (H4+M30 direction-compatible, non-zero; OD-1) void AFSetupEngine::StartChain(const AFSetupInputs &inp) { if(inp.h4==0 || inp.m30==0) return; if(inp.h4!=inp.m30) return; // H4<->M30 conflict -> BLOCKED ResetEntity(m_current); m_current.direction=inp.h4; m_current.state=AF_SETUP_CONTEXT_VALID; m_current.reason="context valid"; } // forming stage transitions (strict order; no skipping; lapsed stage // discards the forming setup, frozen §F) void AFSetupEngine::Advance(int r,const AFSetupInputs &inp) { if(m_current.setup_id>0) return; // already CANDIDATE/terminal if(m_current.state==AF_SETUP_NONE) return; int D=m_current.direction; // ---- re-validate context gates at every decision bar (OD-1) ---- if(inp.h4!=D || inp.m30!=D) { ResetEntity(m_current); m_current.reason="context break"; return; } // ---- re-validate ESTABLISHED legs at every decision bar (§F: a // lapsed stage before CANDIDATE_SETUP discards the forming // setup; e.g. a stale sweep at ZONE_READY -> no setup) ---- if(m_current.sweep_onset>=0) { if(inp.sweep.dir!=D || inp.sweep.onset!=m_current.sweep_onset || !EventActive(inp.sweep,r,m_wSweep)) { ResetEntity(m_current); m_current.reason="sweep lapsed"; return; } } if(m_current.choch_onset>=0) { if(inp.choch.dir!=D || inp.choch.onset!=m_current.choch_onset || !EventActive(inp.choch,r,m_wChoch)) { ResetEntity(m_current); m_current.reason="choch lapsed"; return; } } if(m_current.zone_formation>=0) { if(inp.zone.dir!=D || inp.zone.formation!=m_current.zone_formation || !inp.zone.IsActive()) { ResetEntity(m_current); m_current.reason="zone lapsed"; return; } } // ---- CONTEXT_VALID -> LIQUIDITY_TRIGGERED (fresh sweep EVENT) ---- if(m_current.state==AF_SETUP_CONTEXT_VALID || m_current.state==AF_SETUP_LIQUIDITY_TRIGGERED) { bool sweepPresent=(inp.sweep.dir!=0 && inp.sweep.onset>=0); if(!sweepPresent) { m_current.reason="waiting sweep"; return; } if(inp.sweep.dir!=D) { ResetEntity(m_current); m_current.reason="sweep dir mismatch"; return; } if(!EventActive(inp.sweep,r,m_wSweep)) { ResetEntity(m_current); m_current.reason="stale sweep"; return; } m_current.sweep_onset=inp.sweep.onset; m_current.sweep_dir=inp.sweep.dir; m_current.state=AF_SETUP_LIQUIDITY_TRIGGERED; } // ---- LIQUIDITY_TRIGGERED -> STRUCTURE_CONFIRMED (CHoCH after sweep) ---- if(m_current.state==AF_SETUP_LIQUIDITY_TRIGGERED || m_current.state==AF_SETUP_STRUCTURE_CONFIRMED) { bool chochPresent=(inp.choch.dir!=0 && inp.choch.onset>=0); if(!chochPresent) { m_current.reason="waiting choch"; return; } if(inp.choch.dir!=D) { ResetEntity(m_current); m_current.reason="choch dir mismatch"; return; } // causal order: CHoCH AFTER sweep (monotonic onset) if(inp.choch.onset < m_current.sweep_onset) { ResetEntity(m_current); m_current.reason="choch before sweep"; return; } // chain bound: CHoCH within the sweep validity chain if((inp.choch.onset-m_current.sweep_onset)>m_wSweep) { ResetEntity(m_current); m_current.reason="choch outside sweep window"; return; } // freshness: CHoCH active at the decision bar if(!EventActive(inp.choch,r,m_wChoch)) { ResetEntity(m_current); m_current.reason="stale choch"; return; } m_current.choch_onset=inp.choch.onset; m_current.choch_dir=inp.choch.dir; m_current.state=AF_SETUP_STRUCTURE_CONFIRMED; } // ---- STRUCTURE_CONFIRMED -> ZONE_READY (unmitigated OB|FVG) ---- if(m_current.state==AF_SETUP_STRUCTURE_CONFIRMED || m_current.state==AF_SETUP_ZONE_READY) { if(inp.zone.dir==0 || inp.zone.formation<0) { m_current.reason="waiting zone"; return; } if(inp.zone.dir!=D) { ResetEntity(m_current); m_current.reason="zone dir mismatch"; return; } if(!inp.zone.IsActive()) { ResetEntity(m_current); m_current.reason="zone mitigated/invalidated"; return; } m_current.zone_type=(AFSetupZoneType)inp.zone.type; m_current.zone_formation=inp.zone.formation; m_current.zone_top=0.0; m_current.zone_bot=0.0; m_current.zone_dir=inp.zone.dir; m_current.zone_mit_at_creation=inp.zone.mit; m_current.state=AF_SETUP_ZONE_READY; } // ---- ZONE_READY -> ENTRY_ARMED (M15 entry condition) ---- if(m_current.state==AF_SETUP_ZONE_READY || m_current.state==AF_SETUP_ENTRY_ARMED) { if(inp.m15==0) { m_current.reason="waiting m15 entry"; return; } if(inp.m15!=D) { ResetEntity(m_current); m_current.reason="M15 conflict -> BLOCKED"; return; } m_current.state=AF_SETUP_ENTRY_ARMED; } // ---- ENTRY_ARMED -> CANDIDATE_SETUP (chain complete; identity) ---- if(m_current.state==AF_SETUP_ENTRY_ARMED) { // dedup by construction (§G): same chain key -> no duplicate setup if(RegistryHas(m_current)>=0) { ResetEntity(m_current); m_current.reason="duplicate chain (dedup)"; return; } m_current.setup_id=m_nextId; m_nextId++; m_current.creation_bar=r; m_current.state=AF_SETUP_CANDIDATE; m_current.reason="candidate setup"; int n=ArraySize(m_registry); ArrayResize(m_registry,n+1); m_registry[n]=m_current; } } // post-formation: optional M3 / expiry / invalidation / consumption void AFSetupEngine::PostFormation(int r,const AFSetupInputs &inp) { if(m_current.setup_id<=0) return; int D=m_current.direction; int cb=m_current.creation_bar; // one entry per setup (§H) — checked FIRST so consumption wins if(inp.entry && !m_current.entry_bound) { m_current.entry_bound=true; m_entryBindings++; m_current.state=AF_SETUP_CONSUMED; m_current.reason="consumed by entry"; return; } // zone mitigated after formation -> INVALIDATED (§F) if(inp.zone.dir!=0 && !inp.zone.IsActive()) { m_current.state=AF_SETUP_INVALIDATED; m_current.reason="zone mitigated"; return; } // contrary CHoCH after formation -> INVALIDATED (§F) if(inp.choch.dir==-D) { m_current.state=AF_SETUP_INVALIDATED; m_current.reason="contrary choch"; return; } // context break after formation -> INVALIDATED (§F canonical) if(inp.h4==-D || inp.m30==-D) { m_current.state=AF_SETUP_INVALIDATED; m_current.reason="context break"; return; } // setup validity exceeded -> EXPIRED (§M W_setup) if(cb>=0 && (r-cb)>m_wSetup) { m_current.state=AF_SETUP_EXPIRED; m_current.reason="setup expired"; return; } // optional M3 confirmation (no veto, OD-4) if(m_current.state==AF_SETUP_CANDIDATE) { if(inp.m3==D && (r-cb)<=m_wM3) { m_current.state=AF_SETUP_M3_CONFIRMED; m_current.m3_bar=r; m_current.reason="m3 confirmed"; } else if(inp.m3==-D) { m_current.reason="m3 contrary (not confirmed)"; } } } void AFSetupEngine::Step(int decisionBar,const AFSetupInputs &inp, AFSetupOutcome &out) { out.bar=decisionBar; out.score=inp.score; out.blocked=false; out.block_reason=""; // ---- gates (OD-1/OD-4): conflict -> BLOCKED ---- bool contextConflict=(inp.h4!=0 && inp.m30!=0 && inp.h4!=inp.m30); bool chainConflict=false; int D=0; if(inp.sweep.dir!=0) D=inp.sweep.dir; else if(inp.choch.dir!=0) D=inp.choch.dir; else if(inp.zone.dir!=0) D=inp.zone.dir; if(D!=0 && inp.m15!=0 && inp.m15!=D) chainConflict=true; bool blocked=contextConflict || chainConflict; if(blocked) { out.blocked=true; out.block_reason=contextConflict? "H4_M30_CONFLICT" : "M15_CONFLICT"; } // ---- forming / active handling (only when not blocked) ---- if(!blocked) { if(m_current.setup_id==0) { // no active setup: start a new chain if gates permit (OD-1) StartChain(inp); Advance(decisionBar,inp); } else if(m_current.state==AF_SETUP_CONSUMED || m_current.state==AF_SETUP_EXPIRED || m_current.state==AF_SETUP_INVALIDATED) { // terminal slot: free; a NEW chain may start on a later bar ResetEntity(m_current); StartChain(inp); Advance(decisionBar,inp); } else { Advance(decisionBar,inp); } } // ---- post-formation ALWAYS for an active setup (even if this bar // is gate-blocked — context break invalidation still applies) ---- if(m_current.setup_id>0 && (m_current.state==AF_SETUP_CANDIDATE || m_current.state==AF_SETUP_M3_CONFIRMED)) { PostFormation(decisionBar,inp); } // ---- expose outcome ---- out.dir=m_current.direction; out.state=m_current.state; out.setup_id=m_current.setup_id; out.setup_exists=(m_current.setup_id>0 && (m_current.state==AF_SETUP_CANDIDATE || m_current.state==AF_SETUP_M3_CONFIRMED)); out.m3_confirmed=(m_current.state==AF_SETUP_M3_CONFIRMED); out.entry_bound=m_current.entry_bound; out.entity=m_current; // ---- terminal -> slot free; a NEW chain may start next bar ---- if(m_current.setup_id>0 && (m_current.state==AF_SETUP_CONSUMED || m_current.state==AF_SETUP_EXPIRED || m_current.state==AF_SETUP_INVALIDATED)) { ResetEntity(m_current); } } #endif // AF_ENGINE2_SETUP_MQH