jebel-quant/jquantstats/quantstats-migration
Claude Code and agent skill by jebel-quant/jquantstats
- community
Checking that the SKILL.md file is still in the repository.
Translate QuantStats (pandas) code into jquantstats (Polars) — the function-to-object shift, the API mapping, and the semantic differences that silently change numbers. Use whenever writing or reviewing jquantstats code, porting a `qs.stats.*` / `qs.plots.*` / `qs.reports.*` workflow, or converting a pandas tearsheet to Polars.
Install
npx vskill@latest install jebel-quant/jquantstats/quantstats-migrationSKILL.md
QuantStats → jquantstats
jquantstats is not a drop-in replacement for QuantStats. It is a different library with a similar vocabulary. Most metric names carry over; the call shape, the data type, and the return type do not.
If you are about to write jqs.stats.sharpe(returns), stop — that function does not exist. Read the model below first.
The model
| QuantStats | jquantstats | ||
|---|---|---|---|
| Call shape | module-level function | method on an instance | |
| Input | pd.Series with DatetimeIndex | pl.DataFrame with a date column | |
| Return | scalar | dict keyed by column name | |
| Benchmark | passed per call | passed once at construction | |
| Plots | matplotlib | Plotly go.Figure |
There are no module-level analytics functions in jquantstats. Everything hangs off a constructed object.
Step 1 — get the data into Polars
The date column is auto-detected as the first temporal column, whatever it is called, and is exposed as date on the resulting object (data.date_col == ["date"]). Pass date_col= only to nominate a column explicitly — required for a date-free, integer-indexed frame. Every other column is an asset — multi-asset is native, so port a loop over series into a single wide frame and one call.
Nulls matter. pandas drops NaN silently; Polars propagates null, so one missing value turns a whole metric into null. To reproduce QuantStats numbers, pass null_strategy="drop":
Options: None (default, pass through), "drop", "forward_fill", "raise". Note that in Polars null (missing) and NaN (IEEE-754) are distinct; null_strategy acts on null only.
Step 2 — pick an entry point
`Data` — the QuantStats-equivalent route. Use it for any return or price series.
`Portfolio` — no QuantStats equivalent. Use it when the user has prices and positions, or asks for turnover, trading costs, execution delay, or tilt/timing attribution.
vola is an EWMA lookback in periods (int, or dict[str, int] per asset) — not a volatility frame.
Step 3 — accessors
The two entry points are not symmetric. This is the most common mistake:
Data | Portfolio | ||
|---|---|---|---|
| Stats | data.stats | pf.stats | |
| Plots | data.plots (DataPlots) | pf.plots (PortfolioPlots — a different, smaller set) | |
| Reports | data.reports → .metrics(), .full() | pf.report → .to_html() |
Note the singular/plural split: `Data.reports`, `Portfolio.report`.
PortfolioPlots is not a superset of DataPlots. It offers only snapshot, correlation_heatmap, monthly_returns_heatmap, rolling_sharpe_plot, rolling_volatility_plot, annual_sharpe_plot, lead_lag_ir_plot, lagged_performance_plot, smoothed_holdings_performance_plot, trading_cost_impact_plot.
For anything else — drawdown(), histogram(), monthly_heatmap(), … — drop down to the returns view, which is always available:
Step 4 — translate the calls
The default rule covers the large majority of metrics:
Names that do not carry over
jquantstats exposes canonical names only — QuantStats' short aliases are absent and raise AttributeError:
| QuantStats | jquantstats | |
|---|---|---|
var | value_at_risk() | |
cvar, expected_shortfall | conditional_value_at_risk() | |
ror | risk_of_ruin() | |
upi | ulcer_performance_index() | |
r2 | r_squared() | |
ghpr | geometric_mean() | |
win_loss_ratio | payoff_ratio() | |
to_drawdown_series | drawdown() (series) / drawdown_details() (episode table) |
Numbers that differ for the same name
- `conditional_value_at_risk` accepts either spelling of the tail —
confidence=0.95 (the QuantStats convention) or alpha=0.05 — but not both in the same call, which raises ValueError. Omitting both gives alpha=0.05.
- `value_at_risk` takes
alpha(default0.05) only; it has no
confidence parameter, unlike its CVaR sibling on the same object.
- `information_ratio` matches QuantStats by default (raw, non-annualised).
Pass annualise=True for the √periods-scaled version.
- `volatility` annualises by default (
annualize=True). - Periodicity kwargs are not uniform:
sharpe/sortino/volatilitytake
periods, while information_ratio/greeks take periods_per_year. Both default to None → inferred from the data (data.stats.periods_per_year).
Reports
There is no data.reports.summary() and no data.reports.to_html(). The composite stats table is data.stats.summary() → pl.DataFrame.
Step 5 — verify, don't recall
This mapping is a summary of a moving API. Before finalising code, confirm the symbols exist rather than trusting memory:
--show reports signature and docstring from the installed version, and says so plainly when a name is absent. A missing name was either renamed to its canonical form (see the table above) or never ported — say so rather than inventing a wrapper.
Step 6 — record the context once
Ported code that rebuilds its own portfolio in every script drifts. Record the construction once so later analysis shares one object:
See ${CLAUDE_PLUGIN_ROOT}/reference/portfolio-context.md. Subsequent snippets then use load() instead of repeating the constructor, and the digest it prints surfaces the null and periodicity problems that make ported numbers disagree with QuantStats.
Portfolio-only capabilities
Reach for these when the request goes beyond what a return series can express; none have a QuantStats counterpart.
Mind which of these are properties: only lag, turnover_summary, trading_cost_impact, smoothed_holding and truncate take parentheses.
Checklist
- Convert to
pl.DataFramewith a leading date column. - Construct once —
Data.from_returns(...), withnull_strategy="drop"to
mirror pandas.
- Pass the benchmark at construction, not per call.
- Replace
qs.stats.foo(r)withdata.stats.foo()["col"]. - Check the alias table and the differing-semantics list before trusting a
ported number.
- Use
Portfolioonly when positions exist; otherwiseData.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingplugin/skills/quantstats-migration/SKILL.md
Popularity
- Repository stars
- 42
- Repository forks
- 9
- 7-day trend score
- 19
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/jebel-quant/jquantstats/quantstats-migration)