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
|