Forward-Simulation/Article-22927-For-Sim-V2/For Sim.mq5

698 lines
28 KiB
MQL5
Raw Permalink Normal View History

2026-06-22 13:02:39 +02:00
//+------------------------------------------------------------------+
//| Forward Simulation_V2.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com/en/users/johnhlomohang/"
#property version "2.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_label1 "Fast EMA"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_label2 "Slow EMA"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrOrangeRed
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
//+------------------------------------------------------------------+
//| INDICATOR BUFFERS |
//+------------------------------------------------------------------+
double FastEMABuffer[];
double SlowEMABuffer[];
//+------------------------------------------------------------------+
//| INPUT PARAMETERS |
//+------------------------------------------------------------------+
input int FastEMA_Period = 9; // Fast EMA period
input int SlowEMA_Period = 21; // Slow EMA period
input int FutureBars = 30; // Number of future candles to project
input double SpreadMultiplier = 2.0; // Wick size multiplier
input bool AutoAnchor = true; // Auto-move anchor to latest cross bar
input string AnchorLineName = "FSE_Anchor"; // Anchor vertical line name
input color BullishColor = clrDodgerBlue; // Bullish body color
input color BearishColor = clrCrimson; // Bearish body color
input color WickColor = clrDimGray; // Wick color
input bool ShowZoneLabel = true; // Show projection label
input bool ShowSeparatorLine = true; // Show dashed separator at anchor
input int InvalidationPips = 10; // Pip distance to trigger invalidation
input double CandleGapFraction = 0.08; // Gap between candles as fraction of bar width (0.04–0.20)
input int CounterCandleFreq = 4; // Insert 1 counter-trend candle every N candles (min 3)
input int AvgLookback = 50; // Bars used to measure average candle size
//+------------------------------------------------------------------+
//| STRUCTS |
//+------------------------------------------------------------------+
struct PredictedCandle
{
double open;
double high;
double low;
double close;
bool bullish;
};
//+------------------------------------------------------------------+
//| GLOBALS |
//+------------------------------------------------------------------+
int g_FastHandle = INVALID_HANDLE;
int g_SlowHandle = INVALID_HANDLE;
int g_LastSignal = 0; // +1 bull, -1 bear, 0 none
bool g_SignalActive = false;
double g_SignalPrice = 0.0;
datetime g_SignalBarTime = 0;
datetime g_DrawnAnchor = 0; // anchor time of the last successful draw
//--- Measured average candle metrics (refreshed on each new signal)
double g_AvgBody = 0.0; // average |close - open| over AvgLookback bars
double g_AvgUpperWick = 0.0; // average (high - max(open,close))
double g_AvgLowerWick = 0.0; // average (min(open,close) - low)
double g_AvgRange = 0.0; // average (high - low) – full candle height
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Bind and configure the two visible EMA plot buffers
SetIndexBuffer(0, FastEMABuffer, INDICATOR_DATA);
SetIndexBuffer(1, SlowEMABuffer, INDICATOR_DATA);
ArraySetAsSeries(FastEMABuffer, false);
ArraySetAsSeries(SlowEMABuffer, false);
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, FastEMA_Period);
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, SlowEMA_Period);
//--- Internal calculation handles
g_FastHandle = iMA(_Symbol, _Period, FastEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
g_SlowHandle = iMA(_Symbol, _Period, SlowEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
if(g_FastHandle == INVALID_HANDLE || g_SlowHandle == INVALID_HANDLE)
{
Print("ForwardSimEngine [ERROR]: iMA handle creation failed. "
"Symbol=", _Symbol, " TF=", EnumToString(_Period));
return INIT_FAILED;
}
Print("ForwardSimEngine [INIT]: OK FastEMA=", FastEMA_Period,
" SlowEMA=", SlowEMA_Period,
" FutureBars=", FutureBars,
" InvalidationPips=", InvalidationPips,
" AvgLookback=", AvgLookback);
//--- Pre-compute average candle metrics from history
CalcAvgCandleMetrics();
EventSetTimer(3);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
CleanAllObjects();
Print("ForwardSimEngine [DEINIT]: objects cleaned, reason=", reason);
}
//+-----------------------------------------------------------------------+
//| ON CALCULATE |
//+-----------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total < SlowEMA_Period + 5)
{
Print("ForwardSimEngine [WARN]: Not enough bars: ", rates_total);
return 0;
}
//--- How many bars to (re)calculate
int limit = (prev_calculated > 1) ? rates_total - prev_calculated + 1
: rates_total;
//--- Start index in the buffer (oldest bar that needs updating)
int startBar = rates_total - limit;
//--- Local arrays for engine logic
double tmpFast[], tmpSlow[];
ArraySetAsSeries(tmpFast, false);
ArraySetAsSeries(tmpSlow, false);
ArrayResize(tmpFast, rates_total);
ArrayResize(tmpSlow, rates_total);
//--- Copy ALL bars into local arrays (needed by RunEngine for crossover)
int copiedFast = CopyBuffer(g_FastHandle, 0, 0, rates_total, tmpFast);
int copiedSlow = CopyBuffer(g_SlowHandle, 0, 0, rates_total, tmpSlow);
if(copiedFast <= 0 || copiedSlow <= 0)
{
Print("ForwardSimEngine [WARN]: CopyBuffer returned <=0. fast=",
copiedFast, " slow=", copiedSlow);
return prev_calculated;
}
/* Write into plot buffers ONLY within the confirmed copied range
and only within what MT5 has allocated (ArraySize guard).*/
int bufSzFast = ArraySize(FastEMABuffer);
int bufSzSlow = ArraySize(SlowEMABuffer);
int safeFast = MathMin(copiedFast, bufSzFast);
int safeSlow = MathMin(copiedSlow, bufSzSlow);
for(int i = startBar; i < safeFast; i++)
FastEMABuffer[i] = tmpFast[i];
for(int i = startBar; i < safeSlow; i++)
SlowEMABuffer[i] = tmpSlow[i];
// ── Run the simulation engine ─────────────────────────────────
RunEngine(rates_total, time, close, tmpFast, tmpSlow);
return rates_total;
}
//+------------------------------------------------------------------+
//| ON TIMER |
//+------------------------------------------------------------------+
void OnTimer()
{
// If user moved the anchor manually, force a redraw on next tick
if(!AutoAnchor)
{
datetime curAnchor = GetAnchorTime();
if(curAnchor != g_DrawnAnchor && curAnchor != 0)
{
g_DrawnAnchor = 0;
ChartRedraw();
}
}
}
//+------------------------------------------------------------------+
//| CALC AVERAGE CANDLE METRICS |
//+------------------------------------------------------------------+
void CalcAvgCandleMetrics()
{
int lookback = (AvgLookback < 10) ? 10 : AvgLookback; // minimum 10 bars
double hiBuf[], loBuf[], opBuf[], clBuf[];
ArraySetAsSeries(hiBuf, true);
ArraySetAsSeries(loBuf, true);
ArraySetAsSeries(opBuf, true);
ArraySetAsSeries(clBuf, true);
//--- Copy starting at shift 1 (skip the live bar), going back lookback bars
int copiedH = CopyHigh(_Symbol, _Period, 1, lookback, hiBuf);
int copiedL = CopyLow(_Symbol, _Period, 1, lookback, loBuf);
int copiedO = CopyOpen(_Symbol, _Period, 1, lookback, opBuf);
int copiedC = CopyClose(_Symbol, _Period, 1, lookback, clBuf);
int n = MathMin(MathMin(copiedH, copiedL), MathMin(copiedO, copiedC));
if(n <= 0)
{
Print("ForwardSimEngine [AVG]: CopyPrice failed, keeping previous averages.");
return;
}
double sumBody = 0, sumUWick = 0, sumLWick = 0, sumRange = 0;
for(int i = 0; i < n; i++)
{
double bodyTop = MathMax(opBuf[i], clBuf[i]);
double bodyBot = MathMin(opBuf[i], clBuf[i]);
sumBody += bodyTop - bodyBot;
sumUWick += hiBuf[i] - bodyTop;
sumLWick += bodyBot - loBuf[i];
sumRange += hiBuf[i] - loBuf[i];
}
double pip = _Point * 10.0; // 1 pip floor
g_AvgBody = MathMax(sumBody / n, pip);
g_AvgUpperWick = MathMax(sumUWick / n, pip * 0.5);
g_AvgLowerWick = MathMax(sumLWick / n, pip * 0.5);
g_AvgRange = MathMax(sumRange / n, pip * 2.0);
Print("ForwardSimEngine [AVG]: lookback=", n,
" AvgBody=", DoubleToString(g_AvgBody / _Point, 1), " pts",
" AvgUpperWick=", DoubleToString(g_AvgUpperWick / _Point, 1), " pts",
" AvgLowerWick=", DoubleToString(g_AvgLowerWick / _Point, 1), " pts",
" AvgRange=", DoubleToString(g_AvgRange / _Point, 1), " pts");
}
//+------------------------------------------------------------------+
//| CORE ENGINE |
//| All arrays: index 0 = oldest, rates_total-1 = live forming bar |
//| rates_total-2 = last fully closed bar (bar index 1) |
//| rates_total-3 = bar before that (bar index 2) |
//+------------------------------------------------------------------+
void RunEngine(const int rates_total,
const datetime &time[],
const double &close[],
const double &fastBuf[],
const double &slowBuf[])
{
int barLive = rates_total - 1; // live/forming bar
int barClosed = rates_total - 2; // last fully closed bar
int barPrev = rates_total - 3; // bar before that
if(barPrev < SlowEMA_Period)
return;
//--- 1. Crossover detection
//--- We look at the two most recently CLOSED bars (barPrev and barClosed).
//--- Cross occurs when the fast EMA crossed over the slow EMA between them.
double fCur = fastBuf[barClosed];
double fPrev = fastBuf[barPrev];
double sCur = slowBuf[barClosed];
double sPrev = slowBuf[barPrev];
int newSignal = 0;
if(fPrev <= sPrev && fCur > sCur)
newSignal = 1; // bullish
if(fPrev >= sPrev && fCur < sCur)
newSignal = -1; // bearish
//--- Heartbeat log (first 3 ticks + every new signal)
static int s_ticks = 0;
s_ticks++;
if(s_ticks <= 3 || newSignal != 0)
{
Print("ForwardSimEngine [TICK #", s_ticks, "]",
" fCur=", DoubleToString(fCur, _Digits),
" sCur=", DoubleToString(sCur, _Digits),
" gap=", DoubleToString(fCur - sCur, _Digits),
" newSig=", newSignal,
" active=", g_SignalActive,
" lastSig=",g_LastSignal);
}
//--- 2. Latch new signal
if(newSignal != 0)
{
g_LastSignal = newSignal;
g_SignalActive = true;
g_SignalPrice = close[barClosed];
g_SignalBarTime = time[barClosed];
g_DrawnAnchor = 0; // force full redraw
/* Refresh measured candle averages at the moment of the signal
so the projection uses the most recent volatility context. */
CalcAvgCandleMetrics();
Print("ForwardSimEngine [SIGNAL]: ",
(newSignal == 1 ? ">>> BULLISH CROSS <<<" : ">>> BEARISH CROSS <<<"),
" bar=", TimeToString(g_SignalBarTime),
" px=", DoubleToString(g_SignalPrice, _Digits));
}
//--- 3. Invalidation check
if(g_SignalActive)
{
double livePx = close[barLive];
//--- 1 pip = 10 * _Point for a 5-digit broker (covers 3-digit too)
double pipSize = _Point * 10.0;
double thresh = InvalidationPips * pipSize;
bool inv = false;
if(g_LastSignal == 1 && livePx < g_SignalPrice - thresh)
inv = true;
if(g_LastSignal == -1 && livePx > g_SignalPrice + thresh)
inv = true;
if(inv)
{
Print("ForwardSimEngine [INVALIDATED]",
" signalPx=", DoubleToString(g_SignalPrice, _Digits),
" livePx=", DoubleToString(livePx, _Digits),
" thresh=", DoubleToString(thresh, _Digits));
g_SignalActive = false;
g_LastSignal = 0;
g_DrawnAnchor = 0;
CleanAllObjects();
DrawInvalidationLabel(g_SignalBarTime, g_SignalPrice);
ChartRedraw();
return;
}
}
//--- 4. Only draw/redraw when there is an active signal
if(!g_SignalActive || g_LastSignal == 0)
return;
datetime anchorTime = (AutoAnchor) ? g_SignalBarTime : GetAnchorTime();
if(anchorTime == 0)
anchorTime = g_SignalBarTime;
//--- Skip if already drawn at this anchor
if(anchorTime == g_DrawnAnchor)
return;
g_DrawnAnchor = anchorTime;
//--- 5. Build synthetic candles
double emaSlope = fCur - fPrev;
PredictedCandle pred[];
ArrayResize(pred, FutureBars);
GeneratePrediction(close[barClosed], emaSlope, pred);
//--- 6. Render
CleanAllObjects();
DrawAllCandles(anchorTime, pred);
if(ShowSeparatorLine)
DrawSeparator(anchorTime);
if(ShowZoneLabel)
DrawZoneLabel(anchorTime, close[barClosed]);
UpdateAnchorLine(anchorTime);
ChartRedraw();
Print("ForwardSimEngine [DRAWN]",
" signal=", g_LastSignal,
" anchor=", TimeToString(anchorTime),
" bars=", FutureBars,
" slope=", DoubleToString(emaSlope, _Digits));
}
//+------------------------------------------------------------------+
//| PREDICTION ENGINE |
//+------------------------------------------------------------------+
void GeneratePrediction(double startPrice, double emaSlope, PredictedCandle &out[])
{
MathSrand((int)(TimeLocal() % 32767));
int n = ArraySize(out);
int sig = g_LastSignal; // +1 bull, -1 bear
double pip = _Point * 10.0;
//--- Baseline body size from measured history
//--- Guard: if CalcAvgCandleMetrics hasn't run yet use a pip floor
double avgBody = (g_AvgBody > pip) ? g_AvgBody : pip * 3.0;
double avgUWick = (g_AvgUpperWick > 0) ? g_AvgUpperWick : avgBody * 0.5;
double avgLWick = (g_AvgLowerWick > 0) ? g_AvgLowerWick : avgBody * 0.5;
//--- EMA-slope momentum scale: 0.5× (weak) … 1.5× (strong)
//--- Normalise slope against avgBody so it is always unit-consistent
double slopeAbs = MathAbs(emaSlope);
double slopeRatio = slopeAbs / avgBody; // 0 = flat, 1 = slope equals avg body
//--- Clamp to a ±0.5 band around 1.0
double momentumScale = 0.5 + MathMin(slopeRatio, 1.0); // 0.5 … 1.5
//--- baseStep = what one average trend-direction candle body should be
double baseStep = avgBody * momentumScale;
//--- Counter-candle frequency guard
int ccFreq = (CounterCandleFreq < 3) ? 3 : CounterCandleFreq;
double prevClose = startPrice;
for(int i = 0; i < n; i++)
{
//--- Sine envelope: body pulses between 40% and 130% of baseStep
double phase = (double)i / (double)n * 2.0 * M_PI;
double sinSq = MathSin(phase) * MathSin(phase); // 0 … 1
double envelope = 0.40 + 0.90 * sinSq; // 0.40 … 1.30
//--- Exponential decay: projection flattens toward the tail
double decay = MathPow(0.93, i);
//--- Body for this candle
double bodySize = baseStep * envelope * decay;
//--- ±8% random jitter
double jitter = 1.0 + ((double)(MathRand() % 160) - 80.0) * 0.001;
bodySize *= jitter;
//--- Hard floor: at least 0.5 × avgBody so candles are always visible
if(bodySize < avgBody * 0.5)
bodySize = avgBody * 0.5;
//--- Counter-trend injection every ccFreq bars
bool isCounter = (i > 0 && (i % ccFreq == 0));
double retracePct = 0.25 + (MathRand() % 21) * 0.01; // 0.25–0.45
double step;
if(isCounter)
step = -(double)sig * bodySize * retracePct;
else
step = (double)sig * bodySize;
//--- OHLC
out[i].open = prevClose;
out[i].close = prevClose + step;
out[i].bullish = (out[i].close >= out[i].open);
double bodyTop = MathMax(out[i].open, out[i].close);
double bodyBot = MathMin(out[i].open, out[i].close);
double bHeight = bodyTop - bodyBot;
if(bHeight < _Point)
bHeight = _Point;
//--- Wicks anchored to measured avg wick ratios
//--- Base ratio = avgWick / avgBody, then add ±25% random spread
double uRatioBase = avgUWick / avgBody;
double lRatioBase = avgLWick / avgBody;
double uJitter = 1.0 + ((double)(MathRand() % 50) - 25.0) * 0.01; // ±25%
double lJitter = 1.0 + ((double)(MathRand() % 50) - 25.0) * 0.01;
double upWick = bHeight * uRatioBase * uJitter;
double dnWick = bHeight * lRatioBase * lJitter;
//--- Trend bias: the shadow in the signal direction is slightly longer
if(out[i].bullish)
upWick *= 1.20;
else
dnWick *= 1.20;
//--- Minimum wick: 20% of body height
if(upWick < bHeight * 0.20)
upWick = bHeight * 0.20;
if(dnWick < bHeight * 0.20)
dnWick = bHeight * 0.20;
out[i].high = bodyTop + upWick;
out[i].low = bodyBot - dnWick;
prevClose = out[i].close;
}
}
//+------------------------------------------------------------------+
//| DRAW ALL CANDLES – project one bar-width ahead of anchor |
//+------------------------------------------------------------------+
void DrawAllCandles(datetime startTime, PredictedCandle &candles[])
{
int barSec = PeriodSeconds();
int total = ArraySize(candles);
//--- Gap in seconds on each side of the body (max 45% each side)
double gapFrac = CandleGapFraction;
if(gapFrac < 0.01)
gapFrac = 0.01;
if(gapFrac > 0.45)
gapFrac = 0.45;
int gapSec = (int)(barSec * gapFrac);
for(int i = 0; i < total; i++)
{
//--- Slot boundaries (full bar width)
datetime slotStart = startTime + (datetime)((i + 1) * barSec);
datetime slotEnd = slotStart + (datetime)barSec;
//--- Body boundaries (inset by gap on each side)
datetime bodyStart = slotStart + (datetime)gapSec;
datetime bodyEnd = slotEnd - (datetime)gapSec;
datetime bodyMid = slotStart + (datetime)(barSec / 2); // wick centre
DrawSingleCandle(i, bodyStart, bodyEnd, bodyMid, candles[i]);
}
}
//+------------------------------------------------------------------+
//| DRAW ONE CANDLE |
//+------------------------------------------------------------------+
void DrawSingleCandle(int idx,
datetime t1, datetime t2, datetime tMid,
const PredictedCandle &c)
{
string pfx = "FutureCandle_" + IntegerToString(idx);
color bodyCol = c.bullish ? BullishColor : BearishColor;
//--- Doji guard
double bOpen = c.open;
double bClose = c.close;
if(MathAbs(bOpen - bClose) < _Point)
bClose = bOpen + (c.bullish ? _Point * 2 : -_Point * 2);
//--- Filled body
string nm = pfx + "_body";
ObjectDelete(0, nm);
if(ObjectCreate(0, nm, OBJ_RECTANGLE, 0, t1, bOpen, t2, bClose))
{
ObjectSetInteger(0, nm, OBJPROP_COLOR, bodyCol);
ObjectSetInteger(0, nm, OBJPROP_FILL, true);
ObjectSetInteger(0, nm, OBJPROP_BACK, false);
ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
//--- Body outline
nm = pfx + "_bord";
ObjectDelete(0, nm);
if(ObjectCreate(0, nm, OBJ_RECTANGLE, 0, t1, bOpen, t2, bClose))
{
ObjectSetInteger(0, nm, OBJPROP_COLOR, bodyCol);
ObjectSetInteger(0, nm, OBJPROP_FILL, false);
ObjectSetInteger(0, nm, OBJPROP_BACK, false);
ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
//--- Upper wick (centred on full slot, not the inset body)
nm = pfx + "_wU";
ObjectDelete(0, nm);
double wTop = MathMax(bOpen, bClose);
if(ObjectCreate(0, nm, OBJ_TREND, 0, tMid, wTop, tMid, c.high))
{
ObjectSetInteger(0, nm, OBJPROP_COLOR, WickColor);
ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, nm, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
//--- Lower wick
nm = pfx + "_wD";
ObjectDelete(0, nm);
double wBot = MathMin(bOpen, bClose);
if(ObjectCreate(0, nm, OBJ_TREND, 0, tMid, wBot, tMid, c.low))
{
ObjectSetInteger(0, nm, OBJPROP_COLOR, WickColor);
ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, nm, OBJPROP_RAY_RIGHT, false);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
}
//+------------------------------------------------------------------+
//| SEPARATOR |
//+------------------------------------------------------------------+
void DrawSeparator(datetime t)
{
string nm = "FSE_Sep";
ObjectDelete(0, nm);
if(ObjectCreate(0, nm, OBJ_VLINE, 0, t, 0))
{
ObjectSetInteger(0, nm, OBJPROP_COLOR, clrSilver);
ObjectSetInteger(0, nm, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, nm, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, nm, OBJPROP_BACK, true);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
}
//+------------------------------------------------------------------+
//| ZONE LABEL |
//+------------------------------------------------------------------+
void DrawZoneLabel(datetime t, double price)
{
string nm = "FSE_Label";
string txt = (g_LastSignal == 1) ? "[ BULLISH PROJECTION ]" : "[ BEARISH PROJECTION ]";
color col = (g_LastSignal == 1) ? BullishColor : BearishColor;
ObjectDelete(0, nm);
if(ObjectCreate(0, nm, OBJ_TEXT, 0, t + (datetime)PeriodSeconds(), price))
{
ObjectSetString(0, nm, OBJPROP_TEXT, txt);
ObjectSetInteger(0, nm, OBJPROP_COLOR, col);
ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 10);
ObjectSetString(0, nm, OBJPROP_FONT, "Courier New");
ObjectSetInteger(0, nm, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
}
//+------------------------------------------------------------------+
//| INVALIDATION LABEL |
//+------------------------------------------------------------------+
void DrawInvalidationLabel(datetime t, double price)
{
string nm = "FSE_Invalid";
ObjectDelete(0, nm);
if(ObjectCreate(0, nm, OBJ_TEXT, 0, t + (datetime)PeriodSeconds(), price))
{
ObjectSetString(0, nm, OBJPROP_TEXT, "[ INVALIDATED - AWAITING NEXT CROSS ]");
ObjectSetInteger(0, nm, OBJPROP_COLOR, clrOrange);
ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, nm, OBJPROP_FONT, "Courier New");
ObjectSetInteger(0, nm, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, false);
}
}
//+------------------------------------------------------------------+
//| ANCHOR LINE HELPERS |
//+------------------------------------------------------------------+
void PlaceAnchorLine(datetime t)
{
ObjectDelete(0, AnchorLineName);
if(ObjectCreate(0, AnchorLineName, OBJ_VLINE, 0, t, 0))
{
ObjectSetInteger(0, AnchorLineName, OBJPROP_COLOR, clrGold);
ObjectSetInteger(0, AnchorLineName, OBJPROP_STYLE, STYLE_DASHDOTDOT);
ObjectSetInteger(0, AnchorLineName, OBJPROP_WIDTH, 2);
ObjectSetInteger(0, AnchorLineName, OBJPROP_SELECTABLE, true);
ObjectSetInteger(0, AnchorLineName, OBJPROP_HIDDEN, false);
}
}
//+------------------------------------------------------------------+
//| Update Anchor Line |
//+------------------------------------------------------------------+
void UpdateAnchorLine(datetime t)
{
if(ObjectFind(0, AnchorLineName) < 0)
PlaceAnchorLine(t);
else
ObjectSetInteger(0, AnchorLineName, OBJPROP_TIME, t);
}
//+------------------------------------------------------------------+
//| Get Anchor Time |
//+------------------------------------------------------------------+
datetime GetAnchorTime()
{
if(ObjectFind(0, AnchorLineName) >= 0)
return (datetime)ObjectGetInteger(0, AnchorLineName, OBJPROP_TIME);
return 0;
}
//+------------------------------------------------------------------+
//| CLEANUP |
//+------------------------------------------------------------------+
void CleanAllObjects()
{
ObjectsDeleteAll(0, "FutureCandle_");
ObjectsDeleteAll(0, "FSE_");
}
//+------------------------------------------------------------------+