Skip to content

Rebalancer

Stateless allocation math library for MultiStrategyVault. Computes per-strategy target debt, deviation, and deposit caps. Not imported at deploy — logic inlined in MSV (titanoboa 0.2.8 + Vyper 0.4.3 constructor issue).

Source: khomdev-keep/src/Rebalancer.vy (Vyper 0.4.x)

Consumer: MSV rebalance · allocateCapital

Implementation overview

  • Fixed mode — targets from manager target_weight_bps, minus liquid buffer, capped by max_debt
  • Smart mode — APR-proportional targets; falls back to fixed weights when all APRs zero
  • Lazy rebalance — skip moves within REBALANCE_THRESHOLD_BPS (5%) deviation
  • CoilMakercompute_coilmaker_allocation bounds idle → maker deploy
flowchart TD
    TVL[TVL idle + debt] --> RT{smart routing?}
    RT -->|no| FIX[compute_fixed_targets]
    RT -->|yes| SMART[compute_smart_targets]
    FIX --> DEV[needs_rebalance]
    SMART --> DEV

Constants

Name Value Role
REBALANCE_THRESHOLD_BPS 500 (5%) Min deviation to trigger rebalance
MAX_BPS 10_000 Weight / buffer math

Internal functions

compute_fixed_targets

Rebalancer.compute_fixed_targets(tvl, buffer_bps, target_weights, max_debts) -> DynArray[uint256, 10]

Per-strategy target debt from manager-set weight BPS (fixed allocation mode).

Param Type Description
tvl uint256 total_idle + total_debt
buffer_bps uint256 Liquid buffer kept idle (BPS of TVL)
target_weights uint256[≤10] Each strategy's target_weight_bps
max_debts uint256[≤10] Per-strategy max_debt ceiling (0 = uncapped)

Returns: DynArray[uint256, 10] — target debt per strategy in underlying units. Empty if no strategies.

Math: deployable = tvl - tvl * buffer_bps / MAX_BPS; each target = deployable * weight / MAX_BPS, capped by max_debt.

Access: @internal @pure — inlined in MSV when smart routing off.

Source (khomdev-keep/src/Rebalancer.vy:25-58):

@internal
@pure
def compute_fixed_targets(tvl, buffer_bps, target_weights, max_debts):
    deployable = tvl - tvl * buffer_bps // MAX_BPS
    target = deployable * weight // MAX_BPS
    if max_debt != 0 and target > max_debt: target = max_debt
    return targets

Example:

targets = Rebalancer.compute_fixed_targets(tvl, buffer_bps, weights, caps)

Cross-links: compute_smart_targets · MultiStrategyVault rebalance

compute_smart_targets

Rebalancer.compute_smart_targets(tvl, buffer_bps, aprs, max_debts, fallback_weights) -> DynArray[uint256, 10]

APR-proportional target debt (smart routing mode). Zero-APR strategies get 0. All APRs zero → compute_fixed_targets.

Param Type Description
tvl uint256 Total assets under management
buffer_bps uint256 Liquid buffer BPS
aprs uint256[≤10] Each strategy's getApr()
max_debts uint256[≤10] Per-strategy debt ceilings
fallback_weights uint256[≤10] Fixed weights when sum(aprs) == 0

Returns: DynArray[uint256, 10] — target debt per strategy, max_debt-capped.

Access: @internal @pure — inlined when MSV smart routing on.

Source (khomdev-keep/src/Rebalancer.vy:63-108):

@internal
@pure
def compute_smart_targets(tvl, buffer_bps, aprs, max_debts, fallback_weights):
    if sum_apr == 0:
        return self.compute_fixed_targets(tvl, buffer_bps, fallback_weights, max_debts)
    target = deployable * aprs[i] // sum_apr
    return targets

Example:

targets = Rebalancer.compute_smart_targets(tvl, buffer_bps, aprs, caps, weights)

Cross-links: compute_fixed_targets · MultiStrategyVault smart routing

compute_deviation

Rebalancer.compute_deviation(current, target) -> uint256

Absolute deviation between current and target debt in BPS.

Param Type Description
current uint256 Strategy's current_debt
target uint256 Target debt from fixed/smart targets

Returns: uint256 — deviation bps. 0 when equal (including both zero). Divisor = smaller of the two (max_uint if smaller is 0).

Access: @internal @pure — lazy-rebalance gate input.

Source (khomdev-keep/src/Rebalancer.vy:113-127):

@internal
@pure
def compute_deviation(current, target) -> uint256:
    if current == target:
        return 0
    if target > current:
        divisor = current if current > 0 else max_value(uint256)
        return (target - current) * 10_000 // divisor
    # symmetric when current > target

Example:

bps = Rebalancer.compute_deviation(current_debt, target_debt)

Cross-links: needs_rebalance · REBALANCE_THRESHOLD_BPS

needs_rebalance

Rebalancer.needs_rebalance(current, target) -> bool

Whether deviation exceeds the 5% lazy-rebalance threshold.

Param Type Description
current uint256 Strategy current_debt
target uint256 Computed target debt

Returns: boolTrue if compute_deviation > REBALANCE_THRESHOLD_BPS (500).

Access: @internal @pure — MSV skips micro-moves when False.

Source (khomdev-keep/src/Rebalancer.vy:132-138):

@internal
@pure
def needs_rebalance(current, target) -> bool:
    return self.compute_deviation(current, target) > REBALANCE_THRESHOLD_BPS

Example:

if Rebalancer.needs_rebalance(debt, target):
    # push or pull capital

Cross-links: compute_deviation · MultiStrategyVault rebalance

cap_deposit

Rebalancer.cap_deposit(want, current_debt, max_debt, total_idle) -> uint256

Bound a strategy deposit by max_debt headroom and vault idle.

Param Type Description
want uint256 Desired deposit amount
current_debt uint256 Strategy's booked debt
max_debt uint256 Strategy ceiling (0 = uncapped)
total_idle uint256 Vault idle USDC

Returns: uint256 — capped deposit (may be 0).

Access: @internal @pure — used during allocate/rebalance pushes.

Source (khomdev-keep/src/Rebalancer.vy:143-158):

@internal
@pure
def cap_deposit(want, current_debt, max_debt, total_idle) -> uint256:
    if max_debt != 0 and current_debt + want > max_debt:
        want = max_debt - current_debt if max_debt > current_debt else 0
    if want > total_idle:
        want = total_idle
    return want

Example:

amount = Rebalancer.cap_deposit(push, debt, max_debt, idle)

Cross-links: compute_fixed_targets · MultiStrategyVault allocateCapital

compute_coilmaker_allocation

Rebalancer.compute_coilmaker_allocation(idle, deploy_available, current_debt, max_debt) -> uint256

How much idle USDC to route into CoilMakerStrategy (FLYWHEEL 2.0).

Param Type Description
idle uint256 Vault idle balance
deploy_available uint256 Maker's remaining deploy capacity
current_debt uint256 CoilMaker booked debt
max_debt uint256 CoilMaker max_debt (0 = uncapped)

Returns: uint256min(idle, deploy_available), further capped by max_debt - current_debt. 0 at capacity.

Access: @internal @pure — MSV CoilMaker allocation path.

Source (khomdev-keep/src/Rebalancer.vy:163-177):

@internal
@pure
def compute_coilmaker_allocation(idle, deploy_available, current_debt, max_debt):
    to_coil = min(idle, deploy_available)
    if max_debt != 0 and current_debt + to_coil > max_debt:
        to_coil = max_debt - current_debt if max_debt > current_debt else 0
    return to_coil

Example:

to_coil = Rebalancer.compute_coilmaker_allocation(idle, deploy_cap, debt, cap)

Cross-links: CoilMakerStrategy · cap_deposit