diff --git a/Experts/MotherBarBreakout.mq5 b/Experts/MotherBarBreakout.mq5 new file mode 100644 index 0000000..0300558 --- /dev/null +++ b/Experts/MotherBarBreakout.mq5 @@ -0,0 +1,165 @@ +//+------------------------------------------------------------------+ +//| MotherBarBreakout.mq5 | +//| M15: thân nến >= K × TB thân N nến liền trước | +//+------------------------------------------------------------------+ +#property copyright "MotherBarBreakout" +#property version "2.01" + +#property description "M15 large body: body >= K × average of N prior bodies. Marks on chart." + +input ENUM_TIMEFRAMES InpTf = PERIOD_M15; +input int InpAvgPriorBodies = 10; // TB của 10 thân nến liền trước +input double InpBodyVsAvgMult = 3.0; // thân hiện tại >= hệ số này × TB (vd 3 = gấp 3) +input int InpScanBarsOnInit = 300; +input bool InpClearMarksOnInit = true; + +input color InpClrBullBar = clrDodgerBlue; // nến tăng (chỉ phân biệt hướng nến, không lọc trend) +input color InpClrBearBar = clrDarkOrange; +input int InpArrowSize = 2; +input int InpArrowOffsetPts = 20; + +const string OBJ_PFX = "MBB_"; + +datetime g_lastBarTime = 0; + +//+------------------------------------------------------------------+ +double BodyAbs(const string sym, const ENUM_TIMEFRAMES tf, const int sh) +{ + return MathAbs(iClose(sym, tf, sh) - iOpen(sym, tf, sh)); +} + +//+------------------------------------------------------------------+ +//| Thân nến sh >= InpBodyVsAvgMult × TB thân sh+1 .. sh+n // +//+------------------------------------------------------------------+ +bool IsBodyLargerThanPriorAverage(const string sym, const ENUM_TIMEFRAMES tf, + const int sh, const int nAvg) +{ + if(nAvg < 1) + return false; + + if(InpBodyVsAvgMult <= 0.0) + return false; + + const int need = sh + nAvg + 1; + if(Bars(sym, tf) < need) + return false; + + double sum = 0.0; + for(int k = 1; k <= nAvg; k++) + sum += BodyAbs(sym, tf, sh + k); + + const double avg = sum / (double)nAvg; + const double body = BodyAbs(sym, tf, sh); + const double threshold = InpBodyVsAvgMult * avg; + + return (body >= threshold); +} + +//+------------------------------------------------------------------+ +void DeleteMarkersByPrefix() +{ + const long chart_id = ChartID(); + const int n = ObjectsTotal(chart_id, 0, -1); + for(int i = n - 1; i >= 0; i--) + { + const string name = ObjectName(chart_id, i, 0, -1); + if(StringFind(name, OBJ_PFX) == 0) + ObjectDelete(chart_id, name); + } +} + +//+------------------------------------------------------------------+ +void PlaceMarker(const string sym, const ENUM_TIMEFRAMES tf, const int sh) +{ + const datetime t = iTime(sym, tf, sh); + const string name = OBJ_PFX + IntegerToString((long)t); + const long chart_id = ChartID(); + + if(ObjectFind(chart_id, name) >= 0) + return; + + const bool bull = (iClose(sym, tf, sh) >= iOpen(sym, tf, sh)); + const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); + const double pt = SymbolInfoDouble(sym, SYMBOL_POINT); + + double price = 0.0; + if(bull) + price = iLow(sym, tf, sh) - InpArrowOffsetPts * pt; + else + price = iHigh(sym, tf, sh) + InpArrowOffsetPts * pt; + price = NormalizeDouble(price, dig); + + if(!ObjectCreate(chart_id, name, OBJ_ARROW, 0, t, price)) + return; + + ObjectSetInteger(chart_id, name, OBJPROP_ARROWCODE, bull ? 233 : 234); + ObjectSetInteger(chart_id, name, OBJPROP_COLOR, bull ? InpClrBullBar : InpClrBearBar); + ObjectSetInteger(chart_id, name, OBJPROP_WIDTH, InpArrowSize); + ObjectSetInteger(chart_id, name, OBJPROP_ANCHOR, bull ? ANCHOR_TOP : ANCHOR_BOTTOM); + ObjectSetInteger(chart_id, name, OBJPROP_BACK, false); + ObjectSetInteger(chart_id, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chart_id, name, OBJPROP_HIDDEN, false); +} + +//+------------------------------------------------------------------+ +void TryMarkAtShift(const string sym, const ENUM_TIMEFRAMES tf, const int sh) +{ + if(IsBodyLargerThanPriorAverage(sym, tf, sh, InpAvgPriorBodies)) + PlaceMarker(sym, tf, sh); +} + +//+------------------------------------------------------------------+ +void ScanHistory(const string sym, const ENUM_TIMEFRAMES tf, const int maxBars) +{ + if(maxBars <= 0) + return; + + const int n = MathMin(maxBars, Bars(sym, tf) - InpAvgPriorBodies - 2); + for(int sh = 1; sh <= n; sh++) + TryMarkAtShift(sym, tf, sh); +} + +//+------------------------------------------------------------------+ +int OnInit() +{ + if(InpAvgPriorBodies < 1) + { + Print("[MBB] InpAvgPriorBodies phải >= 1"); + return INIT_FAILED; + } + if(InpBodyVsAvgMult <= 0.0) + { + Print("[MBB] InpBodyVsAvgMult phải > 0"); + return INIT_FAILED; + } + + if(InpClearMarksOnInit) + DeleteMarkersByPrefix(); + + ScanHistory(_Symbol, InpTf, InpScanBarsOnInit); + + g_lastBarTime = iTime(_Symbol, InpTf, 0); + ChartRedraw(); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + DeleteMarkersByPrefix(); + ChartRedraw(); +} + +//+------------------------------------------------------------------+ +void OnTick() +{ + const datetime t0 = iTime(_Symbol, InpTf, 0); + if(t0 == 0 || t0 == g_lastBarTime) + return; + g_lastBarTime = t0; + + TryMarkAtShift(_Symbol, InpTf, 1); + ChartRedraw(); +} + +//+------------------------------------------------------------------+