diff --git a/.cursor/rules/ict2026-readme.mdc b/.cursor/rules/ict2026-readme.mdc new file mode 100644 index 0000000..5cc47ca --- /dev/null +++ b/.cursor/rules/ict2026-readme.mdc @@ -0,0 +1,17 @@ +--- +description: ICT2026 — luôn cập nhật README khi đổi logic +globs: Include/ICT2026/**,Experts/ICT_2026.mq5 +alwaysApply: false +--- + +# ICT2026 — cập nhật README bắt buộc + +Khi sửa bất kỳ logic nào trong `Include/ICT2026/` hoặc `Experts/ICT_2026.mq5`: + +1. **Cập nhật `Include/ICT2026/README.md` trong cùng lần sửa code** +2. Sửa mục logic tương ứng (pipeline, BOS/CHoCH, veto, input, API) +3. Thêm dòng vào mục **Changelog** (version + mô tả ngắn) + +README là **single source of truth** cho business logic ICT2026 — không chỉ comment trong code. + +Không merge/t commit logic mới mà thiếu cập nhật README. diff --git a/Experts/ICT_2026.mq5 b/Experts/ICT_2026.mq5 new file mode 100644 index 0000000..68cc3b8 --- /dev/null +++ b/Experts/ICT_2026.mq5 @@ -0,0 +1,95 @@ +//+------------------------------------------------------------------+ +//| ICT_2026.mq5 — Daily Bias + Intraday Structure (H1) | +//| BOS = Continue | CHoCH = Reversal | +//+------------------------------------------------------------------+ +#property copyright "ICT 2026" +#property version "1.112" +#property description "Daily Bias + Intraday H1 + iTF FVG + IsAllowTrade" + +#include +#include +#include +#include +#include +#include + +//+------------------------------------------------------------------+ +int OnInit() +{ + const bool biasOk = IctDailyBias_Init(_Symbol); + const bool intraOk = IctIntraday_Init(_Symbol); + const bool lowOk = IctLowTfTrend_Init(_Symbol); + + if(!biasOk) + Print("[ICT2026] Daily Bias: chưa đủ dữ liệu ", EnumToString(InpBiasTf)); + if(!intraOk) + Print("[ICT2026] Intraday: chưa đủ dữ liệu ", EnumToString(InpIntradayTf)); + if(!lowOk) + Print("[ICT2026] LowTF/FVG: chưa đủ dữ liệu ", EnumToString(InpFvgTf)); + + if(biasOk || intraOk) + { + PrintFormat("[ICT2026] Init — Bias=%s | Intraday=%s | Allow=%s | FVG=%d", + IctBiasDisplayShort(g_ictDailyBias.bias), + IctTrendDisplayShort(g_ictIntraday.trend), + g_ictIntraday.isAllowTrade ? "true" : "false", + g_ictLowTf.availableCount); + IctPanel_Render(_Symbol); + IctDraw_Render(_Symbol); + } + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + IctPanel_Clear(); + IctDraw_Clear(); + IctFvgDraw_DeleteAll(); + ChartRedraw(); +} + +//+------------------------------------------------------------------+ +void OnTick() +{ + static bool panelBoot = false; + + bool refresh = false; + if(IctDailyBias_Update(_Symbol)) + refresh = true; + if(IctIntraday_Update(_Symbol)) + refresh = true; + if(IctLowTfTrend_Update(_Symbol)) + refresh = true; + + IctLowTfTrend_TickRefresh(_Symbol); + + if(refresh || !panelBoot) + { + IctPanel_Render(_Symbol); + IctDraw_Render(_Symbol); + panelBoot = true; + } +} + +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + if(id == CHARTEVENT_CHART_CHANGE) + { + IctPanel_Render(_Symbol); + IctDraw_Render(_Symbol); + IctFvgDraw_Render(_Symbol); + } +} + +//+------------------------------------------------------------------+ +ENUM_ICT_BIAS ICT2026_GetDailyBias() { return g_ictDailyBias.bias; } + +ENUM_ICT_TREND ICT2026_GetIntradayTrend() { return g_ictIntraday.trend; } + +bool ICT2026_IsAllowTrade() { return g_ictIntraday.isAllowTrade; } + +int ICT2026_GetFvgAvailableCount() { return g_ictLowTf.availableCount; } + +//+------------------------------------------------------------------+ diff --git a/Experts/ICT_2026_compile.log b/Experts/ICT_2026_compile.log new file mode 100644 index 0000000..e109db4 Binary files /dev/null and b/Experts/ICT_2026_compile.log differ diff --git a/Include/ICT2026/Config.mqh b/Include/ICT2026/Config.mqh new file mode 100644 index 0000000..aca6563 --- /dev/null +++ b/Include/ICT2026/Config.mqh @@ -0,0 +1,64 @@ +//+------------------------------------------------------------------+ +//| Config.mqh — ICT 2026 inputs | +//+------------------------------------------------------------------+ +#ifndef ICT2026_CONFIG_MQH +#define ICT2026_CONFIG_MQH + +#include + +input group "══ Daily Bias ══" +input ENUM_TIMEFRAMES InpBiasTf = PERIOD_D1; // TF xác định Daily Bias +input int InpSwingRange = 2; // Pivot: nến mỗi bên +input int InpSwingLookback = 300; // Quét swing lịch sử +input double InpFibMinRatio = 0.382; // Fib tối thiểu (0 = tắt) +input bool InpRequireFib = false; // Bắt buộc Fib OK cho structural + +input group "══ Intraday Structure ══" +input ENUM_TIMEFRAMES InpIntradayTf = PERIOD_H1; // TF intraday (mặc định H1) +input int InpIntradaySwingRange = 2; // Pivot intraday +input int InpIntradaySwingLookback = 120; // Lookback quét pivot H1 +input int InpIntradayRecentBars = 80; // Chỉ dùng pivot trong N bar gần nhất + +input group "══ Hiển thị ══" +input bool InpDrawPanel = true; // Panel bias góc trên trái +input int InpPanelX = 12; +input int InpPanelY = 28; +input int InpPanelFontSize = 10; +input int InpPanelLineSpacing = 22; // Khoảng cách mỗi dòng (pixel) + +input group "══ Vẽ trên chart ══" +input bool InpDrawChartLabels = true; // Bật vẽ label swing trên chart +input bool InpDrawBiasSwingLabels = true; // bH0–bL1 (theo TF chart) +input bool InpDrawIntraSwingLabels = true; // iH0–iL1 (theo TF chart) +input bool InpDrawConfirmLabels = true; // H0–L1 confirm (theo TF chart) +input int InpChartLabelFontSize = 9; + +input group "══ Confirm swing (M5) ══" +input ENUM_TIMEFRAMES InpConfirmTf = PERIOD_M5; // TF confirmation (tương lai) +input int InpConfirmSwingRange = 2; +input int InpConfirmSwingLookback = 80; +input int InpConfirmRecentBars = 40; + +input group "══ Low TF / iTF FVG ══" +input ENUM_TIMEFRAMES InpLowTf = PERIOD_M5; // Low TF trend (tương lai) +input ENUM_TIMEFRAMES InpFvgTf = PERIOD_H1; // Quét FVG (mặc định = Intraday) +input int InpFvgLookbackBars = 60; // Bar quét FVG (full / khi Allow bật) +input int InpFvgScanBarsPerUpdate = 40; // Mỗi nến mới: quét lại N bar gần nhất +input int InpFvgAtrPeriod = 14; // ATR cho lọc kích thước gap +input double InpFvgMinGapATRPct = 12.0; // Gap tối thiểu (% ATR, 0=tắt) +input double InpFvgMinGapPoints = 0.0; // Gap tối thiểu (giá, 0=chỉ ATR) +input double InpFvgMinGapVsBarPct = 25.0; // Gap >= % range nến giữa B (0=tắt) +input double InpFvgUsedFillPct = 38.2; // % lấp FVG → Used +input int InpFvgExpireDays = 3; // Xóa Available sau N ngày +input int InpFvgMaxZones = 24; // Số FVG tối đa trên chart +input bool InpDrawFvgZones = true; // Vẽ FVG + Premium/Discount +input color InpFvgBullColor = clrDodgerBlue; +input color InpFvgBearColor = clrOrangeRed; +input color InpFvgUsedColor = clrDimGray; +input color InpPdPremiumColor = clrMaroon; +input color InpPdDiscountColor = clrDarkGreen; + +input group "══ Debug ══" +input bool InpDebug = true; + +#endif diff --git a/Include/ICT2026/DailyBias.mqh b/Include/ICT2026/DailyBias.mqh new file mode 100644 index 0000000..a5301a9 --- /dev/null +++ b/Include/ICT2026/DailyBias.mqh @@ -0,0 +1,326 @@ +//+------------------------------------------------------------------+ +//| DailyBias.mqh — Bias rõ (HH-HL/LH-LL) vs sớm (sau CHoCH) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_DAILYBIAS_MQH +#define ICT2026_DAILYBIAS_MQH + +#include +#include +#include +#include + +IctDailyBiasState g_ictDailyBias; +IctTrendResolveCtx g_ictDailyCtx; + +ENUM_ICT_BIAS IctBiasFromTrend(const ENUM_ICT_TREND trend) +{ + if(trend == ICT_TREND_UP) + return ICT_BIAS_BULL; + if(trend == ICT_TREND_DOWN) + return ICT_BIAS_BEAR; + return ICT_BIAS_NONE; +} + +void IctUpdateHTFBias(const string sym, const ENUM_TIMEFRAMES tf) +{ + IctHTFBiasState state = g_ictDailyBias.htf; + const datetime tf0Time = iTime(sym, tf, 0); + if(tf0Time == state.lastUpdateTime) + return; + + state.lastUpdateTime = tf0Time; + + const double b1Close = iClose(sym, tf, 1); + const double b1High = iHigh(sym, tf, 1); + const double b1Low = iLow(sym, tf, 1); + const double b2High = iHigh(sym, tf, 2); + const double b2Low = iLow(sym, tf, 2); + + state.rangeHigh = 0.0; + state.rangeLow = 0.0; + + if(b1Close > b2High) + state.bias = ICT_HTF_UP; + else if(b1Close < b2Low) + state.bias = ICT_HTF_DOWN; + else if(b1High <= b2High && b1Low >= b2Low) + { + state.bias = ICT_HTF_SIDEWAY; + state.rangeHigh = b2High; + state.rangeLow = b2Low; + } + else if(b1Close < b2High && b1Close > b2Low) + { + state.bias = ICT_HTF_SIDEWAY; + state.rangeHigh = b1High; + state.rangeLow = b1Low; + } + else + state.bias = ICT_HTF_NONE; + + g_ictDailyBias.htf = state; +} + +ENUM_ICT_BIAS IctBiasFromHtf(IctHTFBiasState &htf) +{ + if(htf.bias == ICT_HTF_UP) + return ICT_BIAS_BULL; + if(htf.bias == ICT_HTF_DOWN) + return ICT_BIAS_BEAR; + if(htf.bias == ICT_HTF_SIDEWAY) + return ICT_BIAS_RANGE; + return ICT_BIAS_NONE; +} + +string IctBiasText(const ENUM_ICT_BIAS bias) +{ + switch(bias) + { + case ICT_BIAS_BULL: return "BULL"; + case ICT_BIAS_BEAR: return "BEAR"; + case ICT_BIAS_RANGE: return "RANGE"; + default: return "NONE"; + } +} + +string IctStructText(const ENUM_ICT_STRUCT s) +{ + switch(s) + { + case ICT_STRUCT_BULL: return "BULL"; + case ICT_STRUCT_BEAR: return "BEAR"; + default: return "NONE"; + } +} + +string IctMsEventText(const ENUM_ICT_MS_EVENT ev) +{ + switch(ev) + { + case ICT_MS_BOS: return "BOS (Continue)"; + case ICT_MS_CHOCH: return "CHoCH (Reversal)"; + default: return "None"; + } +} + +string IctHtfBiasText(const ENUM_ICT_HTF_BIAS bias) +{ + switch(bias) + { + case ICT_HTF_UP: return "UP"; + case ICT_HTF_DOWN: return "DOWN"; + case ICT_HTF_SIDEWAY: return "SIDEWAY"; + default: return "NONE"; + } +} + +string IctBiasDisplayShort(const ENUM_ICT_BIAS bias) +{ + if(bias == ICT_BIAS_BULL) + return IctTrendPhaseDisplay(ICT_TREND_UP, g_ictDailyBias.biasPhase); + if(bias == ICT_BIAS_BEAR) + return IctTrendPhaseDisplay(ICT_TREND_DOWN, g_ictDailyBias.biasPhase); + switch(bias) + { + case ICT_BIAS_RANGE: return "Range"; + default: return "None"; + } +} + +void IctDailyBias_SyncFromCtx(IctSwingSet &sw, const ENUM_ICT_STRUCT pivotStruct) +{ + g_ictDailyBias.bias = IctBiasFromTrend(g_ictDailyCtx.trend); + g_ictDailyBias.biasPhase = g_ictDailyCtx.phase; + g_ictDailyBias.lastEvent = g_ictDailyCtx.lastEvent; + g_ictDailyBias.lastClearStructural = g_ictDailyCtx.lastClearStructural; + g_ictDailyBias.swings = sw; + g_ictDailyBias.structural = pivotStruct; + + if(g_ictDailyBias.bias == ICT_BIAS_BULL || g_ictDailyBias.bias == ICT_BIAS_BEAR) + { + const ENUM_ICT_STRUCT keyStruct = (g_ictDailyBias.bias == ICT_BIAS_BULL) + ? ICT_STRUCT_BULL : ICT_STRUCT_BEAR; + if(sw.IsComplete()) + IctGetKeyLevels(keyStruct, sw, g_ictDailyBias.keyLv1, g_ictDailyBias.keyLv2); + } + else + { + g_ictDailyBias.keyLv1 = 0.0; + g_ictDailyBias.keyLv2 = 0.0; + } +} + +void IctDailyBias_ApplyHtfFallback() +{ + IctHTFBiasState htfRef = g_ictDailyBias.htf; + g_ictDailyBias.bias = IctBiasFromHtf(htfRef); + g_ictDailyBias.biasPhase = ICT_TREND_PHASE_NONE; + switch(g_ictDailyBias.htf.bias) + { + case ICT_HTF_UP: + g_ictDailyBias.reason = "Fallback: outside day UP (close > prior high)"; + break; + case ICT_HTF_DOWN: + g_ictDailyBias.reason = "Fallback: outside day DOWN (close < prior low)"; + break; + case ICT_HTF_SIDEWAY: + g_ictDailyBias.reason = StringFormat("Fallback: range [%.5f .. %.5f]", + g_ictDailyBias.htf.rangeLow, + g_ictDailyBias.htf.rangeHigh); + break; + default: + g_ictDailyBias.reason = "Fallback: không đủ structural + HTF indecision"; + break; + } +} + +string IctBuildDisplayReason(IctDailyBiasState &st, + const string sym, const ENUM_TIMEFRAMES tf) +{ + if(st.bias == ICT_BIAS_BULL || st.bias == ICT_BIAS_BEAR) + { + const ENUM_ICT_TREND trend = (st.bias == ICT_BIAS_BULL) ? ICT_TREND_UP : ICT_TREND_DOWN; + string parts = IctBuildTrendDisplayReason(st.swings, st.structural, + trend, st.biasPhase, st.lastEvent, "D"); + if(st.htf.bias == ICT_HTF_UP) + { + parts += ", D[1].close > D[2].High"; + } + else if(st.htf.bias == ICT_HTF_DOWN) + { + parts += ", D[1].close < D[2].Low"; + } + return parts; + } + + string parts = IctStructPatternText(st.structural); + if(st.htf.bias == ICT_HTF_UP) + { + if(parts != "") + parts += ", "; + parts += "D[1].close > D[2].High"; + } + else if(st.htf.bias == ICT_HTF_DOWN) + { + if(parts != "") + parts += ", "; + parts += "D[1].close < D[2].Low"; + } + else if(st.htf.bias == ICT_HTF_SIDEWAY) + { + const double b1High = iHigh(sym, tf, 1); + const double b1Low = iLow(sym, tf, 1); + const double b2High = iHigh(sym, tf, 2); + const double b2Low = iLow(sym, tf, 2); + if(parts != "") + parts += ", "; + if(b1High <= b2High && b1Low >= b2Low) + parts += "Inside D[1] trong D[2]"; + else + parts += "Indecision D[1] trong D[2]"; + } + + if(parts == "") + parts = "Không xác định"; + + return parts; +} + +void IctDailyBias_Reset() +{ + g_ictDailyCtx.Reset(); + g_ictDailyBias.bias = ICT_BIAS_NONE; + g_ictDailyBias.biasPhase = ICT_TREND_PHASE_NONE; + g_ictDailyBias.structural = ICT_STRUCT_NONE; + g_ictDailyBias.lastEvent = ICT_MS_NONE; + g_ictDailyBias.fibOk = false; + g_ictDailyBias.swings.Clear(); + g_ictDailyBias.keyLv1 = 0.0; + g_ictDailyBias.keyLv2 = 0.0; + g_ictDailyBias.lastClearStructural = ICT_STRUCT_NONE; + g_ictDailyBias.pdh = 0.0; + g_ictDailyBias.pdl = 0.0; + g_ictDailyBias.pdc = 0.0; + g_ictDailyBias.eq = 0.0; + g_ictDailyBias.htf.bias = ICT_HTF_NONE; + g_ictDailyBias.htf.rangeHigh = 0.0; + g_ictDailyBias.htf.rangeLow = 0.0; + g_ictDailyBias.htf.lastUpdateTime = 0; + g_ictDailyBias.lastBarTime = 0; + g_ictDailyBias.reason = ""; + g_ictDailyBias.displayReason = ""; +} + +bool IctDailyBias_Init(const string sym) +{ + IctDailyBias_Reset(); + if(Bars(sym, InpBiasTf) < 10) + return false; + + g_ictDailyBias.htf.lastUpdateTime = 0; + IctDailyBias_Update(sym, true); + return true; +} + +bool IctDailyBias_Update(const string sym, const bool force = false) +{ + const ENUM_TIMEFRAMES tf = InpBiasTf; + const datetime bar0 = iTime(sym, tf, 0); + if(!force && bar0 == g_ictDailyBias.lastBarTime) + return false; + + g_ictDailyBias.lastBarTime = bar0; + + g_ictDailyBias.pdh = iHigh(sym, tf, 1); + g_ictDailyBias.pdl = iLow(sym, tf, 1); + g_ictDailyBias.pdc = iClose(sym, tf, 1); + g_ictDailyBias.eq = (g_ictDailyBias.pdh + g_ictDailyBias.pdl) * 0.5; + + IctUpdateHTFBias(sym, tf); + + IctSwingSet sw; + ENUM_ICT_STRUCT pivotStruct = ICT_STRUCT_NONE; + sw = IctBuildSwingSet(sym, tf, InpSwingRange, InpSwingLookback, pivotStruct); + g_ictDailyBias.fibOk = sw.IsComplete() && IctIsPullbackValid(sw, InpFibMinRatio); + if(InpRequireFib && !g_ictDailyBias.fibOk) + { + sw.Clear(); + pivotStruct = ICT_STRUCT_NONE; + } + + IctResolveTrend(sym, tf, sw, pivotStruct, g_ictDailyCtx, 1); + IctDailyBias_SyncFromCtx(sw, pivotStruct); + + if(g_ictDailyBias.bias == ICT_BIAS_NONE) + IctDailyBias_ApplyHtfFallback(); + else + g_ictDailyBias.reason = StringFormat("Structural: %s", + IctBiasText(g_ictDailyBias.bias)); + + g_ictDailyBias.displayReason = IctBuildDisplayReason(g_ictDailyBias, sym, tf); + + if(InpDebug) + { + PrintFormat("[ICT2026/DailyBias] %s %s | Bias=%s | Phase=%d | Struct=%s | Event=%s | HTF=%s", + sym, EnumToString(tf), + IctBiasDisplayShort(g_ictDailyBias.bias), + g_ictDailyBias.biasPhase, + IctStructText(g_ictDailyBias.structural), + IctMsEventText(g_ictDailyBias.lastEvent), + IctHtfBiasText(g_ictDailyBias.htf.bias)); + if(g_ictDailyBias.swings.IsComplete()) + PrintFormat(" H0=%.5f H1=%.5f L0=%.5f L1=%.5f | Key1=%.5f Key2=%.5f | %s", + g_ictDailyBias.swings.h0.price, g_ictDailyBias.swings.h1.price, + g_ictDailyBias.swings.l0.price, g_ictDailyBias.swings.l1.price, + g_ictDailyBias.keyLv1, g_ictDailyBias.keyLv2, + g_ictDailyBias.reason); + } + return true; +} + +void IctDailyBias_Get(IctDailyBiasState &out) { out = g_ictDailyBias; } + +bool IctDailyBias_IsBull() { return g_ictDailyBias.bias == ICT_BIAS_BULL; } +bool IctDailyBias_IsBear() { return g_ictDailyBias.bias == ICT_BIAS_BEAR; } + +#endif diff --git a/Include/ICT2026/Draw.mqh b/Include/ICT2026/Draw.mqh new file mode 100644 index 0000000..22aef33 --- /dev/null +++ b/Include/ICT2026/Draw.mqh @@ -0,0 +1,139 @@ +//+------------------------------------------------------------------+ +//| Draw.mqh — label swing theo TF chart hiện tại | +//| Chart lớn (D1): b* | Chart H1: b*+i* | Chart M5: b*+i*+H0–L1 | +//+------------------------------------------------------------------+ +#ifndef ICT2026_DRAW_MQH +#define ICT2026_DRAW_MQH + +#include +#include +#include + +const string ICT26_DRAW_PFX = "ICT26_DR_"; + +int IctDraw_ChartPeriodSeconds() +{ + return (int)PeriodSeconds((ENUM_TIMEFRAMES)_Period); +} + +// Chỉ vẽ swing TF hiện tại + TF lớn hơn; ẩn swing TF nhỏ hơn chart +void IctDraw_ResolveSwingVisibility(bool &showBias, bool &showIntra, bool &showConfirm) +{ + showBias = false; + showIntra = false; + showConfirm = false; + + const int chartSec = IctDraw_ChartPeriodSeconds(); + const int biasSec = (int)PeriodSeconds(InpBiasTf); + const int intraSec = (int)PeriodSeconds(InpIntradayTf); + const int confirmSec = (int)PeriodSeconds(InpConfirmTf); + + if(chartSec <= 0 || biasSec <= 0 || intraSec <= 0 || confirmSec <= 0) + return; + + showConfirm = (chartSec <= confirmSec); + showIntra = (chartSec <= intraSec); + showBias = (chartSec <= biasSec) || (chartSec >= biasSec); +} + +void IctDraw_DeleteByPrefix(const string prefix) +{ + const long ch = ChartID(); + for(int i = ObjectsTotal(ch, 0, -1) - 1; i >= 0; i--) + { + const string name = ObjectName(ch, i, 0, -1); + if(StringFind(name, prefix) == 0) + ObjectDelete(ch, name); + } +} + +void IctDraw_Clear() +{ + const long ch = ChartID(); + IctDraw_DeleteByPrefix(ICT26_DRAW_PFX); + ObjectDelete(ch, ICT26_DRAW_PFX + "TXT_DAILY"); + ObjectDelete(ch, ICT26_DRAW_PFX + "TXT_INTRA"); +} + +void IctDraw_SwingLabel(const string id, const string text, + const datetime t, const double price, + const bool isHigh, const color clr) +{ + const long ch = ChartID(); + if(ObjectFind(ch, id) < 0) + ObjectCreate(ch, id, OBJ_TEXT, 0, t, price); + ObjectMove(ch, id, 0, t, price); + ObjectSetString(ch, id, OBJPROP_TEXT, text); + ObjectSetInteger(ch, id, OBJPROP_COLOR, clr); + ObjectSetInteger(ch, id, OBJPROP_FONTSIZE, InpChartLabelFontSize); + ObjectSetString(ch, id, OBJPROP_FONT, "Arial Bold"); + ObjectSetInteger(ch, id, OBJPROP_ANCHOR, isHigh ? ANCHOR_BOTTOM : ANCHOR_TOP); + ObjectSetInteger(ch, id, OBJPROP_SELECTABLE, false); +} + +void IctDraw_SwingSet(const string objPfx, const string tagPfx, + IctSwingSet &sw, + const color clrHi, const color clrLo) +{ + if(!sw.IsComplete()) + return; + + IctDraw_SwingLabel(objPfx + tagPfx + "H0", tagPfx + "H0", + sw.h0.time, sw.h0.price, true, clrHi); + IctDraw_SwingLabel(objPfx + tagPfx + "H1", tagPfx + "H1", + sw.h1.time, sw.h1.price, true, clrHi); + IctDraw_SwingLabel(objPfx + tagPfx + "L0", tagPfx + "L0", + sw.l0.time, sw.l0.price, false, clrLo); + IctDraw_SwingLabel(objPfx + tagPfx + "L1", tagPfx + "L1", + sw.l1.time, sw.l1.price, false, clrLo); +} + +bool IctDraw_BuildConfirmSwings(const string sym, IctSwingSet &sw) +{ + sw.Clear(); + ENUM_ICT_STRUCT structural = ICT_STRUCT_NONE; + return IctBuildSwingSetRecentPivots(sym, InpConfirmTf, + InpConfirmSwingRange, + InpConfirmSwingLookback, + InpConfirmRecentBars, + sw, structural); +} + +void IctDraw_Render(const string sym) +{ + if(!InpDrawChartLabels) + { + IctDraw_Clear(); + return; + } + + IctDraw_Clear(); + + bool showBias = false, showIntra = false, showConfirm = false; + IctDraw_ResolveSwingVisibility(showBias, showIntra, showConfirm); + + const string p = ICT26_DRAW_PFX; + + if(InpDrawBiasSwingLabels && showBias && g_ictDailyBias.swings.IsComplete()) + { + IctSwingSet biasSw = g_ictDailyBias.swings; + IctDraw_SwingSet(p, "b", biasSw, clrGold, clrKhaki); + } + + if(InpDrawIntraSwingLabels && showIntra && g_ictIntraday.swings.IsComplete()) + { + IctSwingSet intraSw = g_ictIntraday.swings; + IctDraw_SwingSet(p, "i", intraSw, clrDodgerBlue, clrOrange); + } + + if(InpDrawConfirmLabels && showConfirm) + { + IctSwingSet confirmSw; + if(IctDraw_BuildConfirmSwings(sym, confirmSw)) + IctDraw_SwingSet(p, "", confirmSw, clrLightGreen, clrLightCoral); + } + + ChartRedraw(); +} + +#endif diff --git a/Include/ICT2026/Fvg.mqh b/Include/ICT2026/Fvg.mqh new file mode 100644 index 0000000..ea036ed --- /dev/null +++ b/Include/ICT2026/Fvg.mqh @@ -0,0 +1,773 @@ +//+------------------------------------------------------------------+ +//| Fvg.mqh — iTF Fair Value Gap (Available / Used) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_FVG_MQH +#define ICT2026_FVG_MQH + +#include +#include +#include + +IctFvgZone g_ictFvgZones[]; +int g_ictFvgCount = 0; +static ulong g_ictFvgNextId = 1; + +ENUM_ICT_FVG_SIDE IctFvgSideFromBias(const ENUM_ICT_BIAS bias) +{ + if(bias == ICT_BIAS_BULL) + return ICT_FVG_BULL; + if(bias == ICT_BIAS_BEAR) + return ICT_FVG_BEAR; + return ICT_FVG_NONE; +} + +string IctFvgSideText(const ENUM_ICT_FVG_SIDE side) +{ + switch(side) + { + case ICT_FVG_BULL: return "Bull"; + case ICT_FVG_BEAR: return "Bear"; + default: return "None"; + } +} + +string IctFvgStateText(const ENUM_ICT_FVG_STATE state) +{ + return (state == ICT_FVG_USED) ? "Used" : "Available"; +} + +string IctPdZoneText(const ENUM_ICT_PD_ZONE zone) +{ + switch(zone) + { + case ICT_PD_PREMIUM: return "Premium"; + case ICT_PD_DISCOUNT: return "Discount"; + case ICT_PD_EQUILIBRIUM: return "EQ"; + default: return ""; + } +} + +double IctFvg_MinRequiredGapHeight(const string sym, const ENUM_TIMEFRAMES tf, const int shiftC) +{ + double minH = 0.0; + + if(InpFvgMinGapPoints > 0.0) + minH = MathMax(minH, InpFvgMinGapPoints); + + if(InpFvgMinGapATRPct > 0.0) + { + const int hAtr = iATR(sym, tf, InpFvgAtrPeriod); + if(hAtr != INVALID_HANDLE) + { + double atr[]; + ArraySetAsSeries(atr, true); + if(CopyBuffer(hAtr, 0, shiftC, 1, atr) == 1 && atr[0] > 0.0) + minH = MathMax(minH, atr[0] * InpFvgMinGapATRPct / 100.0); + IndicatorRelease(hAtr); + } + } + + return minH; +} + +bool IctFvg_IsValidGapSize(const string sym, const ENUM_TIMEFRAMES tf, const int shiftC, + const double upper, const double lower) +{ + const double height = upper - lower; + if(height <= _Point) + return false; + + const double minH = IctFvg_MinRequiredGapHeight(sym, tf, shiftC); + if(minH > 0.0 && height + _Point * 0.5 < minH) + return false; + + if(InpFvgMinGapVsBarPct > 0.0) + { + const int shiftB = shiftC + 1; + const double barRange = iHigh(sym, tf, shiftB) - iLow(sym, tf, shiftB); + if(barRange > _Point && height < barRange * InpFvgMinGapVsBarPct / 100.0) + return false; + } + + return true; +} + +bool IctFvg_IsDuplicate(const datetime createdTime, const double upper, const double lower) +{ + const double tol = _Point * 2.0; + for(int i = 0; i < g_ictFvgCount; i++) + { + if(g_ictFvgZones[i].createdTime != createdTime) + continue; + if(MathAbs(g_ictFvgZones[i].upper - upper) <= tol && + MathAbs(g_ictFvgZones[i].lower - lower) <= tol) + return true; + } + return false; +} + +bool IctFvg_TryDetectAtShift(const string sym, const ENUM_TIMEFRAMES tf, const int shiftC, + ENUM_ICT_FVG_SIDE wantSide, + ENUM_ICT_FVG_SIDE &sideOut, + double &upperOut, double &lowerOut, + datetime &timeAOut, datetime &timeCOut) +{ + sideOut = ICT_FVG_NONE; + upperOut = 0.0; + lowerOut = 0.0; + timeAOut = 0; + timeCOut = 0; + + const int shiftA = shiftC + 2; + const int shiftB = shiftC + 1; + if(shiftA >= Bars(sym, tf)) + return false; + + const double aHi = iHigh(sym, tf, shiftA); + const double aLo = iLow(sym, tf, shiftA); + const double cHi = iHigh(sym, tf, shiftC); + const double cLo = iLow(sym, tf, shiftC); + + if(wantSide == ICT_FVG_BULL) + { + if(aHi >= cLo) + return false; + const double upper = cLo; + const double lower = aHi; + if(!IctFvg_IsValidGapSize(sym, tf, shiftC, upper, lower)) + return false; + sideOut = ICT_FVG_BULL; + upperOut = upper; + lowerOut = lower; + timeAOut = iTime(sym, tf, shiftA); + timeCOut = iTime(sym, tf, shiftC); + return true; + } + + if(wantSide == ICT_FVG_BEAR) + { + if(aLo <= cHi) + return false; + const double upper = aLo; + const double lower = cHi; + if(!IctFvg_IsValidGapSize(sym, tf, shiftC, upper, lower)) + return false; + sideOut = ICT_FVG_BEAR; + upperOut = upper; + lowerOut = lower; + timeAOut = iTime(sym, tf, shiftA); + timeCOut = iTime(sym, tf, shiftC); + return true; + } + + return false; +} + +ENUM_ICT_PD_ZONE IctFvg_ClassifyPd(const ENUM_ICT_FVG_SIDE side, + const double fvgMid, + const double pdEq, + const double pdHigh, + const double pdLow) +{ + if(pdHigh <= pdLow || pdEq <= 0.0) + return ICT_PD_NONE; + + const double tol = (pdHigh - pdLow) * 0.02; + if(MathAbs(fvgMid - pdEq) <= tol) + return ICT_PD_EQUILIBRIUM; + + if(side == ICT_FVG_BULL) + return (fvgMid < pdEq) ? ICT_PD_DISCOUNT : ICT_PD_PREMIUM; + + if(side == ICT_FVG_BEAR) + return (fvgMid > pdEq) ? ICT_PD_PREMIUM : ICT_PD_DISCOUNT; + + return ICT_PD_NONE; +} + +double IctFvg_BarExtremeSince(const string sym, const ENUM_TIMEFRAMES tf, + const datetime fromTime, const bool wantLow) +{ + double v = wantLow ? DBL_MAX : -DBL_MAX; + bool ok = false; + + for(int sh = 0; sh < Bars(sym, tf); sh++) + { + const datetime t = iTime(sym, tf, sh); + if(t < fromTime) + break; + + const double px = wantLow ? iLow(sym, tf, sh) : iHigh(sym, tf, sh); + if(wantLow) + v = MathMin(v, px); + else + v = MathMax(v, px); + ok = true; + } + return ok ? v : 0.0; +} + +bool IctFvg_IsConfirmedPivot(const string sym, const ENUM_TIMEFRAMES tf, + const IctSwingPoint &pt, const bool isHigh) +{ + if(!pt.Valid() || pt.shift < 1) + return false; + const int str = MathMax(1, InpIntradaySwingRange); + if(pt.shift < str + 1) + return false; + if(isHigh) + return IctIsSwingHigh(sym, tf, pt.shift, str); + return IctIsSwingLow(sym, tf, pt.shift, str); +} + +// Đỉnh PD bear = swing high gần nhất (theo thời gian) trên FVG tại lúc tạo FVG — không dùng iH0/iH1 live +bool IctFvg_ResolvePdHighBear(const string sym, const IctFvgZone &zone, double &outHigh) +{ + outHigh = 0.0; + datetime bestTime = 0; + bool found = false; + + const ENUM_TIMEFRAMES pdTf = InpIntradayTf; + const int pdSec = (int)PeriodSeconds(pdTf); + const datetime tCutoff = zone.createdTime + (datetime)pdSec; + const datetime tMin = zone.createdTime - (datetime)(InpIntradayRecentBars * pdSec); + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, pdTf, InpIntradaySwingRange, InpIntradaySwingLookback, highs, lows); + const int nH = ArraySize(highs); + + for(int i = 0; i < nH; i++) + { + if(highs[i].price <= zone.upper + _Point) + continue; + if(highs[i].time > tCutoff || highs[i].time < tMin) + continue; + if(!found || highs[i].time > bestTime) + { + bestTime = highs[i].time; + outHigh = highs[i].price; + found = true; + } + } + + const int maxBars = MathMax(InpIntradayRecentBars, 30); + for(int sh = 0; sh < maxBars && sh < Bars(sym, pdTf); sh++) + { + const datetime t = iTime(sym, pdTf, sh); + if(t > tCutoff || t < tMin) + continue; + const double h = iHigh(sym, pdTf, sh); + if(h <= zone.upper + _Point) + continue; + if(!found || t > bestTime) + { + bestTime = t; + outHigh = h; + found = true; + } + } + + return found; +} + +// Đáy PD bull = swing low gần nhất (theo thời gian) dưới FVG tại lúc tạo FVG +bool IctFvg_ResolvePdLowBull(const string sym, const IctFvgZone &zone, double &outLow) +{ + outLow = 0.0; + datetime bestTime = 0; + bool found = false; + + const ENUM_TIMEFRAMES pdTf = InpIntradayTf; + const int pdSec = (int)PeriodSeconds(pdTf); + const datetime tCutoff = zone.createdTime + (datetime)pdSec; + const datetime tMin = zone.createdTime - (datetime)(InpIntradayRecentBars * pdSec); + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, pdTf, InpIntradaySwingRange, InpIntradaySwingLookback, highs, lows); + const int nL = ArraySize(lows); + + for(int i = 0; i < nL; i++) + { + if(lows[i].price >= zone.lower - _Point) + continue; + if(lows[i].time > tCutoff || lows[i].time < tMin) + continue; + if(!found || lows[i].time > bestTime) + { + bestTime = lows[i].time; + outLow = lows[i].price; + found = true; + } + } + + const int maxBars = MathMax(InpIntradayRecentBars, 30); + for(int sh = 0; sh < maxBars && sh < Bars(sym, pdTf); sh++) + { + const datetime t = iTime(sym, pdTf, sh); + if(t > tCutoff || t < tMin) + continue; + const double l = iLow(sym, pdTf, sh); + if(l >= zone.lower - _Point) + continue; + if(!found || t > bestTime) + { + bestTime = t; + outLow = l; + found = true; + } + } + + return found; +} + +bool IctFvg_NearestLowBelow(const string sym, const double refLower, + const datetime beforeTime, double &outLow) +{ + outLow = 0.0; + double best = -DBL_MAX; + bool found = false; + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, InpIntradayTf, InpIntradaySwingRange, InpIntradaySwingLookback, + highs, lows); + const int nL = ArraySize(lows); + + for(int i = 0; i < nL; i++) + { + if(lows[i].price >= refLower - _Point) + continue; + if(lows[i].time > beforeTime) + continue; + if(lows[i].price > best) + { + best = lows[i].price; + found = true; + } + } + + IctSwingSet sw = g_ictIntraday.swings; + if(sw.hasL0 && sw.l0.price < refLower - _Point && sw.l0.time <= beforeTime && + sw.l0.price > best) + { + best = sw.l0.price; + found = true; + } + if(sw.hasL1 && sw.l1.price < refLower - _Point && sw.l1.time <= beforeTime && + sw.l1.price > best) + { + best = sw.l1.price; + found = true; + } + + if(!found) + { + const int maxBars = MathMax(InpIntradayRecentBars, 30); + for(int sh = 0; sh < maxBars && sh < Bars(sym, InpIntradayTf); sh++) + { + const datetime t = iTime(sym, InpIntradayTf, sh); + if(t > beforeTime) + continue; + const double l = iLow(sym, InpIntradayTf, sh); + if(l < refLower - _Point && l > best) + { + best = l; + found = true; + } + } + } + + if(!found) + return false; + + outLow = best; + return true; +} + +bool IctFvg_FindFirstSwingLowAfter(const IctSwingPoint &lows[], const int nL, + const datetime afterTime, IctSwingPoint &out) +{ + out.Clear(); + for(int i = 0; i < nL; i++) + { + if(lows[i].time >= afterTime) + { + out = lows[i]; + return true; + } + } + return false; +} + +bool IctFvg_FindFirstSwingHighAfter(const IctSwingPoint &highs[], const int nH, + const datetime afterTime, IctSwingPoint &out) +{ + out.Clear(); + for(int i = 0; i < nH; i++) + { + if(highs[i].time >= afterTime) + { + out = highs[i]; + return true; + } + } + return false; +} + +bool IctFvg_MatchesBias(const IctFvgZone &zone) +{ + const ENUM_ICT_FVG_SIDE want = IctFvgSideFromBias(g_ictDailyBias.bias); + return (want != ICT_FVG_NONE && zone.side == want); +} + +void IctFvg_FinalizePdMetrics(IctFvgZone &zone) +{ + if(zone.pdHigh <= zone.pdLow + _Point) + return; + + zone.pdEq = (zone.pdHigh + zone.pdLow) * 0.5; + const double mid = (zone.upper + zone.lower) * 0.5; + zone.pdZone = IctFvg_ClassifyPd(zone.side, mid, zone.pdEq, zone.pdHigh, zone.pdLow); +} + +void IctFvg_UpdatePdForZone(const string sym, IctFvgZone &zone) +{ + if(zone.pdComplete) + return; + + zone.pdRangeStart = zone.createdTime; + + const ENUM_TIMEFRAMES pdTf = InpIntradayTf; + const int pdPeriod = (int)PeriodSeconds(pdTf); + datetime nowEnd = iTime(sym, pdTf, 0) + pdPeriod; + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, pdTf, InpIntradaySwingRange, InpIntradaySwingLookback, highs, lows); + const int nL = ArraySize(lows); + const int nH = ArraySize(highs); + + if(zone.side == ICT_FVG_BEAR) + { + if(!zone.pdSwing1Set) + { + if(!IctFvg_ResolvePdHighBear(sym, zone, zone.pdHigh)) + return; + zone.pdSwing1Set = true; + } + + IctSwingPoint swingLow; + if(IctFvg_FindFirstSwingLowAfter(lows, nL, zone.createdTime, swingLow) && + IctFvg_IsConfirmedPivot(sym, pdTf, swingLow, false)) + { + zone.pdLow = swingLow.price; + zone.pdRangeEnd = swingLow.time + pdPeriod; + zone.pdComplete = true; + } + else + { + const double runLo = IctFvg_BarExtremeSince(sym, pdTf, zone.createdTime, true); + if(runLo <= 0.0) + return; + zone.pdLow = runLo; + zone.pdRangeEnd = nowEnd; + } + } + else if(zone.side == ICT_FVG_BULL) + { + if(!zone.pdSwing1Set) + { + if(!IctFvg_ResolvePdLowBull(sym, zone, zone.pdLow)) + return; + zone.pdSwing1Set = true; + } + + IctSwingPoint swingHigh; + if(IctFvg_FindFirstSwingHighAfter(highs, nH, zone.createdTime, swingHigh) && + IctFvg_IsConfirmedPivot(sym, pdTf, swingHigh, true)) + { + zone.pdHigh = swingHigh.price; + zone.pdRangeEnd = swingHigh.time + pdPeriod; + zone.pdComplete = true; + } + else + { + const double runHi = IctFvg_BarExtremeSince(sym, pdTf, zone.createdTime, false); + if(runHi <= 0.0) + return; + zone.pdHigh = runHi; + zone.pdRangeEnd = nowEnd; + } + } + else + return; + + IctFvg_FinalizePdMetrics(zone); +} + +void IctFvg_UpdateAllPd(const string sym) +{ + for(int i = 0; i < g_ictFvgCount; i++) + IctFvg_UpdatePdForZone(sym, g_ictFvgZones[i]); +} + +void IctFvg_AddZone(const IctFvgZone &zone) +{ + if(g_ictFvgCount >= InpFvgMaxZones) + { + for(int i = 0; i < g_ictFvgCount - 1; i++) + g_ictFvgZones[i] = g_ictFvgZones[i + 1]; + g_ictFvgCount--; + } + + ArrayResize(g_ictFvgZones, g_ictFvgCount + 1); + g_ictFvgZones[g_ictFvgCount] = zone; + g_ictFvgCount++; +} + +bool IctFvg_BarTouchesZone(const ENUM_ICT_FVG_SIDE side, + const double barHigh, const double barLow, + const double upper, const double lower) +{ + if(barHigh < barLow) + return false; + if(barHigh < lower - _Point || barLow > upper + _Point) + return false; + + if(side == ICT_FVG_BULL) + return (barLow <= upper + _Point); + if(side == ICT_FVG_BEAR) + return (barHigh >= lower - _Point); + return false; +} + +void IctFvg_RegisterTouch(IctFvgZone &zone, const datetime barTime) +{ + if(barTime <= zone.createdTime) + return; + if(zone.firstTouchTime == 0 || barTime < zone.firstTouchTime) + zone.firstTouchTime = barTime; +} + +void IctFvg_UpdateTouchAndFill(const string sym, const ENUM_TIMEFRAMES tf, IctFvgZone &zone) +{ + zone.maxFillRatio = 0.0; + zone.firstTouchTime = 0; + + const double height = zone.upper - zone.lower; + if(height <= _Point) + return; + + double deepest = zone.upper; + + for(int sh = 1; sh < Bars(sym, tf); sh++) + { + const datetime t = iTime(sym, tf, sh); + if(t <= zone.createdTime) + break; + + const double barHi = iHigh(sym, tf, sh); + const double barLo = iLow(sym, tf, sh); + + if(IctFvg_BarTouchesZone(zone.side, barHi, barLo, zone.upper, zone.lower)) + IctFvg_RegisterTouch(zone, t); + + if(zone.side == ICT_FVG_BULL) + { + if(barLo >= zone.upper) + continue; + if(barLo <= zone.lower) + zone.maxFillRatio = 1.0; + else + deepest = MathMin(deepest, barLo); + } + else if(zone.side == ICT_FVG_BEAR) + { + if(barHi <= zone.lower) + continue; + if(barHi >= zone.upper) + zone.maxFillRatio = 1.0; + else + deepest = MathMax(deepest, barHi); + } + } + + const datetime t0 = iTime(sym, tf, 0); + if(t0 > zone.createdTime) + { + const double barHi0 = iHigh(sym, tf, 0); + const double barLo0 = iLow(sym, tf, 0); + if(IctFvg_BarTouchesZone(zone.side, barHi0, barLo0, zone.upper, zone.lower)) + IctFvg_RegisterTouch(zone, t0); + + if(zone.side == ICT_FVG_BULL && barLo0 < zone.upper) + { + if(barLo0 <= zone.lower) + zone.maxFillRatio = 1.0; + else + deepest = MathMin(deepest, barLo0); + } + else if(zone.side == ICT_FVG_BEAR && barHi0 > zone.lower) + { + if(barHi0 >= zone.upper) + zone.maxFillRatio = 1.0; + else + deepest = MathMax(deepest, barHi0); + } + } + + if(zone.maxFillRatio < 1.0) + { + if(zone.side == ICT_FVG_BULL) + zone.maxFillRatio = (zone.upper - deepest) / height; + else if(zone.side == ICT_FVG_BEAR) + zone.maxFillRatio = (deepest - zone.lower) / height; + } +} + +datetime IctFvg_GetFvgDrawTimeEnd(const string sym, const ENUM_TIMEFRAMES tf, + const IctFvgZone &zone) +{ + const int periodSec = (int)PeriodSeconds(tf); + + if(zone.state == ICT_FVG_USED) + { + if(zone.fvgUsedTime > 0) + return zone.fvgUsedTime + periodSec; + return zone.createdTime + periodSec; + } + + const datetime barEnd = iTime(sym, tf, 0) + periodSec; + if(zone.expireTime > 0 && barEnd > zone.expireTime) + return zone.expireTime; + return barEnd; +} + +datetime IctFvg_GetPdDrawTimeEnd(const IctFvgZone &zone) +{ + if(zone.pdRangeEnd > zone.pdRangeStart) + return zone.pdRangeEnd; + return zone.createdTime + (datetime)PeriodSeconds(InpIntradayTf); +} + +void IctFvg_UpdateZoneState(const string sym, const ENUM_TIMEFRAMES tf, IctFvgZone &zone) +{ + const ENUM_ICT_FVG_STATE prevState = zone.state; + + IctFvg_UpdateTouchAndFill(sym, tf, zone); + + const double usedPct = InpFvgUsedFillPct / 100.0; + if(zone.maxFillRatio >= usedPct || zone.maxFillRatio >= 0.999) + zone.state = ICT_FVG_USED; + + if(zone.state == ICT_FVG_USED && prevState == ICT_FVG_AVAILABLE && zone.fvgUsedTime == 0) + zone.fvgUsedTime = iTime(sym, tf, 0); + + zone.timeEnd = IctFvg_GetFvgDrawTimeEnd(sym, tf, zone); +} + +void IctFvg_RemoveAt(const int index) +{ + if(index < 0 || index >= g_ictFvgCount) + return; + for(int i = index; i < g_ictFvgCount - 1; i++) + g_ictFvgZones[i] = g_ictFvgZones[i + 1]; + g_ictFvgCount--; + ArrayResize(g_ictFvgZones, g_ictFvgCount); +} + +void IctFvg_PurgeExpired() +{ + const datetime now = TimeCurrent(); + for(int i = g_ictFvgCount - 1; i >= 0; i--) + { + if(!IctFvg_MatchesBias(g_ictFvgZones[i]) && + g_ictFvgZones[i].state == ICT_FVG_AVAILABLE) + { + IctFvg_RemoveAt(i); + continue; + } + if(g_ictFvgZones[i].state == ICT_FVG_AVAILABLE && + g_ictFvgZones[i].expireTime > 0 && + now >= g_ictFvgZones[i].expireTime) + IctFvg_RemoveAt(i); + } +} + +void IctFvg_ScanNew(const string sym, const ENUM_TIMEFRAMES tf, + const ENUM_ICT_FVG_SIDE wantSide, + const bool fullLookback = false) +{ + if(wantSide == ICT_FVG_NONE) + return; + + const int maxShift = MathMin(InpFvgLookbackBars, Bars(sym, tf) - 3); + if(maxShift < 1) + return; + + int fromSh = 1; + if(fullLookback) + fromSh = maxShift; + else + { + const int scanN = MathMax(1, InpFvgScanBarsPerUpdate); + fromSh = MathMin(maxShift, scanN); + } + for(int sh = fromSh; sh >= 1; sh--) + { + ENUM_ICT_FVG_SIDE side = ICT_FVG_NONE; + double upper = 0.0, lower = 0.0; + datetime timeA = 0, timeC = 0; + + if(!IctFvg_TryDetectAtShift(sym, tf, sh, wantSide, side, upper, lower, timeA, timeC)) + continue; + if(IctFvg_IsDuplicate(timeC, upper, lower)) + continue; + + IctFvgZone zone; + zone.Clear(); + zone.id = g_ictFvgNextId++; + zone.side = side; + zone.state = ICT_FVG_AVAILABLE; + zone.upper = upper; + zone.lower = lower; + zone.createdTime = timeC; + zone.timeStart = timeA; + zone.expireTime = timeC + (datetime)(InpFvgExpireDays * 86400); + zone.locked = true; + IctFvg_UpdateZoneState(sym, tf, zone); + + IctFvg_AddZone(zone); + + if(InpDebug) + PrintFormat("[ICT2026/FVG] New %s %s [%.2f – %.2f] | %s | fill=%.1f%%", + IctFvgSideText(zone.side), IctFvgStateText(zone.state), + zone.lower, zone.upper, IctPdZoneText(zone.pdZone), + zone.maxFillRatio * 100.0); + } + + IctFvg_UpdateAllPd(sym); +} + +void IctFvg_UpdateAll(const string sym, const ENUM_TIMEFRAMES tf) +{ + for(int i = 0; i < g_ictFvgCount; i++) + IctFvg_UpdateZoneState(sym, tf, g_ictFvgZones[i]); + IctFvg_UpdateAllPd(sym); + IctFvg_PurgeExpired(); +} + +void IctFvg_Reset() +{ + g_ictFvgCount = 0; + ArrayResize(g_ictFvgZones, 0); +} + +int IctFvg_CountAvailable() +{ + int n = 0; + for(int i = 0; i < g_ictFvgCount; i++) + if(g_ictFvgZones[i].state == ICT_FVG_AVAILABLE) + n++; + return n; +} + +#endif diff --git a/Include/ICT2026/FvgDraw.mqh b/Include/ICT2026/FvgDraw.mqh new file mode 100644 index 0000000..cf47f54 --- /dev/null +++ b/Include/ICT2026/FvgDraw.mqh @@ -0,0 +1,126 @@ +//+------------------------------------------------------------------+ +//| FvgDraw.mqh — vẽ iTF FVG + Premium/Discount trên chart | +//+------------------------------------------------------------------+ +#ifndef ICT2026_FVGDRAW_MQH +#define ICT2026_FVGDRAW_MQH + +#include +#include + +const string ICT26_FVG_PFX = "ICT26_FVG_"; + +void IctFvgDraw_DeleteAll() +{ + const long ch = ChartID(); + for(int i = ObjectsTotal(ch, 0, -1) - 1; i >= 0; i--) + { + const string name = ObjectName(ch, i, 0, -1); + if(StringFind(name, ICT26_FVG_PFX) == 0) + ObjectDelete(ch, name); + } +} + +void IctFvgDraw_Rect(const string name, + const datetime t1, const double p1, + const datetime t2, const double p2, + const color clr, const bool fill) +{ + const long ch = ChartID(); + if(ObjectFind(ch, name) < 0) + ObjectCreate(ch, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2); + + ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); + ObjectSetInteger(ch, name, OBJPROP_FILL, fill); + ObjectSetInteger(ch, name, OBJPROP_BACK, true); + ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); + ObjectMove(ch, name, 0, t1, p1); + ObjectMove(ch, name, 1, t2, p2); +} + +void IctFvgDraw_HLine(const string name, const datetime t1, const datetime t2, + const double price, const color clr, const ENUM_LINE_STYLE style) +{ + const long ch = ChartID(); + if(ObjectFind(ch, name) < 0) + ObjectCreate(ch, name, OBJ_TREND, 0, t1, price, t2, price); + + ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); + ObjectSetInteger(ch, name, OBJPROP_STYLE, style); + ObjectSetInteger(ch, name, OBJPROP_WIDTH, 1); + ObjectSetInteger(ch, name, OBJPROP_RAY_RIGHT, false); + ObjectSetInteger(ch, name, OBJPROP_BACK, true); + ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); + ObjectMove(ch, name, 0, t1, price); + ObjectMove(ch, name, 1, t2, price); +} + +void IctFvgDraw_Label(const string name, const datetime t, const double price, + const string text, const color clr) +{ + const long ch = ChartID(); + if(ObjectFind(ch, name) < 0) + ObjectCreate(ch, name, OBJ_TEXT, 0, t, price); + + ObjectMove(ch, name, 0, t, price); + ObjectSetString(ch, name, OBJPROP_TEXT, text); + ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); + ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, InpChartLabelFontSize); + ObjectSetString(ch, name, OBJPROP_FONT, "Arial Bold"); + ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); + ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); +} + +void IctFvgDraw_Render(const string sym) +{ + if(!InpDrawFvgZones) + { + IctFvgDraw_DeleteAll(); + return; + } + + IctFvgDraw_DeleteAll(); + + for(int i = 0; i < g_ictFvgCount; i++) + { + if(!IctFvg_MatchesBias(g_ictFvgZones[i])) + continue; + + const string id = IntegerToString((long)g_ictFvgZones[i].id); + const string pfx = ICT26_FVG_PFX + id + "_"; + + const datetime fvgStart = g_ictFvgZones[i].createdTime; + const datetime fvgEnd = IctFvg_GetFvgDrawTimeEnd(sym, InpFvgTf, g_ictFvgZones[i]); + + color fvgClr = (g_ictFvgZones[i].side == ICT_FVG_BULL) ? InpFvgBullColor : InpFvgBearColor; + if(g_ictFvgZones[i].state == ICT_FVG_USED) + fvgClr = InpFvgUsedColor; + + IctFvgDraw_Rect(pfx + "BOX", fvgStart, g_ictFvgZones[i].upper, + fvgEnd, g_ictFvgZones[i].lower, fvgClr, true); + + if(g_ictFvgZones[i].pdHigh > g_ictFvgZones[i].pdLow && g_ictFvgZones[i].pdEq > 0.0 && + g_ictFvgZones[i].pdRangeStart > 0) + { + const datetime pdStart = g_ictFvgZones[i].pdRangeStart; + const datetime pdEnd = IctFvg_GetPdDrawTimeEnd(g_ictFvgZones[i]); + + IctFvgDraw_Rect(pfx + "PD_PRE", pdStart, g_ictFvgZones[i].pdHigh, + pdEnd, g_ictFvgZones[i].pdEq, InpPdPremiumColor, true); + IctFvgDraw_Rect(pfx + "PD_DIS", pdStart, g_ictFvgZones[i].pdEq, + pdEnd, g_ictFvgZones[i].pdLow, InpPdDiscountColor, true); + IctFvgDraw_HLine(pfx + "PD_EQ", pdStart, pdEnd, + g_ictFvgZones[i].pdEq, clrWhite, STYLE_DOT); + } + + string tag = StringFormat("iFVG %s %s", IctFvgSideText(g_ictFvgZones[i].side), + IctFvgStateText(g_ictFvgZones[i].state)); + if(g_ictFvgZones[i].pdZone != ICT_PD_NONE) + tag += " " + IctPdZoneText(g_ictFvgZones[i].pdZone); + + IctFvgDraw_Label(pfx + "LBL", fvgEnd, g_ictFvgZones[i].upper, tag, fvgClr); + } + + ChartRedraw(); +} + +#endif diff --git a/Include/ICT2026/IntradayStructure.mqh b/Include/ICT2026/IntradayStructure.mqh new file mode 100644 index 0000000..75c3913 --- /dev/null +++ b/Include/ICT2026/IntradayStructure.mqh @@ -0,0 +1,161 @@ +//+------------------------------------------------------------------+ +//| IntradayStructure.mqh — trend rõ (HH-HL/LH-LL) vs sớm (CHoCH) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_INTRADAYSTRUCTURE_MQH +#define ICT2026_INTRADAYSTRUCTURE_MQH + +#include +#include +#include +#include +#include + +IctIntradayState g_ictIntraday; +IctTrendResolveCtx g_ictIntradayCtx; + +string IctTfBarPrefix(const ENUM_TIMEFRAMES tf) +{ + switch(tf) + { + case PERIOD_M5: return "M5"; + case PERIOD_M15: return "M15"; + case PERIOD_M30: return "M30"; + case PERIOD_H1: return "H"; + case PERIOD_H4: return "H4"; + case PERIOD_D1: return "D"; + case PERIOD_W1: return "W"; + case PERIOD_MN1: return "MN"; + default: return "T"; + } +} + +string IctTrendDisplayShort(const ENUM_ICT_TREND trend) +{ + return IctTrendPhaseDisplay(trend, g_ictIntraday.trendPhase); +} + +ENUM_ICT_STRUCT IctStructFromTrend(const ENUM_ICT_TREND trend) +{ + if(trend == ICT_TREND_UP) + return ICT_STRUCT_BULL; + if(trend == ICT_TREND_DOWN) + return ICT_STRUCT_BEAR; + return ICT_STRUCT_NONE; +} + +void IctIntraday_SyncFromCtx(IctSwingSet &sw, const ENUM_ICT_STRUCT pivotStruct) +{ + g_ictIntraday.trend = g_ictIntradayCtx.trend; + g_ictIntraday.trendPhase = g_ictIntradayCtx.phase; + g_ictIntraday.lastEvent = g_ictIntradayCtx.lastEvent; + g_ictIntraday.lastClearStructural = g_ictIntradayCtx.lastClearStructural; + g_ictIntraday.swings = sw; + g_ictIntraday.structural = pivotStruct; + + const ENUM_ICT_STRUCT keyStruct = IctStructFromTrend(g_ictIntraday.trend); + if(keyStruct != ICT_STRUCT_NONE && sw.IsComplete()) + IctGetKeyLevels(keyStruct, sw, g_ictIntraday.keyLv1, g_ictIntraday.keyLv2); + else + { + g_ictIntraday.keyLv1 = 0.0; + g_ictIntraday.keyLv2 = 0.0; + } +} + +// Up/Bull sớm đều là ICT_TREND_UP; Down/Bear sớm đều là ICT_TREND_DOWN +// → bật sớm ngay khi H1 CHoCH, không cần đợi HH-HL/LH-LL rõ +bool IctIntraday_TrendAlignsWithBias(const ENUM_ICT_BIAS bias, + const ENUM_ICT_TREND trend) +{ + if(bias == ICT_BIAS_BULL && trend == ICT_TREND_UP) + return true; + if(bias == ICT_BIAS_BEAR && trend == ICT_TREND_DOWN) + return true; + return false; +} + +void IctIntraday_UpdateAllowTrade() +{ + g_ictIntraday.isAllowTrade = IctIntraday_TrendAlignsWithBias(g_ictDailyBias.bias, + g_ictIntraday.trend); +} + +void IctIntraday_Reset() +{ + g_ictIntradayCtx.Reset(); + g_ictIntraday.trend = ICT_TREND_NONE; + g_ictIntraday.trendPhase = ICT_TREND_PHASE_NONE; + g_ictIntraday.structural = ICT_STRUCT_NONE; + g_ictIntraday.lastEvent = ICT_MS_NONE; + g_ictIntraday.swings.Clear(); + g_ictIntraday.keyLv1 = 0.0; + g_ictIntraday.keyLv2 = 0.0; + g_ictIntraday.lastClearStructural = ICT_STRUCT_NONE; + g_ictIntraday.lastBarTime = 0; + g_ictIntraday.displayReason = ""; + g_ictIntraday.isAllowTrade = false; +} + +bool IctIntraday_Init(const string sym) +{ + IctIntraday_Reset(); + if(Bars(sym, InpIntradayTf) < 10) + return false; + + IctIntraday_Update(sym, true); + return true; +} + +bool IctIntraday_Update(const string sym, const bool force = false) +{ + const ENUM_TIMEFRAMES tf = InpIntradayTf; + const datetime bar0 = iTime(sym, tf, 0); + if(!force && bar0 == g_ictIntraday.lastBarTime) + return false; + + g_ictIntraday.lastBarTime = bar0; + + IctSwingSet sw; + ENUM_ICT_STRUCT pivotStruct = ICT_STRUCT_NONE; + if(!IctBuildSwingSetRecentPivots(sym, tf, + InpIntradaySwingRange, + InpIntradaySwingLookback, + InpIntradayRecentBars, + sw, pivotStruct)) + sw.Clear(); + + IctResolveTrend(sym, tf, sw, pivotStruct, g_ictIntradayCtx, 1); + IctIntraday_SyncFromCtx(sw, pivotStruct); + + g_ictIntraday.displayReason = IctBuildTrendDisplayReason(sw, pivotStruct, + g_ictIntraday.trend, + g_ictIntraday.trendPhase, + g_ictIntraday.lastEvent, + IctTfBarPrefix(tf)); + IctIntraday_UpdateAllowTrade(); + + if(InpDebug) + { + PrintFormat("[ICT2026/Intraday] %s %s | Trend=%s | Phase=%d | Struct=%s | Event=%s | Allow=%s", + sym, EnumToString(tf), + IctTrendPhaseDisplay(g_ictIntraday.trend, g_ictIntraday.trendPhase), + g_ictIntraday.trendPhase, + IctStructText(g_ictIntraday.structural), + IctMsEventText(g_ictIntraday.lastEvent), + g_ictIntraday.isAllowTrade ? "YES" : "NO"); + if(g_ictIntraday.swings.IsComplete()) + PrintFormat(" H0=%.2f H1=%.2f L0=%.2f L1=%.2f | Key1=%.2f Key2=%.2f", + g_ictIntraday.swings.h0.price, g_ictIntraday.swings.h1.price, + g_ictIntraday.swings.l0.price, g_ictIntraday.swings.l1.price, + g_ictIntraday.keyLv1, g_ictIntraday.keyLv2); + PrintFormat(" %s", g_ictIntraday.displayReason); + } + + return true; +} + +void IctIntraday_Get(IctIntradayState &out) { out = g_ictIntraday; } + +bool IctIntraday_IsAllowTrade() { return g_ictIntraday.isAllowTrade; } + +#endif diff --git a/Include/ICT2026/LowTfTrend.mqh b/Include/ICT2026/LowTfTrend.mqh new file mode 100644 index 0000000..831c7de --- /dev/null +++ b/Include/ICT2026/LowTfTrend.mqh @@ -0,0 +1,112 @@ +//+------------------------------------------------------------------+ +//| LowTfTrend.mqh — Low TF layer: iTF FVG khi IsAllowTrade | +//+------------------------------------------------------------------+ +#ifndef ICT2026_LOWTFTREND_MQH +#define ICT2026_LOWTFTREND_MQH + +#include +#include +#include + +IctLowTfState g_ictLowTf; + +void IctLowTfTrend_Reset() +{ + g_ictLowTf.lastBarTime = 0; + g_ictLowTf.activeCount = 0; + g_ictLowTf.availableCount = 0; + g_ictLowTf.displayReason = ""; + IctFvg_Reset(); +} + +bool IctLowTfTrend_Init(const string sym) +{ + IctLowTfTrend_Reset(); + if(Bars(sym, InpFvgTf) < 10) + return false; + IctLowTfTrend_Update(sym, true); + return true; +} + +bool IctLowTfTrend_Update(const string sym, const bool force = false) +{ + const ENUM_TIMEFRAMES tf = InpFvgTf; + const datetime bar0 = iTime(sym, tf, 0); + const bool newBar = (force || bar0 != g_ictLowTf.lastBarTime); + if(!newBar) + return false; + + g_ictLowTf.lastBarTime = bar0; + + IctIntraday_UpdateAllowTrade(); + IctFvg_UpdateAll(sym, tf); + + static bool s_prevAllowTrade = false; + const bool allowNow = g_ictIntraday.isAllowTrade; + const bool allowJustOn = (allowNow && !s_prevAllowTrade); + s_prevAllowTrade = allowNow; + + if(allowNow) + { + const ENUM_ICT_FVG_SIDE side = IctFvgSideFromBias(g_ictDailyBias.bias); + IctFvg_ScanNew(sym, tf, side, force || allowJustOn); + g_ictLowTf.displayReason = StringFormat("Scan iTF FVG %s | %d avail / %d total", + IctFvgSideText(side), + IctFvg_CountAvailable(), g_ictFvgCount); + } + else + { + g_ictLowTf.displayReason = "IsAllowTrade=false — giữ FVG đã khóa"; + } + + g_ictLowTf.activeCount = g_ictFvgCount; + g_ictLowTf.availableCount = IctFvg_CountAvailable(); + + if(InpDebug && (force || g_ictIntraday.isAllowTrade)) + PrintFormat("[ICT2026/LowTF] %s | Allow=%s | FVG avail=%d total=%d | %s", + sym, + g_ictIntraday.isAllowTrade ? "YES" : "NO", + g_ictLowTf.availableCount, g_ictLowTf.activeCount, + g_ictLowTf.displayReason); + + IctFvgDraw_Render(sym); + return true; +} + +void IctLowTfTrend_TickRefresh(const string sym) +{ + if(!InpDrawFvgZones || g_ictFvgCount == 0) + return; + + bool dirty = false; + double oldPdHi[], oldPdLo[]; + ArrayResize(oldPdHi, g_ictFvgCount); + ArrayResize(oldPdLo, g_ictFvgCount); + + for(int i = 0; i < g_ictFvgCount; i++) + { + oldPdHi[i] = g_ictFvgZones[i].pdHigh; + oldPdLo[i] = g_ictFvgZones[i].pdLow; + + const datetime oldTouch = g_ictFvgZones[i].firstTouchTime; + const datetime oldEnd = g_ictFvgZones[i].timeEnd; + IctFvg_UpdateZoneState(sym, InpFvgTf, g_ictFvgZones[i]); + if(g_ictFvgZones[i].firstTouchTime != oldTouch || g_ictFvgZones[i].timeEnd != oldEnd) + dirty = true; + } + + IctFvg_UpdateAllPd(sym); + + for(int i = 0; i < g_ictFvgCount; i++) + { + if(MathAbs(g_ictFvgZones[i].pdHigh - oldPdHi[i]) > _Point || + MathAbs(g_ictFvgZones[i].pdLow - oldPdLo[i]) > _Point) + dirty = true; + } + if(dirty) + IctFvgDraw_Render(sym); +} + +void IctLowTfTrend_Get(IctLowTfState &out) { out = g_ictLowTf; } + +#endif diff --git a/Include/ICT2026/Panel.mqh b/Include/ICT2026/Panel.mqh new file mode 100644 index 0000000..f3ebb7f --- /dev/null +++ b/Include/ICT2026/Panel.mqh @@ -0,0 +1,174 @@ +//+------------------------------------------------------------------+ +//| Panel.mqh — Daily Bias + Intraday (mỗi dòng = 1 OBJ_LABEL) | +//| Bias/Intraday: xanh = Up, đỏ = Down — cùng màu = canh trade | +//+------------------------------------------------------------------+ +#ifndef ICT2026_PANEL_MQH +#define ICT2026_PANEL_MQH + +#include +#include +#include +#include + +const string ICT26_PANEL_PFX = "ICT26_PNL_"; +const string ICT26_PANEL_LEGACY = "ICT26_BIAS_PANEL"; +const int ICT26_PANEL_MAXLINE = 12; + +const color ICT_PANEL_CLR_UP = clrLime; +const color ICT_PANEL_CLR_DOWN = clrOrangeRed; +const color ICT_PANEL_CLR_NEUTRAL = clrSilver; +const color ICT_PANEL_CLR_MUTED = clrGainsboro; +const color ICT_PANEL_CLR_ALLOW = clrAqua; + +string IctPanel_LineName(const int index) +{ + return ICT26_PANEL_PFX + IntegerToString(index); +} + +int IctPanel_LineHeight() +{ + const int minByFont = InpPanelFontSize + 10; + return MathMax(InpPanelLineSpacing, minByFont); +} + +color IctPanelBiasDirectionColor(const ENUM_ICT_BIAS bias) +{ + if(bias == ICT_BIAS_BULL) + return ICT_PANEL_CLR_UP; + if(bias == ICT_BIAS_BEAR) + return ICT_PANEL_CLR_DOWN; + return ICT_PANEL_CLR_NEUTRAL; +} + +color IctPanelIntradayDirectionColor(const ENUM_ICT_TREND trend) +{ + if(trend == ICT_TREND_UP) + return ICT_PANEL_CLR_UP; + if(trend == ICT_TREND_DOWN) + return ICT_PANEL_CLR_DOWN; + return ICT_PANEL_CLR_NEUTRAL; +} + +color IctPanelAllowTradeColor() +{ + if(g_ictIntraday.isAllowTrade) + return ICT_PANEL_CLR_ALLOW; + if(g_ictIntraday.trend != ICT_TREND_NONE && + (g_ictDailyBias.bias == ICT_BIAS_BULL || g_ictDailyBias.bias == ICT_BIAS_BEAR)) + return ICT_PANEL_CLR_MUTED; + return ICT_PANEL_CLR_NEUTRAL; +} + +void IctPanel_Clear() +{ + const long ch = ChartID(); + ObjectDelete(ch, ICT26_PANEL_LEGACY); + for(int i = 0; i < ICT26_PANEL_MAXLINE; i++) + ObjectDelete(ch, IctPanel_LineName(i)); +} + +void IctPanel_SetLine(const long ch, const int index, const int yOffset, + const string text, const color clr) +{ + const string name = IctPanel_LineName(index); + if(ObjectFind(ch, name) < 0) + { + ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0); + ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); + ObjectSetString(ch, name, OBJPROP_FONT, "Consolas"); + ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(ch, name, OBJPROP_HIDDEN, false); + ObjectSetInteger(ch, name, OBJPROP_BACK, false); + } + + ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpPanelX); + ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, InpPanelY + yOffset); + ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, InpPanelFontSize); + ObjectSetInteger(ch, name, OBJPROP_COLOR, clr); + ObjectSetString(ch, name, OBJPROP_TEXT, text); +} + +void IctPanel_Render(const string sym) +{ + if(!InpDrawPanel) + { + IctPanel_Clear(); + return; + } + + IctIntraday_UpdateAllowTrade(); + + const long ch = ChartID(); + ObjectDelete(ch, ICT26_PANEL_LEGACY); + const string dTag = IctTfBarPrefix(InpBiasTf); + const string iTag = IctTfBarPrefix(InpIntradayTf); + const int lh = IctPanel_LineHeight(); + + const color clrBias = IctPanelBiasDirectionColor(g_ictDailyBias.bias); + const color clrIntra = IctPanelIntradayDirectionColor(g_ictIntraday.trend); + const color clrAllow = IctPanelAllowTradeColor(); + + string lines[]; + color colors[]; + ArrayResize(lines, 0); + ArrayResize(colors, 0); + + int n = 0; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("ICT2026 %s | %s[0] chua dong", EnumToString(InpBiasTf), dTag); + colors[n++] = ICT_PANEL_CLR_NEUTRAL; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("Bias: %s", IctBiasDisplayShort(g_ictDailyBias.bias)); + colors[n++] = clrBias; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("Ly do: %s", g_ictDailyBias.displayReason); + colors[n++] = clrBias; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = "-------------------------"; + colors[n++] = ICT_PANEL_CLR_NEUTRAL; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("%s | %s[0] dang chay", EnumToString(InpIntradayTf), iTag); + colors[n++] = ICT_PANEL_CLR_NEUTRAL; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("Intraday: %s", IctTrendDisplayShort(g_ictIntraday.trend)); + colors[n++] = clrIntra; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("Ly do: %s", g_ictIntraday.displayReason); + colors[n++] = clrIntra; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("IsAllowTrade: %s", + g_ictIntraday.isAllowTrade ? "true" : "false"); + colors[n++] = clrAllow; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = StringFormat("iTF FVG: %s", g_ictLowTf.displayReason); + colors[n++] = (g_ictLowTf.availableCount > 0) ? ICT_PANEL_CLR_ALLOW : ICT_PANEL_CLR_MUTED; + + for(int i = 0; i < n; i++) + IctPanel_SetLine(ch, i, i * lh, lines[i], colors[i]); + + for(int i = n; i < ICT26_PANEL_MAXLINE; i++) + ObjectDelete(ch, IctPanel_LineName(i)); + + ChartRedraw(ch); +} + +#endif diff --git a/Include/ICT2026/README.md b/Include/ICT2026/README.md new file mode 100644 index 0000000..df42a94 --- /dev/null +++ b/Include/ICT2026/README.md @@ -0,0 +1,636 @@ +# ICT 2026 + +Expert Advisor ICT 2026 — module hóa theo hướng ICT/SMC. **Không phụ thuộc HyperICT** (chỉ tham khảo ý tưởng swing & key level). + +Phiên bản hiện tại: **1.10** + +--- + +## Quy ước bảo trì (bắt buộc) + +> **Từ giờ, mọi logic mới hoặc thay đổi hành vi phải được cập nhật vào file README này trong cùng lần sửa code.** +> +> Khi thêm module, sửa rule BOS/CHoCH, đổi veto, input mới, API mới → cập nhật các mục tương ứng bên dưới **và** mục [Changelog](#changelog). + +--- + +## Mục lục + +1. [Cấu trúc thư mục](#folder-structure) +2. [Pipeline Daily Bias](#pipeline) +3. [Logic nghiệp vụ](#business-logic) +4. [Mô tả từng file](#file-reference) +5. [API](#api) +6. [Input](#inputs) +7. [Chưa implement](#roadmap) +8. [Changelog](#changelog) + +--- + + + +## Cấu trúc thư mục + +``` +MQL5/ +├── Experts/ +│ └── ICT_2026.mq5 ← EA chính (OnInit / OnTick) +│ +└── Include/ + └── ICT2026/ + ├── README.md ← tài liệu này (luôn cập nhật khi đổi logic) + ├── Types.mqh ← enum, struct + ├── Config.mqh ← input + ├── Swing.mqh ← pivot, swing set H0–L1, key level + ├── StructureCore.mqh ← body break, BOS/CHoCH detect (Key H0/L0) + ├── StructureTrend.mqh ← trend rõ vs sớm (dùng chung H1 + D1) + ├── DailyBias.mqh ← Daily Bias + UpdateHTFBias + ├── IntradayStructure.mqh ← Intraday trend H1 + IsAllowTrade + ├── LowTfTrend.mqh ← iTF FVG khi IsAllowTrade + ├── Fvg.mqh ← detect / Available-Used + ├── FvgDraw.mqh ← vẽ FVG + Premium/Discount + ├── Panel.mqh ← bias + intraday + IsAllowTrade (góc trên) + └── Draw.mqh ← label b*/i*/H0–L1 trên chart +``` + +**Include trong code:** `#include ` + +--- + + + +## Pipeline Daily Bias + +Chạy trên **mỗi nến mới** của `InpBiasTf` (mặc định D1), hoặc `force=true` lúc Init: + +``` +IctDailyBias_Update(sym) + │ + ├─1─ Tính PDH, PDL, PDC, EQ (nến shift=1) + │ + ├─2─ IctUpdateHTFBias() ← Previous Day Model (fallback) + │ + ├─3─ IctResolveTrend() ← rõ (HH-HL/LH-LL) hoặc sớm (sau CHoCH) + │ └─ IctDetectStructureEvent() trên Key H0/L0 (body close D[1]) + │ + └─4─ Fallback HTF (bias = None) ← Previous Day Model + └─ IctBuildDisplayReason() ← chuỗi lý do D[n] cho panel + └─ IctPanel_Render() ← khi có nến D1 mới (EA gọi) +``` + +**Notation nến:** `D[0]` = nến đang chạy (chưa đóng), `D[1]` = nến vừa đóng, `D[2]` = nến trước đó. +Bias tính từ **D[1]** và swing trên các nến đã đóng. + +--- + + + +## Pipeline Intraday Structure + +Chạy trên **mỗi nến mới** của `InpIntradayTf` (mặc định **H1**): + +``` +IctIntraday_Update(sym) + │ + ├─1─ IctBuildSwingSet() ← H0–L1 trên H1 + ├─2─ IctGetKeyLevels() + ├─3─ IctDetectStructureEvent() ← BOS / CHoCH trên bar [1] + ├─4─ IctApplyIntradayTrend() ← Up / Down + ├─5─ IctBuildIntradayDisplayReason() + └─6─ IctIntraday_UpdateAllowTrade() +``` + +**Notation H1:** `H[0]` đang chạy, `H[1]` vừa đóng. + +### Intraday trend (HH-HL / LH-LL + BOS / CHoCH) + +**Phân loại intraday (khác Daily):** dùng **2 đỉnh + 2 đáy gần nhất** trong `InpIntradayRecentBars` (mặc định 80 bar H1), không ghép leg 4 swing dài — tránh nhầm HH-HL từ sóng cũ khi giá đã LH-LL. + +| So sánh | Điều kiện | +|---------|-----------| +| **HH-HL** | Đỉnh mới > đỉnh cũ **và** đáy mới > đáy cũ | +| **LH-LL** | Đỉnh mới < đỉnh cũ **và** đáy mới < đáy cũ | + +Key level cho BOS/CHoCH: Bear Key1=H0, Key2=L0; Bull Key1=L0, Key2=H0. + +### Hai trạng thái trend (H1 + Daily) + +| Phase | Hiển thị | Điều kiện | +|-------|-----------|-----------| +| **Rõ ràng** | `Up` / `Down` | Pivot HH-HL hoặc LH-LL | +| **Sớm** | `Bull sớm` / `Bear sớm` | Sau CHoCH, MS mới chưa rõ (pivot lộn xộn) | + +**CHoCH (đảo, body close):** + +| Cấu trúc trước | Điều kiện | Trend sau | +|----------------|-----------|-----------| +| LH-LL (Bear) | Body phá **H0** | **Bull sớm** | +| HH-HL (Bull) | Body phá **L0** | **Bear sớm** | + +**Giữ trend sớm** khi pivot lộn xộn cho đến khi HH-HL/LH-LL rõ hoặc CHoCH ngược. + +**CHoCH ưu tiên trước pivot rõ:** pivot vẫn LH-LL nhưng H[1] phá H0 → **Bull sớm** (không giữ Down). + +| Pivot | Trend | +|-------|-------| +| **HH-HL** | **Up** (rõ) | +| **LH-LL** | **Down** (rõ) | +| Mixed + đang sớm | Giữ Bull/Bear sớm | +| Mixed, không sớm | **None** | + +--- + + + +## Pipeline Low TF — iTF FVG (v1.10) + +Chạy trên **mỗi nến mới** của `InpFvgTf` (mặc định H1 = Intraday): + +``` +IctLowTfTrend_Update(sym) + │ + ├─1─ IsAllowTrade == true? + │ └─ IctFvg_ScanNew() — Bull FVG nếu Bias Up, Bear nếu Bias Down + ├─2─ IctFvg_UpdateAll() — cập nhật fill % → Available / Used + ├─3─ IctFvg_PurgeExpired() — xóa Available quá InpFvgExpireDays (3 ngày) + └─4─ IctFvgDraw_Render() — hình chữ nhật + Premium/Discount +``` + +### FVG 3 nến (A–B–C) + +| Loại | Điều kiện | Vùng | +|------|-----------|------| +| **Bull** | A.High < C.Low (gap 3 nến) | [A.High .. C.Low] | +| **Bear** | A.Low > C.High (gap 3 nến) | [C.High .. A.Low] | + +**Lọc gap quá nhỏ** (phải thỏa tất cả bật): + +| Input | Mặc định | Ý nghĩa | +|-------|----------|---------| +| `InpFvgMinGapATRPct` | 12% | Chiều cao gap ≥ % ATR14 trên `InpFvgTf` | +| `InpFvgMinGapPoints` | 0 | Thêm ngưỡng tuyệt đối (giá), 0 = chỉ ATR | +| `InpFvgMinGapVsBarPct` | 25% | Gap ≥ % range nến giữa (B) | + +Quét: init / khi `IsAllowTrade` vừa bật → full lookback; mỗi nến mới → `InpFvgScanBarsPerUpdate` bar. + +### Available / Used + +| State | Điều kiện | +|-------|-----------| +| **Available** | Giá chưa chạm FVG, hoặc lấp < `InpFvgUsedFillPct` (38.2%) | +| **Used** | Lấp ≥ 38.2% hoặc xuyên qua FVG | + +Chỉ **quét / giữ** FVG thuận Daily Bias. **Vẽ FVG** (ngang): + +| | | +|--|--| +| **Bắt đầu** | `createdTime` (lúc hình thành gap) | +| **Kết thúc** | Used (`fvgUsedTime`) **hoặc** hết hạn `InpFvgExpireDays` (3 ngày) — không kéo tới hiện tại vô hạn | + +### Premium / Discount + +**Một vùng P/D cho mỗi FVG** (không gộp nhóm — tránh chồng lấn). + +| Bear FVG | Bull FVG | +|----------|----------| +| Swing 1: đỉnh **gần nhất theo thời gian** trên FVG (snapshot lúc tạo FVG) → `pdHigh` | Swing 1: đáy gần nhất dưới FVG → `pdLow` | +| Swing 2: **đáy đầu tiên** sau FVG khi pivot xác nhận (`pdLow`) | Swing 2: **đỉnh đầu tiên** sau FVG (`pdHigh`) | + +**Vẽ P/D (ngang):** + +| | | +|--|--| +| **Bắt đầu** | `createdTime` FVG — **không** kéo sang trái | +| **Kết thúc** | Thời điểm swing 2 hoàn thiện; trước đó kéo tới nến hiện tại (đáy/đỉnh chạy) | +| **Sau swing 2** | `pdComplete` — **khóa** `pdHigh`/`pdLow`/`pdRangeEnd`; không đọc lại `iH0`/`iH1` live | + +Swing 1 (`pdSwing1Set`) gán **một lần** khi FVG mới; swing 2 xác nhận → `pdComplete` → không cập nhật nữa khi intraday swing đổi. + +FVG Used **không** kéo dài P/D thêm; P/D độc lập với Used (theo swing 2). + +### IsAllowTrade + +Mục tiêu: **phát hiện sớm đảo chiều H1** khi CHoCH — không cần đợi HH-HL/LH-LL rõ. + +```text +true ⟺ Daily Bias hướng Up AND H1 hướng Up (Up rõ HOẶC Bull sớm) + OR Daily Bias hướng Down AND H1 hướng Down (Down rõ HOẶC Bear sớm) + +false ⟺ khác hướng, hoặc Bias/Trend = None/Range +``` + +`Bull sớm` / `Bear sớm` vẫn map `ICT_TREND_UP` / `ICT_TREND_DOWN` — `IctIntraday_TrendAlignsWithBias()` xử lý cả hai phase. + +| Daily Bias | Intraday H1 | IsAllowTrade | +|------------|-------------|--------------| +| Up / Bull sớm | Up / Bull sớm | **true** | +| Down / Bear sớm | Down / Bear sớm | **true** | +| Up | Down / Bear sớm | false | +| Down | Up / Bull sớm | false | +| RANGE / NONE | bất kỳ | false | + +--- + + + +## Logic nghiệp vụ + +### Mục tiêu Daily Bias + +Trả lời: **hôm nay ưu tiên BUY, SELL, RANGE, hay đứng ngoài (NONE)?** + +Daily Bias là **filter hướng**, không phải tín hiệu entry. + +--- + +### Tầng 1 — Structural Bias (ưu tiên cao nhất) + +Build bộ **4 swing** trên `InpBiasTf`: **H0, L0, H1, L1** (ghép theo leg, không lấy 2 đỉnh + 2 đáy tách rời). + +| Bias | Điều kiện | Thứ tự thời gian | +|------|-----------|------------------| +| **Bull** | `H0 > H1` và `L0 > L1` (HH-HL) | L1 → H1 → L0 → H0 | +| **Bear** | `H0 < H1` và `L0 < L1` (LH-LL) | H1 → L1 → H0 → L0 | +| **None** | Không đủ 4 swing hoặc cấu trúc lẫn lộn | — | + +**Fib (tùy chọn):** + +- Công thức: sóng hồi **H1→L0** ≥ `InpFibMinRatio` × sóng **L1→H1** +- `InpRequireFib = false` (mặc định): Fib chỉ ghi nhận `fibOk`, không chặn structural +- `InpRequireFib = true`: structural chỉ hợp lệ khi `fibOk = true` + +--- + +### Key Level + +| Structural | Key LV1 | Key LV2 | +|------------|---------|---------| +| **Bull** | L0 | H0 | +| **Bear** | H0 | L0 | + +- **Phá Key** = thân nến (`max/min` open–close) trên bar **đã đóng** (shift = 1) +- Không dùng wick để xác nhận phá level + +--- + +### BOS vs CHoCH (Key H0 / L0, body close) + +| Event | Ý nghĩa | Bear (LH-LL) | Bull (HH-HL) | +|-------|---------|--------------|--------------| +| **CHoCH** | Đảo | Body phá **H0** | Body phá **L0** | +| **BOS** | Tiếp diễn | Body phá **L0** | Body phá **H0** | + +- **Phá level** = thân nến bar **đã đóng** (shift = 1) +- CHoCH kiểm tra trước BOS +- Logic trong `IctResolveTrend()` (`StructureTrend.mqh`) + +--- + +### Tầng 2 — Previous Day Model (`IctUpdateHTFBias`) + +Dùng khi **bias = None** (không rõ và không ở phase sớm). + +So sánh nến **shift=1** vs **shift=2** trên `InpBiasTf`: + +| Điều kiện | HTF Bias | Daily Bias | +|-----------|----------|------------| +| `Close[1] > High[2]` | UP | **BULL** | +| `Close[1] < Low[2]` | DOWN | **BEAR** | +| Inside bar (`High[1] ≤ High[2]` và `Low[1] ≥ Low[2]`) | SIDEWAY | **RANGE** (range = High/Low[2]) | +| Close trong range[2] nhưng không inside | SIDEWAY | **RANGE** (range = High/Low[1]) | +| Không khớp | NONE | **NONE** | + +*Nguồn tham khảo: `XAU_H1_M5_Pullback.mq5` → `UpdateHTFBias`.* + +--- + +### Daily Bias — cùng logic rõ / sớm + +Giống H1: `IctResolveTrend()` trên D1. Chưa có bias structural → fallback **Previous Day Model**. + +--- + +### Mức giá tham chiếu (PDH / PDL / EQ) + +| Field | Công thức | +|-------|-----------| +| `pdh` | High[1] | +| `pdl` | Low[1] | +| `pdc` | Close[1] | +| `eq` | (pdh + pdl) / 2 | + +*Chưa dùng Premium/Discount filter — xem [Roadmap](#roadmap).* + +--- + +### Label swing trên chart (`Draw.mqh`) + +Tự động theo **TF chart đang mở** — chỉ vẽ swing TF hiện tại + TF **lớn hơn**, ẩn TF **nhỏ hơn**: + +| Chart hiện tại | Hiển thị | +|----------------|----------| +| **Bias** (D1, W1…) | `bH0, bH1, bL0, bL1` | +| **Intraday** (H1, H4…) | `b*` + `i*` | +| **Confirm** (M5, M15…) | `b*` + `i*` + `H0–L1` | + +Bật/tắt từng lớp: `InpDrawBiasSwingLabels`, `InpDrawIntraSwingLabels`, `InpDrawConfirmLabels`. + +So sánh `PeriodSeconds(chart)` với `InpBiasTf`, `InpIntradayTf`, `InpConfirmTf`. + +--- + +### Panel chart (góc trên trái) + +Cập nhật **mỗi khi mở nến mới** của `InpBiasTf` (mặc định D1). + +**Màu theo hướng (mỗi dòng riêng):** + +| Dòng | Xanh (`Lime`) | Đỏ (`OrangeRed`) | +|------|---------------|------------------| +| **Bias** + lý do | Up / Bull sớm | Down / Bear sớm | +| **Intraday** + lý do | Up / Bull sớm | Down / Bear sớm | + +- **Cùng xanh** hoặc **cùng đỏ** → Daily + H1 cùng hướng → canh trade (`IsAllowTrade: true`, màu Aqua) +- **Bias xanh + Intraday đỏ** (hoặc ngược lại) → lệch hướng, nhìn phát hiện ngay +- Header / separator: xám; `IsAllowTrade: false` khi lệch: xám nhạt + +Ví dụ hiển thị: + +``` +ICT2026 PERIOD_D1 | D[0] chưa đóng +Bias: Up +Ly do: HH-HL, D[1].close > D[2].High +------------------------- +PERIOD_H1 | H[0] dang chay +Intraday: Up +Ly do: HH-HL, BOS H[1].close > Key(H0) +IsAllowTrade: true +``` + +| Thành phần `displayReason` | Khi nào | +|----------------------------|---------| +| `HH-HL` / `LH-LL` | Có structural bias | +| `BOS D[1].close > Key(H0)` / `< Key(L0)` | BOS trên bar D[1] | +| `CHoCH D[1].close … Key` | CHoCH trên bar D[1] | +| `D[1].close > D[2].High` | Previous day outside up | +| `D[1].close < D[2].Low` | Previous day outside down | +| `Inside D[1] trong D[2]` | Inside bar | + +Màu chữ: **Up** = xanh lá, **Down** = đỏ cam, **Range** = vàng, **None** = xám. + +**Lưu ý MT5:** `OBJ_LABEL` **không** hiển thị xuống dòng `\n` — panel dùng **nhiều label** (`ICT26_PNL_0`, `ICT26_PNL_1`, …) xếp dọc. + +--- + + + +## Mô tả từng file + +| File | Trách nhiệm | +|------|-------------| +| `Types.mqh` | `ENUM_ICT_BIAS`, `ENUM_ICT_STRUCT`, `ENUM_ICT_MS_EVENT`, `IctSwingSet`, `IctDailyBiasState` | +| `Config.mqh` | Input: TF, swing range, lookback, Fib, debug | +| `Swing.mqh` | Pivot detection, build swing set, classify HH-HL/LH-LL, key level, Fib validate | +| `StructureCore.mqh` | `IctBodyBreak*`, `IctDetectStructureEvent` | +| `DailyBias.mqh` | `IctUpdateHTFBias`, veto, `g_ictDailyBias` | +| `IntradayStructure.mqh` | `IctIntraday_Update`, trend, `isAllowTrade`, `g_ictIntraday` | +| `Panel.mqh` | Bias + Intraday + IsAllowTrade trên chart | +| `ICT_2026.mq5` | Init/Update Daily + Intraday → panel | + +--- + + + +## API + +```cpp +#include + +// Init (gọi OnInit) +bool ok = IctDailyBias_Init(_Symbol); + +// Update (gọi OnTick — return true khi có nến Bias TF mới) +if(IctDailyBias_Update(_Symbol) || IctIntraday_Update(_Symbol)) + IctPanel_Render(_Symbol); + +bool allow = IctIntraday_IsAllowTrade(); +ENUM_ICT_TREND t = ICT2026_GetIntradayTrend(); + +// Đọc state +IctDailyBiasState db; +IctDailyBias_Get(db); // struct: tham số phải dùng & +// db.bias, db.structural, db.lastEvent, db.pdh, db.pdl, db.eq +// db.keyLv1, db.keyLv2, db.swings, db.reason + +bool bull = IctDailyBias_IsBull(); +bool bear = IctDailyBias_IsBear(); + +// EA export +ENUM_ICT_BIAS ICT2026_GetDailyBias(); +``` + +--- + + + +## Input + +| Input | Mặc định | Mô tả | +|-------|----------|-------| +| `InpBiasTf` | D1 | Timeframe xác định Daily Bias | +| `InpSwingRange` | 2 | Số nến mỗi bên để xác nhận pivot | +| `InpSwingLookback` | 300 | Số bar quét swing lịch sử | +| `InpFibMinRatio` | 0.382 | Tỷ lệ Fib tối thiểu (0 = luôn pass) | +| `InpRequireFib` | false | Bắt buộc Fib OK mới dùng structural veto | +| `InpIntradayTf` | H1 | TF intraday structure | +| `InpIntradaySwingRange` | 2 | Pivot intraday | +| `InpIntradaySwingLookback` | 120 | Lookback quét pivot | +| `InpIntradayRecentBars` | 80 | Chỉ pivot trong N bar gần nhất (2H+2L) | +| `InpDrawPanel` | true | Hiển thị panel bias góc trên trái | +| `InpPanelX` / `InpPanelY` | 12 / 28 | Vị trí panel (pixel) | +| `InpPanelFontSize` | 10 | Cỡ chữ panel | +| `InpPanelLineSpacing` | 22 | Khoảng cách giữa các dòng (px) | +| `InpDrawChartLabels` | true | Master: vẽ label swing | +| `InpDrawBiasSwingLabels` | true | bH0–bL1 | +| `InpDrawIntraSwingLabels` | true | iH0–iL1 | +| `InpDrawConfirmLabels` | true | H0–L1 (M5) | +| `InpChartLabelFontSize` | 9 | Cỡ chữ label chart | +| `InpConfirmTf` | M5 | TF confirm swing | +| `InpConfirmSwingRange` | 2 | Pivot confirm | +| `InpConfirmSwingLookback` | 80 | Lookback confirm | +| `InpConfirmRecentBars` | 40 | Pivot gần nhất confirm | +| `InpLowTf` | M5 | Low TF trend (phase sau) | +| `InpFvgTf` | H1 | TF quét iTF FVG | +| `InpFvgLookbackBars` | 60 | Lookback quét FVG (init) | +| `InpFvgUsedFillPct` | 38.2 | % lấp → Used | +| `InpFvgExpireDays` | 3 | Xóa Available sau N ngày | +| `InpDrawFvgZones` | true | Vẽ FVG + P/D | +| `InpDebug` | true | Log `[ICT2026/DailyBias]` mỗi lần update | + +--- + + + +## Chưa implement + +| Tính năng | Ghi chú | +|-----------|---------| +| Tầng 3 intraday confirm (logic trade) | Chưa có — chỉ vẽ pivot M5 (H0–L1) | +| Entry từ iTF FVG Available | Chưa có — chỉ scan + vẽ | +| Premium / Discount filter | Chỉ buy discount, sell premium | +| Vẽ Key / PDH-PDL trên chart | Panel bias đã có; chưa vẽ level | +| Entry H1 / M5 | MSS, FVG, OB — module riêng | +| Roll swing incremental | Hiện rebuild full swing set mỗi nến | +| Session filter | Asia / London / NY | + +--- + + + +## Changelog + +### v1.112 — PD lock: không cập nhật đỉnh sau pdComplete + +- `pdSwing1Set`: đỉnh/đáy swing 1 snapshot một lần +- `pdComplete`: khóa toàn bộ P/D; bỏ max(iH0,iH1) live gây đỉnh bay lên + +### v1.111 — Lọc FVG: gap tối thiểu theo ATR / range nến B + +### v1.110 — PD bear: đỉnh = iH0/iH1 (không còn pivot thấp nhất trên gap) + +### v1.109 — Định nghĩa lại vẽ FVG / P/D (gọn, không gộp nhóm) + +- FVG: `createdTime` → Used hoặc expire 3 ngày; chỉ vẽ thuận bias +- P/D: `createdTime` → swing 2; đỉnh/đáy theo pivot; bỏ nhóm PD chồng chart + +### v1.108 — FVG: quét N bar; gap 3 nến (không bắt B giảm) + +- `InpFvgScanBarsPerUpdate` (40): mỗi nến mới quét lại N bar — fix FVG sau iH1 bị bỏ qua +- Khi `IsAllowTrade` vừa bật → full lookback một lần + +### v1.107 — Fix P/D không vẽ (khóa sau SyncAllPd) + +### v1.106 — P/D: đỉnh gần FVG + nhóm FVG liền kề + +- Bear `pdHigh`: pivot gần nhất trên FVG (không còn `max(iH0,iH1)`) +- FVG bear liền kề → một vùng P/D chung; vẽ một lần / nhóm + +### v1.105 — Khóa P/D khi FVG Used + +- `pdLocked` trên `IctFvgZone`: không cập nhật `pdHigh`/`pdLow` sau khi FVG Used + +### v1.104 — PD khớp iH0/iH1 (trên) và iL0 (dưới) trên chart + +- Bỏ quét pivot lịch sử (gây khối PD quá cao/thấp) +- Bear: `pdHigh = max(iH0,iH1)`, `pdLow = iL0` (hoặc min low chạy trước khi có iL0) +- Bull: `pdLow = min(iL0,iL1)`, `pdHigh = iH0` (hoặc max high chạy) + +### v1.103 — Premium/Discount theo từng FVG + đáy/đỉnh leg động + +- Bear: `pdHigh` = pivot trên FVG; `pdLow` = min low chạy đến khi swing low xác nhận +- Bull: đối xứng (`pdLow` dưới FVG, `pdHigh` chạy / swing high) +- Cập nhật P/D mỗi `UpdateZoneState` + vẽ lại khi `pdHigh`/`pdLow` đổi + +### v1.102 — FVG/P/D: cắt vẽ đúng lần chạm đầu tiên (thời gian) + +- `Fvg.mqh`: `firstTouchTime` = **nến sớm nhất** sau khi tạo FVG (min `t`), không còn lấy nến mới nhất trong vòng quét +- Bỏ `return` sớm khi fill 100% (vẫn ghi nhận chạm trên mọi nến) +- Chạm: overlap wick/body với vùng; gồm nến đang hình thành (shift 0) +- `LowTfTrend.mqh`: `IctLowTfTrend_TickRefresh` — cập nhật độ dài vẽ mỗi tick khi phát hiện chạm mới + +### v1.10 — iTF FVG (Available/Used) + Premium/Discount + +- `Fvg.mqh`, `FvgDraw.mqh`, `LowTfTrend.mqh` +- Swing labels theo TF chart (b* / i* / H0–L1) +- Scan FVG khi `IsAllowTrade`; khóa vẽ; expire 3 ngày + +### v1.09 — Label swing: b* (Bias), i* (Intraday), H0–L1 (Confirm M5) + +- `Draw.mqh`: 3 lớp label, bật/tắt riêng +- Confirm: `InpConfirmTf` mặc định M5 (scaffold entry layer) + +### v1.081 — CHoCH ưu tiên trước LH-LL rõ (flip Bull sớm khi phá H0) + +- Fix: Bear sớm / LH-LL + H[1] phá H0 → Bull sớm (trước đó bị ghi đè Down) + +### v1.08 — Bỏ lock Key; trend rõ vs Bull/Bear sớm + +- Xóa `keysLocked` +- `IctResolveTrend()`: HH-HL/LH-LL = rõ; CHoCH phá H0/L0 = sớm; giữ sớm khi pivot lộn xộn +- Daily + H1 dùng chung `StructureTrend.mqh` + +### v1.07 — BOS/CHoCH trên swing cũ H1/L1 + Daily lock Key + +- `IctDetectTrendSwingEvent()`: BOS/CHoCH theo trend + phá H1/L1 (body close) +- Intraday: trend Down + phá H1 → CHoCH Up (fix case H0 > H1) +- Daily: cơ chế `keysLocked` giống H1; bỏ veto mỗi bar + +### v1.06 — Intraday: khóa Key H0/L0, chỉ đổi trend khi phá Key + +- `keysLocked`: sau khi có trend, giữ Key H0/L0 +- Không reset trend khi pivot lộn xộn sau CHoCH +- CHoCH/BOS trên Key → flip hoặc continue + re-lock swing mới + +### v1.051 — Bỏ nhãn trend/bias trên chart + +- Xóa vẽ `Intraday trend` / `Daily Bias` trên giá (đủ thông tin ở panel góc trái) +- `Draw.mqh` chỉ còn label **H0–L1** (tắt bằng `InpDrawChartLabels = false`) + +### v1.05 — Vẽ chart: label swing (đã thu gọn) + +- `Draw.mqh`: H0–L1 trên pivot intraday + +### v1.04 — Intraday: 2 pivot gần nhất (fix LH-LL vs HH-HL) + +- `IctBuildSwingSetRecentPivots`: 2H+2L trong `InpIntradayRecentBars` thay cho leg 4 swing trên H1 +- Tránh intraday Up/HH-HL khi chart đang LH-LL rõ +- Debug log H0–L1 + Key levels + +### v1.03.1 — Panel line spacing + +- `InpPanelLineSpacing` (mặc định 22px) +- Tách `Bias` / `Ly do` và `Intraday` / `Ly do` thành 2 dòng + +### v1.03 — Panel intraday hiển thị đúng trên chart + +- Tách panel thành nhiều `OBJ_LABEL` (MT5 không hỗ trợ `\n` trong 1 label) +- `OnTick`: vẽ panel lần đầu + mỗi khi nến D1/H1 mới +- `OnChartEvent(CHARTEVENT_CHART_CHANGE)`: refresh panel khi đổi chart + +### v1.02 — Intraday Structure (H1) + IsAllowTrade + +- `IntradayStructure.mqh`: HH-HL/LH-LL, BOS continue, CHoCH đảo trend +- `IsAllowTrade`: Bias Up + Intraday Up, hoặc Bias Down + Intraday Down +- Panel: intraday dưới bias + `IsAllowTrade: true/false` +- `StructureCore.mqh`: tách body break / detect event dùng chung +- Input: `InpIntradayTf`, `InpIntradaySwingRange`, `InpIntradaySwingLookback` +- API: `ICT2026_GetIntradayTrend()`, `ICT2026_IsAllowTrade()` + +### v1.01.2 — Sửa compile MQL5 (struct reference) + +- Struct **bắt buộc** truyền bằng `&` (không by value, không `const &`) +- Không return `const Struct &` — dùng `void Get(Struct &out)` hoặc return struct by value +- `IctClassifyStructure(IctSwingSet &sw)` v.v. + +### v1.01.1 — Sửa compile MQL5 (lần 1) + +- `IctApplyVetoBias()` / `IctDailyBias_Reset()` thao tác `g_ictDailyBias` + +### v1.01 — Panel bias góc trên trái + +- `Panel.mqh`: OBJ_LABEL góc trên trái, cập nhật khi nến `InpBiasTf` mới +- `IctBuildDisplayReason()`: lý do dạng `HH-HL`, `D[1].close > D[2].High`, BOS/CHoCH +- `IctBiasDisplayShort()`: Up / Down / Range / None +- `IctDailyBias_Update()` return `bool` (true = nến mới) +- Input: `InpDrawPanel`, `InpPanelX`, `InpPanelY`, `InpPanelFontSize` + +### v1.00 — Daily Bias module (initial) + +- Tạo module `ICT2026` độc lập (Types, Config, Swing, DailyBias) +- Structural bias: swing set H0–L1, HH-HL / LH-LL +- Key Level: Bull L0/H0, Bear H0/L0 +- BOS = Continue (phá Key LV2), CHoCH = Reversal (phá Key LV1) +- Veto logic (Cách A): structural ưu tiên, CHoCH flip bias +- `IctUpdateHTFBias`: Previous Day Model (outside / inside / indecision) +- EA shell `ICT_2026.mq5` +- PDH, PDL, PDC, EQ tính từ nến shift=1 diff --git a/Include/ICT2026/StructureCore.mqh b/Include/ICT2026/StructureCore.mqh new file mode 100644 index 0000000..80c5a28 --- /dev/null +++ b/Include/ICT2026/StructureCore.mqh @@ -0,0 +1,88 @@ +//+------------------------------------------------------------------+ +//| StructureCore.mqh — body break & BOS/CHoCH detect (dùng chung) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_STRUCTURECORE_MQH +#define ICT2026_STRUCTURECORE_MQH + +#include + +double IctBodyTop(const string sym, const ENUM_TIMEFRAMES tf, const int shift) +{ + return MathMax(iOpen(sym, tf, shift), iClose(sym, tf, shift)); +} + +double IctBodyBottom(const string sym, const ENUM_TIMEFRAMES tf, const int shift) +{ + return MathMin(iOpen(sym, tf, shift), iClose(sym, tf, shift)); +} + +bool IctBodyBreakAbove(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const double level) +{ + return IctBodyTop(sym, tf, shift) > level + _Point; +} + +bool IctBodyBreakBelow(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const double level) +{ + return IctBodyBottom(sym, tf, shift) < level - _Point; +} + +// BOS = Continue (Key LV2) | CHoCH = Reversal (Key LV1) +ENUM_ICT_MS_EVENT IctDetectStructureEvent(const string sym, const ENUM_TIMEFRAMES tf, + const ENUM_ICT_STRUCT structural, + const double keyLv1, const double keyLv2, + const int shift = 1) +{ + if(structural == ICT_STRUCT_NONE || keyLv1 <= 0.0 || keyLv2 <= 0.0) + return ICT_MS_NONE; + + if(structural == ICT_STRUCT_BULL) + { + if(IctBodyBreakBelow(sym, tf, shift, keyLv1)) + return ICT_MS_CHOCH; + if(IctBodyBreakAbove(sym, tf, shift, keyLv2)) + return ICT_MS_BOS; + } + else if(structural == ICT_STRUCT_BEAR) + { + if(IctBodyBreakAbove(sym, tf, shift, keyLv1)) + return ICT_MS_CHOCH; + if(IctBodyBreakBelow(sym, tf, shift, keyLv2)) + return ICT_MS_BOS; + } + + return ICT_MS_NONE; +} + +// BOS/CHoCH trên swing cũ H1/L1 theo trend hiện tại (body close shift=1) +// UP: BOS = phá H1 | CHoCH = phá L1 +// DOWN: BOS = phá L1 | CHoCH = phá H1 +ENUM_ICT_MS_EVENT IctDetectTrendSwingEvent(const string sym, const ENUM_TIMEFRAMES tf, + const ENUM_ICT_TREND trend, + const double swingHighH1, + const double swingLowL1, + const int shift = 1) +{ + if(trend == ICT_TREND_NONE || swingHighH1 <= 0.0 || swingLowL1 <= 0.0) + return ICT_MS_NONE; + + if(trend == ICT_TREND_UP) + { + if(IctBodyBreakBelow(sym, tf, shift, swingLowL1)) + return ICT_MS_CHOCH; + if(IctBodyBreakAbove(sym, tf, shift, swingHighH1)) + return ICT_MS_BOS; + } + else if(trend == ICT_TREND_DOWN) + { + if(IctBodyBreakAbove(sym, tf, shift, swingHighH1)) + return ICT_MS_CHOCH; + if(IctBodyBreakBelow(sym, tf, shift, swingLowL1)) + return ICT_MS_BOS; + } + + return ICT_MS_NONE; +} + +#endif diff --git a/Include/ICT2026/StructureTrend.mqh b/Include/ICT2026/StructureTrend.mqh new file mode 100644 index 0000000..103f303 --- /dev/null +++ b/Include/ICT2026/StructureTrend.mqh @@ -0,0 +1,193 @@ +//+------------------------------------------------------------------+ +//| StructureTrend.mqh — trend rõ (HH-HL/LH-LL) vs sớm (sau CHoCH) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_STRUCTURETREND_MQH +#define ICT2026_STRUCTURETREND_MQH + +#include +#include +#include + +struct IctTrendResolveCtx +{ + ENUM_ICT_TREND trend; + ENUM_ICT_TREND_PHASE phase; + ENUM_ICT_STRUCT lastClearStructural; + ENUM_ICT_MS_EVENT lastEvent; + + void Reset() + { + trend = ICT_TREND_NONE; + phase = ICT_TREND_PHASE_NONE; + lastClearStructural = ICT_STRUCT_NONE; + lastEvent = ICT_MS_NONE; + } +}; + +ENUM_ICT_STRUCT IctPickDetectStructural(const ENUM_ICT_STRUCT pivotStruct, + const IctTrendResolveCtx &ctx) +{ + if(pivotStruct != ICT_STRUCT_NONE) + return pivotStruct; + if(ctx.phase == ICT_TREND_PHASE_EARLY) + { + if(ctx.trend == ICT_TREND_UP) + return ICT_STRUCT_BULL; + if(ctx.trend == ICT_TREND_DOWN) + return ICT_STRUCT_BEAR; + } + return ctx.lastClearStructural; +} + +void IctResolveTrend(const string sym, const ENUM_TIMEFRAMES tf, + IctSwingSet &sw, + const ENUM_ICT_STRUCT pivotStruct, + IctTrendResolveCtx &ctx, + const int shift = 1) +{ + ctx.lastEvent = ICT_MS_NONE; + + if(!sw.IsComplete()) + { + if(ctx.phase == ICT_TREND_PHASE_EARLY && ctx.trend != ICT_TREND_NONE) + return; + ctx.trend = ICT_TREND_NONE; + ctx.phase = ICT_TREND_PHASE_NONE; + return; + } + + const ENUM_ICT_STRUCT detectStruct = IctPickDetectStructural(pivotStruct, ctx); + ENUM_ICT_MS_EVENT ev = ICT_MS_NONE; + if(detectStruct != ICT_STRUCT_NONE) + { + double k1 = 0.0, k2 = 0.0; + IctGetKeyLevels(detectStruct, sw, k1, k2); + ev = IctDetectStructureEvent(sym, tf, detectStruct, k1, k2, shift); + } + ctx.lastEvent = ev; + + // CHoCH ưu tiên trước HH-HL/LH-LL — pivot vẫn LH-LL nhưng phá H0 → Bull sớm + if(ev == ICT_MS_CHOCH) + { + if(detectStruct == ICT_STRUCT_BEAR) + { + ctx.trend = ICT_TREND_UP; + ctx.phase = ICT_TREND_PHASE_EARLY; + ctx.lastClearStructural = ICT_STRUCT_NONE; + return; + } + if(detectStruct == ICT_STRUCT_BULL) + { + ctx.trend = ICT_TREND_DOWN; + ctx.phase = ICT_TREND_PHASE_EARLY; + ctx.lastClearStructural = ICT_STRUCT_NONE; + return; + } + } + + if(pivotStruct == ICT_STRUCT_BULL) + { + ctx.trend = ICT_TREND_UP; + ctx.phase = ICT_TREND_PHASE_CLEAR; + ctx.lastClearStructural = ICT_STRUCT_BULL; + return; + } + if(pivotStruct == ICT_STRUCT_BEAR) + { + ctx.trend = ICT_TREND_DOWN; + ctx.phase = ICT_TREND_PHASE_CLEAR; + ctx.lastClearStructural = ICT_STRUCT_BEAR; + return; + } + + if(ev == ICT_MS_BOS) + { + if(detectStruct == ICT_STRUCT_BEAR) + { + ctx.trend = ICT_TREND_DOWN; + ctx.phase = ICT_TREND_PHASE_EARLY; + return; + } + if(detectStruct == ICT_STRUCT_BULL) + { + ctx.trend = ICT_TREND_UP; + ctx.phase = ICT_TREND_PHASE_EARLY; + return; + } + } + + if(ctx.phase == ICT_TREND_PHASE_EARLY && ctx.trend != ICT_TREND_NONE) + return; + + ctx.trend = ICT_TREND_NONE; + ctx.phase = ICT_TREND_PHASE_NONE; +} + +string IctTrendPhaseDisplay(const ENUM_ICT_TREND trend, const ENUM_ICT_TREND_PHASE phase) +{ + if(trend == ICT_TREND_UP) + return (phase == ICT_TREND_PHASE_EARLY) ? "Bull sớm" : "Up"; + if(trend == ICT_TREND_DOWN) + return (phase == ICT_TREND_PHASE_EARLY) ? "Bear sớm" : "Down"; + return "None"; +} + +string IctStructPatternText(const ENUM_ICT_STRUCT structural) +{ + switch(structural) + { + case ICT_STRUCT_BULL: return "HH-HL"; + case ICT_STRUCT_BEAR: return "LH-LL"; + default: return ""; + } +} + +string IctBuildTrendDisplayReason(const IctSwingSet &sw, + const ENUM_ICT_STRUCT pivotStruct, + const ENUM_ICT_TREND trend, + const ENUM_ICT_TREND_PHASE phase, + const ENUM_ICT_MS_EVENT ev, + const string barTag) +{ + string parts = ""; + + if(phase == ICT_TREND_PHASE_CLEAR) + parts = IctStructPatternText(pivotStruct); + else if(phase == ICT_TREND_PHASE_EARLY) + { + if(trend == ICT_TREND_UP) + parts = "Bull sớm"; + else if(trend == ICT_TREND_DOWN) + parts = "Bear sớm"; + } + + if(ev == ICT_MS_CHOCH && sw.IsComplete()) + { + if(parts != "") + parts += ", "; + if(trend == ICT_TREND_UP || pivotStruct == ICT_STRUCT_BEAR) + parts += StringFormat("CHoCH %s[1].close > H0(%.2f)", barTag, sw.h0.price); + else if(trend == ICT_TREND_DOWN || pivotStruct == ICT_STRUCT_BULL) + parts += StringFormat("CHoCH %s[1].close < L0(%.2f)", barTag, sw.l0.price); + else + parts += StringFormat("CHoCH %s[1]", barTag); + } + else if(ev == ICT_MS_BOS && sw.IsComplete()) + { + if(parts != "") + parts += ", "; + if(trend == ICT_TREND_UP || pivotStruct == ICT_STRUCT_BULL) + parts += StringFormat("BOS %s[1].close > H0(%.2f)", barTag, sw.h0.price); + else if(trend == ICT_TREND_DOWN || pivotStruct == ICT_STRUCT_BEAR) + parts += StringFormat("BOS %s[1].close < L0(%.2f)", barTag, sw.l0.price); + else + parts += StringFormat("BOS %s[1]", barTag); + } + + if(parts == "") + parts = "Không xác định cấu trúc"; + + return parts; +} + +#endif diff --git a/Include/ICT2026/Swing.mqh b/Include/ICT2026/Swing.mqh new file mode 100644 index 0000000..b249547 --- /dev/null +++ b/Include/ICT2026/Swing.mqh @@ -0,0 +1,331 @@ +//+------------------------------------------------------------------+ +//| Swing.mqh — pivot & swing set H0–L1 (tham khảo HyperICT) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_SWING_MQH +#define ICT2026_SWING_MQH + +#include + +bool IctIsSwingHigh(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const int range) +{ + const int str = MathMax(1, range); + const double h = iHigh(sym, tf, shift); + for(int k = 1; k <= str; k++) + { + if(shift + k >= Bars(sym, tf) || shift - k < 0) + return false; + if(h <= iHigh(sym, tf, shift + k) || h <= iHigh(sym, tf, shift - k)) + return false; + } + return true; +} + +bool IctIsSwingLow(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const int range) +{ + const int str = MathMax(1, range); + const double l = iLow(sym, tf, shift); + for(int k = 1; k <= str; k++) + { + if(shift + k >= Bars(sym, tf) || shift - k < 0) + return false; + if(l >= iLow(sym, tf, shift + k) || l >= iLow(sym, tf, shift - k)) + return false; + } + return true; +} + +void IctSortSwingsByTime(IctSwingPoint &pts[], const int count) +{ + for(int i = 0; i < count - 1; i++) + for(int j = i + 1; j < count; j++) + if(pts[j].time < pts[i].time) + { + const IctSwingPoint t = pts[i]; + pts[i] = pts[j]; + pts[j] = t; + } +} + +void IctCollectSwings(const string sym, const ENUM_TIMEFRAMES tf, + const int range, const int lookback, + IctSwingPoint &highs[], IctSwingPoint &lows[]) +{ + ArrayResize(highs, 0); + ArrayResize(lows, 0); + const int str = MathMax(1, range); + const int bars = Bars(sym, tf); + const int last = MathMin(lookback, bars - str - 1); + if(last < str + 5) + return; + + int nH = 0, nL = 0; + for(int sh = last; sh >= 1; sh--) + { + if(IctIsSwingHigh(sym, tf, sh, range)) + { + ArrayResize(highs, nH + 1); + highs[nH].price = iHigh(sym, tf, sh); + highs[nH].time = iTime(sym, tf, sh); + highs[nH].shift = sh; + nH++; + } + if(IctIsSwingLow(sym, tf, sh, range)) + { + ArrayResize(lows, nL + 1); + lows[nL].price = iLow(sym, tf, sh); + lows[nL].time = iTime(sym, tf, sh); + lows[nL].shift = sh; + nL++; + } + } + IctSortSwingsByTime(highs, nH); + IctSortSwingsByTime(lows, nL); +} + +IctSwingPoint IctFindMostRecentOlder(IctSwingPoint &pts[], const int count, + const int anchorShift) +{ + IctSwingPoint empty; + empty.Clear(); + int best = -1; + for(int i = 0; i < count; i++) + { + if(pts[i].shift <= anchorShift) + continue; + if(best < 0 || pts[i].shift < pts[best].shift) + best = i; + } + if(best < 0) + return empty; + return pts[best]; +} + +IctSwingSet IctPickStructuralBear(IctSwingPoint &highs[], const int nH, + IctSwingPoint &lows[], const int nL) +{ + IctSwingSet out; + out.Clear(); + if(nH < 2 || nL < 2) + return out; + + out.l0 = lows[nL - 1]; + out.hasL0 = true; + + out.l1 = IctFindMostRecentOlder(lows, nL, out.l0.shift); + if(!out.l1.Valid()) + return out; + out.hasL1 = true; + + out.h0 = IctFindMostRecentOlder(highs, nH, out.l1.shift); + if(!out.h0.Valid()) + return out; + out.hasH0 = true; + + out.h1 = IctFindMostRecentOlder(highs, nH, out.h0.shift); + if(!out.h1.Valid()) + return out; + out.hasH1 = true; + + return out; +} + +IctSwingSet IctPickStructuralBull(IctSwingPoint &highs[], const int nH, + IctSwingPoint &lows[], const int nL) +{ + IctSwingSet out; + out.Clear(); + if(nH < 2 || nL < 2) + return out; + + out.h0 = highs[nH - 1]; + out.hasH0 = true; + + out.l0 = IctFindMostRecentOlder(lows, nL, out.h0.shift); + if(!out.l0.Valid()) + return out; + out.hasL0 = true; + + out.h1 = IctFindMostRecentOlder(highs, nH, out.l0.shift); + if(!out.h1.Valid()) + return out; + out.hasH1 = true; + + out.l1 = IctFindMostRecentOlder(lows, nL, out.h1.shift); + if(!out.l1.Valid()) + return out; + out.hasL1 = true; + + return out; +} + +ENUM_ICT_STRUCT IctClassifyStructure(IctSwingSet &sw) +{ + if(!sw.IsComplete()) + return ICT_STRUCT_NONE; + if(sw.h0.price > sw.h1.price && sw.l0.price > sw.l1.price) + return ICT_STRUCT_BULL; + if(sw.h0.price < sw.h1.price && sw.l0.price < sw.l1.price) + return ICT_STRUCT_BEAR; + return ICT_STRUCT_NONE; +} + +bool IctIsPullbackValid(IctSwingSet &sw, const double ratio) +{ + if(!sw.IsComplete() || ratio <= 0.0) + return true; + const double impulse = MathAbs(sw.h1.price - sw.l1.price); + const double pullback = MathAbs(sw.h1.price - sw.l0.price); + if(impulse <= 0.0) + return false; + return pullback >= impulse * ratio; +} + +IctSwingSet IctBuildSwingSet(const string sym, const ENUM_TIMEFRAMES tf, + const int range, const int lookback, + ENUM_ICT_STRUCT &biasOut) +{ + IctSwingSet out; + out.Clear(); + biasOut = ICT_STRUCT_NONE; + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, tf, range, lookback, highs, lows); + const int nH = ArraySize(highs); + const int nL = ArraySize(lows); + + IctSwingSet bear = IctPickStructuralBear(highs, nH, lows, nL); + IctSwingSet bull = IctPickStructuralBull(highs, nH, lows, nL); + const bool hasBear = bear.IsComplete(); + const bool hasBull = bull.IsComplete(); + + const ENUM_ICT_STRUCT biasBear = hasBear ? IctClassifyStructure(bear) : ICT_STRUCT_NONE; + const ENUM_ICT_STRUCT biasBull = hasBull ? IctClassifyStructure(bull) : ICT_STRUCT_NONE; + + if(biasBear == ICT_STRUCT_BEAR) + { + out = bear; + biasOut = ICT_STRUCT_BEAR; + } + else if(biasBull == ICT_STRUCT_BULL) + { + out = bull; + biasOut = ICT_STRUCT_BULL; + } + else if(hasBear) + { + out = bear; + biasOut = IctClassifyStructure(bear); + } + else if(hasBull) + { + out = bull; + biasOut = IctClassifyStructure(bull); + } + + return out; +} + +// Intraday: 2 đỉnh + 2 đáy gần nhất (mới hơn = phần tử cuối sau sort time) +bool IctTakeLastTwoSwings(IctSwingPoint &pts[], const int count, + IctSwingPoint &older, IctSwingPoint &newer) +{ + older.Clear(); + newer.Clear(); + if(count < 2) + return false; + older = pts[count - 2]; + newer = pts[count - 1]; + return older.Valid() && newer.Valid(); +} + +int IctFilterSwingsByMaxShift(IctSwingPoint &pts[], const int count, const int maxShift) +{ + if(maxShift <= 0 || count < 1) + return count; + + int w = 0; + for(int i = 0; i < count; i++) + { + if(pts[i].shift > maxShift) + continue; + if(w != i) + pts[w] = pts[i]; + w++; + } + if(w < count) + ArrayResize(pts, w); + return w; +} + +// Phân loại HH-HL / LH-LL từ pivot gần nhất — tránh ghép leg 4 swing cũ (sai trên H1) +bool IctBuildSwingSetRecentPivots(const string sym, const ENUM_TIMEFRAMES tf, + const int range, const int lookback, + const int maxRecentShift, + IctSwingSet &out, ENUM_ICT_STRUCT &biasOut) +{ + out.Clear(); + biasOut = ICT_STRUCT_NONE; + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, tf, range, lookback, highs, lows); + int nH = ArraySize(highs); + int nL = ArraySize(lows); + if(maxRecentShift > 0) + { + nH = IctFilterSwingsByMaxShift(highs, nH, maxRecentShift); + nL = IctFilterSwingsByMaxShift(lows, nL, maxRecentShift); + } + + IctSwingPoint hOld, hNew, lOld, lNew; + if(!IctTakeLastTwoSwings(highs, nH, hOld, hNew)) + return false; + if(!IctTakeLastTwoSwings(lows, nL, lOld, lNew)) + return false; + + out.h0 = hNew; + out.hasH0 = true; + out.h1 = hOld; + out.hasH1 = true; + out.l0 = lNew; + out.hasL0 = true; + out.l1 = lOld; + out.hasL1 = true; + + const bool hh = hNew.price > hOld.price + _Point; + const bool hl = lNew.price > lOld.price + _Point; + const bool lh = hNew.price < hOld.price - _Point; + const bool ll = lNew.price < lOld.price - _Point; + + if(hh && hl) + biasOut = ICT_STRUCT_BULL; + else if(lh && ll) + biasOut = ICT_STRUCT_BEAR; + else + biasOut = ICT_STRUCT_NONE; + + return out.IsComplete(); +} + +void IctGetKeyLevels(const ENUM_ICT_STRUCT bias, IctSwingSet &sw, + double &keyLv1, double &keyLv2) +{ + keyLv1 = 0.0; + keyLv2 = 0.0; + if(!sw.IsComplete()) + return; + + if(bias == ICT_STRUCT_BULL) + { + keyLv1 = sw.l0.price; + keyLv2 = sw.h0.price; + } + else if(bias == ICT_STRUCT_BEAR) + { + keyLv1 = sw.h0.price; + keyLv2 = sw.l0.price; + } +} + +#endif diff --git a/Include/ICT2026/Types.mqh b/Include/ICT2026/Types.mqh new file mode 100644 index 0000000..26efbdd --- /dev/null +++ b/Include/ICT2026/Types.mqh @@ -0,0 +1,209 @@ +//+------------------------------------------------------------------+ +//| Types.mqh — ICT 2026 data model | +//+------------------------------------------------------------------+ +#ifndef ICT2026_TYPES_MQH +#define ICT2026_TYPES_MQH + +enum ENUM_ICT_BIAS +{ + ICT_BIAS_NONE = 0, + ICT_BIAS_BULL = 1, + ICT_BIAS_BEAR = -1, + ICT_BIAS_RANGE = 2 +}; + +enum ENUM_ICT_STRUCT +{ + ICT_STRUCT_NONE = 0, + ICT_STRUCT_BULL = 1, + ICT_STRUCT_BEAR = -1 +}; + +enum ENUM_ICT_HTF_BIAS +{ + ICT_HTF_NONE = 0, + ICT_HTF_UP = 1, + ICT_HTF_DOWN = -1, + ICT_HTF_SIDEWAY = 2 +}; + +// BOS = Continue | CHoCH = Reversal +enum ENUM_ICT_MS_EVENT +{ + ICT_MS_NONE = 0, + ICT_MS_BOS = 1, + ICT_MS_CHOCH = 2 +}; + +enum ENUM_ICT_TREND +{ + ICT_TREND_NONE = 0, + ICT_TREND_UP = 1, + ICT_TREND_DOWN = -1 +}; + +// Rõ ràng (HH-HL/LH-LL) hoặc sớm (sau CHoCH, MS chưa rõ) +enum ENUM_ICT_TREND_PHASE +{ + ICT_TREND_PHASE_NONE = 0, + ICT_TREND_PHASE_CLEAR = 1, + ICT_TREND_PHASE_EARLY = 2 +}; + +struct IctSwingPoint +{ + double price; + datetime time; + int shift; + + void Clear() + { + price = 0.0; + time = 0; + shift = -1; + } + + bool Valid() const { return shift >= 0 && price > 0.0; } +}; + +struct IctSwingSet +{ + IctSwingPoint h0, h1, l0, l1; + bool hasH0, hasH1, hasL0, hasL1; + + bool IsComplete() const + { + return hasH0 && hasH1 && hasL0 && hasL1; + } + + void Clear() + { + h0.Clear(); h1.Clear(); l0.Clear(); l1.Clear(); + hasH0 = hasH1 = hasL0 = hasL1 = false; + } +}; + +struct IctHTFBiasState +{ + ENUM_ICT_HTF_BIAS bias; + double rangeHigh; + double rangeLow; + datetime lastUpdateTime; +}; + +struct IctDailyBiasState +{ + ENUM_ICT_BIAS bias; + ENUM_ICT_TREND_PHASE biasPhase; + ENUM_ICT_STRUCT structural; + ENUM_ICT_MS_EVENT lastEvent; + bool fibOk; + IctSwingSet swings; + double keyLv1; + double keyLv2; + ENUM_ICT_STRUCT lastClearStructural; + double pdh; + double pdl; + double pdc; + double eq; + IctHTFBiasState htf; + datetime lastBarTime; + string reason; + string displayReason; +}; + +struct IctIntradayState +{ + ENUM_ICT_TREND trend; + ENUM_ICT_TREND_PHASE trendPhase; + ENUM_ICT_STRUCT structural; + ENUM_ICT_MS_EVENT lastEvent; + IctSwingSet swings; + double keyLv1; + double keyLv2; + ENUM_ICT_STRUCT lastClearStructural; + datetime lastBarTime; + string displayReason; + bool isAllowTrade; +}; + +enum ENUM_ICT_FVG_SIDE +{ + ICT_FVG_NONE = 0, + ICT_FVG_BULL = 1, + ICT_FVG_BEAR = -1 +}; + +enum ENUM_ICT_FVG_STATE +{ + ICT_FVG_AVAILABLE = 0, + ICT_FVG_USED = 1 +}; + +enum ENUM_ICT_PD_ZONE +{ + ICT_PD_NONE = 0, + ICT_PD_PREMIUM = 1, + ICT_PD_DISCOUNT = -1, + ICT_PD_EQUILIBRIUM = 2 +}; + +struct IctFvgZone +{ + ulong id; + ENUM_ICT_FVG_SIDE side; + ENUM_ICT_FVG_STATE state; + ENUM_ICT_PD_ZONE pdZone; + double upper; + double lower; + double pdHigh; + double pdLow; + double pdEq; + double maxFillRatio; + datetime createdTime; + datetime timeStart; + datetime timeEnd; + datetime firstTouchTime; + datetime fvgUsedTime; + datetime expireTime; + bool locked; + datetime pdRangeStart; + datetime pdRangeEnd; + bool pdComplete; + bool pdSwing1Set; + + void Clear() + { + id = 0; + side = ICT_FVG_NONE; + state = ICT_FVG_AVAILABLE; + pdZone = ICT_PD_NONE; + upper = 0.0; + lower = 0.0; + pdHigh = 0.0; + pdLow = 0.0; + pdEq = 0.0; + maxFillRatio = 0.0; + createdTime = 0; + timeStart = 0; + timeEnd = 0; + firstTouchTime = 0; + fvgUsedTime = 0; + expireTime = 0; + locked = false; + pdRangeStart = 0; + pdRangeEnd = 0; + pdComplete = false; + pdSwing1Set = false; + } +}; + +struct IctLowTfState +{ + datetime lastBarTime; + int activeCount; + int availableCount; + string displayReason; +}; + +#endif