//+------------------------------------------------------------------+ //| ICT EA – FVG Edition | //| Architecture: 3 TF | 4 State Machine | //+------------------------------------------------------------------+ #property strict //==================================================== // INPUTS //==================================================== input ENUM_TIMEFRAMES InpBiasTF = PERIOD_D1; // Bias Timeframe input ENUM_TIMEFRAMES InpMiddleTF = PERIOD_H1; // Middle TF – nơi quét FVG input ENUM_TIMEFRAMES InpTriggerTF = PERIOD_M5; // Trigger TF – xác nhận vào lệnh input double InpRiskPercent = 1.0; input double InpRiskReward = 2.0; input double InpMaxDailyLossPct = 3.0; input int InpLondonStartHour = 8; input int InpLondonEndHour = 17; input int InpNYStartHour = 13; input int InpNYEndHour = 22; input int InpSessionAvoidLastMin = 60; input int InpFVGMaxAliveMin = 180; // FVG sống tối đa bao nhiêu phút input int InpTriggerMaxBars = 30; // Số bar TriggerTF chờ sau khi touched input bool InpDebugLog = true; input bool InpDebugDraw = true; //==================================================== // ENUMS //==================================================== enum EAState { EA_IDLE, // Chờ điều kiện: bias + structure + FVG candidate EA_WAIT_TOUCH, // Có FVG candidate, chờ price chạm vào vùng FVG EA_WAIT_TRIGGER, // Price đã chạm FVG, chờ Trigger TF xác nhận entry EA_IN_TRADE // Đang có lệnh, chờ kết thúc }; enum HTFBias { BIAS_NONE = 0, BIAS_UP = 1, BIAS_DOWN = -1, BIAS_SIDEWAY = 2 }; enum MarketDir { DIR_NONE = 0, DIR_UP = 1, DIR_DOWN = -1 }; enum BlockReason { BLOCK_NONE, BLOCK_SESSION, BLOCK_DAILY_LOSS, BLOCK_BIAS_MISMATCH, BLOCK_NO_BIAS }; //==================================================== // STRUCTS //==================================================== // --- Bias từ Bias TF (D1) struct BiasContext { HTFBias bias; double rangeHigh; // Dùng khi sideway double rangeLow; datetime lastBarTime; // Guard: chỉ update khi bar mới }; // --- FVG đang theo dõi (quét từ Middle TF) struct FVGContext { bool active; // Đang có FVG candidate bool touched; // Price đã chạm vào FVG chưa MarketDir direction; // FVG thuận chiều bias double high; // Cạnh trên FVG double low; // Cạnh dưới FVG double mid; // Midpoint – dùng làm entry tham chiếu datetime createdTime; // Thời điểm FVG hình thành datetime touchTime; // Thời điểm price chạm lần đầu int barsAlive; // Số bar TriggerTF đã qua kể từ touched int touchBarIdx; // Bar index TriggerTF lúc touched }; // --- Structure của Trigger TF (sau khi FVG touched) struct TriggerContext { bool valid; MarketDir direction; double swingHigh; double swingLow; int idxHigh; int idxLow; double breakLevel; // Level cần phá để confirm entry }; // --- Kế hoạch lệnh struct OrderPlan { bool valid; int direction; double entry; double stopLoss; double takeProfit; double lot; }; // --- Risk quản lý theo ngày struct DailyRiskContext { double startBalance; datetime dayStartTime; bool limitHit; }; //==================================================== // GLOBAL STATE //==================================================== EAState g_State = EA_IDLE; BlockReason g_BlockReason = BLOCK_NONE; BiasContext g_Bias; FVGContext g_FVG; TriggerContext g_Trigger; DailyRiskContext g_DailyRisk; int g_TradeBarIndex = -1; //==================================================== // CONTEXT UPDATERS // Mỗi hàm có guard – chỉ chạy khi bar mới //==================================================== void UpdateBiasContext() { // TODO: So sánh bar 1 và bar 2 của BiasTF // → set g_Bias.bias } void UpdateDailyRiskContext() { // TODO: Reset startBalance khi ngày mới // Kiểm tra balance hiện tại vs startBalance // → set g_DailyRisk.limitHit } void UpdateAllContexts() { UpdateDailyRiskContext(); UpdateBiasContext(); // Không có StructureContext riêng nữa // Structure check sẽ nằm trong từng state handler khi cần } //==================================================== // GUARDS //==================================================== bool IsSessionAllowed() { return true; // TODO } bool IsDailyLossOK() { return !g_DailyRisk.limitHit; } bool IsBiasValid() { return g_Bias.bias == BIAS_UP || g_Bias.bias == BIAS_DOWN; } bool EvaluateGuards() { g_BlockReason = BLOCK_NONE; if (!IsSessionAllowed()) { g_BlockReason = BLOCK_SESSION; return false; } if (!IsDailyLossOK()) { g_BlockReason = BLOCK_DAILY_LOSS; return false; } if (!IsBiasValid()) { g_BlockReason = BLOCK_NO_BIAS; return false; } return true; } //==================================================== // TRANSITIONS //==================================================== void TransitionTo(EAState next) { if (InpDebugLog) PrintFormat("[STATE] %s → %s", EnumToString(g_State), EnumToString(next)); g_State = next; } void ResetToIdle(string reason = "") { if (InpDebugLog && reason != "") PrintFormat("[RESET] %s", reason); ZeroMemory(g_FVG); ZeroMemory(g_Trigger); g_TradeBarIndex = -1; TransitionTo(EA_IDLE); } //==================================================== // STATE HANDLERS //==================================================== void OnStateIdle() { // Điều kiện: bias phải rõ (UP hoặc DOWN) // TODO: Quét Middle TF tìm FVG candidate khớp với bias // Nếu tìm được FVG hợp lệ: // → g_FVG = // → g_FVG.active = true // → TransitionTo(EA_WAIT_TOUCH) } void OnStateWaitTouch() { // TODO: Kiểm tra FVG còn hợp lệ không // - Expired (quá InpFVGMaxAliveMin)? // - Bị invalidate (price close xuyên qua FVG ngược chiều)? // → ResetToIdle() // TODO: Kiểm tra price có nằm trong vùng FVG không // Nếu có: // → g_FVG.touched = true // → g_FVG.touchTime = now // → g_FVG.touchBarIdx = // → TransitionTo(EA_WAIT_TRIGGER) } void OnStateWaitTrigger() { // TODO: Kiểm tra FVG có bị phá không → ResetToIdle() // TODO: Đếm barsAlive, nếu > InpTriggerMaxBars → ResetToIdle() // TODO: Mỗi bar mới của TriggerTF: // → UpdateTriggerContext() // TODO: Kiểm tra điều kiện trigger (vd: structure break trên TriggerTF) // Nếu đủ: // → plan = BuildOrderPlan() // → ExecuteOrder(plan) // → g_TradeBarIndex = Bars(_Symbol, InpTriggerTF) // → TransitionTo(EA_IN_TRADE) } void OnStateInTrade() { // Không xử lý trong cùng bar vừa gửi lệnh if (Bars(_Symbol, InpTriggerTF) <= g_TradeBarIndex) return; // TODO: Kiểm tra position còn sống không // (PositionSelect hoặc OrdersTotal) // Nếu lệnh đã đóng → ResetToIdle() } //==================================================== // STATE MACHINE //==================================================== void RunStateMachine() { switch (g_State) { case EA_IDLE: OnStateIdle(); break; case EA_WAIT_TOUCH: OnStateWaitTouch(); break; case EA_WAIT_TRIGGER: OnStateWaitTrigger(); break; case EA_IN_TRADE: OnStateInTrade(); break; } } //==================================================== // DRAW (stub) //==================================================== void DrawVisuals() { /* TODO */ } //==================================================== // EA LIFECYCLE //==================================================== int OnInit() { ZeroMemory(g_Bias); ZeroMemory(g_FVG); ZeroMemory(g_Trigger); ZeroMemory(g_DailyRisk); g_State = EA_IDLE; g_BlockReason = BLOCK_NONE; g_TradeBarIndex = -1; // Chạy update ngay lần đầu để có data sẵn UpdateAllContexts(); PrintFormat("✅ EA initialized | BiasTF=%s | MiddleTF=%s | TriggerTF=%s", EnumToString(InpBiasTF), EnumToString(InpMiddleTF), EnumToString(InpTriggerTF)); return INIT_SUCCEEDED; } void OnTick() { UpdateAllContexts(); if (EvaluateGuards()) RunStateMachine(); else ResetToIdle(EnumToString(g_BlockReason)); if (InpDebugDraw) DrawVisuals(); } void OnDeinit(const int reason) { // TODO: ObjectsDeleteAll hoặc xóa từng object đã vẽ PrintFormat("EA deinitialized | reason=%d", reason); }