Excursions
MAE and MFE per trade from the bars between entry and exit, the edge ratio over them, and the running trade metrics — one excursion primitive that the live tearsheet back-fills through.
A trade's maximum adverse excursion is how far price moved against it before it closed; its
maximum favourable excursion is how far price moved in its favour. crates/vike-analytics/src/ excursions.rs's header defines the module as pure functions over the trade list and OHLC bars,
with MAE and MFE derived by scanning the bars between each trade's entry and exit timestamps.
Direction is inferred, and the field that looks like a side test is not one
trade_direction(trade) returns +1 for long and −1 for short, and its doc calls itself the
single source of truth for that inference. The reason it has to exist: Trade.size is recorded
as an absolute value — side is lost at the engine level — so size >= 0 is not a side test. The
rule is that a profit on a price rise is a long and a profit on a price fall is a short; the
degenerate case, pnl == 0 or exit == entry, defaults to long.
MAE and MFE
mae_mfe(trade, bars, direction) returns (mae, mfe) as positive fractions of entry price over
the trade's bar window — every bar with entry_ts <= ts <= exit_ts, inclusive. direction is an
optional override; None means infer it as above. For a long, adverse is the lowest low below
entry and favourable the highest high above it; for a short the two are mirrored. A zero entry
price or an empty window returns (0.0, 0.0).
That last case matters more than it looks, because it is how the two fields on the trade itself
behave. vike_model::Trade carries mae and mfe as fractions of entry price, and its field
doc says portfolio mode only; 0.0 = untracked. There are two ways those fields get a value:
- During a portfolio backtest, the engine's bar loop folds each bar's high and low into the
running extremes of every open position — this bar's high/low, no look-ahead — and the
simulator's fill path resets those extremes to the fill price when a position opens or flips,
then writes the excursions at the moment a reduce, close or flip closes a trade, using the
prior sign of the position for direction. The per-bar fold is in
crates/vike-backtest/src/engine.rs'srunloop; the reset and the write are incrates/vike-backtest/src/engine/sim_broker.rs'sfold_fill; the sim broker page carries the rest of it. - After the fact, from a bar series, through this module.
crates/vike-report/src/ excursions.rs'sbackfill_excursionsis the worked example: a bare fill stream carries no intrabar path, so a trade reconstructed from the live journal leavesmae/mfeat0.0— correct, not a shortcut. When a bar series for the traded symbol exists in a history store, it loads the bars spanning[entry_ts, exit_ts]and hands them tomae_mfe— which its header calls the one excursion primitive; no math is reimplemented here. It is best-effort: an empty window or a load error leaves that trade at0.0rather than failing the tearsheet, because the store is an enrichment, not a hard dependency. The tearsheet binary opens the concrete store behind itshistfeature and passes it in as a trait object.
Two direction sources
The simulator writes excursions using is_long, the opening side it recorded. mae_mfe with
direction = None infers the side from pnl and the price change. Because Trade.pnl is gross
price PnL, the two agree for any trade with a non-zero PnL; for the degenerate zero-PnL trade the
inference defaults to long. Pass Some(direction) when you hold the recorded side and want it honoured.
The edge ratio
edge_ratio(trades, bars) is mean MFE over mean MAE across the trades, computed through
mae_mfe for each — its doc frames it as entry quality: a value above 1 favours profitable
excursions. The sentinels follow the crate convention: 0.0 with no trades, inf when mean MAE
is zero and mean MFE is positive (a trade set that never went against you).
Running trade metrics
expanding_trade_metrics(trades) produces one ExpandingMetric row per trade, in order, each
carrying the state after that trade: n, win_rate, profit_factor and avg_pnl. It is the
series a "metrics as the run progressed" chart is drawn from. profit_factor is inf while there
are no losing trades and gross profit is positive — the same rule as the
metrics catalog's whole-sample version, applied at every prefix.
What to read alongside
The result type page describes the Trade fields these functions
read; the metrics page carries the sentinel convention the ratios share.
Benchmark
Beta, alpha, correlation, tracking error, information ratio, the capture ratios and Treynor over two aligned equity curves — and the length rule every one of them enforces.
Monte Carlo
Shuffle and bootstrap resampling of trade P&L into many equity paths, the percentile bands and ruin statistics over them, and the seed rule that keeps every run reproducible.