Files
2026-05-17 23:54:51 +07:00

386 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| SwingEngine.mqh |
//| Pivot HTF, H0L1, khóa snapshot, roll swing §1.4 |
//+------------------------------------------------------------------+
//| ĐÃ GIẢI QUYẾT: |
//| • H0L1 ghép theo leg (Key LV1 tạo Key LV2), không 2H+2L tách rời |
//| • Bear: L0=KeyLV2, H0=đỉnh trước L1 (tạo đáy L0); đỉnh sau L0→newH0 |
//| • Bull: H0=KeyLV2, L0=đáy trước H1 (tạo đỉnh H0) |
//| • HH-HL / LH-LL — ClassifyStructure |
//| • Khóa swing lúc bot chạy — LockInitialSnapshot (không đổi mỗi nến) |
//| • §1.4 roll: Continue, CHoCH case1 (L1 giữ), CHoCH case2 bear |
//| • Xác nhận pivot sau phá Key — IsConfirmedSwingHigh/Low + range |
//| CHƯA TỐI ƯU: quét OB chất lượng ICT (mitigation) |
//+------------------------------------------------------------------+
#ifndef HYPERICT_SWINGENGINE_MQH
#define HYPERICT_SWINGENGINE_MQH
#include <HyperICT/Types.mqh>
#include <HyperICT/Config.mqh>
class CSwingEngine
{
public:
//--- Pivot chuẩn: `range` nến mỗi phía (dùng cho §1.4 confirm đỉnh/đáy)
static bool IsSwingHigh(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;
}
static bool IsSwingLow(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;
}
static int MinConfirmShift(const int range)
{
return MathMax(1, range) + 1;
}
static bool CanConfirmPivot(const int pivotShift, const int range)
{
return pivotShift >= MinConfirmShift(range);
}
static void FillSwingHigh(const string sym, const ENUM_TIMEFRAMES tf,
const int shift, SwingPoint &out)
{
out.price = iHigh(sym, tf, shift);
out.time = iTime(sym, tf, shift);
out.shift = shift;
}
static void FillSwingLow(const string sym, const ENUM_TIMEFRAMES tf,
const int shift, SwingPoint &out)
{
out.price = iLow(sym, tf, shift);
out.time = iTime(sym, tf, shift);
out.shift = shift;
}
static bool IsConfirmedSwingHigh(const string sym, const ENUM_TIMEFRAMES tf,
const SwingPoint &pt, const int range)
{
if(!pt.Valid() || !CanConfirmPivot(pt.shift, range))
return false;
return IsSwingHigh(sym, tf, pt.shift, range);
}
static bool IsConfirmedSwingLow(const string sym, const ENUM_TIMEFRAMES tf,
const SwingPoint &pt, const int range)
{
if(!pt.Valid() || !CanConfirmPivot(pt.shift, range))
return false;
return IsSwingLow(sym, tf, pt.shift, range);
}
static void SortByTime(SwingPoint &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 SwingPoint t = pts[i];
pts[i] = pts[j];
pts[j] = t;
}
}
static void CollectSwings(const string sym, const ENUM_TIMEFRAMES tf,
const int range, const int lookback,
SwingPoint &highs[], SwingPoint &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(IsSwingHigh(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(IsSwingLow(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++;
}
}
SortByTime(highs, nH);
SortByTime(lows, nL);
}
// Pivot cũ hơn anchor (shift > anchor) và GẦN anchor nhất (shift nhỏ nhất trong các ứng viên)
static bool FindMostRecentOlder(const SwingPoint &pts[], const int count,
const int anchorShift,
SwingPoint &out)
{
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 false;
out = pts[best];
return true;
}
// Thứ tự thời gian (cũ → mới): Bear H1→L1→H0→L0 | Bull L1→H1→L0→H0
static bool ValidateSwingChronology(const SwingSet &sw, const ENUM_STRUCT_BIAS bias)
{
if(!sw.IsComplete())
return false;
if(bias == STRUCT_BEAR)
return sw.h1.shift > sw.l1.shift &&
sw.l1.shift > sw.h0.shift &&
sw.h0.shift > sw.l0.shift;
if(bias == STRUCT_BULL)
return sw.l1.shift > sw.h1.shift &&
sw.h1.shift > sw.l0.shift &&
sw.l0.shift > sw.h0.shift;
return false;
}
//--- Bear: L0=KeyLV2, H0=KeyLV1 | Chuỗi: H1 → L1 → H0 → L0
static bool PickStructuralBear(const SwingPoint &highs[], const int nH,
const SwingPoint &lows[], const int nL,
SwingSet &out)
{
out.Clear();
if(nH < 2 || nL < 2)
return false;
out.l0 = lows[nL - 1];
out.hasL0 = true;
if(!FindMostRecentOlder(lows, nL, out.l0.shift, out.l1))
return false;
out.hasL1 = true;
if(!FindMostRecentOlder(highs, nH, out.l1.shift, out.h0))
return false;
out.hasH0 = true;
if(!FindMostRecentOlder(highs, nH, out.h0.shift, out.h1))
return false;
out.hasH1 = true;
return true;
}
//--- Bull: H0=KeyLV2, L0=KeyLV1 | Chuỗi: L1 → H1 → L0 → H0
static bool PickStructuralBull(const SwingPoint &highs[], const int nH,
const SwingPoint &lows[], const int nL,
SwingSet &out)
{
out.Clear();
if(nH < 2 || nL < 2)
return false;
out.h0 = highs[nH - 1];
out.hasH0 = true;
if(!FindMostRecentOlder(lows, nL, out.h0.shift, out.l0))
return false;
out.hasL0 = true;
if(!FindMostRecentOlder(highs, nH, out.l0.shift, out.h1))
return false;
out.hasH1 = true;
if(!FindMostRecentOlder(lows, nL, out.h1.shift, out.l1))
return false;
out.hasL1 = true;
return true;
}
//--- §1.1 tiên quyết đặc điểm 1: HH-HL hoặc LH-LL
static ENUM_STRUCT_BIAS ClassifyStructure(const SwingSet &sw)
{
if(!sw.IsComplete())
return STRUCT_NONE;
if(sw.h0.price > sw.h1.price && sw.l0.price > sw.l1.price)
return STRUCT_BULL;
if(sw.h0.price < sw.h1.price && sw.l0.price < sw.l1.price)
return STRUCT_BEAR;
return STRUCT_NONE;
}
static bool BuildSwingSet(const string sym, const ENUM_TIMEFRAMES tf,
const int range, const int lookback,
SwingSet &out)
{
out.Clear();
SwingPoint highs[], lows[];
CollectSwings(sym, tf, range, lookback, highs, lows);
const int nH = ArraySize(highs);
const int nL = ArraySize(lows);
SwingSet bear, bull;
const bool hasBear = PickStructuralBear(highs, nH, lows, nL, bear);
const bool hasBull = PickStructuralBull(highs, nH, lows, nL, bull);
const ENUM_STRUCT_BIAS biasBear = hasBear ? ClassifyStructure(bear) : STRUCT_NONE;
const ENUM_STRUCT_BIAS biasBull = hasBull ? ClassifyStructure(bull) : STRUCT_NONE;
ENUM_STRUCT_BIAS chosen = STRUCT_NONE;
if(biasBear == STRUCT_BEAR)
{
out = bear;
chosen = STRUCT_BEAR;
}
else if(biasBull == STRUCT_BULL)
{
out = bull;
chosen = STRUCT_BULL;
}
else if(hasBear)
{
out = bear;
chosen = ClassifyStructure(bear);
}
else if(hasBull)
{
out = bull;
chosen = ClassifyStructure(bull);
}
else
return false;
if(InpDebug && chosen != STRUCT_NONE && !ValidateSwingChronology(out, chosen))
Print("[HyperICT/Swing] Cảnh báo: thứ tự H/L không khớp chuỗi chuẩn");
return out.IsComplete();
}
//--- § chú ý: snapshot HTF cố định từ khi bot chạy (chỉ đổi qua UpdateEngine)
static bool LockInitialSnapshot(HtfContext &ctx)
{
ctx.swings.Clear();
if(!BuildSwingSet(ctx.symbol, ctx.htf, InpSwingRange, InpSwingLookback, ctx.swings))
return false;
ctx.structBias = ClassifyStructure(ctx.swings);
ctx.lockedAt = TimeCurrent();
ctx.snapshotReady = true;
return true;
}
//--- §1.4a Bull Continue P2: H0→H1, L0→L1, newH0→H0, newL0→L0
static void RollBullContinue(SwingSet &sw, const SwingPoint &newH0, const SwingPoint &newL0)
{
sw.h1 = sw.h0;
sw.hasH1 = sw.hasH0;
sw.l1 = sw.l0;
sw.hasL1 = sw.hasL0;
sw.h0 = newH0;
sw.hasH0 = true;
sw.l0 = newL0;
sw.hasL0 = true;
}
//--- §1.4b Bull CHoCH P3 case1: H0→H1, L1 giữ, newL0→L0, newH0→H0
static void RollBullChochCase1(SwingSet &sw, const SwingPoint &newH0, const SwingPoint &newL0)
{
sw.h1 = sw.h0;
sw.hasH1 = sw.hasH0;
// L1 giữ nguyên theo spec
sw.l0 = newL0;
sw.hasL0 = true;
sw.h0 = newH0;
sw.hasH0 = true;
}
//--- §1.4b Bull CHoCH P3 case2: confirm bear — H0→H1, newH0→H0, newL0→L1, newL02→L0
static void RollBullChochCase2(SwingSet &sw,
const SwingPoint &newH0,
const SwingPoint &newL0,
const SwingPoint &newL02)
{
sw.h1 = sw.h0;
sw.hasH1 = sw.hasH0;
sw.h0 = newH0;
sw.hasH0 = true;
sw.l1 = newL0;
sw.hasL1 = true;
sw.l0 = newL02;
sw.hasL0 = true;
}
//--- §1.4c Bear Continue (đối xứng bull continue)
static void RollBearContinue(SwingSet &sw, const SwingPoint &newH0, const SwingPoint &newL0)
{
sw.l1 = sw.l0;
sw.hasL1 = sw.hasL0;
sw.h1 = sw.h0;
sw.hasH1 = sw.hasH0;
sw.l0 = newL0;
sw.hasL0 = true;
sw.h0 = newH0;
sw.hasH0 = true;
}
static void RollBearChochCase1(SwingSet &sw, const SwingPoint &newH0, const SwingPoint &newL0)
{
sw.l1 = sw.l0;
sw.hasL1 = sw.hasL0;
sw.h0 = newH0;
sw.hasH0 = true;
sw.l0 = newL0;
sw.hasL0 = true;
}
static void RollBearChochCase2(SwingSet &sw,
const SwingPoint &newH0,
const SwingPoint &newL0,
const SwingPoint &newH02)
{
sw.l1 = sw.l0;
sw.hasL1 = sw.hasL0;
sw.l0 = newL0;
sw.hasL0 = true;
sw.h0 = newH02;
sw.hasH0 = true;
sw.h1 = newH0;
sw.hasH1 = true;
}
};
#endif // HYPERICT_SWINGENGINE_MQH