vike
Analytics

Analytics

The pure, engine-free performance-analytics cluster — every number a backtest report or a live tearsheet prints, computed in one place from vike-model and nothing else.

Vike Trader's analytics live in one crate, vike-analytics, whose header states the design in a sentence: the pure performance-analytics cluster, extracted verbatim out of vike-backtest. Nothing in it runs a simulation, signs an order or opens a store. It takes an equity curve and a trade list and returns numbers — and because the backtest runner and the live tearsheet both call the same functions, a backtest number and a live number over the same fills are the same number.

Why it is a crate of its own

crates/vike-analytics/src/lib.rs's header carries the measurement that justified the split. vike-report, the reader that renders a tearsheet from the live journal, reimplements no metric: every figure it prints is a call into this catalog. While the catalog lived inside vike-backtest, that reader compiled 32,259 lines to reach about 3,900 — 89% of what it built it could not call, including the simulator, the paper exchange and the whole feature-gated harness tree. The seam was already there: these modules name vike_model and nothing else (the one vike_exec mention in the cluster was a doc comment in result.rs). Splitting on it lets a consumer take the numbers without the machine that produced them. The header is explicit that the extraction moved code, not behaviour.

The rule the crate holds

vike-model is the only Vike dependency — no vike-exec, no vike-core, no vike-data. The header calls that the property the split exists to create, and says a would-be dependency on any of the three is a signal that the item belongs in vike-backtest, not here. The manifest says the same thing structurally: crates/vike-analytics/Cargo.toml declares layer = 20 under [package.metadata.vike], the domain tier ("one hop over the vocabulary: a single-purpose pure core"), and the layer gate fails any normal dependency that does not sit strictly lower. The architecture page carries the tiers.

The non-Vike dependencies are few, and each carries its reason in the manifest: serde (the report and the zero-trade report serialize), chrono (calendar bucketing), indexmap (insertion order where it affects a floating-point sum), rand (seeded resampling), libm (the manifest's row names what first brought it in — the erfc normal CDF and the inverse normal CDF in overfit.rs; the rule that every transcendental in the crate now goes through it is the header's, below).

What is in it

modulepagewhat it is
metricsMetricsthe catalog — sharpe, sortino, drawdown, CAGR, VaR, ES, SQN and the rest; the one home for every performance number
resultThe result typeBacktestResult, the run's raw output
reportThe reportBacktestReport, the flat serializable summary composed from it by metrics:: calls only
periodsPeriodscalendar bucketing, the monthly matrix, the drawdown table
benchmarkBenchmarkbenchmark-relative statistics over two aligned curves
excursionsExcursionsMAE/MFE per trade, the edge ratio, running trade metrics
montecarloMonte Carloseeded resampling of trade P&L
overfitOverfittingPBO and the deflated Sharpe
stabilityStabilityrolling parameter-stability scoring
validationValidationwalk-forward and purged k-fold split generators
statsSurvival statisticsthe autocorrelation-robust t-statistic
signal, signal_backtestSignals, The signal backtestprobability to position; the vectorized signal backtest
sizing, zero_tradeSizing, and zero-trade diagnosisthe PositionSizer registry (eight sizers); the zero-trade cause analyzer

A last module, binutil, is the pure argv glue the analytics-family binaries share; the header notes that store_root, the one function that reads the environment, deliberately stayed in vike-backtest.

Numbers that do not depend on the box

The header's "Cross-platform determinism" section is the part of this crate most worth carrying into your own reading of a report. Every transcendental in the crate is the libm crate, never f64's inherent method — libm::pow, libm::log, libm::exp, libm::erfc. IEEE 754 does not require pow, log or exp to be correctly rounded, so f64::powf is whatever the platform's maths library does: the MSVC CRT on the Windows desktop, glibc on the Linux servers. The two disagree by up to one ulp, and the equity curve produced by signal_backtest::equity_from_pnl was measurably among the casualties, so a backtest's headline number depended on which machine ran it. crates/vike-analytics/tests/libm_platform_probe.rs is the measurement that established it, and converted_functions_are_platform_invariant in that file is the gate that now holds it. The decision record behind the rule is dated 2026-08-25 and accepted.

Two refinements the header states because each was once got wrong: sqrt stays f64::sqrt, because IEEE 754 does require it correctly rounded; and powi is banned at every arity, because on MSVC in a dev build it becomes the CRT's pow() — the cure is a plain multiplication.

What the portability costs

The header states the trade rather than hiding it: the libm crate does not agree with glibc either (the probe measures 0.07%–10% of samples differing per domain), so the conversion moved values on Linux too — where CI and live trading run. The numbers also no longer track the platform library the retired reference implementation computed with. That is a deliberate choice of reproducibility over bit-parity with an oracle that no longer exists; the parity gate page carries what retiring that oracle did and did not license.

The parity rules the crate inherited are unchanged: f64 end to end, the same expression order, no fused multiply-add or fast-math; % on timestamps is floor-mod (rem_euclid); IndexMap wherever insertion order affects the order of a floating-point sum.

Two conventions you will meet on every page

Degenerate inputs return a sentinel, not NaN. A ratio with nothing to divide is 0.0, and a ratio whose denominator is zero while the numerator is positive is f64::INFINITY — profit_factor with no losing trades is the canonical case. The report page shows how that sentinel reaches JSON.

One percentile. Everything that quotes a quantile — VaR, the tail ratio, the Monte Carlo bands — goes through metrics::percentile, and metrics::PERCENTILE_METHOD ("numpy_linear") names that convention wherever an artifact has to say which percentile produced its numbers.

Where the numbers surface

The standalone backtest binary prints a BacktestReport and ranks sweeps on it; the read a report guide walks one. The tearsheet binary builds a BacktestResult from reconstructed live trades and fills its LiveTearsheet from the same catalog. And vike-backtest re-exports metrics, overfit, periods, report, result, sizing, validation and zero_trade at its own root so every pre-split path still resolves — while benchmark, excursions, montecarlo and stability are deliberately not re-exported, because, as crates/vike-backtest/src/lib.rs's header records, no path of either spelling ever resolved through that crate.

On this page