Skip to content

Perpetual futures: initial margin, and a pool-maintained price average with a trading band - #176

Merged
mikemaccana merged 1 commit into
mainfrom
claude/perps-margin-price-band
Oct 2, 2026
Merged

mikemaccana merged 1 commit into
mainfrom
claude/perps-margin-price-band

Conversation

@mikemaccana

@mikemaccana mikemaccana commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

From Noah Prince's review of the book's Perpetual Futures chapter. All three implementations (Anchor v2, Anchor v1, Quasar).

Initial margin

max_leverage becomes initial_margin_bps, validated above maintenance_margin_bps (InitialMarginNotAboveMaintenance). open_position requires net collateral of at least initial_margin_bps of the size (InitialMarginNotMet, replacing LeverageTooHigh); the separate maintenance check at open is implied by the initial margin being higher. Maximum leverage is one over the initial margin.

Price band

The pool keeps a time-weighted average of the oracle price: average_price, last_oracle_price, average_price_timestamp, over PRICE_AVERAGE_WINDOW_SECONDS (600). Each update credits the elapsed time to the price seen at the previous read, so a single manipulated read after an idle spell moves nothing (test_one_manipulated_read_after_idle_does_not_move_average).

  • open_position, close_position, add_liquidity and remove_liquidity refuse an oracle price more than max_price_deviation_bps from the stored average (PriceOutsideBand).
  • liquidate_position folds the price in without the check, so liquidations run through a genuine move.
  • New permissionless update_price_average() lets the average catch up after a genuine move.

Tests

Anchor v2: 37, Anchor v1: 37, Quasar: 28, all passing; cargo fmt and clippy -D warnings clean.

Split out of #175, which is the fundraiser fix only. #179 (the haircut risk model, replacing the closed #83) builds on this branch; merge this one first.

🤖 Generated with Claude Code

https://claude.ai/code/session_019G9tytYrS3Qp42fZ1hBnDu

…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)
@mikemaccana
mikemaccana merged commit f529467 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