Skip to content

API Reference

jquantstats

jQuantStats: Portfolio analytics for quants.

Two entry points

Entry point 1 — prices + positions (recommended for active portfolios):

Use :class:~jquantstats.portfolio.Portfolio when you have price series and position sizes. Portfolio compiles the NAV curve from raw inputs and exposes the full analytics suite via .stats, .plots, and .report.

from jquantstats import Portfolio
import polars as pl

pf = Portfolio.from_cash_position(
    prices=prices_df,
    cash_position=positions_df,
    aum=1_000_000,
)
pf.stats.sharpe()
pf.plots.snapshot()

Entry point 2 — returns series (for arbitrary return streams):

Use :class:~jquantstats.data.Data when you already have a returns series (e.g. downloaded from a data vendor) and want benchmark comparison or factor analytics.

from jquantstats import Data
import polars as pl

data = Data.from_returns(returns=returns_df, benchmark=bench_df)
data.stats.sharpe()
data.plots.plot_snapshot(title="Performance")

The two APIs are layered: portfolio.data returns a :class:~jquantstats.data.Data object so you can always drop into the returns-series API from a Portfolio.

For more information, visit the jQuantStats Documentation <https://tschm.github.io/jquantstats/book>_.

CostModel dataclass

Unified representation of a portfolio transaction-cost model.

Eliminates the implicit "pick one" contract between the two independent cost parameters (cost_per_unit and cost_bps) on :class:~jquantstats.portfolio.Portfolio. A CostModel instance encapsulates one model at a time and can be passed to any Portfolio factory method instead of specifying the raw float parameters.

Attributes:

Name Type Description
cost_per_unit float

One-way cost per unit of position change (Model A). Defaults to 0.0.

cost_bps float

One-way cost in basis points of AUM turnover (Model B). Defaults to 0.0.

Raises:

Type Description
ValueError

If cost_per_unit or cost_bps is negative, or if both are non-zero (which would silently double-count costs).

Examples:

>>> CostModel.per_unit(0.01)
CostModel(cost_per_unit=0.01, cost_bps=0.0)
>>> CostModel.turnover_bps(5.0)
CostModel(cost_per_unit=0.0, cost_bps=5.0)
>>> CostModel.zero()
CostModel(cost_per_unit=0.0, cost_bps=0.0)

per_unit(cost: float) -> CostModel classmethod

Create a Model A (position-delta) cost model.

Parameters:

Name Type Description Default
cost float

One-way cost per unit of position change. Must be non-negative.

required

Returns:

Name Type Description
A CostModel

class:CostModel with cost_per_unit=cost and

CostModel

cost_bps=0.0.

Examples:

>>> CostModel.per_unit(0.01)
CostModel(cost_per_unit=0.01, cost_bps=0.0)

turnover_bps(bps: float) -> CostModel classmethod

Create a Model B (turnover-bps) cost model.

Parameters:

Name Type Description Default
bps float

One-way cost in basis points of AUM turnover. Must be non-negative.

required

Returns:

Name Type Description
A CostModel

class:CostModel with cost_per_unit=0.0 and

CostModel

cost_bps=bps.

Examples:

>>> CostModel.turnover_bps(5.0)
CostModel(cost_per_unit=0.0, cost_bps=5.0)

zero() -> CostModel classmethod

Create a zero-cost model (no transaction costs).

Returns:

Name Type Description
A CostModel

class:CostModel with both parameters set to 0.0.

Examples:

>>> CostModel.zero()
CostModel(cost_per_unit=0.0, cost_bps=0.0)

Data dataclass

A container for financial returns data and an optional benchmark.

This class provides methods for analyzing and manipulating financial returns data, including converting returns to prices, calculating drawdowns, and resampling data to different time periods. It also provides access to statistical metrics through the stats property and visualization through the plots property.

Attributes:

Name Type Description
returns DataFrame

DataFrame containing returns data with assets as columns.

benchmark DataFrame

DataFrame containing benchmark returns data. Defaults to None.

index DataFrame

DataFrame containing the date index for the returns data.

plots: DataPlots property

Provides access to visualization methods for the financial data.

Returns:

Name Type Description
DataPlots DataPlots

An instance of the DataPlots class initialized with this data.

stats: Stats property

Provides access to statistical analysis methods for the financial data.

Returns:

Name Type Description
Stats Stats

An instance of the Stats class initialized with this data.

reports: Reports property

Provides access to reporting methods for the financial data.

Returns:

Name Type Description
Reports Reports

An instance of the Reports class initialized with this data.

date_col: list[str] property

Return the column names of the index DataFrame.

Returns:

Type Description
list[str]

list[str]: List of column names in the index DataFrame, typically containing the date column name.

assets: list[str] property

Return the combined list of asset column names from returns and benchmark.

Returns:

Type Description
list[str]

list[str]: List of all asset column names from both returns and benchmark (if available).

all: pl.DataFrame property

Combine index, returns, and benchmark data into a single DataFrame.

This property provides a convenient way to access all data in a single DataFrame, which is useful for analysis and visualization.

Returns:

Type Description
DataFrame

pl.DataFrame: A DataFrame containing the index, all returns data, and benchmark data (if available) combined horizontally.

__post_init__() -> None

Validate the Data object after initialization.

from_returns(returns: NativeFrame, rf: NativeFrameOrScalar = 0.0, benchmark: NativeFrame | None = None, date_col: str = 'Date') -> Data classmethod

Create a Data object from returns and optional benchmark.

Parameters

returns : NativeFrame Financial returns data. First column should be the date column, remaining columns are asset returns.

float | NativeFrame, optional

Risk-free rate. Default is 0.0 (no risk-free rate adjustment).

  • If float: Constant risk-free rate applied to all dates.
  • If NativeFrame: Time-varying risk-free rate with dates matching returns.
NativeFrame | None, optional

Benchmark returns. Default is None (no benchmark). First column should be the date column, remaining columns are benchmark returns.

str, optional

Name of the date column in the DataFrames. Default is "Date".

Returns:

Data Object containing excess returns and benchmark (if any), with methods for analysis and visualization through the stats and plots properties.

Raises:

ValueError If there are no overlapping dates between returns and benchmark.

Examples:

Basic usage:

from jquantstats import Data
import polars as pl

returns = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Asset1": [0.01, -0.02, 0.03]
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_returns(returns=returns)

With benchmark and risk-free rate:

benchmark = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Market": [0.005, -0.01, 0.02]
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_returns(returns=returns, benchmark=benchmark, rf=0.0002)

from_prices(prices: NativeFrame, rf: NativeFrameOrScalar = 0.0, benchmark: NativeFrame | None = None, date_col: str = 'Date') -> Data classmethod

Create a Data object from prices and optional benchmark.

Converts price levels to returns via percentage change and delegates to :meth:from_returns. The first row of each asset is dropped because no prior price is available to compute a return.

Parameters

prices : NativeFrame Price-level data. First column should be the date column; remaining columns are asset prices.

float | NativeFrame, optional

Risk-free rate. Forwarded unchanged to :meth:from_returns. Default is 0.0 (no risk-free rate adjustment).

NativeFrame | None, optional

Benchmark prices. Converted to returns in the same way as prices before being forwarded to :meth:from_returns. Default is None (no benchmark).

str, optional

Name of the date column in the DataFrames. Default is "Date".

Returns:

Data Object containing excess returns derived from the supplied prices, with methods for analysis and visualization through the stats and plots properties.

Examples:
from jquantstats import Data
import polars as pl

prices = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Asset1": [100.0, 101.0, 99.0]
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_prices(prices=prices)

__repr__() -> str

Return a string representation of the Data object.

resample(every: str = '1mo') -> Data

Resamples returns and benchmark to a different frequency using Polars.

Parameters:

Name Type Description Default
every str

Resampling frequency (e.g., '1mo', '1y'). Defaults to '1mo'.

'1mo'

Returns:

Name Type Description
Data Data

Resampled data.

describe() -> pl.DataFrame

Return a tidy summary of shape, date range and asset names.

Returns:

pl.DataFrame One row per asset with columns: asset, start, end, rows, has_benchmark.

copy() -> Data

Create a deep copy of the Data object.

Returns:

Name Type Description
Data Data

A new Data object with copies of the returns and benchmark.

head(n: int = 5) -> Data

Return the first n rows of the combined returns and benchmark data.

Parameters:

Name Type Description Default
n int

Number of rows to return. Defaults to 5.

5

Returns:

Name Type Description
Data Data

A new Data object containing the first n rows of the combined data.

tail(n: int = 5) -> Data

Return the last n rows of the combined returns and benchmark data.

Parameters:

Name Type Description Default
n int

Number of rows to return. Defaults to 5.

5

Returns:

Name Type Description
Data Data

A new Data object containing the last n rows of the combined data.

truncate(start: object = None, end: object = None) -> Data

Return a new Data object truncated to the inclusive [start, end] range.

When the index is temporal (Date/Datetime), truncation is performed by comparing the date column against start and end values.

When the index is integer-based, row slicing is used instead, and start and end must be non-negative integers. Passing non-integer bounds to an integer-indexed Data raises :exc:TypeError.

Parameters:

Name Type Description Default
start object

Optional lower bound (inclusive). A date/datetime value when the index is temporal; a non-negative :class:int row index when the data has no temporal index.

None
end object

Optional upper bound (inclusive). Same type rules as start.

None

Returns:

Name Type Description
Data Data

A new Data object filtered to the specified range.

Raises:

Type Description
TypeError

When the index is not temporal and a non-integer bound is supplied.

items() -> Iterator[tuple[str, pl.Series]]

Iterate over all assets and their corresponding data series.

This method provides a convenient way to iterate over all assets in the data, yielding each asset name and its corresponding data series.

Yields:

Type Description
tuple[str, Series]

tuple[str, pl.Series]: A tuple containing the asset name and its data series.

Portfolio dataclass

Portfolio analytics class for quant finance.

Stores the three raw inputs — cash positions, prices, and AUM — and exposes the standard derived data series, analytics facades, transforms, and attribution tools.

Derived data series:

  • :attr:profits — per-asset daily cash P&L
  • :attr:profit — aggregate daily portfolio profit
  • :attr:nav_accumulated — cumulative additive NAV
  • :attr:nav_compounded — compounded NAV
  • :attr:returns — daily returns (profit / AUM)
  • :attr:monthly — monthly compounded returns
  • :attr:highwater — running high-water mark
  • :attr:drawdown — drawdown from high-water mark
  • :attr:all — merged view of all derived series

  • Lazy composition accessors: :attr:stats, :attr:plots, :attr:report

  • Portfolio transforms: :meth:truncate, :meth:lag, :meth:smoothed_holding
  • Attribution: :attr:tilt, :attr:timing, :attr:tilt_timing_decomp
  • Turnover: :attr:turnover, :attr:turnover_weekly, :meth:turnover_summary
  • Cost analysis: :meth:cost_adjusted_returns, :meth:trading_cost_impact
  • Utility: :meth:correlation

Attributes:

Name Type Description
cashposition DataFrame

Polars DataFrame of positions per asset over time (includes date column if present).

prices DataFrame

Polars DataFrame of prices per asset over time (includes date column if present).

aum float

Assets under management used as base NAV offset.

Analytics facades
  • .stats : delegates to the legacy Stats pipeline via .data; all 50+ metrics available.
  • .plots : portfolio-specific Plots; NAV overlays, lead-lag IR, rolling Sharpe/vol, heatmaps.
  • .report : HTML Report; self-contained portfolio performance report.
  • .data : bridge to the legacy Data / Stats / DataPlots pipeline.

.plots and .report are intentionally not delegated to the legacy path: the legacy path operates on a bare returns series, while the analytics path has access to raw prices, positions, and AUM for richer portfolio-specific visualisations.

Cost models

Two independent cost models are provided. They are not interchangeable:

Model A — position-delta (stateful, set at construction): cost_per_unit: float — one-way cost per unit of position change (e.g. 0.01 per share). Used by .position_delta_costs and .net_cost_nav. Best for: equity portfolios where cost scales with shares traded.

Model B — turnover-bps (stateless, passed at call time): cost_bps: float — one-way cost in basis points of AUM turnover (e.g. 5 bps). Used by .cost_adjusted_returns(cost_bps) and .trading_cost_impact(max_bps). Best for: macro / fund-of-funds portfolios where cost scales with notional traded.

To sweep a range of cost assumptions use trading_cost_impact(max_bps=20) (Model B). To compute a net-NAV curve set cost_per_unit at construction and read .net_cost_nav (Model A).

Date column requirement

Most analytics work with or without a date column. The following features require a temporal date column (pl.Date or pl.Datetime):

  • portfolio.plots.correlation_heatmap()
  • portfolio.plots.lead_lag_ir_plot()
  • stats.monthly_win_rate() — returns NaN per column when no date is present
  • stats.annual_breakdown() — raises ValueError when no date is present
  • stats.max_drawdown_duration() — returns period count (int) instead of days

Portfolios without a date column (integer-indexed) are fully supported for NAV, returns, Sharpe, drawdown, cost analytics, and most rolling metrics.

Examples:

>>> import polars as pl
>>> from datetime import date
>>> prices = pl.DataFrame({"date": [date(2020, 1, 1), date(2020, 1, 2)], "A": [100.0, 110.0]})
>>> pos = pl.DataFrame({"date": [date(2020, 1, 1), date(2020, 1, 2)], "A": [1000.0, 1000.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e6)
>>> pf.assets
['A']

cost_model: CostModel property

Return the active cost model as a :class:~jquantstats.CostModel instance.

Returns:

Name Type Description
A CostModel

class:CostModel whose cost_per_unit and cost_bps fields

CostModel

reflect the values stored on this portfolio.

assets: list[str] property

List the asset column names from prices (numeric columns).

Returns:

Type Description
list[str]

list[str]: Names of numeric columns in prices; typically excludes

list[str]

'date'.

profits: pl.DataFrame property

Compute per-asset daily cash profits, preserving non-numeric columns.

Returns:

Type Description
DataFrame

pl.DataFrame: Per-asset daily profit series along with any

DataFrame

non-numeric columns (e.g., 'date').

Examples:

>>> import polars as pl
>>> prices = pl.DataFrame({"A": [100.0, 110.0, 105.0]})
>>> pos = pl.DataFrame({"A": [1000.0, 1000.0, 1000.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e6)
>>> pf.profits.columns
['A']

profit: pl.DataFrame property

Return total daily portfolio profit including the 'date' column.

Aggregates per-asset profits into a single 'profit' column and validates that no day's total profit is NaN/null.

nav_accumulated: pl.DataFrame property

Compute cumulative additive NAV of the portfolio, preserving 'date'.

returns: pl.DataFrame property

Return daily returns as profit scaled by AUM, preserving 'date'.

The returned DataFrame contains the original 'date' column with the 'profit' column scaled by AUM (i.e., per-period returns), and also an additional convenience column named 'returns' with the same values for downstream consumers.

monthly: pl.DataFrame property

Return monthly compounded returns and calendar columns.

Aggregates daily returns (profit/AUM) by calendar month and computes the compounded monthly return: prod(1 + r_d) - 1. The resulting frame includes:

  • date: month-end label as a Polars Date (end of the grouping window)
  • returns: compounded monthly return
  • NAV_accumulated: last NAV within the month
  • profit: summed profit within the month
  • year: integer year (e.g., 2020)
  • month: integer month number (1-12)
  • month_name: abbreviated month name (e.g., "Jan", "Feb")

Raises:

Type Description
MissingDateColumnError

If the portfolio data has no 'date' column.

nav_compounded: pl.DataFrame property

Compute compounded NAV from returns (profit/AUM), preserving 'date'.

highwater: pl.DataFrame property

Return the cumulative maximum of NAV as the high-water mark series.

The resulting DataFrame preserves the 'date' column and adds a 'highwater' column computed as the cumulative maximum of 'NAV_accumulated'.

drawdown: pl.DataFrame property

Return drawdown as the distance from high-water mark to current NAV.

Computes 'drawdown' = 'highwater' - 'NAV_accumulated' and preserves the 'date' column alongside the intermediate columns.

all: pl.DataFrame property

Return a merged view of drawdown and compounded NAV.

When a 'date' column is present the two frames are joined on that column to ensure temporal alignment. When the data is integer-indexed (no 'date' column) the frames are stacked horizontally — they are guaranteed to have identical row counts because both are derived from the same source portfolio.

data: Data property

Build a legacy :class:~jquantstats._data.Data object from this portfolio's returns.

This bridges the two entry points: Portfolio compiles the NAV curve from prices and positions; the returned :class:~jquantstats._data.Data object gives access to the full legacy analytics pipeline (data.stats, data.plots, data.reports).

Returns:

Type Description
Data

class:~jquantstats._data.Data: A Data object whose returns column

Data

is the portfolio's daily return series and whose index holds the date

Data

column (or a synthetic integer index for date-free portfolios).

Examples:

>>> import polars as pl
>>> from datetime import date
>>> prices = pl.DataFrame({"date": [date(2020, 1, 1), date(2020, 1, 2)], "A": [100.0, 110.0]})
>>> pos = pl.DataFrame({"date": [date(2020, 1, 1), date(2020, 1, 2)], "A": [1000.0, 1000.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e6)
>>> d = pf.data
>>> "returns" in d.returns.columns
True

stats: Stats property

Return a Stats object built from the portfolio's daily returns.

Delegates to the legacy :class:~jquantstats._stats.Stats pipeline via :attr:data, so all analytics (Sharpe, drawdown, summary, etc.) are available through the shared implementation.

The result is cached after first access so repeated calls are O(1).

plots: PortfolioPlots property

Convenience accessor returning a PortfolioPlots facade for this portfolio.

Use this to create Plotly visualizations such as snapshots, lagged performance curves, and lead/lag IR charts.

Returns:

Type Description
PortfolioPlots

class:~jquantstats._plots.PortfolioPlots: Helper object with

PortfolioPlots

plotting methods.

The result is cached after first access so repeated calls are O(1).

report: Report property

Convenience accessor returning a Report facade for this portfolio.

Use this to generate a self-contained HTML performance report containing statistics tables and interactive charts.

Returns:

Type Description
Report

class:~jquantstats._reports.Report: Helper object with

Report

report methods.

The result is cached after first access so repeated calls are O(1).

tilt: Portfolio property

Return the 'tilt' portfolio with constant average weights.

Computes the time-average of each asset's cash position (ignoring nulls/NaNs) and builds a new Portfolio with those constant weights applied across time. Prices and AUM are preserved.

timing: Portfolio property

Return the 'timing' portfolio capturing deviations from the tilt.

Constructs weights as original cash positions minus the tilt's constant positions, per asset. This isolates timing (alloc-demeaned) effects. Prices and AUM are preserved.

tilt_timing_decomp: pl.DataFrame property

Return the portfolio's tilt/timing NAV decomposition.

When a 'date' column is present the three NAV series are joined on it. When data is integer-indexed the frames are stacked horizontally.

turnover: pl.DataFrame property

Daily one-way portfolio turnover as a fraction of AUM.

Computes the sum of absolute position changes across all assets for each period, normalised by AUM. The first row is always zero because there is no prior position to form a difference against.

Returns:

Type Description
DataFrame

pl.DataFrame: Frame with an optional 'date' column and a

DataFrame

'turnover' column (dimensionless fraction of AUM).

Examples:

>>> import polars as pl
>>> from datetime import date
>>> _d = [date(2020, 1, 1), date(2020, 1, 2), date(2020, 1, 3)]
>>> prices = pl.DataFrame({"date": _d, "A": [100.0, 110.0, 121.0]})
>>> pos = pl.DataFrame({"date": prices["date"], "A": [1000.0, 1200.0, 900.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e5)
>>> pf.turnover["turnover"].to_list()
[0.0, 0.002, 0.003]

turnover_weekly: pl.DataFrame property

Weekly aggregated one-way portfolio turnover as a fraction of AUM.

When a 'date' column is present, sums the daily turnover within each calendar week (Monday-based group_by_dynamic). Without a date column, a rolling 5-period sum with min_samples=5 is returned (the first four rows will be null).

Returns:

Type Description
DataFrame

pl.DataFrame: Frame with an optional 'date' column (week

DataFrame

start) and a 'turnover' column (fraction of AUM, summed over

DataFrame

the week).

position_delta_costs: pl.DataFrame property

Daily trading cost using the position-delta model.

Computes the per-period cost as::

cost_t = sum_i( |x_{i,t} - x_{i,t-1}| ) * cost_per_unit

where x_{i,t} is the cash position in asset i at time t and cost_per_unit is the one-way cost per unit of traded notional. The first row is always zero because there is no prior position to form a difference against.

Returns:

Type Description
DataFrame

pl.DataFrame: Frame with an optional 'date' column and a

DataFrame

'cost' column (absolute cash cost per period).

Examples:

>>> import polars as pl
>>> from datetime import date
>>> _d = [date(2020, 1, 1), date(2020, 1, 2), date(2020, 1, 3)]
>>> prices = pl.DataFrame({"date": _d, "A": [100.0, 110.0, 121.0]})
>>> pos = pl.DataFrame({"date": _d, "A": [1000.0, 1200.0, 900.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e5, cost_per_unit=0.01)
>>> pf.position_delta_costs["cost"].to_list()
[0.0, 2.0, 3.0]

net_cost_nav: pl.DataFrame property

Net-of-cost cumulative additive NAV using the position-delta cost model.

Deducts :attr:position_delta_costs from daily portfolio profit and computes the running cumulative sum offset by AUM. The result represents the realised NAV path a strategy would achieve after paying cost_per_unit on every unit of position change.

When cost_per_unit is zero the result equals :attr:nav_accumulated.

Returns:

Type Description
DataFrame

pl.DataFrame: Frame with an optional 'date' column,

DataFrame

'profit', 'cost', and 'NAV_accumulated_net' columns.

Examples:

>>> import polars as pl
>>> from datetime import date
>>> _d = [date(2020, 1, 1), date(2020, 1, 2), date(2020, 1, 3)]
>>> prices = pl.DataFrame({"date": _d, "A": [100.0, 110.0, 121.0]})
>>> pos = pl.DataFrame({"date": _d, "A": [1000.0, 1200.0, 900.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e5, cost_per_unit=0.0)
>>> net = pf.net_cost_nav
>>> list(net.columns)
['date', 'profit', 'cost', 'NAV_accumulated_net']

__post_init__() -> None

Validate input types, shapes, and parameters post-initialization.

__repr__() -> str

Return a string representation of the Portfolio object.

describe() -> pl.DataFrame

Return a tidy summary of shape, date range and asset names.

Returns:

pl.DataFrame One row per asset with columns: asset, start, end, rows.

Examples:

>>> import polars as pl
>>> from datetime import date
>>> prices = pl.DataFrame({"date": [date(2020, 1, 1), date(2020, 1, 2)], "A": [100.0, 110.0]})
>>> pos = pl.DataFrame({"date": [date(2020, 1, 1), date(2020, 1, 2)], "A": [1000.0, 1000.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e6)
>>> df = pf.describe()
>>> list(df.columns)
['asset', 'start', 'end', 'rows']

from_risk_position(prices: pl.DataFrame, risk_position: pl.DataFrame, aum: float, vola: int | dict[str, int] = 32, vol_cap: float | None = None, cost_per_unit: float = 0.0, cost_bps: float = 0.0, cost_model: CostModel | None = None) -> Self classmethod

Create a Portfolio from per-asset risk positions.

De-volatizes each risk position using an EWMA volatility estimate derived from the corresponding price series.

Parameters:

Name Type Description Default
prices DataFrame

Price levels per asset over time (may include a date column).

required
risk_position DataFrame

Risk units per asset aligned with prices.

required
vola int | dict[str, int]

EWMA lookback (span-equivalent) used to estimate volatility. Pass an int to apply the same span to every asset, or a dict[str, int] to set a per-asset span (assets absent from the dict default to 32). Every span value must be a positive integer; a ValueError is raised otherwise. Dict keys that do not correspond to any numeric column in prices also raise a ValueError.

32
vol_cap float | None

Optional lower bound for the EWMA volatility estimate. When provided, the vol series is clipped from below at this value before dividing the risk position, preventing position blow-up in calm, low-volatility regimes. For example, vol_cap=0.05 ensures annualised vol is never estimated below 5%. Must be positive when not None.

None
aum float

Assets under management used as the base NAV offset.

required
cost_per_unit float

One-way trading cost per unit of position change. Defaults to 0.0 (no cost). Ignored when cost_model is given.

0.0
cost_bps float

One-way trading cost in basis points of AUM turnover. Defaults to 0.0 (no cost). Ignored when cost_model is given.

0.0
cost_model CostModel | None

Optional :class:~jquantstats.CostModel instance. When supplied, its cost_per_unit and cost_bps values take precedence over the individual parameters above.

None

Returns:

Type Description
Self

A Portfolio instance whose cash positions are risk_position

Self

divided by EWMA volatility.

Raises:

Type Description
ValueError

If any span value in vola is ≤ 0, or if a key in a vola dict does not match any numeric column in prices, or if vol_cap is provided but is not positive.

from_position(prices: pl.DataFrame, position: pl.DataFrame, aum: float, cost_per_unit: float = 0.0, cost_bps: float = 0.0, cost_model: CostModel | None = None) -> Self classmethod

Create a Portfolio from share/unit positions.

Converts position (number of units held per asset) to cash exposure by multiplying element-wise with prices, then delegates to :py:meth:from_cash_position.

Parameters:

Name Type Description Default
prices DataFrame

Price levels per asset over time (may include a date column).

required
position DataFrame

Number of units held per asset over time, aligned with prices. Non-numeric columns (e.g. 'date') are passed through unchanged.

required
aum float

Assets under management used as the base NAV offset.

required
cost_per_unit float

One-way trading cost per unit of position change. Defaults to 0.0 (no cost). Ignored when cost_model is given.

0.0
cost_bps float

One-way trading cost in basis points of AUM turnover. Defaults to 0.0 (no cost). Ignored when cost_model is given.

0.0
cost_model CostModel | None

Optional :class:~jquantstats.CostModel instance. When supplied, its cost_per_unit and cost_bps values take precedence over the individual parameters above.

None

Returns:

Type Description
Self

A Portfolio instance whose cash positions equal position x prices.

Examples:

>>> import polars as pl
>>> prices = pl.DataFrame({"A": [100.0, 110.0, 105.0]})
>>> pos = pl.DataFrame({"A": [10.0, 10.0, 10.0]})
>>> pf = Portfolio.from_position(prices=prices, position=pos, aum=1e6)
>>> pf.cashposition["A"].to_list()
[1000.0, 1100.0, 1050.0]

from_cash_position(prices: pl.DataFrame, cash_position: pl.DataFrame, aum: float, cost_per_unit: float = 0.0, cost_bps: float = 0.0, cost_model: CostModel | None = None) -> Self classmethod

Create a Portfolio directly from cash positions aligned with prices.

Parameters:

Name Type Description Default
prices DataFrame

Price levels per asset over time (may include a date column).

required
cash_position DataFrame

Cash exposure per asset over time.

required
aum float

Assets under management used as the base NAV offset.

required
cost_per_unit float

One-way trading cost per unit of position change. Defaults to 0.0 (no cost). Ignored when cost_model is given.

0.0
cost_bps float

One-way trading cost in basis points of AUM turnover. Defaults to 0.0 (no cost). Ignored when cost_model is given.

0.0
cost_model CostModel | None

Optional :class:~jquantstats.CostModel instance. When supplied, its cost_per_unit and cost_bps values take precedence over the individual parameters above.

None

Returns:

Type Description
Self

A Portfolio instance with the provided cash positions.

truncate(start: object = None, end: object = None) -> Portfolio

Return a new Portfolio truncated to the inclusive [start, end] range.

When a 'date' column is present in both prices and cash positions, truncation is performed by comparing the 'date' column against start and end (which should be date/datetime values or strings parseable by Polars).

When the 'date' column is absent, integer-based row slicing is used instead. In this case start and end must be non-negative integers representing 0-based row indices. Passing non-integer bounds to an integer-indexed portfolio raises :exc:TypeError.

In all cases the aum value is preserved.

Parameters:

Name Type Description Default
start object

Optional lower bound (inclusive). A date/datetime or Polars-parseable string when a 'date' column exists; a non-negative int row index when the data has no 'date' column.

None
end object

Optional upper bound (inclusive). Same type rules as start.

None

Returns:

Type Description
Portfolio

A new Portfolio instance with prices and cash positions filtered

Portfolio

to the specified range.

Raises:

Type Description
TypeError

When the portfolio has no 'date' column and a non-integer bound is supplied.

lag(n: int) -> Portfolio

Return a new Portfolio with cash positions lagged by n steps.

This method shifts the numeric asset columns in the cashposition DataFrame by n rows, preserving the 'date' column and any non-numeric columns unchanged. Positive n delays weights (moves them down); negative n leads them (moves them up); n == 0 returns the current portfolio unchanged.

Notes

Missing values introduced by the shift are left as nulls; downstream profit computation already guards and treats nulls as zero when multiplying by returns.

Parameters:

Name Type Description Default
n int

Number of rows to shift (can be negative, zero, or positive).

required

Returns:

Type Description
Portfolio

A new Portfolio instance with lagged cash positions and the same

Portfolio

prices/AUM as the original.

smoothed_holding(n: int) -> Portfolio

Return a new Portfolio with cash positions smoothed by a rolling mean.

Applies a trailing window average over the last n steps for each numeric asset column (excluding 'date'). The window length is n + 1 so that:

  • n=0 returns the original weights (no smoothing),
  • n=1 averages the current and previous weights,
  • n=k averages the current and last k weights.

Parameters:

Name Type Description Default
n int

Non-negative integer specifying how many previous steps to include.

required

Returns:

Type Description
Portfolio

A new Portfolio with smoothed cash positions and the same

Portfolio

prices/AUM.

turnover_summary() -> pl.DataFrame

Return a summary DataFrame of turnover statistics.

Computes three metrics from the daily turnover series:

  • mean_daily_turnover: mean of daily one-way turnover (fraction of AUM).
  • mean_weekly_turnover: mean of weekly-aggregated turnover (fraction of AUM).
  • turnover_std: standard deviation of daily turnover (fraction of AUM); complements the mean to detect regime switches.

Returns:

Type Description
DataFrame

pl.DataFrame: One row per metric with columns 'metric' and

DataFrame

'value'.

Examples:

>>> import polars as pl
>>> from datetime import date, timedelta
>>> import numpy as np
>>> start = date(2020, 1, 1)
>>> dates = pl.date_range(start=start, end=start + timedelta(days=9), interval="1d", eager=True)
>>> prices = pl.DataFrame({"date": dates, "A": pl.Series(np.ones(10) * 100.0)})
>>> pos = pl.DataFrame({"date": dates, "A": pl.Series([float(i) * 100 for i in range(10)])})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e4)
>>> summary = pf.turnover_summary()
>>> list(summary["metric"])
['mean_daily_turnover', 'mean_weekly_turnover', 'turnover_std']

cost_adjusted_returns(cost_bps: float | None = None) -> pl.DataFrame

Return daily portfolio returns net of estimated one-way trading costs.

Trading costs are modelled as a linear function of daily one-way turnover: for every unit of AUM traded, the strategy incurs cost_bps basis points (i.e. cost_bps / 10_000 fractional cost). The daily cost deduction is therefore::

daily_cost = turnover * (cost_bps / 10_000)

where turnover is the fraction-of-AUM one-way turnover already computed by :attr:turnover. The deduction is applied to the returns column of :attr:returns, leaving all other columns (including date) untouched.

Parameters:

Name Type Description Default
cost_bps float | None

One-way trading cost in basis points per unit of AUM traded. Must be non-negative. Defaults to self.cost_bps set at construction time.

None

Returns:

Type Description
DataFrame

pl.DataFrame: Same schema as :attr:returns but with the

DataFrame

returns column reduced by the per-period trading cost.

Raises:

Type Description
ValueError

If cost_bps is negative.

Examples:

>>> import polars as pl
>>> from datetime import date
>>> _d = [date(2020, 1, 1), date(2020, 1, 2), date(2020, 1, 3)]
>>> prices = pl.DataFrame({"date": _d, "A": [100.0, 110.0, 121.0]})
>>> pos = pl.DataFrame({"date": _d, "A": [1000.0, 1200.0, 900.0]})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e5)
>>> adj = pf.cost_adjusted_returns(0.0)
>>> float(adj["returns"][1]) == float(pf.returns["returns"][1])
True

trading_cost_impact(max_bps: int = 20) -> pl.DataFrame

Estimate the impact of trading costs on the Sharpe ratio.

Computes the annualised Sharpe ratio of cost-adjusted returns for each integer cost level from 0 up to and including max_bps basis points (1 bp = 0.01 %). The result lets you quickly assess at what cost level the strategy's edge is eroded.

Parameters:

Name Type Description Default
max_bps int

Maximum one-way trading cost to evaluate, in basis points. Defaults to 20 (i.e., evaluates 0, 1, 2, …, 20 bps). Must be a positive integer.

20

Returns:

Type Description
DataFrame

pl.DataFrame: Frame with columns 'cost_bps' (Int64) and

DataFrame

'sharpe' (Float64), one row per cost level from 0 to

DataFrame

max_bps inclusive.

Raises:

Type Description
ValueError

If max_bps is not a positive integer.

Examples:

>>> import polars as pl
>>> from datetime import date, timedelta
>>> import numpy as np
>>> start = date(2020, 1, 1)
>>> dates = pl.date_range(
...     start=start, end=start + timedelta(days=99), interval="1d", eager=True
... )
>>> rng = np.random.default_rng(0)
>>> prices = pl.DataFrame({
...     "date": dates,
...     "A": pl.Series(np.cumprod(1 + rng.normal(0.001, 0.01, 100)) * 100),
... })
>>> pos = pl.DataFrame({"date": dates, "A": pl.Series(np.ones(100) * 1000.0)})
>>> pf = Portfolio(prices=prices, cashposition=pos, aum=1e5)
>>> impact = pf.trading_cost_impact(max_bps=5)
>>> list(impact["cost_bps"])
[0, 1, 2, 3, 4, 5]

correlation(frame: pl.DataFrame, name: str = 'portfolio') -> pl.DataFrame

Compute a correlation matrix of asset returns plus the portfolio.

Computes percentage changes for all numeric columns in frame, appends the portfolio profit series under the provided name, and returns the Pearson correlation matrix across all numeric columns.

Parameters:

Name Type Description Default
frame DataFrame

A Polars DataFrame containing at least the asset price columns (and a date column which will be ignored if non-numeric).

required
name str

The column name to use when adding the portfolio profit series to the input frame.

'portfolio'

Returns:

Type Description
DataFrame

A square Polars DataFrame where each cell is the correlation

DataFrame

between a pair of series (values in [-1, 1]).