373 lines
No EOL
19 KiB
Markdown
373 lines
No EOL
19 KiB
Markdown
# 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 | <!-- ADD 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` — measures `g_AvgBody`, `g_AvgUpperWick`, `g_AvgLowerWick`, `g_AvgRange` from `AvgLookback` closed bars
|
|
- EMA slope normalised against `g_AvgBody` to produce a momentum scale factor in `[0.5, 1.5]`
|
|
- `sin²(phase)` envelope pulsing body sizes between 40% and 130% of `baseStep`
|
|
- `0.93^i` exponential 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
|
|
- `CandleGapFraction` insets the body rectangle inside each bar slot; wicks stay centred on the full slot
|
|
- pip-based invalidation: projection is cleared when live price moves `InvalidationPips` against the signal price
|
|
- interactive removal: `FutureCandle_0` objects are deleted each time `rates_total` increments
|
|
- 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
|
|
|
|
1. Copy `ForwardSimEngine.mq5` to your MetaTrader 5 data folder:
|
|
```
|
|
MQL5/Indicators/ForwardSimEngine.mq5
|
|
```
|
|
2. Open MetaEditor (F4 in MT5) and compile the file (F7). Zero errors expected.
|
|
3. In the MT5 Navigator panel under Custom Indicators, drag the indicator onto a chart.
|
|
4. Recommended timeframe: **M15**.
|
|
5. 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=...
|
|
```
|
|
6. 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, the `0.93^i` decay 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 an `OBJ_VLINE` named `FSE_Anchor` on 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 `DetectCrossover` in `RunEngine` with a structure-based (BOS/CHoCH) trigger
|
|
- Replace the EMA slope scale factor in `GeneratePrediction` with an ATR-based step size
|
|
- Add a confidence score input that reduces `FutureBars` or applies opacity graduation
|
|
- Feed `g_AvgBody` and `g_AvgRange` into 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_calculated` gating
|
|
- `ArraySize()` vs `rates_total` distinction 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 |