Types & registry¶
Core types¶
types
¶
Shared data structures for jaxfolio.
These are lightweight, framework-agnostic containers. Optimizers are configured
by an :class:OptimizerConfig (plus a :class:TradingCosts spec for the
cost-aware multi-period optimizer) and return a :class:PortfolioResult; the
backtester and plotting utilities consume it.
OptimizerConfig
dataclass
¶
OptimizerConfig(
risk_free_rate: float = 0.0,
long_only: bool = True,
weight_bounds: tuple[float, float] | None = None,
max_iter: int = 2000,
solver: str | Callable[..., Any] = "spg",
solver_options: Mapping[str, Any] | None = None,
learning_rate: float | None = None,
tol: float = 1e-07,
l2_reg: float = 0.0,
constraints: tuple[Any, ...] = (),
attribution: bool = True,
)
Configuration shared by the projected-gradient classical optimizers.
Attributes:
| Name | Type | Description |
|---|---|---|
risk_free_rate |
float
|
Per-period risk-free rate used by Sharpe-style objectives. |
long_only |
bool
|
If |
weight_bounds |
tuple[float, float] | None
|
Explicit |
max_iter |
int
|
Maximum projected-gradient iterations. |
solver |
str | Callable[..., Any]
|
Which projected-gradient solver to use. Three spellings are accepted:
The optax solvers keep smooth fixed-step dynamics, which are preferable
when differentiating through the optimizer to train an allocation
policy. Pass the factory, not a pre-built |
solver_options |
Mapping[str, Any] | None
|
Extra keyword arguments forwarded to the optax factory, e.g.
|
learning_rate |
float | None
|
Step size for the solver. |
tol |
float
|
Convergence tolerance. For |
l2_reg |
float
|
Optional L2 penalty on weights (encourages diversification). |
attribution |
bool
|
Capture the first-order (KKT) diagnostics needed by
:func: |
constraints |
tuple[Any, ...]
|
Named constraint specifications from :mod: Unlike Validation is two-stage, like |
with_constraints
¶
with_constraints(constraints: Any) -> OptimizerConfig
solver_spec
¶
solver_spec() -> SolverSpec
Resolve solver / solver_options into a hashable solver key.
bounds
¶
Resolve the effective per-asset weight bounds.
Honors an explicit weight_bounds; otherwise defaults to (0, 1)
for a long-only portfolio and (-1, 1) when shorting is allowed.
Source code in src/jaxfolio/types.py
TradingCosts
dataclass
¶
TradingCosts(
spread_bps: float | tuple[float, ...] = 0.0,
commission_bps: float | tuple[float, ...] = 0.0,
impact_bps: float | tuple[float, ...] = 0.0,
turnover_penalty: float = 0.0,
smoothing: float = 0.1,
)
Per-period trading frictions for the multi-period optimizer.
The three cost coefficients are quoted in basis points of traded notional
per unit of |dw|, one-way, per period — the units a trader states
them in. Each accepts a scalar (applied to every asset) or a per-asset
sequence of length n; sequences are stored as tuples so the dataclass
stays hashable.
Costs are per period and never annualized: the objective trades them off
against per-period mu and Sigma, so annualizing would double-count.
Attributes:
| Name | Type | Description |
|---|---|---|
spread_bps |
float | tuple[float, ...]
|
Bid-ask half-spread, in bps, charged linearly on |
commission_bps |
float | tuple[float, ...]
|
Proportional commission / fee, in bps, also linear on |
impact_bps |
float | tuple[float, ...]
|
Quadratic market-impact coefficient, in bps per unit of |
turnover_penalty |
float
|
An extra L1 penalty on |
smoothing |
float
|
Starting (widest) relative Huber half-width used to make the L1 cost
differentiable. The absolute width is The optimizer walks a geometrically shrinking ladder from this width and keeps whichever stage is best under the exact objective, so this is a starting point for a search rather than a value the answer hinges on — which is why it is a tuning knob and not part of the API contract. |
Notes
Unit bridge to the backtester: TradingCosts(spread_bps=10.0) prices the
same linear cost that backtest(transaction_cost=0.0010) charges. The
engine keyword is decimal for historical reasons; this class is bps.
Turnover control here is soft — costs are priced, not capped. See the multi-period guide for why a hard turnover budget is not expressible in this solver, and what to do instead.
Examples:
>>> TradingCosts(spread_bps=5.0, commission_bps=1.0).linear_decimal(3)
array([0.0006, 0.0006, 0.0006])
is_active
¶
True if any cost coefficient is non-zero (reporting/metadata only).
Source code in src/jaxfolio/types.py
linear_decimal
¶
Total linear cost (spread_bps + commission_bps) / 1e4, length n.
quadratic_decimal
¶
as_params
¶
Traced objective parameters for n assets.
Returns {"c_lin": (n,), "c_quad": (n,)} as JAX arrays. Both are
traced rather than static, so changing a cost never recompiles the
solver kernel. turnover_penalty is folded into c_lin because it
enters the objective identically; the split is preserved for reporting by
:meth:linear_decimal.
Source code in src/jaxfolio/types.py
PortfolioResult
dataclass
¶
PortfolioResult(
weights: ndarray,
assets: list[str],
method: str,
expected_return: float | None = None,
volatility: float | None = None,
sharpe: float | None = None,
metadata: dict[str, Any] = dict(),
trajectory: ndarray | None = None,
attribution: Any | None = None,
)
The output of an optimizer: weights plus diagnostics.
Attributes:
| Name | Type | Description |
|---|---|---|
weights |
ndarray
|
Portfolio weights as a 1-D numpy array (sums to 1). |
assets |
list[str]
|
Asset names aligned with |
method |
str
|
Human-readable optimizer name. |
expected_return |
float | None
|
Annualized (or per-period, if unscaled) expected portfolio return. |
volatility |
float | None
|
Portfolio volatility on the same scale as |
sharpe |
float | None
|
Sharpe ratio implied by |
metadata |
dict[str, Any]
|
Free-form diagnostics (iterations, converged flag, cluster labels, ...).
Values are kept JSON-serializable by convention; bulk arrays belong in a
dedicated field like |
trajectory |
ndarray | None
|
Optional |
attribution |
Any | None
|
Optional :class: |
as_dict
¶
Return {asset: weight} sorted by descending absolute weight.
Registry¶
registry
¶
A lightweight registry for portfolio strategies.
Any callable with the optimizer shape method(returns, ...) -> PortfolioResult
can be registered by name and then looked up, listed, and mixed with the
built-ins in the backtester. Built-in optimizers register themselves at import
time (see :func:_register_builtins), so :func:list_strategies always reflects
the full catalog — including the user's own additions.
Usage
from jaxfolio import register_strategy, get_strategy, list_strategies @register_strategy("my_momentum", description="12-1 momentum tilt") ... def my_momentum(returns): ... ... get_strategy("my_momentum") is my_momentum True "my_momentum" in list_strategies() True
StrategyInfo
dataclass
¶
A registered strategy: its callable, canonical name, and description.
register_strategy
¶
register_strategy(
name: str | None = None,
*,
description: str = "",
builtin: bool = False,
overwrite: bool = False,
) -> Callable[[Strategy], Strategy]
Register a strategy under name (defaults to the function's name).
Usable as a decorator (@register_strategy("x")) or directly
(register_strategy("x")(fn)). The wrapped function is returned unchanged,
so decorating never alters behavior. Re-registering an existing name raises
unless overwrite=True.
Source code in src/jaxfolio/registry.py
get_strategy
¶
Return the registered strategy callable for name.
strategy_info
¶
strategy_info(name: str) -> StrategyInfo
Return the full :class:StrategyInfo record for name.
list_strategies
¶
List registered strategy names (optionally filtered by origin).
Source code in src/jaxfolio/registry.py
unregister
¶
registry
¶
registry() -> dict[str, StrategyInfo]