ICT 2026 Bias + intraday Trend, FVG, PD zone
This commit is contained in:
@@ -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.
|
||||
@@ -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.
@@ -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; // 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
|
||||
@@ -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
|
||||
@@ -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 <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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 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 <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() ← 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** |
|
||||
|
||||
---
|
||||
|
||||
<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 (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 |
|
||||
|
||||
---
|
||||
|
||||
<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` 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.
|
||||
|
||||
---
|
||||
|
||||
<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 | 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 |
|
||||
|
||||
---
|
||||
|
||||
<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 (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 |
|
||||
|
||||
---
|
||||
|
||||
<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* / 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,331 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Swing.mqh — pivot & swing set H0–L1 (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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user