project_weights¶
Defined in fynance.portfolio.constraints
- project_weights(w, box=(-1.0, 1.0), gross_max=None, net_range=None, groups=None, group_bounds=None, method='auto')[source]
Project weights onto a feasible set: box, gross, net and group bounds.
Least-distance (Euclidean) projection of a weight vector, or of every row of a
(T, N)weight book independently, onto the intersection of:a per-asset box (
lo_i <= w_i <= hi_i);a gross-leverage cap (
sum(|w_i|) <= gross_max);a net-exposure range (
net_lo <= sum(w_i) <= net_hi);named group bounds (
lo_g <= sum_{i in g} w_i <= hi_g).
Meant as a composable overlay run after any allocator or signal and before
fynance.backtest.engine.backtest.- Parameters:
- warray_like
Weights, shape
(N,)or(T, N). When 2-D, each row is projected independently (no cross-row interaction) and the output has the same shape.- boxtuple of float or array_like, optional
Either a scalar
(lo, hi)pair applied to every asset, or a(2, N)array-like of per-asset(lo_i, hi_i)bounds. Must satisfylo <= hielement-wise. Default(-1.0, 1.0).- gross_maxfloat, optional
Cap on gross leverage
sum(|w_i|). DefaultNone(no cap).- net_rangetuple of float, optional
(lo, hi)bounds on the net exposuresum(w_i). DefaultNone(unconstrained).- groupsdict of str to sequence of int, optional
Named groups of asset indices, e.g.
{'tech': [0, 1], 'bond': [2]}. Only groups referenced by group_bounds constrain the projection. DefaultNone.- group_boundsdict of str to tuple of float, optional
(lo, hi)bounds on each named group’s net sumsum_{i in group} w_i. Every key must exist in groups. DefaultNone(no group constraints).- method{‘auto’, ‘exact’}, optional
'auto'(default) uses a closed-form fast path (clip then scale) when only box and/or gross_max are active and the box contains zero;'exact'always uses the SLSQP least-distance projection.
- Returns:
- np.ndarray
Projected weights, same shape as w.
- Raises:
- ValueError
If box lower bounds exceed upper bounds, if a group_bounds key is not in groups, if a group’s indices are out of range, if the constraint set is infeasible (message contains
'infeasible'), or if SLSQP fails to converge on the exact path.
Notes
The fast path is exact for the box (a plain clip), but the gross cap is applied as a uniform scaling of the clipped vector, not the strict least-distance projection onto
{box} \cap {gross <= gross_max}— this is standard practice (cheap, and identical to the exact projection whenever the box does not bind at the optimum). Passmethod='exact'to force the SLSQP least-distance projection instead.Examples
Fast-path gross cap on a 3-asset long-short book:
>>> import numpy as np >>> w = np.array([0.8, -0.6, 0.3]) >>> v = project_weights(w, box=(-1.0, 1.0), gross_max=1.0) >>> np.round(v, 4) array([ 0.4706, -0.3529, 0.1765])
Group-bound example — cap the net “tech” exposure to
[-0.5, 0.5]:>>> w = np.array([0.8, 0.6, -0.3]) >>> v = project_weights( ... w, groups={'tech': [0, 1], 'bond': [2]}, ... group_bounds={'tech': (-0.5, 0.5)}, ... ) >>> np.round(v, 6) array([ 0.35, 0.15, -0.3 ])