Periods
Calendar-period returns, the year-by-month matrix and the top-N drawdown table — bucketed on the same UTC day law the history store partitions by.
crates/vike-analytics/src/periods.rs turns an equity curve plus its timestamps into the three
calendar views a tearsheet shows: returns per period, a year × month heatmap, and a table of the
worst drawdown episodes. Its header lists exactly those three entry points — periodic_returns,
monthly_return_matrix and drawdown_table — and everything on this page is one of them or the
label function underneath.
All three take the curve and a parallel timestamps slice in epoch milliseconds, UTC. On a
result that is equity_curve and equity_ts, and equity_ts is empty
when a run did not track it — so calendar bucketing is only available for a run that carried its
timestamps. All three panic when the two slices differ in length; each one's # Panics section
states it.
The period label
period_key(ts_ms, period) produces a comparable label for the calendar period containing a
timestamp. period is one of five strings and the doc gives the shape of each:
period | label |
|---|---|
"daily" | 2024-03-15 |
"weekly" | 2024-W11 — ISO week |
"monthly" | 2024-03 |
"quarterly" | 2024-Q1 |
"yearly" | 2024 |
Anything else panics. The doc records that as inherited behaviour — the ported module raised a
ValueError — and adds that it is provenance only: nothing compares this function against the
old implementation any more.
The daily bucket is the store's day
The "daily" arm does not derive year-month-day locally. It delegates to
vike_model::time::epoch_ms_to_utc_date, the workspace's one day-law home, so this bucket key can
never drift from the history store's date= partition math or the
end-of-day day-floor. The output is identical — the same YYYY-MM-DD off the same UTC calendar
day — and a test in the module proves the two ways of computing it agree across pre-1970
timestamps, the epoch boundary, a leap day and the millisecond either side of midnight.
Periodic returns
periodic_returns(equity_curve, timestamps, period) returns (label, return_fraction) pairs
grouped by calendar period, in chronological order. The doc defines the return as
last_equity_in_period / first_equity_at_start_of_period − 1, and the body makes "start of
period" precise: a bucket's entry equity is the last equity point before the bucket opened (the
previous period's closing value), except for the first bucket, which starts from the curve's first
point. A period whose entry equity is zero reports 0.0. Insertion order is kept with an
IndexMap, mirroring the insertion-ordered dictionary of the port so the output order is the
order the periods were first seen.
The monthly matrix
monthly_return_matrix(equity_curve, timestamps) builds the MonthlyReturnMatrix a heatmap
renders from: years (ascending), matrix (year → month → Option<f64>, present only for months
the curve touched) and annual (year → return). The annual figure is compounded from the months
present — the product of 1 + r over them, minus one — not summed.
This is the structure the tearsheet binary's HTML output
renders as its monthly-returns table. crates/vike-report/src/html.rs's header states the
arrangement: the table reuses monthly_return_matrix over the same equity curve and timestamps
the sheet was built from, nothing is re-derived, the table appears only when timestamps align one
to one with the curve, and a non-finite monthly cell renders as the empty marker rather than a
+NaN%.
The drawdown table
drawdown_table(equity_curve, timestamps, top_n) finds the top-N drawdown episodes sorted by
depth, worst first. Each DrawdownEpisode carries:
depth— the fractional drawdown from peak, positive (0.20 = 20%)peak_ts,trough_ts— the timestamps of the peak and the troughrecovery_ts—Noneif the curve never recoveredlength— bars from peak to troughrecovery— bars from trough to recovery,Noneif not recovered
The scan is a single pass. A new peak (equity at or above the running peak) closes any open
episode with that bar as its recovery point; while below the peak the trough is tracked; an
episode still open when the curve ends is closed with no recovery and included. Episodes of zero
depth are dropped, so a monotonically rising curve yields an empty table, and the result is
truncated to top_n after sorting. The single worst episode's depth is the same number
metrics::max_drawdown reports on the metrics page; this table adds
the where and the how long.
Where it fits
A BacktestReport does not carry any of these — it is deliberately compact, and the
report page says so. Calendar views are composed by the caller that
has timestamps in hand: the HTML tearsheet today. A backtest that wants them calls these
functions over equity_curve and equity_ts directly.
Metrics
Every performance statistic Vike Trader computes, one row per metric with its inputs, its definition and its unit, plus the sentinel, summation, percentile and platform conventions the whole catalog shares.
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.