forked from chiki2bum2/SniperGold_ML
563 lines
21 KiB
MQL5
563 lines
21 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| 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;i<ArraySize(m_registry);i++)
| |||
if(m_registry[i].setup_id>0 && 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
|