19 KiB
For_Sim.mq5 — Culmination Version
EMA Crossover Forward Simulation: Interactive Synthetic Candles
This repository contains the final version of the Forward Simulation Engine, a custom MQL5 indicator that projects synthetic OHLC candles ahead of an EMA crossover signal and advances that projection in real time as new bars close.
It does not claim to reproduce any third-party source code. All logic is original and derived from the accompanying two-part article series.
Overview
The culmination version extends the original Forward Simulation Engine across three dimensions that were absent in part one:
- Instrument-calibrated sizing — body heights and wick lengths are derived from the measured averages of recent closed bars on the active symbol and timeframe, not from the EMA slope alone
- Organic visual variation — body sizes follow a sine-wave envelope over an exponential decay, and counter-trend candles are injected periodically to simulate realistic pullbacks within the dominant direction
- Live interactivity — as each new real candle closes, the leading projected candle is removed, keeping the simulation perpetually one bar ahead of the chart without any manual intervention
The indicator renders two visible EMA lines via indicator buffers, projects up
to FutureBars synthetic candles into future time slots using chart objects,
and supports both automatic and manual anchor placement.
Designed for M15 but adapts to any timeframe via PeriodSeconds().
Original Article Series
| Field | Value |
|---|---|
| Part 1 Title | Creating an EMA Crossover Forward Simulation Indicator in MQL5 |
| Part 2 Title | Creating an EMA Crossover Forward Simulation (Culmination): Interactive Synthetic Candles |
| Author | johnhlomohang |
| Publication Date | |
| Category | Indicators |
| Part 1 URL | https://www.mql5.com/en/articles/22323 |
| Part 2 URL | https://www.mql5.com/en/articles/22927 |
Repository Purpose
This repository accompanies the published article series. Its purpose is to:
- preserve the complete culmination-version source for study and extension
- document every input parameter, internal function, and design decision
- demonstrate the calibration, prediction, rendering, and interactive-removal patterns described across both articles
- serve as a clean base for replacing the EMA crossover with structure-based, ATR-driven, or machine-learning signal triggers without touching the rendering or invalidation layers
What Changed from Part 1
| Feature | Part 1 | Culmination (this version) |
|---|---|---|
| Body size source | Raw EMA slope | Measured g_AvgBody × momentum scale factor |
| Wick size source | Fixed pip constant | Measured g_AvgUpperWick / g_AvgLowerWick ratios |
| Body variation across run | Exponential decay only | Sine-wave envelope × exponential decay |
| Counter-trend candles | None | Injected every CounterCandleFreq bars at 25–45% |
| Inter-candle spacing | None | Time-inset OBJ_RECTANGLE via CandleGapFraction |
| Projection advancement | Static — never moves | Leading candle removed as each real bar closes |
| Calibration refresh | None | CalcAvgCandleMetrics at init and on each signal |
| New inputs | — | CandleGapFraction, CounterCandleFreq, AvgLookback |
Key Concepts
- fast and slow EMA crossover detection on fully closed bars only
- automatic anchor placement at the crossover bar (
AutoAnchor = true) CalcAvgCandleMetrics— measuresg_AvgBody,g_AvgUpperWick,g_AvgLowerWick,g_AvgRangefromAvgLookbackclosed bars- EMA slope normalised against
g_AvgBodyto produce a momentum scale factor in[0.5, 1.5] sin²(phase)envelope pulsing body sizes between 40% and 130% ofbaseStep0.93^iexponential decay flattening the projection toward the tail- counter-trend candle injection every N bars retracing 25–45% of the prior body
- wick lengths =
bodyHeight × (avgWick / avgBody)± 25% jitter, floored at 20% of body CandleGapFractioninsets the body rectangle inside each bar slot; wicks stay centred on the full slot- pip-based invalidation: projection is cleared when live price moves
InvalidationPipsagainst the signal price - interactive removal:
FutureCandle_0objects are deleted each timerates_totalincrements - dual-mode anchor: auto (tracks crossover bar) or manual (user-placed
OBJ_VLINE) ArraySize()guard on indicator buffers prevents out-of-range writes during early ticks
Architecture Summary
Module 1 — Signal and Calibration (RunEngine, CalcAvgCandleMetrics)
OnCalculate fires on every tick. RunEngine reads tmpFast[] and tmpSlow[]
(non-series, index 0 = oldest) and compares the two most recently closed bars
(rates_total-2 vs rates_total-3) for a crossover condition.
On a new cross, the signal state is latched and CalcAvgCandleMetrics is called.
That function uses CopyOpen, CopyHigh, CopyLow, CopyClose from shift 1
(skipping the live bar) to compute four averages over AvgLookback bars.
All four values are floored at 1 pip to handle flat markets.
CalcAvgCandleMetrics also runs once in OnInit so the first projection is
calibrated immediately on indicator attach.
Module 2 — Prediction Engine (GeneratePrediction)
Accepts startPrice and emaSlope, builds a PredictedCandle[] of length
FutureBars. For each bar i:
momentumScale = 0.5 + min(|emaSlope| / g_AvgBody, 1.0) // range [0.5, 1.5]
baseStep = g_AvgBody × momentumScale
phase = i / FutureBars × 2π
envelope = 0.40 + 0.90 × sin²(phase) // range [0.40, 1.30]
decay = 0.93^i
jitter = 1.0 ± 0.08
bodySize = baseStep × envelope × decay × jitter
Floor applied: bodySize cannot fall below 0.5 × g_AvgBody.
Every CounterCandleFreq bars (starting at bar 4, 8, 12, …), direction is
reversed and body size is reduced to bodySize × retracePct where
retracePct ∈ [0.25, 0.45].
Wick construction:
upWick = bodyHeight × (g_AvgUpperWick / g_AvgBody) × uJitter × 1.20 (if bullish)
dnWick = bodyHeight × (g_AvgLowerWick / g_AvgBody) × lJitter × 1.20 (if bearish)
Both wicks floored at 0.20 × bodyHeight.
Module 3 — Renderer (DrawAllCandles, DrawSingleCandle)
Each projected candle occupies one full bar-width time slot starting one bar
after the anchor. The body OBJ_RECTANGLE is inset by gapSec seconds on
each side of the slot. The wick OBJ_TREND lines are anchored to tMid
(centre of the full slot) so they remain visually centred despite the inset.
Every object is created with OBJPROP_HIDDEN = false and OBJPROP_SELECTABLE = false.
Supporting objects: FSE_Sep (dashed separator), FSE_Label (zone text),
FSE_Invalid (invalidation notice), FSE_Anchor (gold dash-dot vline).
Cleanup uses prefix-based ObjectsDeleteAll(0, "FutureCandle_") and
ObjectsDeleteAll(0, "FSE_") to remove all objects atomically.
Interactive Removal
On each OnCalculate call, if rates_total > prev_calculated by exactly one,
a new bar has closed. The engine deletes the FutureCandle_0_* group and
renumbers the remaining candles from index 1 downward, keeping the sequence
continuously aligned one bar ahead of the live bar.
Invalidation
On each tick the live bar's close (close[rates_total-1]) is compared to
g_SignalPrice. The threshold is InvalidationPips × _Point × 10.0.
On breach: all objects are deleted, FSE_Invalid is drawn, signal state
is cleared, and the engine waits silently for the next valid cross.
OnTimer (manual anchor mode only)
When AutoAnchor = false, a 3-second timer polls GetAnchorTime(). If the
user has moved the FSE_Anchor vline, g_DrawnAnchor is reset to force a
full redraw on the next tick.
Buffer Safety Pattern
MT5 indicator buffers (FastEMABuffer, SlowEMABuffer) are auto-sized by
the terminal and are not guaranteed to equal rates_total in early ticks.
All writes are gated by MathMin(copiedFast, ArraySize(FastEMABuffer)) to
prevent out-of-range crashes.
Attached Files
| File | Description |
|---|---|
For Sim.mq5 |
Complete MQL5 custom indicator |
Input Parameters
| Parameter | Default | Range / Notes |
|---|---|---|
FastEMA_Period |
9 | Period of the fast EMA (close price) |
SlowEMA_Period |
21 | Period of the slow EMA (close price) |
FutureBars |
30 | Projected candles; keep ≤ 60 for visible tail bodies |
SpreadMultiplier |
2.0 | Legacy floor multiplier; superseded by calibrated wick ratios |
AutoAnchor |
true | true = anchor tracks crossover bar; false = manual OBJ_VLINE |
AnchorLineName |
"FSE_Anchor" | Chart object name for the anchor vertical line |
BullishColor |
DodgerBlue | Fill color for trend-direction bullish projected candles |
BearishColor |
Crimson | Fill color for trend-direction bearish projected candles |
WickColor |
DimGray | Color for all upper and lower wick trend lines |
ShowZoneLabel |
true | Toggle [ BULLISH PROJECTION ] / [ BEARISH PROJECTION ] label |
ShowSeparatorLine |
true | Toggle dashed vertical separator at the anchor bar |
InvalidationPips |
10 | Pip distance from signal price that triggers full projection removal |
CandleGapFraction |
0.08 | Body inset as fraction of bar width; clamped to [0.01, 0.45] |
CounterCandleFreq |
4 | Counter-trend candle every N bars; hard minimum enforced at 3 |
AvgLookback |
50 | Closed bars sampled for calibration; hard minimum enforced at 10 |
Chart Object Naming Convention
| Name pattern | Type | Role |
|---|---|---|
FutureCandle_N_body |
OBJ_RECTANGLE |
Filled candle body |
FutureCandle_N_bord |
OBJ_RECTANGLE |
Unfilled body outline |
FutureCandle_N_wU |
OBJ_TREND |
Upper wick |
FutureCandle_N_wD |
OBJ_TREND |
Lower wick |
FSE_Sep |
OBJ_VLINE |
Dashed silver separator at anchor |
FSE_Label |
OBJ_TEXT |
Projection zone label |
FSE_Invalid |
OBJ_TEXT |
Invalidation notice (orange) |
FSE_Anchor |
OBJ_VLINE |
Gold dash-dot anchor line |
All prefixes are chosen so ObjectsDeleteAll(0, "FutureCandle_") and
ObjectsDeleteAll(0, "FSE_") remove every drawn object in two calls.
Global State Variables
| Variable | Type | Description |
|---|---|---|
g_FastHandle |
int |
iMA handle for the fast EMA calculation |
g_SlowHandle |
int |
iMA handle for the slow EMA calculation |
g_LastSignal |
int |
+1 bullish, -1 bearish, 0 none |
g_SignalActive |
bool |
Whether a valid, non-invalidated signal is live |
g_SignalPrice |
double |
Close price of the bar where the cross occurred |
g_SignalBarTime |
datetime |
Time of the crossover bar |
g_DrawnAnchor |
datetime |
Anchor time of the last completed draw (redraw gate) |
g_AvgBody |
double |
Mean |close - open| over AvgLookback bars |
g_AvgUpperWick |
double |
Mean high - max(open, close) over AvgLookback bars |
g_AvgLowerWick |
double |
Mean min(open, close) - low over AvgLookback bars |
g_AvgRange |
double |
Mean high - low over AvgLookback bars |
Statistics
| Metric | Value |
|---|---|
| Indicator type | Chart window overlay |
| Plot buffers (visible EMA lines) | 2 |
| Crossover evaluation window | 2 closed bars |
| Default projection length | 30 bars |
| Default calibration lookback | 50 bars (min 10) |
| Momentum scale range | 0.5× – 1.5× g_AvgBody |
| Sine envelope range | 0.40× – 1.30× baseStep |
| Exponential decay base | 0.93 per bar |
| Body size floor | 0.5 × g_AvgBody |
| Counter-trend retrace range | 25% – 45% of body |
| Wick minimum floor | 20% of body height |
| Gap clamp range | 1% – 45% of bar width |
| Chart objects per candle | 4 |
| Timer interval | 3 seconds (manual mode) |
| Invalidation unit | Pips (_Point × 10) |
Installation
- Copy
ForwardSimEngine.mq5to your MetaTrader 5 data folder:MQL5/Indicators/ForwardSimEngine.mq5 - Open MetaEditor (F4 in MT5) and compile the file (F7). Zero errors expected.
- In the MT5 Navigator panel under Custom Indicators, drag the indicator onto a chart.
- Recommended timeframe: M15.
- Click OK. Open the Experts tab and confirm the following log lines appear:
ForwardSimEngine [INIT]: OK FastEMA=9 SlowEMA=21 ... ForwardSimEngine [AVG]: lookback=50 AvgBody=... - On the first EMA crossover, a
[SIGNAL]and[DRAWN]log line will appear and the projection will render to the right of the anchor.
Experts Log Reference
| Tag | When it appears | What to check |
|---|---|---|
[INIT] |
On indicator attach | All input values confirmed correct |
[AVG] |
At init and on each new signal | AvgBody in points looks realistic for TF |
[TICK #1-3] |
First three ticks after attach | fCur and sCur are non-zero |
[SIGNAL] |
On EMA crossover | Direction, bar time, and price are right |
[DRAWN] |
After each successful render | Anchor time and bar count are correct |
[INVALIDATED] |
When live price breaches invalidation threshold | signalPx and livePx delta makes sense |
[WARN] |
If CopyBuffer returns ≤ 0 or bar count too low | Enough history loaded on that symbol/TF |
Tuning Notes
AvgLookback— use 80–100 on instruments with regime changes (e.g. XAUUSD around news). Use 20–30 to react faster to recent short-term volatility.InvalidationPips— start at 15–20 on XAUUSD and GBPJPY. Use 5–8 on tight instruments like EURUSD on M15.CandleGapFraction— 0.08 is well-suited for M15. Increase to 0.12 on H1 or H4 where bar slots are visually wider.CounterCandleFreq— 3 gives a more choppy, ranging look. 5–7 gives a cleaner impulsive projection.FutureBars— above 60, the0.93^idecay reduces tail bodies to the 0.5× floor and they all look the same size. 20–40 is the sweet spot.- Manual anchor replay — set
AutoAnchor = false, draw anOBJ_VLINEnamedFSE_Anchoron any historical bar, and the engine projects forward from that bar using the candle averages measured at that moment.
Extension Points
The three modules are fully independent. Common extensions that require no changes to the rendering or invalidation layers:
- Replace
DetectCrossoverinRunEnginewith a structure-based (BOS/CHoCH) trigger - Replace the EMA slope scale factor in
GeneratePredictionwith an ATR-based step size - Add a confidence score input that reduces
FutureBarsor applies opacity graduation - Feed
g_AvgBodyandg_AvgRangeinto a volatility-regime classifier to switch between projection styles (trending vs ranging candle shapes)
Limitations
- The projection is a structured visual scenario. It is not a price forecast and makes no statistical claims about future price direction.
- Crossover detection uses only two closed bars. Whipsaw conditions on ranging markets will produce frequent signal resets.
- The invalidation threshold is fixed in pips and does not adapt dynamically to ATR or volatility regime.
- Calibration quality depends on the quantity and quality of history available. Symbols with thin history on a given timeframe may produce unrealistic averages.
- The indicator does not place, manage, or close any trades. It is a visualization tool only.
Tags
MQL5 MetaTrader5 CustomIndicator ForwardSimulation EMA Crossover
SyntheticCandles ChartObjects InteractiveProjection Calibration
SineEnvelope CounterTrend AlgorithmicTrading Visualization
Difficulty
Intermediate — Advanced
Requires familiarity with:
- MQL5 indicator development (
OnCalculate,SetIndexBuffer,CopyBuffer) - MT5 chart object API (
ObjectCreate,ObjectSetInteger,ObjectsDeleteAll) - MQL5 array indexing: series vs non-series layout,
prev_calculatedgating ArraySize()vsrates_totaldistinction for indicator buffer writes- Basic EMA crossover concepts and pip arithmetic
- MetaTrader 5 Strategy Tester for visual and log validation
Reference
Part 1 article: https://www.mql5.com/en/articles/22323 Part 2 article: https://www.mql5.com/en/articles/22927