vike
Analytics

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's run loop; the reset and the write are in crates/vike-backtest/src/engine/sim_broker.rs's fold_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's backfill_excursions is the worked example: a bare fill stream carries no intrabar path, so a trade reconstructed from the live journal leaves mae/mfe at 0.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 to mae_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 at 0.0 rather than failing the tearsheet, because the store is an enrichment, not a hard dependency. The tearsheet binary opens the concrete store behind its hist feature 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.

On this page