Source code for finance_opt.schemas
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
__all__ = [
"ConstraintSpec",
"ObjectiveSpec",
"OptimizationProblem",
"OptimizationResult",
"OptimizerConfig",
"TradingCostSpec",
]
[docs]
class ObjectiveSpec(BaseModel):
model_config = ConfigDict(frozen=True)
name: Literal["min_variance", "max_sharpe", "mean_variance"] = "mean_variance"
risk_aversion: float = Field(default=1.0, ge=0.0)
[docs]
class TradingCostSpec(BaseModel):
model_config = ConfigDict(frozen=True)
linear_fee_bps: float = Field(default=0.0, ge=0.0)
spread_bps: float = Field(default=0.0, ge=0.0)
turnover_penalty: float = Field(default=0.0, ge=0.0)
impact_coefficient: float = Field(default=0.0, ge=0.0)
[docs]
class ConstraintSpec(BaseModel):
model_config = ConfigDict(frozen=True)
long_only: bool = True
budget: float = 1.0
min_weight: float | None = 0.0
max_weight: float | None = 1.0
leverage_limit: float | None = Field(default=None, gt=0.0)
max_turnover: float | None = Field(default=None, ge=0.0)
factor_bounds: dict[str, tuple[float | None, float | None]] = Field(default_factory=dict)
sector_bounds: dict[str, tuple[float | None, float | None]] = Field(default_factory=dict)
[docs]
@field_validator("max_weight")
@classmethod
def _validate_bounds(cls, value: float | None, info):
min_weight = info.data.get("min_weight")
if value is not None and min_weight is not None and value < min_weight:
raise ValueError("max_weight must be >= min_weight")
return value
[docs]
class OptimizerConfig(BaseModel):
model_config = ConfigDict(frozen=True)
solver: Literal["auto", "native", "projected_gradient"] = "auto"
adapter: Literal["auto", "clarabel_like", "numpy_qp"] = "auto"
max_iterations: int = Field(default=500, ge=1)
tolerance: float = Field(default=1e-8, gt=0.0)
constraint_penalty: float = Field(default=250.0, gt=0.0)
[docs]
class OptimizationProblem(BaseModel):
model_config = ConfigDict(frozen=True)
expected_returns: tuple[float, ...]
covariance: tuple[tuple[float, ...], ...]
asset_names: tuple[str, ...] | None = None
current_weights: tuple[float, ...] | None = None
liquidity_limits: tuple[float, ...] | None = None
lot_sizes: tuple[float, ...] | None = None
factor_names: tuple[str, ...] | None = None
factor_loadings: tuple[tuple[float, ...], ...] | None = None
sector_labels: tuple[str, ...] | None = None
objective: ObjectiveSpec = Field(default_factory=ObjectiveSpec)
costs: TradingCostSpec = Field(default_factory=TradingCostSpec)
constraints: ConstraintSpec = Field(default_factory=ConstraintSpec)
config: OptimizerConfig = Field(default_factory=OptimizerConfig)
[docs]
class OptimizationResult(BaseModel):
model_config = ConfigDict(frozen=True)
weights: tuple[float, ...]
objective_value: float
converged: bool
iterations: int
diagnostics: dict[str, float] = Field(default_factory=dict)