166 lines
5.3 KiB
Plaintext
166 lines
5.3 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| 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();
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|