Define HTF Market Structure, KeyLevels
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
Profiles/
|
||||
Experts/*.ex5
|
||||
*.dat
|
||||
Include/
|
||||
Logs/*.log
|
||||
@@ -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 <HyperICT/Config.mqh>
|
||||
#include <HyperICT/Types.mqh>
|
||||
#include <HyperICT/StateMachine.mqh>
|
||||
#include <HyperICT/Draw.mqh>
|
||||
#include <HyperICT/Panel.mqh>
|
||||
|
||||
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; }
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
Binary file not shown.
Binary file not shown.
@@ -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 <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
#include <HyperICT/FibValidator.mqh>
|
||||
#include <HyperICT/KeyLevels.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
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
|
||||
@@ -0,0 +1,35 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Config.mqh — inputs & hằng số |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef HYPERICT_CONFIG_MQH
|
||||
#define HYPERICT_CONFIG_MQH
|
||||
|
||||
#include <HyperICT/Types.mqh>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,126 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Draw.mqh — labels H0–L1, vùng Key LV1/L2 |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef HYPERICT_DRAW_MQH
|
||||
#define HYPERICT_DRAW_MQH
|
||||
|
||||
#include <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
#include <HyperICT/KeyLevels.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
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
|
||||
@@ -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 <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,159 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| KeyLevels.mqh — Key LV1/L2, supply/demand OB (body break) |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef HYPERICT_KEYLEVELS_MQH
|
||||
#define HYPERICT_KEYLEVELS_MQH
|
||||
|
||||
#include <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
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
|
||||
@@ -0,0 +1,64 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Panel.mqh — nhãn trạng thái góc chart |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef HYPERICT_PANEL_MQH
|
||||
#define HYPERICT_PANEL_MQH
|
||||
|
||||
#include <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
#include <HyperICT/Classifier.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
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
|
||||
@@ -0,0 +1,71 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| StateMachine.mqh — pipeline đánh giá HTF mỗi bar |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef HYPERICT_STATEMACHINE_MQH
|
||||
#define HYPERICT_STATEMACHINE_MQH
|
||||
|
||||
#include <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
#include <HyperICT/SwingEngine.mqh>
|
||||
#include <HyperICT/FibValidator.mqh>
|
||||
#include <HyperICT/KeyLevels.mqh>
|
||||
#include <HyperICT/Classifier.mqh>
|
||||
#include <HyperICT/UpdateEngine.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
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
|
||||
@@ -0,0 +1,257 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SwingEngine.mqh — pivot, H0–L1, snapshot khóa |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef HYPERICT_SWINGENGINE_MQH
|
||||
#define HYPERICT_SWINGENGINE_MQH
|
||||
|
||||
#include <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
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
|
||||
@@ -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
|
||||
@@ -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 <HyperICT/Types.mqh>
|
||||
#include <HyperICT/Config.mqh>
|
||||
#include <HyperICT/SwingEngine.mqh>
|
||||
#include <HyperICT/KeyLevels.mqh>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user