ICT 2026 Bias + intraday Trend, FVG, PD zone

This commit is contained in:
Bell
2026-05-24 19:46:14 +07:00
parent 4755c3dc9b
commit e173949468
16 changed files with 3444 additions and 0 deletions
+17
View File
@@ -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.
+95
View File
@@ -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 <ICT2026/Config.mqh>
#include <ICT2026/DailyBias.mqh>
#include <ICT2026/IntradayStructure.mqh>
#include <ICT2026/LowTfTrend.mqh>
#include <ICT2026/Panel.mqh>
#include <ICT2026/Draw.mqh>
//+------------------------------------------------------------------+
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; }
//+------------------------------------------------------------------+
Binary file not shown.
+64
View File
@@ -0,0 +1,64 @@
//+------------------------------------------------------------------+
//| Config.mqh — ICT 2026 inputs |
//+------------------------------------------------------------------+
#ifndef ICT2026_CONFIG_MQH
#define ICT2026_CONFIG_MQH
#include <ICT2026/Types.mqh>
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; // bH0bL1 (theo TF chart)
input bool InpDrawIntraSwingLabels = true; // iH0iL1 (theo TF chart)
input bool InpDrawConfirmLabels = true; // H0L1 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
+326
View File
@@ -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 <ICT2026/Config.mqh>
#include <ICT2026/Swing.mqh>
#include <ICT2026/StructureCore.mqh>
#include <ICT2026/StructureTrend.mqh>
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
+139
View File
@@ -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*+H0L1 |
//+------------------------------------------------------------------+
#ifndef ICT2026_DRAW_MQH
#define ICT2026_DRAW_MQH
#include <ICT2026/Config.mqh>
#include <ICT2026/Swing.mqh>
#include <ICT2026/IntradayStructure.mqh>
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
+773
View File
@@ -0,0 +1,773 @@
//+------------------------------------------------------------------+
//| Fvg.mqh — iTF Fair Value Gap (Available / Used) |
//+------------------------------------------------------------------+
#ifndef ICT2026_FVG_MQH
#define ICT2026_FVG_MQH
#include <ICT2026/Config.mqh>
#include <ICT2026/IntradayStructure.mqh>
#include <ICT2026/Swing.mqh>
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
+126
View File
@@ -0,0 +1,126 @@
//+------------------------------------------------------------------+
//| FvgDraw.mqh — vẽ iTF FVG + Premium/Discount trên chart |
//+------------------------------------------------------------------+
#ifndef ICT2026_FVGDRAW_MQH
#define ICT2026_FVGDRAW_MQH
#include <ICT2026/Config.mqh>
#include <ICT2026/Fvg.mqh>
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
+161
View File
@@ -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 <ICT2026/Config.mqh>
#include <ICT2026/Swing.mqh>
#include <ICT2026/StructureCore.mqh>
#include <ICT2026/StructureTrend.mqh>
#include <ICT2026/DailyBias.mqh>
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
+112
View File
@@ -0,0 +1,112 @@
//+------------------------------------------------------------------+
//| LowTfTrend.mqh — Low TF layer: iTF FVG khi IsAllowTrade |
//+------------------------------------------------------------------+
#ifndef ICT2026_LOWTFTREND_MQH
#define ICT2026_LOWTFTREND_MQH
#include <ICT2026/Config.mqh>
#include <ICT2026/Fvg.mqh>
#include <ICT2026/FvgDraw.mqh>
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
+174
View File
@@ -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 <ICT2026/Config.mqh>
#include <ICT2026/DailyBias.mqh>
#include <ICT2026/IntradayStructure.mqh>
#include <ICT2026/LowTfTrend.mqh>
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
+636
View File
@@ -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)
---
<a id="folder-structure"></a>
## 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 H0L1, 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*/H0L1 trên chart
```
**Include trong code:** `#include <ICT2026/TênFile.mqh>`
---
<a id="pipeline"></a>
## 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.
---
<a id="pipeline-intraday"></a>
## 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() ← H0L1 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** |
---
<a id="pipeline-lowtf"></a>
## 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 (ABC)
| 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 |
---
<a id="business-logic"></a>
## 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``L0 > L1` (HH-HL) | L1 → H1 → L0 → H0 |
| **Bear** | `H0 < H1``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` openclose) 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]``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*` + `H0L1` |
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.
---
<a id="file-reference"></a>
## 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 |
---
<a id="api"></a>
## API
```cpp
#include <ICT2026/DailyBias.mqh>
// 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();
```
---
<a id="inputs"></a>
## 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 | bH0bL1 |
| `InpDrawIntraSwingLabels` | true | iH0iL1 |
| `InpDrawConfirmLabels` | true | H0L1 (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 |
---
<a id="roadmap"></a>
## Chưa implement
| Tính năng | Ghi chú |
|-----------|---------|
| Tầng 3 intraday confirm (logic trade) | Chưa có — chỉ vẽ pivot M5 (H0L1) |
| 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 |
---
<a id="changelog"></a>
## 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* / H0L1)
- Scan FVG khi `IsAllowTrade`; khóa vẽ; expire 3 ngày
### v1.09 — Label swing: b* (Bias), i* (Intraday), H0L1 (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 **H0L1** (tắt bằng `InpDrawChartLabels = false`)
### v1.05 — Vẽ chart: label swing (đã thu gọn)
- `Draw.mqh`: H0L1 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 H0L1 + Key levels
### v1.03.1 — Panel line spacing
- `InpPanelLineSpacing` (mặc định 22px)
- Tách `Bias` / `Ly do``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 H0L1, 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
+88
View File
@@ -0,0 +1,88 @@
//+------------------------------------------------------------------+
//| StructureCore.mqh — body break & BOS/CHoCH detect (dùng chung) |
//+------------------------------------------------------------------+
#ifndef ICT2026_STRUCTURECORE_MQH
#define ICT2026_STRUCTURECORE_MQH
#include <ICT2026/Config.mqh>
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
+193
View File
@@ -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 <ICT2026/Types.mqh>
#include <ICT2026/Swing.mqh>
#include <ICT2026/StructureCore.mqh>
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
+331
View File
@@ -0,0 +1,331 @@
//+------------------------------------------------------------------+
//| Swing.mqh — pivot & swing set H0L1 (tham khảo HyperICT) |
//+------------------------------------------------------------------+
#ifndef ICT2026_SWING_MQH
#define ICT2026_SWING_MQH
#include <ICT2026/Types.mqh>
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
+209
View File
@@ -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