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