rebalance_band

Defined in fynance.portfolio.rebalance

rebalance_band(W, X, band=0.05, mode='full')[source]

Rebalance only when the drift leaves a no-trade band around the target.

Each bar the held book is drifted with asset returns and compared to the current target; a trade is triggered only when the largest per-asset deviation exceeds band, \(\max_i |w^{\text{drift}}_{t,i} - W_{t,i}| > band\). When it does:

  • mode='full' trades all the way back to the target W[t];

  • mode='edge' trades each asset only to the near band edge, i.e. clips the drifted weight to [W[t] - band, W[t] + band] — the breaching asset lands exactly on the boundary and the book stays inside the band while trading as little as possible.

Bar 0 always establishes the full target book; the band governs bars >= 1.

Parameters:
Warray_like

Target weights, shape (T, N) (1-D promoted to (T, 1), output squeezed back). Long-short books are supported.

Xarray_like

Price/level panel aligned with W, same shape.

bandfloat, optional

No-trade half-width around each target weight; must be >= 0. Default 0.05.

mode{‘full’, ‘edge’}, optional

Whether a triggered trade goes to the target ('full', default) or only to the band edge ('edge').

Returns:
np.ndarray

Effective weights actually held, same shape as W.

Raises:
ValueError

If W and X do not share the same shape, contain non-finite values, band < 0, or mode is not 'full' / 'edge'.

See also

rebalance_calendar
rebalance_turnover_cap

Examples

A 20% one-day divergence breaks a 5% band; 'full' snaps back to target while 'edge' stops on the band boundary:

>>> import numpy as np
>>> W = np.array([[0.5, 0.5], [0.5, 0.5]])
>>> X = np.array([[100.0, 100.0], [120.0, 80.0]])
>>> rebalance_band(W, X, band=0.05, mode='full')
array([[0.5, 0.5],
       [0.5, 0.5]])
>>> rebalance_band(W, X, band=0.05, mode='edge')
array([[0.5 , 0.5 ],
       [0.55, 0.45]])