Skip to main content

Multi-asset rebalance

Problem: You have separate strategies for equities, crypto, and fx. You want to allocate capital across them and rebalance the composite weekly.

Solution: Sleeve composition via mts1b-portfolio.multi_sleeve.

The setup

Three sleeves, each producing target weights internally:

sleeves = {
"equities_momentum": {
"factor": "f_momentum_12_1",
"universe": "russell-1000",
"rebal": "monthly",
"allocation": 0.5, # 50% of fund NAV
},
"crypto_mean_rev": {
"factor": "f_crypto_realized_vol",
"universe": "crypto-top-10",
"rebal": "weekly",
"allocation": 0.3,
},
"fx_carry": {
"factor": "f_fx_carry",
"universe": "g10-fx",
"rebal": "monthly",
"allocation": 0.2,
},
}

Composition

from mts1b_portfolio.multi_sleeve import compose
from mts1b_oms import OmsClient

oms = OmsClient(fund_id="multi-asset-paper", base_url="http://oms:8001")

# Each rebalance window, run all sleeves, compose, diff against current
async def rebalance(asof):
sleeve_signals = {}
for name, cfg in sleeves.items():
signal = await mts1b_research.run_sleeve(name, asof=asof)
sleeve_signals[name] = {
"weights": signal.weights,
"allocation": cfg["allocation"],
}

composite = compose(sleeves=sleeve_signals, overlay="hrp")
# composite is dict[symbol → target_weight] summing to 1.0 (gross)

current = await oms.get_positions()
diffs = compute_diff(current, composite, nav=await oms.get_nav())

for trade in diffs:
await oms.submit(trade.to_order())

Sleeve overlap handling

If a symbol appears in two sleeves (e.g. SPY in both equities momentum and a hedging sleeve), compose sums the contributions:

sleeves = {
"equities_long": {"weights": {"SPY": 0.4, "QQQ": 0.6}, "allocation": 0.6},
"spy_hedge": {"weights": {"SPY": -0.5}, "allocation": 0.4},
}

composite = compose(sleeves=sleeves, overlay="equal")
# {"SPY": 0.6*0.4 + 0.4*(-0.5) = 0.04,
# "QQQ": 0.6*0.6 = 0.36}

Net SPY exposure is small (mostly hedged); QQQ exposure is positive. The composer hides this complexity from each sleeve.

HRP overlay on sleeve returns

Instead of fixed allocation, derive from each sleeve's historical returns:

composite = compose(
sleeves=sleeve_signals,
overlay="hrp", # HRP on sleeve returns matrix
hrp_lookback_days=252,
)

If equities and crypto are negatively correlated over the last year, HRP gives them more allocation (diversification). If they've recently correlated, less.

Turnover budget

Aggressive composition causes high turnover at the seams (sleeve A says "buy SPY", sleeve B says "sell SPY"). Constrain it:

composite = compose(
sleeves=sleeve_signals,
overlay="hrp",
max_weekly_turnover=0.30, # max 30% of NAV traded per week
)

The composer solves a constrained problem: minimize tracking error to target while respecting the turnover cap.

Capacity gating

If the equities momentum sleeve wants to buy $50k of a $20m ADV stock and your envelope says max_position_pct=1% of ADV, the order is capped:

from mts1b_portfolio.capacity import position_capacity

cap = position_capacity(symbol="TINY_CORP", nav_usd=fund_nav, fraction_of_adv=0.01)
order_qty = min(target_qty, cap / order.limit_price)

mts1b-riskengine enforces this as a hard gate. The composer respects it proactively so the order isn't rejected.

Reporting

After rebalance:

from mts1b_reportslibrary import sleeve_report

report = await sleeve_report(
fund_id="multi-asset-paper",
sleeves=list(sleeves.keys()),
window="ytd",
)
# Per-sleeve P/L, Sharpe, contribution to fund return, turnover
print(report.to_table())

See also