base¶
Shared plumbing every other portpy.models submodule builds on: the ModelResult type every builder/optimizer/rebalancer returns, the constraint objects .optimization solvers understand, and the multi-start SLSQP solve routine (solve_weights) they're all built on. See Construction, optimization & management for how these fit together.
ModelResult¶
ModelResult
dataclass
¶
ModelResult(
name: str,
weights: Series,
diagnostics: dict[str, Any] = dict(),
meta: dict[str, Any] = dict(),
)
The structured result of any .models builder/optimizer/rebalancer call -
the model-layer counterpart to :class:~portpy.explain.MetricResult.
Behaves like a plain container (not a float): the number you usually want
is a whole weight vector, not a scalar. Explains itself the same way
MetricResult does, via the _portpy_explain_name/_portpy_explain_value
hook in portpy.explain.explain - _portpy_explain_value is set to the
whole result (not a bare float), since a model's interpret reads
weights/diagnostics, not a single number.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Registry key this result explains itself with (e.g. "mean_variance"). |
weights |
Series
|
Solved (or otherwise resulting) allocation, indexed by asset.
For |
diagnostics |
dict[str, Any]
|
Solver/estimation internals - status, objective value, iterations, inputs used. Shape varies by model; see each model's registered Explanation for what to expect. |
meta |
dict[str, Any]
|
Free-form extra context (e.g. the method/constraints used to build this). |
explain ¶
Print (by default) and return the full explanation for this result's model type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
print_it
|
bool
|
If True (default), also print() the rendered text. |
True
|
Returns:
| Type | Description |
|---|---|
str
|
The rendered explanation text. |
Source code in src/portpy/models/base.py
summary ¶
A tabular view: weights sorted by absolute size (or, for efficient_frontier
results, the full return/volatility/weights curve).
Returns:
| Type | Description |
|---|---|
DataFrame
|
A DataFrame carrying |
DataFrame
|
|
Source code in src/portpy/models/base.py
plot ¶
Render this result via portpy.visualization (not yet implemented).
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Always, until Stage 2 ( |
Source code in src/portpy/models/base.py
compare ¶
Compare this result's weights against another result/portfolio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
ModelResult | Series
|
Another |
required |
**kwargs
|
Any
|
Forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
ModelResult
|
A |
Source code in src/portpy/models/base.py
Constraint objects¶
base ¶
Shared plumbing for :mod:portpy.models: the result type every builder/optimizer/
rebalancer returns, the constraint objects solvers understand, and the numerical
solve routine (multi-start SLSQP) every function in :mod:portpy.models.optimization
is built on.
PortPy's models extra deliberately stays scipy-only (no cvxpy/cvxopt) - every
solver here is a general nonlinear program handed to scipy.optimize.minimize
(SLSQP), not a specialized convex QP/SOCP solve. That trades a little numerical
polish on the classic convex cases (mean-variance, min-variance) for one solver
path that also handles the genuinely non-convex ones (max_sharpe's ratio
objective, maximum_diversification) and arbitrary constraint combinations
without a second code path.
WeightBounds
dataclass
¶
WeightBounds(
low: float = 0.0,
high: float = 1.0,
per_asset: dict[str, tuple[float, float]] | None = None,
)
Per-asset weight bounds. low/high apply to every asset unless overridden
in per_asset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
low
|
float
|
Minimum weight for any asset not listed in |
0.0
|
high
|
float
|
Maximum weight for any asset not listed in |
1.0
|
per_asset
|
dict[str, tuple[float, float]] | None
|
Optional |
None
|
GroupCap
dataclass
¶
GroupCap(
groups: dict[str, list[str]],
max_weight: float | dict[str, float],
min_weight: float | dict[str, float] | None = None,
)
Caps (and optionally floors) total exposure to named groups of assets -
e.g. sectors or core.asset.AssetClass buckets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
groups
|
dict[str, list[str]]
|
|
required |
max_weight
|
float | dict[str, float]
|
Maximum summed weight per group - either one number applied
to every group, or a |
required |
min_weight
|
float | dict[str, float] | None
|
Optional minimum summed weight per group, same shape as |
None
|
TurnoverCap
dataclass
¶
Caps how far the solved weights may move away from a starting weight vector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_turnover
|
float
|
Maximum allowed |
required |
current_weights
|
Series | None
|
Starting weights. When left |
None
|
NetExposure
dataclass
¶
sum(w) == target - the usual full-investment constraint (target=1.0).
Applied automatically with target=1.0 when a constraint list has neither
this nor GrossExposure, so every solver is fully-invested by default.
GrossExposure
dataclass
¶
sum(abs(w)) == target - caps total (long + short) exposure, the natural
constraint for a long/short book (e.g. a 130/30 fund uses target=1.6).
Solver plumbing¶
solve_weights ¶
solve_weights(
objective: Callable[[ndarray], float],
asset_names: Sequence[str],
constraints: Sequence[Any] | None = None,
jac: Callable[[ndarray], ndarray] | None = None,
n_restarts: int = 4,
seed: int = 7,
maxiter: int = 1000,
extra_scipy_constraints: Sequence[dict[str, Any]]
| None = None,
) -> tuple[pd.Series, dict[str, Any]]
Minimize objective(w) over portfolio weights via multi-start SLSQP.
A full-investment constraint (NetExposure(1.0)) is applied automatically
if constraints contains neither NetExposure nor GrossExposure. SLSQP's
result is sensitive to its starting point, so this tries the equal-weight
portfolio plus n_restarts - 1 random feasible-ish (Dirichlet, clipped to
bounds) starts and keeps the best converged solution (or, if none converge,
the lowest-objective attempt, flagged via diagnostics["success"] = False).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
objective
|
Callable[[ndarray], float]
|
Function of a weight array (in |
required |
asset_names
|
Sequence[str]
|
Asset order for the decision vector and the returned Series. |
required |
constraints
|
Sequence[Any] | None
|
Constraint objects (see |
None
|
jac
|
Callable[[ndarray], ndarray] | None
|
Optional analytic gradient of |
None
|
n_restarts
|
int
|
Number of SLSQP starting points to try. |
4
|
seed
|
int
|
RNG seed for the random restarts (deterministic across calls). |
7
|
maxiter
|
int
|
Max iterations per SLSQP attempt. |
1000
|
extra_scipy_constraints
|
Sequence[dict[str, Any]] | None
|
Additional raw |
None
|
Returns:
| Type | Description |
|---|---|
Series
|
|
dict[str, Any]
|
|
tuple[Series, dict[str, Any]]
|
|
Source code in src/portpy/models/base.py
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |