Skip to content

Perpetual futures: Percolator haircut risk model and an insurance fund - #179

Merged
mikemaccana merged 2 commits into
mainfrom
claude/perps-haircut
Oct 2, 2026
Merged

mikemaccana merged 2 commits into
mainfrom
claude/perps-haircut

Conversation

@mikemaccana

Copy link
Copy Markdown
Collaborator

Replaces the reserve-and-cap risk model with the one from Anatoly Yakovenko's Percolator: trader collateral is senior, profit is junior and paid only as far as the pool can back it. All three implementations (Anchor v2, Anchor v1, Quasar).

Replaces #83, which was built on pre-rewrite history; this reimplements its design on current code.

Builds on #176 (initial margin and price band). Merge #176 first; this PR's own change is the top commit.

Changes

  • No reservation, no profit cap. reserved_liquidity and the open-time backing check are removed; positions open regardless of pool size.
  • Haircut. close_position pays a winner profit × h, h = min(1, (liquidity + insurance_fund) / max(net unrealized profit, the closer's own profit)), floored at HAIRCUT_PRECISION, computed before the closer leaves the accumulators. Every winner is paid the same fraction, and no winning close is refused for lack of backing (a winner closing while open losers still offset them is paid at most the pool's backing). Profit is paid from liquidity, then insurance_fund.
  • Profit warm-up. Profit can be taken only profit_warmup_slots after Position.entry_slot (ProfitNotMatured); losses and liquidations are not delayed.
  • Insurance fund. insurance_fee_bps of every open and close fee goes to insurance_fund (the rest to program_fees), which pays a bankrupt position's deficit before the liquidity providers.
  • Forgiven liquidation fee. As in Percolator, a liquidation fee the position's equity cannot cover is forgiven, never paid from insurance or liquidity. Liquidation stays permissionless.
  • Withdrawals are capped at liquidity (InsufficientLiquidity).

Tests

Anchor v2: 47, Anchor v1: 47, Quasar: 39, all passing; cargo fmt and clippy -D warnings clean. New tests include test_haircut_scales_profit_when_pool_stressed, test_winner_offset_by_open_loser_is_paid_not_refused, test_profit_blocked_before_maturation, test_insurance_fund_funded_by_fees and test_liquidation_of_bankrupt_position_charges_insurance_before_liquidity.

🤖 Generated with Claude Code

https://claude.ai/code/session_019G9tytYrS3Qp42fZ1hBnDu


Generated by Claude Code

Mike MacCana added 2 commits October 1, 2026 20:37
…e with a trading band

Initial margin: max_leverage becomes initial_margin_bps, which must exceed
maintenance_margin_bps (InitialMarginNotAboveMaintenance). open_position
requires net collateral of at least initial_margin_bps of the size
(InitialMarginNotMet); the separate maintenance check at open is implied.

Price band: the pool keeps a time-weighted average of the oracle price
(average_price, last_oracle_price, average_price_timestamp, ten-minute
window). Each fold credits the elapsed time to the price seen at the
previous read, so one manipulated read after an idle spell moves nothing.
open_position, close_position, add_liquidity and remove_liquidity refuse
an oracle price more than max_price_deviation_bps from the average
(PriceOutsideBand). liquidate_position and the new permissionless
update_price_average() fold without the check, so liquidations run
through a genuine move and the average can catch up with it.

All three implementations; anchor 37, anchor-v1 37, quasar 28 tests.

Claude-Session: https://claude.ai/code/session_019G9tytYrS3Qp42fZ1hBnDu
(cherry picked from commit 1f1a46e)
Replaces reserve-and-cap with the risk model from Anatoly Yakovenko's
Percolator: trader collateral is senior and profit is junior, paid only
as far as the pool can back it.

- Nothing is reserved when a position opens, and profit has no cap.
  reserved_liquidity is removed.
- close_position pays a winner profit x h, with
  h = min(1, (liquidity + insurance_fund) / max(net unrealized profit,
  the closer's own profit)), computed before the position leaves the
  accumulators. Winners are paid the same fraction and are never refused
  for lack of backing; profit is paid from liquidity, then insurance.
- Profit warm-up: profit can be taken only profit_warmup_slots after
  entry_slot (ProfitNotMatured); losses and liquidations are not delayed.
- insurance_fee_bps of every fee goes to insurance_fund, which pays a
  bankrupt position's deficit before the liquidity providers. A
  liquidation fee the position's equity cannot cover is forgiven.
- remove_liquidity is capped at liquidity (InsufficientLiquidity).

Reimplements #83 on current code, in
all three implementations. Anchor v2 47, Anchor v1 47, Quasar 39 tests.

Claude-Session: https://claude.ai/code/session_019G9tytYrS3Qp42fZ1hBnDu
@mikemaccana
mikemaccana merged commit 6ff65c3 into main Oct 2, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant