Skip to content

Portfolio research

Import public construction and backtesting functions, policies, and result types from persistra.portfolio.

Continuous optimization

persistra.portfolio.optimization

Continuous portfolio optimization with explicit objectives and constraints.

_DIAGNOSTIC_COLUMNS = ['value', 'lower', 'upper', 'residual', 'binding'] module-attribute

OptimizationFailurePolicy = Literal['raise', 'hold_previous']

PortfolioConstraint = WeightBounds | GrossExposureConstraint | NetExposureConstraint | TurnoverConstraint | FactorExposureConstraint | LinearExposureConstraint | GroupedExposureConstraint | RiskBudgetConstraint | ConditionalValueAtRiskConstraint | TrackingErrorConstraint

PortfolioPenalty = LinearTransactionCostPenalty | AsymmetricTransactionCostPenalty | QuadraticTransactionCostPenalty

_Array = NDArray[np.float64]

AnalysisError

Bases: PersistraError, ValueError

Raised when data violates a mathematical assumption.

ActiveMeanVarianceObjective dataclass

Maximize expected active return against tracking-error variance.

AsymmetricTransactionCostPenalty dataclass

Separate linear buy and sell cost rates with one objective multiplier.

ConditionalValueAtRiskConstraint dataclass

Maximum empirical loss CVaR at the requested confidence level.

ConditionalValueAtRiskObjective dataclass

Minimize empirical loss CVaR at the requested confidence level.

CovariancePolicy dataclass

Explicit diagonal shrinkage and eigenvalue-floor conditioning policy.

EllipsoidalExpectedReturnUncertainty dataclass

Ellipsoidal expected-return uncertainty matrix and radius.

FactorExposureConstraint dataclass

Lower and upper bounds for every supplied factor exposure.

GrossExposureConstraint dataclass

Upper bound on total absolute risky-asset weight.

GroupedExposureConstraint dataclass

Bounds for stable or dated caller-defined asset groups.

LinearExposureConstraint dataclass

Bound caller-defined linear loadings without assigning column semantics.

LinearTransactionCostPenalty dataclass

Linear risky-asset trading-cost rates and objective multiplier.

MeanVarianceObjective dataclass

Maximize expected return against an explicit variance penalty.

MinimumTrackingErrorObjective dataclass

Minimize variance relative to supplied benchmark weights.

MinimumVarianceObjective dataclass

Minimize total portfolio variance.

NetExposureConstraint dataclass

Lower and upper bounds on signed risky-asset weight.

PortfolioOptimizationPathResult dataclass

Ordered point-in-time portfolio optimization steps and aligned targets.

PortfolioOptimizationResult dataclass

Optimal weights with objective, risk, exposure, and constraint diagnostics.

PortfolioOptimizationStep dataclass

One dated optimized or explicitly held portfolio in a path.

PortfolioProblem dataclass

One solver-independent continuous portfolio optimization problem.

QuadraticTransactionCostPenalty dataclass

Asset-specific quadratic market-impact rates and objective multiplier.

RiskBudgetConstraint dataclass

Target or cap asset and grouped fractional contributions to variance.

RiskParityObjective dataclass

Minimize squared differences between realized and requested risk budgets.

RobustMeanVarianceObjective dataclass

Mean variance with an ellipsoidal worst-case expected-return penalty.

TrackingErrorConstraint dataclass

Upper bound on portfolio volatility relative to a benchmark.

TurnoverConstraint dataclass

Upper bound on one-way turnover from current risky and residual-cash weights.

WeightBounds dataclass

Per-asset lower and upper portfolio-weight bounds.

PortfolioSolver

Bases: Protocol

Solve one continuous solver-neutral portfolio problem.

name: str property

Return the stable solver identity.

capabilities: PortfolioSolverCapabilities property

Return the exact problem features accepted by this backend.

solve(problem: PortfolioSolverProblem) -> PortfolioSolverResult

Return normalized values, termination state, and statistics.

PortfolioSolverProblem dataclass

One differentiable continuous problem ready for a numerical backend.

ScipySlsqpSolver dataclass

Solve continuous problems with SciPy's SLSQP implementation.

capabilities: PortfolioSolverCapabilities property

Advertise every feature represented by the continuous boundary.

solve(problem: PortfolioSolverProblem) -> PortfolioSolverResult

Translate neutral constraints and run SLSQP.

SolverConstraint dataclass

One equality or nonnegative inequality in solver-neutral form.

FactorRiskModel dataclass

Factor and idiosyncratic components of one asset covariance estimate.

manifest_parameters: Mapping[str, Any] property

Return portable covariance settings for a research manifest.

_Layout dataclass

_Inputs dataclass

require_integer(value: object, *, name: str, minimum: int | None = None) -> int

Return a normalized integer after enforcing an optional inclusive minimum.

optimize_portfolio(problem: PortfolioProblem, *, tolerance: float = 1e-09, maximum_iterations: int = 1000, initial_weights: pd.Series | None = None, solver: PortfolioSolver | None = None) -> PortfolioOptimizationResult

Solve one continuous portfolio problem and validate the returned constraints.

Covariance values use the caller's return frequency. Expected returns, variance, tracking error, and cost rates must use a compatible scale. The residual cash weight is always 1 - sum(weights). A NetExposureConstraint(1, 1) therefore expresses a fully invested risky portfolio.

The optimizer raises AnalysisError when the problem is infeasible, the numerical solver fails, or the returned point violates a requested constraint beyond tolerance.

_objective_feature(objective: MinimumVarianceObjective | MeanVarianceObjective | MinimumTrackingErrorObjective | ActiveMeanVarianceObjective | RiskParityObjective | ConditionalValueAtRiskObjective | RobustMeanVarianceObjective) -> str

_penalty_feature(penalty: PortfolioPenalty) -> str

_constraint_feature(constraint: PortfolioConstraint) -> str

optimize_portfolio_path(problems: tuple[PortfolioProblem, ...], *, failure_policy: OptimizationFailurePolicy = 'raise', tolerance: float = 1e-09, maximum_iterations: int = 1000, solver: PortfolioSolver | None = None) -> PortfolioOptimizationPathResult

Solve ordered dated problems while carrying the preceding portfolio forward.

_problem_inputs(problem: PortfolioProblem) -> _Inputs

_covariance(frame: pd.DataFrame, policy: CovariancePolicy) -> tuple[_Array, pd.Series]

_aligned_series(values: pd.Series | None, assets: pd.Index, *, name: str, default: float) -> _Array

_factor_exposure_frame(exposures: pd.DataFrame | None, assets: pd.Index) -> pd.DataFrame | None

_scenario_return_frame(scenarios: pd.DataFrame | None, assets: pd.Index) -> pd.DataFrame | None

_uncertainty_matrix(uncertainty: EllipsoidalExpectedReturnUncertainty, assets: pd.Index) -> _Array

_validate_constraint_types(constraints: tuple[PortfolioConstraint, ...]) -> None

resolve_grouped_exposure(constraint: GroupedExposureConstraint, assets: pd.Index, *, as_of: pd.Timestamp | None = None) -> LinearExposureConstraint

Return validated linear loadings and bounds for one grouped constraint.

_dated_group_loadings(memberships: pd.DataFrame, assets: pd.Index, *, as_of: pd.Timestamp | None) -> pd.DataFrame

_group_bounds(values: float | pd.Series, groups: pd.Index, *, name: str) -> pd.Series

_linear_bounds(constraint: LinearExposureConstraint, assets: pd.Index) -> tuple[_Array, _Array]

_weight_bounds(constraints: tuple[PortfolioConstraint, ...], assets: pd.Index) -> tuple[_Array, _Array]

_bound_values(values: float | pd.Series, assets: pd.Index, *, name: str) -> _Array

_transaction_costs(penalties: tuple[PortfolioPenalty, ...], assets: pd.Index) -> tuple[_Array, _Array, _Array]

_nonnegative_values(values: float | pd.Series, assets: pd.Index, *, name: str) -> _Array

_risk_budget_values(values: pd.Series | None, labels: pd.Index, *, name: str, default_equal: bool = False) -> _Array

_validate_risk_budget_constraint(constraint: RiskBudgetConstraint, assets: pd.Index) -> None

_factor_bounds(constraint: FactorExposureConstraint, exposures: pd.DataFrame | None) -> tuple[_Array, _Array]

_layout(assets: int, *, use_gross: bool, use_trades: bool) -> _Layout

_initial_point(inputs: _Inputs, layout: _Layout, *, initial_weights: pd.Series | None) -> _Array

_problem_assets(problem: PortfolioProblem) -> pd.Index

_bounds(inputs: _Inputs, layout: _Layout) -> list[tuple[float | None, float | None]]

_solver_constraints(inputs: _Inputs, layout: _Layout) -> list[SolverConstraint]

_inequality(function: Callable[[_Array], _Array | float]) -> SolverConstraint

_equality(function: Callable[[_Array], _Array | float]) -> SolverConstraint

_objective_functions(inputs: _Inputs, layout: _Layout) -> tuple[Callable[[_Array], float], Callable[[_Array], _Array]]

_base_objective(inputs: _Inputs, weights: _Array) -> tuple[float, _Array]

_fractional_risk_contributions(weights: _Array, covariance: _Array) -> _Array

_risk_contribution_values_and_jacobian(weights: _Array, covariance: _Array) -> tuple[_Array, _Array]

_empirical_cvar(weights: _Array, scenario_returns: _Array, confidence_level: float) -> tuple[float, _Array, _Array]

_constraint_diagnostics(inputs: _Inputs, weights: _Array, *, tolerance: float) -> pd.DataFrame

_diagnostic_row(rows: list[dict[str, float | bool]], names: list[str], *, name: str, value: float, lower: float, upper: float, tolerance: float) -> None

_turnover(weights: _Array, current: _Array) -> float

_factor_exposure(exposures: pd.DataFrame | None, weights: _Array) -> pd.Series

_linear_exposure(constraints: tuple[LinearExposureConstraint, ...], weights: _Array) -> pd.Series

_risk_contributions(inputs: _Inputs, weights: _Array) -> pd.Series

_risk_budget_diagnostics(inputs: _Inputs, contributions: pd.Series, *, tolerance: float) -> pd.DataFrame

_downside_diagnostics(inputs: _Inputs, weights: _Array) -> tuple[pd.Series, pd.DataFrame]

_objective_breakdown(inputs: _Inputs, weights: _Array) -> pd.Series

_positive_float(value: float, *, name: str) -> float

Solver boundary

persistra.portfolio.solver

Solver-neutral optimization boundaries and supported backends.

SolverArray = NDArray[np.float64]

SolverFeature = str

PortfolioSolverStatus = Literal['optimal', 'feasible', 'infeasible', 'unbounded', 'iteration_limit', 'solver_error']

PortfolioSolverCapabilities dataclass

Features that one portfolio solver backend accepts exactly.

unsupported(problem: PortfolioSolverProblem) -> tuple[str, ...]

Return stable descriptions of problem features the backend rejects.

SolverConstraint dataclass

One equality or nonnegative inequality in solver-neutral form.

PortfolioSolverProblem dataclass

One differentiable continuous problem ready for a numerical backend.

PortfolioSolverResult dataclass

Normalized numerical result returned by a portfolio solver backend.

PortfolioSolver

Bases: Protocol

Solve one continuous solver-neutral portfolio problem.

name: str property

Return the stable solver identity.

capabilities: PortfolioSolverCapabilities property

Return the exact problem features accepted by this backend.

solve(problem: PortfolioSolverProblem) -> PortfolioSolverResult

Return normalized values, termination state, and statistics.

DiscretePortfolioSolverProblem dataclass

Convex mixed-integer quadratic problem in nonnegative integer lots.

DiscretePortfolioSolver

Bases: Protocol

Solve one explicit mixed-integer portfolio problem without relaxation.

name: str property

Return the stable solver identity.

capabilities: PortfolioSolverCapabilities property

Return the exact mixed-integer capabilities.

solve(problem: DiscretePortfolioSolverProblem) -> PortfolioSolverResult

Return integer lots and normalized optimality diagnostics.

_ScipyResult

Bases: Protocol

ScipySlsqpSolver dataclass

Solve continuous problems with SciPy's SLSQP implementation.

capabilities: PortfolioSolverCapabilities property

Advertise every feature represented by the continuous boundary.

solve(problem: PortfolioSolverProblem) -> PortfolioSolverResult

Translate neutral constraints and run SLSQP.

CvxpySolver dataclass

Solve compatible convex quadratic problems through optional CVXPY.

name: str property

Return the stable backend and concrete solver identity.

capabilities: PortfolioSolverCapabilities property

Advertise the affine-constrained convex problem subset.

solve(problem: PortfolioSolverProblem) -> PortfolioSolverResult

Canonicalize a quadratic problem and solve it through CVXPY.

CvxpyMixedIntegerSolver dataclass

Solve convex mixed-integer quadratic portfolios with optional SCIP.

capabilities: PortfolioSolverCapabilities property

Advertise the focused long-only discrete feature set.

solve(problem: DiscretePortfolioSolverProblem) -> PortfolioSolverResult

Solve integer lots and retain primal and dual bounds when SCIP reports them.

require_integer(value: object, *, name: str, minimum: int | None = None) -> int

Return a normalized integer after enforcing an optional inclusive minimum.

_require_supported(name: str, capabilities: PortfolioSolverCapabilities, problem: PortfolioSolverProblem) -> None

_cvxpy_module(backend: str) -> Any

_solver_error(values: SolverArray, error: Exception) -> PortfolioSolverResult

_mixed_integer_statistics(model: Any) -> tuple[dict[str, float | int | str], float | None, float | None]

_cvxpy_status(status: str) -> PortfolioSolverStatus

_quadratic_objective(problem: PortfolioSolverProblem) -> tuple[SolverArray, SolverArray, float]

Recover the exact quadratic form exposed by the neutral gradient boundary.

_affine_constraint(constraint: SolverConstraint, size: int) -> tuple[SolverArray, SolverArray]

Recover an affine constraint matrix from its solver-neutral function.

Target construction and schedules

persistra.portfolio.construction

Transparent target-weight construction for portfolio research.

PortfolioConfiguration = Literal['long_only', 'long_short']

WeightingMethod = Literal['equal', 'signal_proportional']

AnalysisError

Bases: PersistraError, ValueError

Raised when data violates a mathematical assumption.

PortfolioConstraints dataclass

Hard exposure, position, and one-way turnover limits.

PortfolioConstructionResult dataclass

Transparent unconstrained and final target portfolio weights.

PortfolioRiskControl dataclass

Annualized volatility target and ceiling based on supplied covariance.

require_integer(value: object, *, name: str, minimum: int | None = None) -> int

Return a normalized integer after enforcing an optional inclusive minimum.

asset_panel(frame: pd.DataFrame, *, name: str) -> pd.DataFrame

Copy a numeric date-by-asset panel with a fixed universe.

datetime_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex

Validate a sorted, unique datetime index.

finite_scalar(value: float, *, name: str, minimum: float | None = None) -> float

Validate one finite scalar and an optional inclusive lower bound.

rebalance_schedule(index: pd.DatetimeIndex, *, frequency: Literal['daily', 'weekly', 'monthly', 'quarterly'] | int, anchor: Literal['start', 'end'] = 'end') -> pd.DatetimeIndex

Select deterministic observation dates for a rebalance schedule.

Calendar schedules choose the first or last supplied observation in each calendar bucket. An integer schedule chooses every frequency observations starting with the first observation; its anchor must be "start".

construct_portfolio(signals: pd.DataFrame, *, weighting: WeightingMethod = 'equal', configuration: PortfolioConfiguration = 'long_only', gross_target: float = 1.0, net_target: float | None = None, constraints: PortfolioConstraints | None = None, covariances: Mapping[pd.Timestamp, pd.DataFrame] | None = None, risk_control: PortfolioRiskControl | None = None, initial_weights: pd.Series | None = None) -> PortfolioConstructionResult

Construct date-by-asset targets from an explicit signal panel.

Equal weighting ignores signal magnitude. In long-only mode, every observed asset is eligible. In long-short mode, signal signs define sides. Signal-proportional weighting uses positive signals for long-only portfolios and absolute signal magnitude within each side for long-short portfolios.

Position limits use deterministic capped redistribution within each side. Volatility controls scale the whole risky portfolio and never change relative asset weights. A turnover limit blends each desired target with the preceding target. Infeasible exposure, side-capacity, covariance, or interacting risk constraints raise AnalysisError.

_validate_requested_exposure(gross: float, net: float, *, configuration: PortfolioConfiguration, constraints: PortfolioConstraints) -> None

_initial_weights(initial: pd.Series | None, columns: pd.Index, *, constraints: PortfolioConstraints) -> np.ndarray

_unconstrained_row(signals: np.ndarray, *, weighting: WeightingMethod, configuration: PortfolioConfiguration, gross: float, net: float, date: pd.Timestamp) -> np.ndarray

_allocate_uncapped(scores: np.ndarray, mask: np.ndarray, *, budget: float, side: str, date: pd.Timestamp) -> np.ndarray

_constrained_sides(raw: np.ndarray, *, gross: float, net: float, position_limit: float, tolerance: float, date: pd.Timestamp) -> np.ndarray

_capped_allocation(scores: np.ndarray, *, budget: float, limit: float, tolerance: float, side: str, date: pd.Timestamp) -> np.ndarray

_covariance_matrix(covariance: pd.DataFrame, columns: pd.Index, *, tolerance: float, date: pd.Timestamp) -> np.ndarray

_apply_risk_control(weights: np.ndarray, covariance: np.ndarray, *, constraints: PortfolioConstraints, risk_control: PortfolioRiskControl, date: pd.Timestamp) -> np.ndarray

_scale_bounds(weights: np.ndarray, *, constraints: PortfolioConstraints) -> tuple[float, float]

_annualized_volatility(weights: np.ndarray, covariance: np.ndarray, *, periods_per_year: float) -> float

_validate_final_constraints(weights: np.ndarray, *, constraints: PortfolioConstraints, date: pd.Timestamp | None) -> None

_turnover(previous: np.ndarray, target: np.ndarray, previous_cash: float, target_cash: float) -> float

_exposures(weights: np.ndarray, cash: float) -> dict[str, float]

_constraint_utilization(weights: np.ndarray, *, exposures: dict[str, float], turnover: float, predicted_volatility: float, constraints: PortfolioConstraints, risk_control: PortfolioRiskControl | None) -> dict[str, float]

Vectorized backtesting

persistra.portfolio.backtest

Portfolio-level vectorized backtesting with explicit timing and accounting.

MissingCostPolicy = Literal['error', 'zero']

ReturnAdjustment = Literal['unadjusted', 'adjusted']

AnalysisError

Bases: PersistraError, ValueError

Raised when data violates a mathematical assumption.

BacktestPolicies dataclass

Explicit missing-return and nontradeable-asset behavior.

BacktestResult dataclass

Reconciled portfolio-level backtest paths and benchmark comparisons.

BacktestTiming dataclass

Signal-to-decision and decision-to-holding timing policy.

BorrowPolicy dataclass

Missing-rate and unavailable-short behavior for portfolio backtests.

CorporateAction dataclass

One dated, sourced portfolio-security lifecycle event.

MarketImpactModel dataclass

Nonlinear participation impact calibrated in basis points.

MultiCurrencyPolicy dataclass

Base currency, asset currencies, and bounded missing-FX behavior.

PortfolioConstructionResult dataclass

Transparent unconstrained and final target portfolio weights.

_TimingPlan dataclass

_Simulation dataclass

asset_panel(frame: pd.DataFrame, *, name: str) -> pd.DataFrame

Copy a numeric date-by-asset panel with a fixed universe.

finite_scalar(value: float, *, name: str, minimum: float | None = None) -> float

Validate one finite scalar and an optional inclusive lower bound.

backtest_portfolio(target_weights: PortfolioConstructionResult | pd.DataFrame, *, returns: pd.DataFrame | None = None, prices: pd.DataFrame | None = None, timing: BacktestTiming | None = None, policies: BacktestPolicies | None = None, transaction_cost_bps: float | pd.Series | pd.DataFrame = 0.0, buy_cost_bps: float | pd.Series | pd.DataFrame | None = None, sell_cost_bps: float | pd.Series | pd.DataFrame | None = None, missing_cost: MissingCostPolicy = 'error', market_impact: MarketImpactModel | None = None, liquidity: pd.DataFrame | None = None, shortable: pd.DataFrame | None = None, borrow_rates: float | pd.Series | pd.DataFrame = 0.0, borrow_policy: BorrowPolicy | None = None, fx_rates: pd.DataFrame | None = None, multi_currency: MultiCurrencyPolicy | None = None, corporate_actions: Sequence[CorporateAction] = (), return_adjustment: ReturnAdjustment = 'unadjusted', cash_returns: float | pd.Series = 0.0, tradeable: pd.DataFrame | None = None, benchmarks: Mapping[str, pd.DataFrame | pd.Series] | None = None, initial_equity: float = 1.0, tolerance: float = 1e-10) -> BacktestResult

Simulate rebalances to supplied targets over returns or prices.

Target row dates are signal-observation dates. BacktestTiming maps each observation to a decision date and first holding period. The default applies a target one period after its signal observation. Zero total lag is rejected unless the caller asserts that the signal was available before the assumed trade.

Trade costs may be scalar, asset-specific, or dated and asymmetric. Optional impact uses supplied liquidity, while borrow fees accrue separately on beginning short weights. Cash is the residual needed to make beginning weights sum to one; it can be greater than one after short sales or negative under net leverage. Returns, cash, holdings, and every cost component reconcile in the returned result.

Sourced corporate actions can add dividend cash, normalize unadjusted split returns, or replace a missing observation with a terminal return and liquidate the asset. Adjusted-return inputs explicitly suppress dividend and split application to prevent double counting.

A benchmark value can be a static weight series or a date-by-asset target panel. Static weights enter on the first strategy signal date and then drift. Panel benchmarks use the same timing and policies as the strategy. This supports explicit static and naive-signal comparisons without hiding their definitions.

_target_panel(targets: PortfolioConstructionResult | pd.DataFrame) -> pd.DataFrame

_market_returns(*, returns: pd.DataFrame | None, prices: pd.DataFrame | None) -> pd.DataFrame

_cost_rate_panel(costs: float | pd.Series | pd.DataFrame, index: pd.DatetimeIndex, columns: pd.Index, *, name: str, missing: MissingCostPolicy) -> tuple[np.ndarray, np.ndarray]

_corporate_action_inputs(actions: Sequence[CorporateAction], local_returns: pd.DataFrame, *, return_adjustment: ReturnAdjustment) -> tuple[pd.DataFrame, np.ndarray, np.ndarray, pd.DataFrame]

_resolved_fx_rates(fx_rates: pd.DataFrame, index: pd.DatetimeIndex, assets: pd.Index, policy: MultiCurrencyPolicy) -> tuple[pd.DataFrame, pd.DataFrame]

_rate_panel(rates: float | pd.Series | pd.DataFrame, index: pd.DatetimeIndex, columns: pd.Index, *, name: str, missing: MissingCostPolicy, scale: float) -> tuple[np.ndarray, np.ndarray]

_liquidity_panel(liquidity: pd.DataFrame | None, market_returns: pd.DataFrame, *, required: bool, missing: MissingCostPolicy) -> tuple[np.ndarray, np.ndarray]

_cash_return_path(cash_returns: float | pd.Series, index: pd.DatetimeIndex) -> np.ndarray

_tradeable_panel(tradeable: pd.DataFrame | None, market_returns: pd.DataFrame) -> np.ndarray

_boolean_panel(values: pd.DataFrame | None, market_returns: pd.DataFrame, *, name: str) -> np.ndarray

_timing_plan(targets: pd.DataFrame, return_index: pd.DatetimeIndex, timing: BacktestTiming) -> _TimingPlan

_simulate(targets: pd.DataFrame, market_returns: pd.DataFrame, *, local_returns: np.ndarray, fx_returns: np.ndarray, dividend_yields: np.ndarray, delistings: np.ndarray, plan: _TimingPlan, policies: BacktestPolicies, buy_cost_rates: np.ndarray, sell_cost_rates: np.ndarray, market_impact: MarketImpactModel | None, liquidity: np.ndarray, borrow_rates: np.ndarray, shortable: np.ndarray, borrow_policy: BorrowPolicy, cash_returns: np.ndarray, tradeable: np.ndarray, tolerance: float) -> _Simulation

_impact_costs(trades: np.ndarray, liquidity: np.ndarray, model: MarketImpactModel | None, *, date: object, columns: pd.Index, tolerance: float) -> np.ndarray

_exposures(weights: np.ndarray, cash: float) -> dict[str, float]

_run_benchmarks(benchmarks: Mapping[str, pd.DataFrame | pd.Series] | None, *, strategy_targets: pd.DataFrame, market_returns: pd.DataFrame, local_returns: np.ndarray, fx_returns: np.ndarray, dividend_yields: np.ndarray, delistings: np.ndarray, timing: BacktestTiming, policies: BacktestPolicies, buy_cost_rates: np.ndarray, sell_cost_rates: np.ndarray, market_impact: MarketImpactModel | None, liquidity: np.ndarray, borrow_rates: np.ndarray, shortable: np.ndarray, borrow_policy: BorrowPolicy, cash_returns: np.ndarray, tradeable: np.ndarray, initial_equity: float, tolerance: float) -> tuple[pd.DataFrame, pd.DataFrame]

_compare_benchmarks(strategy: pd.Series, benchmarks: pd.DataFrame) -> pd.DataFrame

Performance reporting

persistra.portfolio.performance

Transparent, reconciled portfolio performance reporting.

BacktestResult dataclass

Reconciled portfolio-level backtest paths and benchmark comparisons.

PortfolioPerformanceReport dataclass

Performance metrics, benchmark comparisons, and accounting aggregates.

finite_scalar(value: float, *, name: str, minimum: float | None = None) -> float

Validate one finite scalar and an optional inclusive lower bound.

portfolio_performance_report(result: BacktestResult, *, periods_per_year: float, risk_free_returns: float | pd.Series) -> PortfolioPerformanceReport

Summarize a backtest with explicit annualization and risk-free assumptions.

_risk_free_path(values: float | pd.Series, index: pd.Index) -> pd.Series

_annualized_geometric(returns: np.ndarray, periods_per_year: float) -> float

_annualized_deviation(returns: np.ndarray, periods_per_year: float) -> float

_maximum_drawdown_duration(drawdown: pd.Series) -> int

_attribution(result: BacktestResult) -> pd.Series

_coverage(result: BacktestResult) -> pd.Series

_benchmark_report(result: BacktestResult, periods_per_year: float) -> pd.DataFrame

Policies and results

persistra.portfolio.model

Typed policies and results for portfolio research.

PortfolioSolverStatus = Literal['optimal', 'feasible', 'infeasible', 'unbounded', 'iteration_limit', 'solver_error']

WeightingMethod = Literal['equal', 'signal_proportional']

PortfolioConfiguration = Literal['long_only', 'long_short']

MissingReturnPolicy = Literal['error', 'zero']

NontradeablePolicy = Literal['error', 'hold']

OptimizationFailurePolicy = Literal['raise', 'hold_previous']

MissingMembershipPolicy = Literal['error', 'zero']

OverlappingMembershipPolicy = Literal['error', 'allow']

MissingCostPolicy = Literal['error', 'zero']

UnavailableShortPolicy = Literal['error', 'cover']

MissingFxPolicy = Literal['error', 'ffill']

CorporateActionKind = Literal['cash_dividend', 'split', 'terminal_return']

ReturnAdjustment = Literal['unadjusted', 'adjusted']

PortfolioObjective = MinimumVarianceObjective | MeanVarianceObjective | MinimumTrackingErrorObjective | ActiveMeanVarianceObjective | RiskParityObjective | ConditionalValueAtRiskObjective | RobustMeanVarianceObjective

PortfolioConstraint = WeightBounds | GrossExposureConstraint | NetExposureConstraint | TurnoverConstraint | FactorExposureConstraint | LinearExposureConstraint | GroupedExposureConstraint | RiskBudgetConstraint | ConditionalValueAtRiskConstraint | TrackingErrorConstraint

PortfolioPenalty = LinearTransactionCostPenalty | AsymmetricTransactionCostPenalty | QuadraticTransactionCostPenalty

FactorRiskModel dataclass

Factor and idiosyncratic components of one asset covariance estimate.

manifest_parameters: Mapping[str, Any] property

Return portable covariance settings for a research manifest.

PortfolioConstraints dataclass

Hard exposure, position, and one-way turnover limits.

PortfolioRiskControl dataclass

Annualized volatility target and ceiling based on supplied covariance.

MinimumVarianceObjective dataclass

Minimize total portfolio variance.

MeanVarianceObjective dataclass

Maximize expected return against an explicit variance penalty.

MinimumTrackingErrorObjective dataclass

Minimize variance relative to supplied benchmark weights.

ActiveMeanVarianceObjective dataclass

Maximize expected active return against tracking-error variance.

RiskParityObjective dataclass

Minimize squared differences between realized and requested risk budgets.

ConditionalValueAtRiskObjective dataclass

Minimize empirical loss CVaR at the requested confidence level.

EllipsoidalExpectedReturnUncertainty dataclass

Ellipsoidal expected-return uncertainty matrix and radius.

RobustMeanVarianceObjective dataclass

Mean variance with an ellipsoidal worst-case expected-return penalty.

WeightBounds dataclass

Per-asset lower and upper portfolio-weight bounds.

GrossExposureConstraint dataclass

Upper bound on total absolute risky-asset weight.

NetExposureConstraint dataclass

Lower and upper bounds on signed risky-asset weight.

TurnoverConstraint dataclass

Upper bound on one-way turnover from current risky and residual-cash weights.

FactorExposureConstraint dataclass

Lower and upper bounds for every supplied factor exposure.

LinearExposureConstraint dataclass

Bound caller-defined linear loadings without assigning column semantics.

GroupedExposureConstraint dataclass

Bounds for stable or dated caller-defined asset groups.

RiskBudgetConstraint dataclass

Target or cap asset and grouped fractional contributions to variance.

ConditionalValueAtRiskConstraint dataclass

Maximum empirical loss CVaR at the requested confidence level.

TrackingErrorConstraint dataclass

Upper bound on portfolio volatility relative to a benchmark.

LinearTransactionCostPenalty dataclass

Linear risky-asset trading-cost rates and objective multiplier.

AsymmetricTransactionCostPenalty dataclass

Separate linear buy and sell cost rates with one objective multiplier.

QuadraticTransactionCostPenalty dataclass

Asset-specific quadratic market-impact rates and objective multiplier.

CovariancePolicy dataclass

Explicit diagonal shrinkage and eigenvalue-floor conditioning policy.

PortfolioProblem dataclass

One solver-independent continuous portfolio optimization problem.

DiscretePortfolioProblem dataclass

Long-only portfolio problem expressed in integer trade lots.

DiscretePortfolioResult dataclass

Discrete holdings, bounds, and normalized mixed-integer diagnostics.

PortfolioOptimizationResult dataclass

Optimal weights with objective, risk, exposure, and constraint diagnostics.

PortfolioOptimizationStep dataclass

One dated optimized or explicitly held portfolio in a path.

PortfolioOptimizationPathResult dataclass

Ordered point-in-time portfolio optimization steps and aligned targets.

PortfolioConstructionResult dataclass

Transparent unconstrained and final target portfolio weights.

BacktestTiming dataclass

Signal-to-decision and decision-to-holding timing policy.

BacktestPolicies dataclass

Explicit missing-return and nontradeable-asset behavior.

MarketImpactModel dataclass

Nonlinear participation impact calibrated in basis points.

BorrowPolicy dataclass

Missing-rate and unavailable-short behavior for portfolio backtests.

MultiCurrencyPolicy dataclass

Base currency, asset currencies, and bounded missing-FX behavior.

CorporateAction dataclass

One dated, sourced portfolio-security lifecycle event.

BacktestResult dataclass

Reconciled portfolio-level backtest paths and benchmark comparisons.

require_integer(value: object, *, name: str, minimum: int | None = None) -> int

Return a normalized integer after enforcing an optional inclusive minimum.

finite_scalar(value: float, *, name: str, minimum: float | None = None) -> float

Validate one finite scalar and an optional inclusive lower bound.