diff --git a/.gitignore b/.gitignore index 907fa5f..8d075ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ Profiles/ Experts/*.ex5 *.dat -Include/ Logs/*.log \ No newline at end of file diff --git a/Experts/HyperICT.mq5 b/Experts/HyperICT.mq5 new file mode 100644 index 0000000..59958d4 --- /dev/null +++ b/Experts/HyperICT.mq5 @@ -0,0 +1,55 @@ +//+------------------------------------------------------------------+ +//| HyperICT.mq5 | +//| HTF structure: swings, Key LV, CHoCH / Continue (modular) | +//+------------------------------------------------------------------+ +#property copyright "HyperICT" +#property version "0.11" +#property description "HTF structure | Fib=init only | pivot confirm=InpSwingRange" + +#include +#include +#include +#include +#include + +HtfContext g_ctx; + +//+------------------------------------------------------------------+ +int OnInit() +{ + g_ctx.symbol = _Symbol; + g_ctx.htf = InpHtf; + g_ctx.lastHtfBar = iTime(_Symbol, InpHtf, 0); + + if(!CStateMachine::Init(g_ctx, _Symbol)) + { + Print("[HyperICT] Init: chưa đủ swing — chờ dữ liệu hoặc giảm InpSwingRange"); + return INIT_SUCCEEDED; + } + + CDraw::Render(g_ctx); + CPanel::Render(g_ctx); + ChartRedraw(); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + CDraw::ClearAll(); + CPanel::Clear(); + ChartRedraw(); +} + +//+------------------------------------------------------------------+ +void OnTick() +{ + CStateMachine::OnChartEvent(g_ctx); + CDraw::Render(g_ctx); + CPanel::Render(g_ctx); +} + +//+------------------------------------------------------------------+ +ENUM_HTF_STATE HyperICT_GetState() { return g_ctx.state; } + +//+------------------------------------------------------------------+ diff --git a/Experts/HyperICT_compile.log b/Experts/HyperICT_compile.log new file mode 100644 index 0000000..6d25b15 Binary files /dev/null and b/Experts/HyperICT_compile.log differ diff --git a/Experts/MyICT_compile.log b/Experts/MyICT_compile.log new file mode 100644 index 0000000..9cee515 Binary files /dev/null and b/Experts/MyICT_compile.log differ diff --git a/Include/HyperICT/Classifier.mqh b/Include/HyperICT/Classifier.mqh new file mode 100644 index 0000000..ebacfdc --- /dev/null +++ b/Include/HyperICT/Classifier.mqh @@ -0,0 +1,93 @@ +//+------------------------------------------------------------------+ +//| Classifier.mqh — 7+ trạng thái §1.1 (bar đóng shift 1) | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_CLASSIFIER_MQH +#define HYPERICT_CLASSIFIER_MQH + +#include +#include +#include +#include + +//+------------------------------------------------------------------+ +class CClassifier +{ +public: + static ENUM_HTF_STATE Evaluate(HtfContext &ctx) + { + if(!ctx.snapshotReady || !ctx.swings.IsComplete()) + return HTF_NEUTRAL; + + if(ctx.structBias == STRUCT_NONE) + return HTF_NEUTRAL; + + const int sh = 1; + const double bodyMid = (iClose(ctx.symbol, ctx.htf, sh) + + iOpen(ctx.symbol, ctx.htf, sh)) * 0.5; + + if(ctx.update.phase != UPD_PHASE_IDLE) + { + if(ctx.structBias == STRUCT_BULL) + { + if(ctx.update.event == UPD_CONTINUE) + return HTF_BULL_CONTINUE; + if(ctx.update.event == UPD_CHOCH) + return HTF_BULL_CHOCH; + } + else if(ctx.structBias == STRUCT_BEAR) + { + if(ctx.update.event == UPD_CONTINUE) + return HTF_BEAR_CONTINUE; + if(ctx.update.event == UPD_CHOCH) + return HTF_BEAR_CHOCH; + } + } + + if(!ctx.fibOk) + { + if(ctx.structBias == STRUCT_BULL) + return HTF_BULL_INCOMPLETE; + if(ctx.structBias == STRUCT_BEAR) + return HTF_BEAR_INCOMPLETE; + } + + if(ctx.structBias == STRUCT_BULL) + { + if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.keyLv1.price)) + return HTF_BULL_CHOCH; + if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.keyLv2.price)) + return HTF_BULL_CONTINUE; + if(CKeyLevels::PriceBetween(bodyMid, ctx.keyLv1.price, ctx.keyLv2.price)) + return HTF_BULL_PULLBACK; + } + else if(ctx.structBias == STRUCT_BEAR) + { + if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.keyLv1.price)) + return HTF_BEAR_CHOCH; + if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.keyLv2.price)) + return HTF_BEAR_CONTINUE; + if(CKeyLevels::PriceBetween(bodyMid, ctx.keyLv1.price, ctx.keyLv2.price)) + return HTF_BEAR_PULLBACK; + } + + return HTF_NEUTRAL; + } + + static string StateText(const ENUM_HTF_STATE s) + { + switch(s) + { + case HTF_BULL_INCOMPLETE: return "BULL (Fib pending)"; + case HTF_BEAR_INCOMPLETE: return "BEAR (Fib pending)"; + case HTF_BULL_PULLBACK: return "BULL PULLBACK"; + case HTF_BULL_CHOCH: return "BULL CHoCH"; + case HTF_BULL_CONTINUE: return "BULL CONTINUE"; + case HTF_BEAR_PULLBACK: return "BEAR PULLBACK"; + case HTF_BEAR_CHOCH: return "BEAR CHoCH"; + case HTF_BEAR_CONTINUE: return "BEAR CONTINUE"; + default: return "NEUTRAL"; + } + } +}; + +#endif // HYPERICT_CLASSIFIER_MQH diff --git a/Include/HyperICT/Config.mqh b/Include/HyperICT/Config.mqh new file mode 100644 index 0000000..3bbc840 --- /dev/null +++ b/Include/HyperICT/Config.mqh @@ -0,0 +1,35 @@ +//+------------------------------------------------------------------+ +//| Config.mqh — inputs & hằng số | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_CONFIG_MQH +#define HYPERICT_CONFIG_MQH + +#include + +input group "══ HTF Structure ══" +input ENUM_TIMEFRAMES InpHtf = PERIOD_H1; +input int InpSwingRange = 3; +input int InpSwingLookback = 400; +input double InpFibMinRatio = 0.382; + +input group "══ Hiển thị ══" +input bool InpDrawSwings = true; +input bool InpDrawZones = true; +input bool InpDrawPanel = true; +input uchar InpZoneFillAlpha = 72; +input color InpClrBullHi = clrDodgerBlue; +input color InpClrBullLo = clrDeepSkyBlue; +input color InpClrBearHi = clrOrangeRed; +input color InpClrBearLo = clrGold; +input color InpClrSupply = C'140,50,55'; +input color InpClrDemand = C'35,110,55'; +input int InpLabelFontSize = 9; +input int InpPanelX = 12; +input int InpPanelY = 28; + +input group "══ Debug ══" +input bool InpDebug = false; + +const string HICT_OBJ_PFX = "HICT_"; + +#endif // HYPERICT_CONFIG_MQH diff --git a/Include/HyperICT/Draw.mqh b/Include/HyperICT/Draw.mqh new file mode 100644 index 0000000..fd305d0 --- /dev/null +++ b/Include/HyperICT/Draw.mqh @@ -0,0 +1,126 @@ +//+------------------------------------------------------------------+ +//| Draw.mqh — labels H0–L1, vùng Key LV1/L2 | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_DRAW_MQH +#define HYPERICT_DRAW_MQH + +#include +#include +#include + +//+------------------------------------------------------------------+ +class CDraw +{ + static long Chart() { return ChartID(); } + + static color WithAlpha(const color clr, const uchar alpha) + { + return (color)((long)clr | ((long)alpha << 24)); + } + + static void DeleteByPrefix(const string prefix) + { + const long ch = Chart(); + 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); + } + } + + static void DrawSwingLabel(const string id, const string text, + const datetime t, const double price, + const bool isHigh, const color clr) + { + const long ch = Chart(); + 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, InpLabelFontSize); + ObjectSetString(ch, id, OBJPROP_FONT, "Arial Bold"); + ObjectSetInteger(ch, id, OBJPROP_ANCHOR, isHigh ? ANCHOR_BOTTOM : ANCHOR_TOP); + ObjectSetInteger(ch, id, OBJPROP_SELECTABLE, false); + } + + static void DrawZoneRect(const string id, + const datetime t1, const double top, const double bottom, + const color fillClr, const string tip) + { + const long ch = Chart(); + const datetime t2 = iTime(_Symbol, PERIOD_CURRENT, 0); + if(t2 == 0) return; + + if(ObjectFind(ch, id) < 0) + ObjectCreate(ch, id, OBJ_RECTANGLE, 0, t1, top, t2, bottom); + ObjectMove(ch, id, 0, t1, top); + ObjectMove(ch, id, 1, t2, bottom); + ObjectSetInteger(ch, id, OBJPROP_COLOR, fillClr); + ObjectSetInteger(ch, id, OBJPROP_FILL, true); + ObjectSetInteger(ch, id, OBJPROP_BACK, true); + ObjectSetInteger(ch, id, OBJPROP_SELECTABLE, false); + ObjectSetString(ch, id, OBJPROP_TOOLTIP, tip); + } + +public: + static void ClearAll() + { + DeleteByPrefix(HICT_OBJ_PFX); + } + + static void Render(HtfContext &ctx) + { + if(!ctx.snapshotReady) + return; + + const string p = HICT_OBJ_PFX; + + if(InpDrawSwings && ctx.swings.IsComplete()) + { + DrawSwingLabel(p + "H0", "H0", ctx.swings.h0.time, ctx.swings.h0.price, true, InpClrBullHi); + DrawSwingLabel(p + "H1", "H1", ctx.swings.h1.time, ctx.swings.h1.price, true, InpClrBullHi); + DrawSwingLabel(p + "L0", "L0", ctx.swings.l0.time, ctx.swings.l0.price, false, InpClrBullLo); + DrawSwingLabel(p + "L1", "L1", ctx.swings.l1.time, ctx.swings.l1.price, false, InpClrBullLo); + + if(ctx.update.newH0.Valid()) + DrawSwingLabel(p + "nH0", "newH0", ctx.update.newH0.time, + ctx.update.newH0.price, true, clrYellow); + if(ctx.update.newL0.Valid()) + DrawSwingLabel(p + "nL0", "newL0", ctx.update.newL0.time, + ctx.update.newL0.price, false, clrYellow); + if(ctx.update.newL02.Valid()) + DrawSwingLabel(p + "nL2", "newL02", ctx.update.newL02.time, + ctx.update.newL02.price, false, clrOrange); + } + + if(!InpDrawZones) + return; + + double top, bot; + datetime tOb; + + if(ctx.keyLv1.zone.valid) + { + tOb = ctx.keyLv1.zone.obTime; + const bool supply = (ctx.structBias == STRUCT_BULL); + CKeyLevels::ZoneRectBounds(ctx.keyLv1.zone, supply, top, bot); + DrawZoneRect(p + "Z_K1", tOb, top, bot, + WithAlpha(supply ? InpClrSupply : InpClrDemand, InpZoneFillAlpha), + "Key LV1"); + } + + if(ctx.keyLv2.zone.valid) + { + tOb = ctx.keyLv2.zone.obTime; + const bool supply = (ctx.structBias == STRUCT_BEAR); + CKeyLevels::ZoneRectBounds(ctx.keyLv2.zone, supply, top, bot); + DrawZoneRect(p + "Z_K2", tOb, top, bot, + WithAlpha(supply ? InpClrSupply : InpClrDemand, InpZoneFillAlpha), + "Key LV2"); + } + } +}; + +#endif // HYPERICT_DRAW_MQH diff --git a/Include/HyperICT/FibValidator.mqh b/Include/HyperICT/FibValidator.mqh new file mode 100644 index 0000000..617c6ce --- /dev/null +++ b/Include/HyperICT/FibValidator.mqh @@ -0,0 +1,31 @@ +//+------------------------------------------------------------------+ +//| FibValidator.mqh — Fib 0.382 chỉ cho tiên quyết trend ban đầu | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_FIBVALIDATOR_MQH +#define HYPERICT_FIBVALIDATOR_MQH + +#include +#include + +class CFibValidator +{ +public: + static double LegSize(const double from, const double to) + { + return MathAbs(to - from); + } + + // Sóng hồi H1→L0 >= ratio × sóng L1→H1 (bull & bear, chỉ lúc lock trend) + static bool IsPullbackValid(const SwingSet &sw, const double ratio) + { + if(!sw.IsComplete()) + return false; + const double impulse = LegSize(sw.l1.price, sw.h1.price); + const double pullback = LegSize(sw.h1.price, sw.l0.price); + if(impulse <= 0.0) + return false; + return pullback >= impulse * ratio; + } +}; + +#endif // HYPERICT_FIBVALIDATOR_MQH diff --git a/Include/HyperICT/KeyLevels.mqh b/Include/HyperICT/KeyLevels.mqh new file mode 100644 index 0000000..4a43246 --- /dev/null +++ b/Include/HyperICT/KeyLevels.mqh @@ -0,0 +1,159 @@ +//+------------------------------------------------------------------+ +//| KeyLevels.mqh — Key LV1/L2, supply/demand OB (body break) | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_KEYLEVELS_MQH +#define HYPERICT_KEYLEVELS_MQH + +#include +#include + +//+------------------------------------------------------------------+ +class CKeyLevels +{ +public: + static double BodyTop(const string sym, const ENUM_TIMEFRAMES tf, const int shift) + { + return MathMax(iOpen(sym, tf, shift), iClose(sym, tf, shift)); + } + + static double BodyBottom(const string sym, const ENUM_TIMEFRAMES tf, const int shift) + { + return MathMin(iOpen(sym, tf, shift), iClose(sym, tf, shift)); + } + + static bool IsBearCandle(const string sym, const ENUM_TIMEFRAMES tf, const int shift) + { + return iClose(sym, tf, shift) < iOpen(sym, tf, shift); + } + + static bool IsBullCandle(const string sym, const ENUM_TIMEFRAMES tf, const int shift) + { + return iClose(sym, tf, shift) > iOpen(sym, tf, shift); + } + + // Nến đỏ cuối cùng trước/at swing low (supply @ L0) + static bool FindSupplyOB(const string sym, const ENUM_TIMEFRAMES tf, + const SwingPoint &swingLow, + OrderBlockZone &out) + { + out.Clear(); + if(swingLow.shift < 1) + return false; + + const int from = swingLow.shift + 8; + const int to = MathMax(1, swingLow.shift - 1); + for(int sh = from; sh >= to; sh--) + { + if(!IsBearCandle(sym, tf, sh)) + continue; + const double l = iLow(sym, tf, sh); + if(l > swingLow.price + 20 * _Point) + continue; + out.valid = true; + out.swingPrice = swingLow.price; + out.obHigh = iHigh(sym, tf, sh); + out.obLow = iLow(sym, tf, sh); + out.obTime = iTime(sym, tf, sh); + out.obShift = sh; + return true; + } + return false; + } + + // Nến xanh cuối cùng tại swing high (demand @ H0) + static bool FindDemandOB(const string sym, const ENUM_TIMEFRAMES tf, + const SwingPoint &swingHigh, + OrderBlockZone &out) + { + out.Clear(); + if(swingHigh.shift < 1) + return false; + + const int from = swingHigh.shift + 8; + const int to = MathMax(1, swingHigh.shift - 1); + for(int sh = from; sh >= to; sh--) + { + if(!IsBullCandle(sym, tf, sh)) + continue; + const double h = iHigh(sym, tf, sh); + if(h < swingHigh.price - 20 * _Point) + continue; + out.valid = true; + out.swingPrice = swingHigh.price; + out.obHigh = iHigh(sym, tf, sh); + out.obLow = iLow(sym, tf, sh); + out.obTime = iTime(sym, tf, sh); + out.obShift = sh; + return true; + } + return false; + } + + static void BuildKeyLevels(const string sym, const ENUM_TIMEFRAMES tf, + const ENUM_STRUCT_BIAS bias, + const SwingSet &sw, + KeyLevelPair &lv1, KeyLevelPair &lv2) + { + lv1.zone.Clear(); + lv2.zone.Clear(); + if(!sw.IsComplete()) + return; + + if(bias == STRUCT_BULL) + { + lv1.price = sw.l0.price; + lv2.price = sw.h0.price; + FindSupplyOB(sym, tf, sw.l0, lv1.zone); + FindDemandOB(sym, tf, sw.h0, lv2.zone); + } + else if(bias == STRUCT_BEAR) + { + lv1.price = sw.h0.price; + lv2.price = sw.l0.price; + FindDemandOB(sym, tf, sw.h0, lv1.zone); // Key LV1 bear = H0 (demand side OB at H0) + FindSupplyOB(sym, tf, sw.l0, lv2.zone); + } + } + + static bool BodyBreakBelow(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const double level) + { + return BodyBottom(sym, tf, shift) < level - _Point; + } + + static bool BodyBreakAbove(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, const double level) + { + return BodyTop(sym, tf, shift) > level + _Point; + } + + static bool PriceBetween(const double price, const double a, const double b) + { + const double lo = MathMin(a, b); + const double hi = MathMax(a, b); + return price >= lo - _Point && price <= hi + _Point; + } + + static void ZoneRectBounds(const OrderBlockZone &z, + const bool supplyAtLow, + double &top, double &bottom) + { + if(!z.valid) + { + top = bottom = z.swingPrice; + return; + } + if(supplyAtLow) + { + top = MathMax(z.swingPrice, z.obHigh); + bottom = MathMin(z.swingPrice, z.obLow); + } + else + { + top = MathMax(z.swingPrice, z.obHigh); + bottom = MathMin(z.swingPrice, z.obLow); + } + } +}; + +#endif // HYPERICT_KEYLEVELS_MQH diff --git a/Include/HyperICT/Panel.mqh b/Include/HyperICT/Panel.mqh new file mode 100644 index 0000000..28ea96e --- /dev/null +++ b/Include/HyperICT/Panel.mqh @@ -0,0 +1,64 @@ +//+------------------------------------------------------------------+ +//| Panel.mqh — nhãn trạng thái góc chart | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_PANEL_MQH +#define HYPERICT_PANEL_MQH + +#include +#include +#include + +//+------------------------------------------------------------------+ +class CPanel +{ +public: + static void Clear() + { + ObjectDelete(ChartID(), HICT_OBJ_PFX + "PANEL"); + } + + static void Render(HtfContext &ctx) + { + if(!InpDrawPanel) + { + Clear(); + return; + } + + const long ch = ChartID(); + const string name = HICT_OBJ_PFX + "PANEL"; + + string bias = "—"; + if(ctx.structBias == STRUCT_BULL) bias = "HH-HL"; + else if(ctx.structBias == STRUCT_BEAR) bias = "LH-LL"; + + string txt = StringFormat("HyperICT | %s %s\n", ctx.symbol, EnumToString(ctx.htf)); + txt += StringFormat("Structure: %s | Fib: %s\n", bias, ctx.fibOk ? "OK" : "pending"); + txt += "State: " + CClassifier::StateText(ctx.state) + "\n"; + if(ctx.swings.IsComplete()) + { + txt += StringFormat("H0=%.5f L0=%.5f | Key1=%.5f Key2=%.5f", + ctx.swings.h0.price, ctx.swings.l0.price, + ctx.keyLv1.price, ctx.keyLv2.price); + } + if(ctx.update.phase != UPD_PHASE_IDLE) + txt += StringFormat("\nUpdate: evt=%d phase=%d | swingR=%d", + ctx.update.event, ctx.update.phase, InpSwingRange); + + 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_XDISTANCE, InpPanelX); + ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, InpPanelY); + ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, InpLabelFontSize + 1); + ObjectSetInteger(ch, name, OBJPROP_COLOR, clrWhite); + ObjectSetString(ch, name, OBJPROP_TEXT, txt); + } +}; + +#endif // HYPERICT_PANEL_MQH diff --git a/Include/HyperICT/StateMachine.mqh b/Include/HyperICT/StateMachine.mqh new file mode 100644 index 0000000..e7daba5 --- /dev/null +++ b/Include/HyperICT/StateMachine.mqh @@ -0,0 +1,71 @@ +//+------------------------------------------------------------------+ +//| StateMachine.mqh — pipeline đánh giá HTF mỗi bar | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_STATEMACHINE_MQH +#define HYPERICT_STATEMACHINE_MQH + +#include +#include +#include +#include +#include +#include +#include + +//+------------------------------------------------------------------+ +class CStateMachine +{ + static void Dbg(const string msg) + { + if(InpDebug) + Print("[HyperICT] ", msg); + } + + static void RefreshDerived(HtfContext &ctx) + { + // Fib 0.382 chỉ kiểm tra tiên quyết trên bộ swing hiện tại (không dùng trong §1.4) + ctx.fibOk = CFibValidator::IsPullbackValid(ctx.swings, InpFibMinRatio); + CKeyLevels::BuildKeyLevels(ctx.symbol, ctx.htf, ctx.structBias, + ctx.swings, ctx.keyLv1, ctx.keyLv2); + ctx.state = CClassifier::Evaluate(ctx); + } + +public: + static bool Init(HtfContext &ctx, const string sym) + { + ctx.symbol = sym; + ctx.htf = InpHtf; + ctx.update.Clear(); + ctx.lastHtfBar = 0; + ctx.snapshotReady = false; + + if(!CSwingEngine::LockInitialSnapshot(ctx)) + { + Dbg("Init fail: không đủ 4 swing HTF"); + return false; + } + ctx.structBias = CSwingEngine::ClassifyStructure(ctx.swings); + RefreshDerived(ctx); + Dbg(StringFormat("Snapshot locked | bias=%d fib=%s state=%s", + ctx.structBias, ctx.fibOk ? "OK" : "pending", + CClassifier::StateText(ctx.state))); + return true; + } + + static void OnChartEvent(HtfContext &ctx, const bool forceRedraw = false) + { + const bool newBar = CUpdateEngine::IsNewHtfBar(ctx); + if(newBar) + { + ctx.lastHtfBar = iTime(ctx.symbol, ctx.htf, 0); + CUpdateEngine::OnHtfBarClose(ctx); + RefreshDerived(ctx); + if(InpDebug) + Dbg("HTF bar → " + CClassifier::StateText(ctx.state)); + } + else if(forceRedraw) + RefreshDerived(ctx); + } +}; + +#endif // HYPERICT_STATEMACHINE_MQH diff --git a/Include/HyperICT/SwingEngine.mqh b/Include/HyperICT/SwingEngine.mqh new file mode 100644 index 0000000..93b3678 --- /dev/null +++ b/Include/HyperICT/SwingEngine.mqh @@ -0,0 +1,257 @@ +//+------------------------------------------------------------------+ +//| SwingEngine.mqh — pivot, H0–L1, snapshot khóa | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_SWINGENGINE_MQH +#define HYPERICT_SWINGENGINE_MQH + +#include +#include + +//+------------------------------------------------------------------+ +class CSwingEngine +{ +public: + 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); + } + + static void PickLastTwo(const SwingPoint &pts[], const int count, + SwingPoint &s0, SwingPoint &s1, + bool &has0, bool &has1) + { + has0 = has1 = false; + if(count < 1) return; + s0 = pts[count - 1]; + has0 = true; + if(count < 2) return; + s1 = pts[count - 2]; + has1 = true; + } + + 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); + PickLastTwo(highs, ArraySize(highs), out.h0, out.h1, out.hasH0, out.hasH1); + PickLastTwo(lows, ArraySize(lows), out.l0, out.l1, out.hasL0, out.hasL1); + return out.IsComplete(); + } + + 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; + } + + 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; + } + + static void RollBullChochCase1(SwingSet &sw, const SwingPoint &newH0, const SwingPoint &newL0) + { + sw.h1 = sw.h0; + sw.hasH1 = sw.hasH0; + // L1 giữ nguyên (spec) + sw.l0 = newL0; + sw.hasL0 = true; + sw.h0 = newH0; + sw.hasH0 = true; + } + + 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; + } + + 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 diff --git a/Include/HyperICT/Types.mqh b/Include/HyperICT/Types.mqh new file mode 100644 index 0000000..c149bd9 --- /dev/null +++ b/Include/HyperICT/Types.mqh @@ -0,0 +1,138 @@ +//+------------------------------------------------------------------+ +//| Types.mqh — enums & structs (spec §1–1.4) | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_TYPES_MQH +#define HYPERICT_TYPES_MQH + +enum ENUM_STRUCT_BIAS +{ + STRUCT_NONE = 0, + STRUCT_BULL = 1, + STRUCT_BEAR = -1 +}; + +enum ENUM_HTF_STATE +{ + HTF_NEUTRAL = 0, + HTF_BULL_INCOMPLETE = 1, + HTF_BEAR_INCOMPLETE = 2, + HTF_BULL_PULLBACK = 3, + HTF_BULL_CHOCH = 4, + HTF_BULL_CONTINUE = 5, + HTF_BEAR_PULLBACK = 6, + HTF_BEAR_CHOCH = 7, + HTF_BEAR_CONTINUE = 8 +}; + +enum ENUM_UPDATE_EVENT +{ + UPD_NONE = 0, + UPD_CHOCH = 1, + UPD_CONTINUE = 2 +}; + +enum ENUM_UPDATE_PHASE +{ + UPD_PHASE_IDLE = 0, + UPD_PHASE_BUILD = 1, + UPD_PHASE_CHOCH_RESOLVE = 2 +}; + +enum ENUM_CHOCH_BRANCH +{ + CHOCH_BRANCH_NONE = 0, + CHOCH_BRANCH_BACK_TO_BULL = 1, + CHOCH_BRANCH_CONFIRM_BEAR = 2 +}; + +struct SwingPoint +{ + 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 OrderBlockZone +{ + double swingPrice; + double obHigh; + double obLow; + datetime obTime; + int obShift; + bool valid; + void Clear() + { + swingPrice = obHigh = obLow = 0.0; + obTime = 0; + obShift = -1; + valid = false; + } +}; + +struct KeyLevelPair +{ + double price; + OrderBlockZone zone; +}; + +struct SwingSet +{ + SwingPoint 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 UpdateContext +{ + ENUM_UPDATE_EVENT event; + ENUM_UPDATE_PHASE phase; + ENUM_CHOCH_BRANCH chochBranch; + SwingPoint newH0; + SwingPoint newL0; + SwingPoint newL02; + bool newH0Locked; + bool newLLocked; + bool case1TrackingH0; + void Clear() + { + event = UPD_NONE; + phase = UPD_PHASE_IDLE; + chochBranch = CHOCH_BRANCH_NONE; + newH0.Clear(); newL0.Clear(); newL02.Clear(); + newH0Locked = newLLocked = false; + case1TrackingH0 = false; + } +}; + +struct HtfContext +{ + string symbol; + ENUM_TIMEFRAMES htf; + SwingSet swings; + ENUM_STRUCT_BIAS structBias; + bool fibOk; + KeyLevelPair keyLv1; + KeyLevelPair keyLv2; + ENUM_HTF_STATE state; + UpdateContext update; + datetime lockedAt; + datetime lastHtfBar; + bool snapshotReady; +}; + +#endif // HYPERICT_TYPES_MQH diff --git a/Include/HyperICT/UpdateEngine.mqh b/Include/HyperICT/UpdateEngine.mqh new file mode 100644 index 0000000..7c2a20f --- /dev/null +++ b/Include/HyperICT/UpdateEngine.mqh @@ -0,0 +1,351 @@ +//+------------------------------------------------------------------+ +//| UpdateEngine.mqh — §1.4: xác nhận đỉnh/đáy bằng InpSwingRange | +//| (Fib chỉ dùng lúc lock trend ban đầu — xem FibValidator) | +//+------------------------------------------------------------------+ +#ifndef HYPERICT_UPDATEENGINE_MQH +#define HYPERICT_UPDATEENGINE_MQH + +#include +#include +#include +#include + +class CUpdateEngine +{ + static void Dbg(const string msg) + { + if(InpDebug) + Print("[HyperICT/Update] ", msg); + } + + static void TrackNewHigh(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, SwingPoint &pt, const bool onlyIfLocked) + { + if(onlyIfLocked) + return; + const double h = iHigh(sym, tf, shift); + if(!pt.Valid() || h > pt.price) + CSwingEngine::FillSwingHigh(sym, tf, shift, pt); + } + + static void TrackNewLow(const string sym, const ENUM_TIMEFRAMES tf, + const int shift, SwingPoint &pt, const bool onlyIfLocked) + { + if(onlyIfLocked) + return; + const double l = iLow(sym, tf, shift); + if(!pt.Valid() || l < pt.price) + CSwingEngine::FillSwingLow(sym, tf, shift, pt); + } + + static void FinalizePivotHigh(const string sym, const ENUM_TIMEFRAMES tf, + SwingPoint &pt, const int range) + { + if(pt.Valid()) + CSwingEngine::FillSwingHigh(sym, tf, pt.shift, pt); + } + + static void FinalizePivotLow(const string sym, const ENUM_TIMEFRAMES tf, + SwingPoint &pt, const int range) + { + if(pt.Valid()) + CSwingEngine::FillSwingLow(sym, tf, pt.shift, pt); + } + + static void FinishRoll(HtfContext &ctx) + { + ctx.update.Clear(); + ctx.structBias = CSwingEngine::ClassifyStructure(ctx.swings); + Dbg(StringFormat("Roll xong — bias=%d", ctx.structBias)); + } + +public: + static bool IsNewHtfBar(HtfContext &ctx) + { + const datetime t0 = iTime(ctx.symbol, ctx.htf, 0); + return t0 != 0 && t0 != ctx.lastHtfBar; + } + + static void DetectBreakEvents(HtfContext &ctx) + { + if(ctx.update.phase != UPD_PHASE_IDLE) + return; + + const int sh = 1; + if(ctx.structBias == STRUCT_BULL) + { + if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.keyLv1.price)) + { + ctx.update.event = UPD_CHOCH; + ctx.update.phase = UPD_PHASE_BUILD; + ctx.update.newL0.Clear(); + ctx.update.newH0Locked = false; + ctx.update.newLLocked = false; + Dbg("Bull CHoCH — body break L0"); + } + else if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.keyLv2.price)) + { + ctx.update.event = UPD_CONTINUE; + ctx.update.phase = UPD_PHASE_BUILD; + ctx.update.newH0.Clear(); + ctx.update.newL0.Clear(); + ctx.update.newH0Locked = false; + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + Dbg("Bull Continue — body break H0"); + } + } + else if(ctx.structBias == STRUCT_BEAR) + { + if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.keyLv1.price)) + { + ctx.update.event = UPD_CHOCH; + ctx.update.phase = UPD_PHASE_BUILD; + ctx.update.newH0.Clear(); + ctx.update.newH0Locked = false; + ctx.update.newLLocked = false; + Dbg("Bear CHoCH — body break H0"); + } + else if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.keyLv2.price)) + { + ctx.update.event = UPD_CONTINUE; + ctx.update.phase = UPD_PHASE_BUILD; + ctx.update.newH0.Clear(); + ctx.update.newL0.Clear(); + ctx.update.newLLocked = false; + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + Dbg("Bear Continue — body break L0"); + } + } + } + + static void ProcessBullContinueBuild(HtfContext &ctx, const int sh) + { + if(CKeyLevels::IsBullCandle(ctx.symbol, ctx.htf, sh)) + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, ctx.update.newH0Locked); + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + + if(ctx.update.newH0Locked) + return; + + if(!CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newH0, InpSwingRange)) + return; + + FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); + ctx.update.newH0Locked = true; + FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); + CSwingEngine::RollBullContinue(ctx.swings, ctx.update.newH0, ctx.update.newL0); + FinishRoll(ctx); + Dbg("Bull Continue — swing high confirmed, rolled"); + } + + static void ProcessBearContinueBuild(HtfContext &ctx, const int sh) + { + if(CKeyLevels::IsBearCandle(ctx.symbol, ctx.htf, sh)) + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, ctx.update.newLLocked); + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + + if(ctx.update.newLLocked) + return; + + if(!CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL0, InpSwingRange)) + return; + + FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); + ctx.update.newLLocked = true; + FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); + CSwingEngine::RollBearContinue(ctx.swings, ctx.update.newH0, ctx.update.newL0); + FinishRoll(ctx); + Dbg("Bear Continue — swing low confirmed, rolled"); + } + + static void ProcessBullChochBuild(HtfContext &ctx, const int sh) + { + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, ctx.update.newLLocked); + if(ctx.update.newLLocked) + return; + + if(!CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL0, InpSwingRange)) + return; + + FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); + ctx.update.newLLocked = true; + ctx.update.phase = UPD_PHASE_CHOCH_RESOLVE; + Dbg("Bull CHoCH — newL0 swing confirmed → phase 3"); + } + + static void ProcessBearChochBuild(HtfContext &ctx, const int sh) + { + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, ctx.update.newH0Locked); + if(ctx.update.newH0Locked) + return; + + if(!CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newH0, InpSwingRange)) + return; + + FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); + ctx.update.newH0Locked = true; + ctx.update.phase = UPD_PHASE_CHOCH_RESOLVE; + Dbg("Bear CHoCH — newH0 swing confirmed → phase 3"); + } + + static void ProcessBullChochResolve(HtfContext &ctx, const int sh) + { + if(CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.swings.h0.price)) + { + if(!ctx.update.case1TrackingH0) + { + ctx.update.case1TrackingH0 = true; + ctx.update.newH0.Clear(); + ctx.update.newH0Locked = false; + Dbg("Bull CHoCH case1 — break H0, track newH0"); + } + if(CKeyLevels::IsBullCandle(ctx.symbol, ctx.htf, sh)) + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, ctx.update.newH0Locked); + + if(ctx.update.case1TrackingH0 && !ctx.update.newH0Locked && + CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newH0, InpSwingRange)) + { + FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); + ctx.update.newH0Locked = true; + CSwingEngine::RollBullChochCase1(ctx.swings, ctx.update.newH0, ctx.update.newL0); + FinishRoll(ctx); + Dbg("Bull CHoCH case1 — newH0 confirmed, rolled"); + } + return; + } + + if(ctx.update.newL0.Valid() && + CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.update.newL0.price)) + { + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL02, false); + + const bool hOk = CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newH0, InpSwingRange); + const bool lOk = CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL02, InpSwingRange); + if(hOk && lOk) + { + FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newH0, InpSwingRange); + FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL02, InpSwingRange); + CSwingEngine::RollBullChochCase2(ctx.swings, + ctx.update.newH0, ctx.update.newL0, ctx.update.newL02); + FinishRoll(ctx); + Dbg("Bull CHoCH case2 — bear confirmed, rolled"); + } + } + } + + static void ProcessBearChochResolve(HtfContext &ctx, const int sh) + { + if(CKeyLevels::BodyBreakBelow(ctx.symbol, ctx.htf, sh, ctx.swings.l0.price)) + { + if(!ctx.update.case1TrackingH0) + { + ctx.update.case1TrackingH0 = true; + ctx.update.newL0.Clear(); + ctx.update.newLLocked = false; + Dbg("Bear CHoCH case1 — break L0, track newL0"); + } + if(CKeyLevels::IsBearCandle(ctx.symbol, ctx.htf, sh)) + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, ctx.update.newLLocked); + + if(ctx.update.case1TrackingH0 && !ctx.update.newLLocked && + CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL0, InpSwingRange)) + { + FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); + ctx.update.newLLocked = true; + CSwingEngine::RollBearChochCase1(ctx.swings, ctx.update.newH0, ctx.update.newL0); + FinishRoll(ctx); + Dbg("Bear CHoCH case1 — newL0 confirmed, rolled"); + } + return; + } + + if(ctx.update.newH0.Valid() && + CKeyLevels::BodyBreakAbove(ctx.symbol, ctx.htf, sh, ctx.update.newH0.price)) + { + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newL02, false); + + const bool lOk = CSwingEngine::IsConfirmedSwingLow(ctx.symbol, ctx.htf, + ctx.update.newL0, InpSwingRange); + const bool hOk = CSwingEngine::IsConfirmedSwingHigh(ctx.symbol, ctx.htf, + ctx.update.newL02, InpSwingRange); + if(lOk && hOk) + { + FinalizePivotLow(ctx.symbol, ctx.htf, ctx.update.newL0, InpSwingRange); + FinalizePivotHigh(ctx.symbol, ctx.htf, ctx.update.newL02, InpSwingRange); + CSwingEngine::RollBearChochCase2(ctx.swings, + ctx.update.newH0, ctx.update.newL0, ctx.update.newL02); + FinishRoll(ctx); + Dbg("Bear CHoCH case2 — bull confirmed, rolled"); + } + } + } + + static void OnHtfBarClose(HtfContext &ctx) + { + const int sh = 1; + + if(ctx.update.phase == UPD_PHASE_IDLE) + { + DetectBreakEvents(ctx); + if(ctx.update.phase != UPD_PHASE_BUILD) + return; + + if(ctx.update.event == UPD_CONTINUE) + { + if(ctx.structBias == STRUCT_BULL) + { + if(CKeyLevels::IsBullCandle(ctx.symbol, ctx.htf, sh)) + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + } + else if(ctx.structBias == STRUCT_BEAR) + { + if(CKeyLevels::IsBearCandle(ctx.symbol, ctx.htf, sh)) + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + } + } + else if(ctx.update.event == UPD_CHOCH) + { + if(ctx.structBias == STRUCT_BULL) + TrackNewLow(ctx.symbol, ctx.htf, sh, ctx.update.newL0, false); + else + TrackNewHigh(ctx.symbol, ctx.htf, sh, ctx.update.newH0, false); + } + return; + } + + if(ctx.update.phase == UPD_PHASE_BUILD) + { + if(ctx.update.event == UPD_CONTINUE && ctx.structBias == STRUCT_BULL) + ProcessBullContinueBuild(ctx, sh); + else if(ctx.update.event == UPD_CONTINUE && ctx.structBias == STRUCT_BEAR) + ProcessBearContinueBuild(ctx, sh); + else if(ctx.update.event == UPD_CHOCH && ctx.structBias == STRUCT_BULL) + ProcessBullChochBuild(ctx, sh); + else if(ctx.update.event == UPD_CHOCH && ctx.structBias == STRUCT_BEAR) + ProcessBearChochBuild(ctx, sh); + return; + } + + if(ctx.update.phase == UPD_PHASE_CHOCH_RESOLVE) + { + if(ctx.structBias == STRUCT_BULL) + ProcessBullChochResolve(ctx, sh); + else if(ctx.structBias == STRUCT_BEAR) + ProcessBearChochResolve(ctx, sh); + } + } +}; + +#endif // HYPERICT_UPDATEENGINE_MQH