Articles ·
Backtest audit, part 3: how the results are shown
Part 1 fixed how the engine trades, part 2 what it is given. This part is about the figures it hands back: a correct simulation is still misread if a total adds text, a drawdown skips the worst bar or a percentage rounds 249 of 250 up to all.
The ledger adds numbers, not text
The engine sends money as exact decimal strings. The ledger of saved backtests added a basket's markets as text, so
"10.5" and "-2.25" became "010.5-2.25" and every basket line showed NaN for its result and return. It also
grouped any backtests of one version on one window as a basket, so a backtest run twice with different fees read as
"2 markets", summed.
Now a basket is recorded as one: its markets carry its id, and its line in the ledger shows what its account did as a whole — result, return and drawdown — next to how many markets made money.
A basket is one account, bar by bar
A basket's account used to be added up from each market's chart line: 200 points per market, at bar closes only. That missed falls inside a bar and, because each market kept different moments, could add two falls that never happened together. Now it is reckoned on every bar of every market — its lowest, highest and closing equity and the notional it held — and markets that fall in the same bar are taken to reach their lows together.
- Fees at the end. Positions still open when the data ended were counted before their exit fee and slippage were paid; with 20 markets at 3× that was 3.6 points of return. The account now ends at what every market kept.
- Wiped out means wiped out. Each market used to be margined on the whole account by itself, so two markets could lose 180% of it and the basket would show a recovery. The account is now one balance: when its equity at a bar's low reaches the maintenance margin of everything it holds, it is liquidated and stays at zero. A basket whose markets held more at once than the account carries at its leverage says so.
- Sharpe from every day. It is computed from the account at the end of every UTC day.
How it all adds up: How a Metrand basket backtest adds markets into one account.
The optimizer's held-out check stays held out
The optimizer ranks combinations on the first part of the window and checks them on the rest. Its map coloured the cells by the held-out result, which invites picking there and turns the check into another fit. The map now shows the in-sample result, where a pick is made; the held-out map is a switch, labelled as a check. The results also say how many combinations would look positive on both sides by chance — the share positive in-sample times the share positive out-of-sample — and each result shows its return in percent beside the USDT.
One meaning for each figure
- Average trade. It divided the mean result of every trade by the mean size of the first hundred listed. Nine
trades of 25 USDT making 0.10 each and one of 775 USDT losing 10 read −0.91%, though the trades averaged +0.23%. The
engine now reports the mean of each trade's return on what it entered with, over every trade
(
avg_trade_return_pct). - A year. A basket showed a compounded rate and a template card a simple one: +100% over two years read +41.4% in one and +50.0% in the other. Both now show what the account made a year on the capital it started with, since that is what positions are sized from. A year the result did not span whole is marked.
- Win rate. 249 winners of 250 used to read 100%. A share that is not all or none now never reads as either.
- Times. Trade times were in the reader's time zone, unmarked, beside windows in UTC. They are in UTC, marked.
- Charts. The line shows the account at bar closes; the drawdown also counts the lowest point inside every bar, and the page says so. A basket's line keeps every trough instead of every k-th point.
- Template figures. A template's measurement was cached by its name alone, so after a release it could show numbers from the previous engine for hours. It is now tied to the template's graph, the way it is measured and the release.
In the API
A basket answer carries basket_id, and its portfolio now has per_year_pct (instead of cagr_pct), ruined_at,
over_margin and years[].partial. POST /backtest takes "marks": true and returns every bar's low, high,
close and held, so a caller can add backtests into one account the same way. Reports carry
avg_trade_return_pct.
Next
The last part, part 4, checks the engine against an independent reference simulation, and that no decision can read a price from after it was made.