The Full Process
Seven stages, strictly ordered. This page is the map: what each stage consumes, what it produces, the exact command it runs, and what to check before moving on.
Order is not a suggestion
Each stage reads the previous stage's artefacts. That is why the chain stops at the first non-zero exit rather than pressing on: continuing after a failed search would build a portfolio out of whatever configs were already on disk — probably yesterday's — and every subsequent number would be plausible and meaningless.
The seven stages
A broker-exported or API-downloaded OHLC CSV, validated against the engine's assumptions before any CPU is spent on it.
Consumes: nothing (the terminal, or a file you already have).
Produces: data/<SYMBOL>_<TF>.csv.
check_ohlc_csv.py data/XAUUSD_M5.csv
A two-stage grid search per feature set: 64 trade-geometry combinations, then 243 training-hyper-parameter combinations, then the top 40 candidates re-verified at the full 2,000-iteration budget. Survivors are filtered by an equity-correlation test down to 5 slots per set.
Consumes: the CSV.
Produces: strategies_lr_m5/<set>/ with summary.json, configs.json, slots.txt, equity_<magic>.csv.
feature_search.py --engine lr --symbol XAUUSD --tf M5 --set all --cores 14
--set accepts a single set name or the literal
all. all9 is not valid and fails with
Unknown set. Group selections are expanded by the GUI into one
process per set.
Takes the best slot from each selected feature set, re-simulates all of them at the full verification budget, and sums their equity onto a shared bar grid.
Consumes: every <set>/summary.json.
Produces: strategies_lr_m5/all9/ with the combined curve and each slot's equity.
build_allstar.py --engine lr --symbol XAUUSD --tf M5 --all9 --cores 14
For each feature set, finds the contiguous cyclic block of server hours that maximised in-sample P&L, then re-scores that winner on the half of the data it was not chosen on.
Consumes: each set's best slot.
Produces: strategies_lr_m5/best_hours.json plus per-set trades.csv.
best_hours.py --engine lr --symbol XAUUSD --tf M5 --cores 14
Re-simulates the frozen portfolio on the last N bars only, where nothing was ever selected against. Run twice: warm (model carried its learned state into the tail) and cold (model started from scratch at the boundary).
Consumes: all9/summary.json.
Produces: all9/tail2500_warm/ and …_cold/ with tail_report.json and per-slot trades.
holdout_tail.py --symbol XAUUSD --tf M5 --folder all9 --mode warm --tail 2500 --cores 14
holdout_tail.py --symbol XAUUSD --tf M5 --folder all9 --mode cold --tail 2500 --cores 14
Emits compile-ready MQL5 that mirrors the engine exactly — the same feature
expressions, the same lam = 0.001, the same login per slot — then
drives MetaEditor and copies the binary into the terminal.
Consumes: the portfolio's summary.json + configs.json, and best_hours.json.
Produces: FxMath_AST_allstar_ALL9_M5.mq5 and its compiled .ex5.
gen_ea.py --engine lr --symbol XAUUSD --tf M5 --all9
# the 24-hour variant, tagged so it cannot overwrite the above
gen_ea.py --engine lr --symbol XAUUSD --tf M5 --all9 --no-hours --tag 24X
.ex5 plus 0 errors in the log — never
from the exit code.
Three self-contained HTML files, each answering a different question. The delivery report is the one to hand to a human.
Consumes: everything above.
Produces: report.html, tail_holdout_report.html, report_m5_performance.html.
report_m5.py --symbol XAUUSD --tf M5 --port all9
report_tail.py --symbol XAUUSD --tf M5 --tail 2500
broker_run_report.py --symbol XAUUSD --tf M5 --tail 2500
The whole thing as one command
Full pipeline → Run everything builds the same ordered task list. These are the exact command lines it produces, in order:
# 1 feature search (one process, loops all 11 sets itself)
feature_search.py --engine lr --symbol XAUUSD --tf M5 --set all --cores N
# 2 portfolio
build_allstar.py --engine lr --symbol XAUUSD --tf M5 --all9 --cores N
# 3 trading hours
best_hours.py --engine lr --symbol XAUUSD --tf M5 --cores N
# 4 holdout, warm then cold
holdout_tail.py --symbol XAUUSD --tf M5 --folder all9 --mode warm --tail 2500 --cores N
holdout_tail.py --symbol XAUUSD --tf M5 --folder all9 --mode cold --tail 2500 --cores N
# 5 generate the EA
gen_ea.py --engine lr --symbol XAUUSD --tf M5 --all9
# 6 reports
report_m5.py --symbol XAUUSD --tf M5 --port all9
report_tail.py --symbol XAUUSD --tf M5 --tail 2500
broker_run_report.py --symbol XAUUSD --tf M5 --tail 2500 --json logs/m5_broker_run.json
Note that the search is one process here, not eleven. The script
loops all eleven sets itself, which is both the cheapest option and what the
progress bar tracks. Group selections such as all9 or core6
are the ones that expand into one process per set.
data/<SYMBOL>_<TF>.csv already exists and is good.
Where everything lands
| Stage | Artefact | Path (relative to the repo root) |
|---|---|---|
| Data | OHLC CSV | data/XAUUSD_M5.csv |
| Search | Per-set summary | strategies_lr_m5/<set>/summary.json |
| Search | Slot parameters | strategies_lr_m5/<set>/configs.json |
| Search | Human-readable slots | strategies_lr_m5/<set>/slots.txt |
| Search | Per-slot equity | strategies_lr_m5/<set>/equity_<magic>.csv |
| Portfolio | Combined book | strategies_lr_m5/all9/summary.json |
| Portfolio | Combined curve | strategies_lr_m5/all9/equity_combined.csv |
| Hours | Windows | strategies_lr_m5/best_hours.json |
| Holdout | Tail results | strategies_lr_m5/all9/tail2500_warm/tail_report.json |
| EA | MQL5 source | strategies_lr_m5/all9/FxMath_AST_allstar_ALL9_M5.mq5 |
| EA | Compiled binary | strategies_lr_m5/all9/FxMath_AST_allstar_ALL9_M5.ex5 |
| Reports | Delivery report | strategies_lr_m5/report_m5_performance.html |
Full schemas and naming rules →
Checkpoints between stages
Each stage has one thing worth confirming before you spend time on the next. These are the checks that catch a wasted run early.
| After… | Confirm | Why |
|---|---|---|
| Data | Validator says no problems found; median spread is non-zero | A zero median spread makes every report charge $0 cost and every result look better than reality. |
| Search | ALL SETS DONE, and each set reported 5 verified slots |
A set that found nothing produces an empty portfolio slot later, silently changing the book size. |
| Portfolio | No single slot dominates net or drawdown | Nine correlated slots are one slot with nine names. |
| Hours | Windows are positive in both halves, or plan for 24h | A negative out-of-sample half means the window is curve-fitted. |
| Holdout | Warm and cold are both positive | This is the only out-of-sample evidence you have. If it is negative, the EA has no demonstrated edge. |
| EA | 0 errors, 0 warnings and no STALE badge | A stale .ex5 is the single easiest way to backtest the wrong EA. |
How long it takes
The search dominates everything else. On a large machine with the full 11 sets at a 2,000-iteration verification, the search is the better part of an hour; the remaining stages together are minutes. Rough shape:
| Stage | Relative cost | Notes |
|---|---|---|
| Data | Seconds | Network-bound, not CPU-bound. |
| Search | Dominant | 307 configurations per set × 11 sets, then 40 re-verified at full budget each. Runs a process pool. |
| Portfolio | Minutes | One full-budget re-simulation per slot — 9 slots. |
| Hours | Minutes | One full-budget run per set. |
| Holdout | Minutes | Two runs (warm, cold) over the tail plus warmup. |
| EA | Seconds | Code generation; MetaEditor compile is fast. |
| Reports | Minutes | The delivery report is slowest — it re-runs one forward pass per slot for the exact 24h-vs-window replay. |
Cores matter almost exclusively for the search. Setting cores to the
machine's core count minus one or two is the usual choice — the app defaults to
cpu_count − 2 so the UI and OS keep a core.
Reproducing a run by hand
Everything the app does is reproducible from a shell. This is intentional: the Output panel shows each command, and the fresh artefacts land in the same paths. A full manual reproduction:
# from the repository root, with any python that has numpy + pandas
python scripts/feature_search.py --engine lr --symbol XAUUSD --tf M5 --set all --cores 14
python scripts/build_allstar.py --engine lr --symbol XAUUSD --tf M5 --all9 --cores 14
python scripts/best_hours.py --engine lr --symbol XAUUSD --tf M5 --cores 14
python scripts/holdout_tail.py --symbol XAUUSD --tf M5 --folder all9 --mode warm --tail 2500 --cores 14
python scripts/holdout_tail.py --symbol XAUUSD --tf M5 --folder all9 --mode cold --tail 2500 --cores 14
python scripts/gen_ea.py --engine lr --symbol XAUUSD --tf M5 --all9
python scripts/report_m5.py --symbol XAUUSD --tf M5 --port all9
python scripts/report_tail.py --symbol XAUUSD --tf M5 --tail 2500
The app will pick all of it up on the next refresh, because it reads state from disk rather than from memory.