回测基本一致
This commit is contained in:
+43
-20
@@ -67,8 +67,13 @@ engine.run(
|
||||
sl_prices, # array — the stop price for an entry on that bar (NaN if none)
|
||||
tp_prices, # array — the target price
|
||||
instrument, # InstrumentConfig — all symbol mechanics
|
||||
lot / money_mode, # position sizing inputs
|
||||
sizing, # SizingInputs — position sizing inputs
|
||||
initial_deposit,
|
||||
*, # keyword-only from here
|
||||
m1_bars=None, # OPTIONAL: M1 bars for tick-level exit simulation
|
||||
# REQUIRED if the EA moves its SL intra-trade (BE / trailing /
|
||||
# basket trailing) — bar-level mode is untrustworthy for that
|
||||
# class (doc 03 §7 failure mode, doc 03 §8 target gates).
|
||||
) -> Result
|
||||
```
|
||||
|
||||
@@ -76,6 +81,12 @@ engine.run(
|
||||
> never decides *where* a stop goes — only *whether* price touched it. This is the seam that
|
||||
> separates "the strategy" from "the simulator". Change your strategy → you change the caller and the
|
||||
> arrays you hand in; the engine is untouched.
|
||||
>
|
||||
> **The `m1_bars` parameter is not optional for trailing/BE strategies.** When the EA updates its SL
|
||||
> during a trade, the bar-level engine can produce a −40% to −50% net gap vs MT5 (doc 03 §7 measured
|
||||
> failure mode). Pass M1 bars and the engine switches to tick-level exit simulation, dropping the
|
||||
> gap to ~−5%. Bar-level mode remains the right (and faster) choice for clean-directional setups that
|
||||
> don't move the SL.
|
||||
|
||||
The engine's output is equally generic:
|
||||
|
||||
@@ -95,32 +106,44 @@ Factor, Win Rate, max Balance/Equity Drawdown, Sharpe, APR, trade count.
|
||||
## 3. Data flow of one backtest
|
||||
|
||||
```
|
||||
data/<symbol>/<SYM>_M1_<years>.parquet
|
||||
│ load_bars() → DataFrame with per-bar spread column
|
||||
▼
|
||||
caller: resample M1 → the signal timeframe (e.g. H1/H4/D1)
|
||||
│ indicators.* on the resampled frame
|
||||
▼
|
||||
caller: edge-detect signals (True only on the bar the condition first flips, not every bar after)
|
||||
│ + optional gates (regime/time filters AND-ed into the signal)
|
||||
▼
|
||||
caller: compute SL/TP price arrays from params (ATR stop, % stop, indicator band, …)
|
||||
▼
|
||||
engine.run(bars, signals, sl/tp, instrument, sizing, deposit)
|
||||
│
|
||||
▼
|
||||
Result → compute_metrics → dict
|
||||
│
|
||||
▼ (finalists only)
|
||||
MT5 bridge: build .set from the same params → run real tester → parse report → compare
|
||||
data/<symbol>/<SYM>_M1_<years>.parquet data/<symbol>/<SYM>_M5_<years>.parquet
|
||||
│ load_bars() → M1 DataFrame │ load_bars() → signal-TF DataFrame
|
||||
│ │ (resampled from M1 if needed)
|
||||
│ ▼
|
||||
│ caller: indicators.* on the signal timeframe
|
||||
│ │
|
||||
│ ▼
|
||||
│ caller: edge-detect signals (True only on the bar
|
||||
│ │ the condition first flips, not every bar after)
|
||||
│ │ + optional gates (regime/time filters AND-ed in)
|
||||
│ ▼
|
||||
│ caller: compute SL/TP price arrays from params
|
||||
│ │ (ATR stop, % stop, indicator band, …)
|
||||
└──────────────┐ ┌──────────────────────────┘
|
||||
▼ ▼
|
||||
engine.run(signal_bars, signals, sl/tp, instrument, sizing, deposit,
|
||||
m1_bars=m1_bars) ← M1 is passed back to the engine
|
||||
│ for tick-level exit simulation when the EA
|
||||
│ moves its SL intra-trade (BE / trailing). Without
|
||||
│ it, the bar-level engine over-credits BE exits
|
||||
│ (doc 03 §7 failure mode).
|
||||
▼
|
||||
Result → compute_metrics → dict
|
||||
│
|
||||
▼ (finalists only)
|
||||
MT5 bridge: build .set from the same params → run real tester → parse report → compare
|
||||
```
|
||||
|
||||
Two subtleties that cause most bugs if missed (both explained in doc 03):
|
||||
Three subtleties that cause most bugs if missed (the first two explained in doc 03):
|
||||
|
||||
- **Edge detection.** Signals must be `True` only on the *transition* bar, not forward-filled, or the
|
||||
engine re-enters every bar.
|
||||
- **Timeframe alignment.** Indicators computed on a higher timeframe must be mapped back onto the M1
|
||||
bars correctly (no look-ahead — a daily value is only known after that day closes).
|
||||
- **M1 must reach the engine for trailing/BE EAs.** Downloading M1 only to resample it up to the signal
|
||||
timeframe is **not** enough if the EA moves its SL during a trade. Pass the M1 bars to `engine.run`
|
||||
(the `m1_bars=` kwarg); otherwise the bar-level exit simulation produces a −40% to −50% net gap
|
||||
(doc 03 §7) that looks like a "fidelity issue" but is actually a missing-input bug.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user