Research (factor / event)
Factor diagnostics and event studies are research workflows, not portfolio
construction. They take panels and return JSON summaries; they are deliberately
outside the lemon AST — no positions, no NAV, no change to backtest
semantics. They live in yuzu_core::research as pure functions, with thin
yuzu-cli entry points.
Factor report
Rank IC (information coefficient), ICIR, quantile-portfolio returns, and the long-short spread of a factor against forward returns.
use yuzu_core::research::{factor_report, forward_returns};let fwd = forward_returns(&close, 21); // 21-day forward simple returnlet rep = factor_report(&factor, &fwd, 5); // 5 quantile buckets// rep.mean_ic, rep.icir, rep.quantile_returns (low→high), rep.long_short,// rep.top_quantile_turnover, rep.ic (per-period series), rep.dates- IC is the per-date Spearman rank correlation between the factor and
forward returns, over the symbols where both are finite (≥2 needed).
mean_ic/ic_std/icir = mean_ic / ic_stdsummarize it (ICIR is not annualized — multiply by √periods-per-year if you want that). - Quantiles bucket symbols by factor rank each date (bucket 0 = lowest);
quantile_returnsis each bucket’s equal-weighted mean forward return averaged over periods, andlong_shortis top − bottom. - Industry-neutral factors: neutralize first —
factor.neutralize_industry(&industry, true)— then pass the residual in.
CLI:
yuzu-cli factor --data ./mydata --spec factor.json \ --from 20180101 --to 20251231 --horizon 21 --quantiles 5 [--neutralize-industry]spec is any lemon/JSON Expr evaluated to the factor panel; forward returns
come from the close panel over --horizon days.
Event study
Average (and cumulative) return path around a 0/1 event panel over a
[-pre, +post] window.
use yuzu_core::research::{event_study, daily_returns};let rets = daily_returns(&close); // backward daily returnslet es = event_study(&events, &rets, 5, 5); // 5 rows pre / post// es.lags (−pre..=post), es.avg_return, es.cumulative, es.event_countFor each cell where events == 1, the same symbol’s returns from pre rows
before to post rows after are averaged across all events by lag. Returns are
raw — for abnormal returns, subtract a benchmark return panel from rets
before the call (a market model is out of scope for v1). Events near a panel edge
simply contribute to fewer lags.
CLI:
yuzu-cli event --data ./mydata --spec event.json \ --from 20180101 --to 20251231 --pre 5 --post 5report_event (the fundamentals 0/1 filing-day series in
data-layout.md) is a natural event input.
In the browser (WASM)
Both workflows are exposed at the yuzu-wasm JSON boundary, mirroring
run_backtest: string in, string out, no market data crosses except the panels
you supply.
run_factor(JSON.stringify({ spec, panels, industry, // panels: { name: { dates, symbols, data } } horizon: 21, quantiles: 5, // defaults: horizon 1, quantiles 5 neutralize_industry: false, // demean the factor within sector first})); // -> FactorReport JSON
run_event(JSON.stringify({ spec, panels, industry, pre: 5, post: 5, // defaults: 5 / 5})); // -> EventStudy JSONThe factor / event panel comes from spec (any lemon/JSON Expr); forward
returns (run_factor) and daily returns (run_event) both come from the close
panel — a missing close is an error. The pure run_factor_json /
run_event_json functions are unit-tested natively.
Non-goals (v1)
Model training / ML pipelines, charts, significance testing, and multi-factor attribution. The outputs are deterministic plain numbers a UI or notebook can plot; the functions are golden-simple to keep them trustworthy.