Repository navigation
feat(execution): Liquidate the simulated venue's spot margin account at a configured margin level - #812
Merged
Conversation
…at a configured margin level `SpotMarginConfig::with_liquidation(SpotMarginLiquidation)` turns it on: a liquidation level, a fee rate on the debt repaid, and an optional margin-call level. After every market tick and clock move, an account at or below the level is sent a `Liquidation` notice, has its open orders cancelled, and has every debt closed by forced taker trades reported as `TradeOrigin::Liquidation` fills, selling other assets for the valuation asset as needed. The fee is reported as the new `CashFlowKind::LiquidationFee`. A debt it cannot close at once stays owed, and the liquidation continues on later inputs. The margin-call level sends `MarginCall` and `MarginRestored` once per crossing. The leverage limit and the liquidation check share one valuation of the account.
…nfig already imply
… fix the rounding nudge A forced trade the account's balance limits, whose quantity rounded up to cost more than the balance, was nudged down a digit and then back up, until its four tries ran out, so it traded nothing. A quantity that costs too much now lowers the cap, and the largest affordable quantity tried is traded. New tests cover selling collateral before a buy-back, a fee the free balance partly covers, the nudge in both directions, the quantity the fill model is asked to price, a pair without a market, a leg whose fee takes all it delivers, distinct fee ids, and the warnings that are given once. `raise` already does nothing for a need that is not positive, so its callers no longer check, and the lending limit takes the headroom's maximum with zero rather than matching on its sign.
…zero, and pin the cancel order - A dust debt at a tiny price can value at zero, and the margin level divides by the debt, so the check now treats a zero valuation as nothing to liquidate rather than panicking. - A test cancels a held market order and two resting orders placed out of order, and pins that each releases what it held, by client order id. - `SpotMarginLiquidation` is `Copy`, and `SpotMarginConfig::liquidation` returns it by value. - The docs say a forced trade draws from the slippage stream once priced, even if it trades nothing, and that `apply_market`'s margin check runs once, at the end of the tick.
…way, and document the fee's priority - While a liquidation that could not close every debt is still under way, the simulated venue lends nothing, so an order that would need a loan is rejected as unaffordable, its reason showing nothing borrowable. An order the account can pay for from its own balance is still accepted, and cancelled by the next input if it is still open. Once nothing is owed, the venue lends again. - The docs and CHANGELOG say that each pass charges its fee before any debt it leaves owed, selling for it as for the debt, as a venue takes its fee out of the proceeds of the liquidation's trades. - The dust-debt check's comment says the liquidation state is left as it was.
… orders, and clarify the docs - The lending-gate test also has a market sale refused for a loan the leverage limit would grant, and asserts the self-funded order is reported Cancelled, not just named. - A new test holds a self-funded market order for the next bar while a liquidation is under way; the bar's open makes its fill need a loan, so the order is cancelled and the liquidation then closes the remaining debt. - The docs and CHANGELOG say each pass's fee comes out of what the account holds after that pass's repayments, that a held order's fill needing a loan is cancelled, and that a debt the venue cannot trade out of keeps lending off until the account repays it.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #803
What
The simulated venue can now liquidate its spot margin account. It is off by default, and a
configuration without it deserialises and behaves exactly as before.
SpotMarginConfig::with_liquidation(SpotMarginLiquidation::new(level, fee_rate)?)sets:the valuation asset), at least 1;
[0, 1), charged on the value of the debt the liquidation repays.SpotMarginLiquidation::with_margin_call(level)also adds a margin-call level above theliquidation level.
When
After every input (
advance_timeandapply_market), last, the venue values the account thesame way the leverage limit does. The mid price is used, else the last price, of a spot pair with
the valuation asset. A margin call or liquidation is checked for at that point.
NoticeKind::MarginCallis sent when the account falls to it andNoticeKind::MarginRestoredwhen it rises back above it. Each is sent once per crossing. Aliquidation ends a margin call without a
MarginRestored.warn!per venue. Collateral it cannotvalue counts as zero.
How a liquidation runs
AccountNoticeofNoticeKind::Liquidation, carrying the margin level.name order, and only as much as needed.
free valuation balance, other assets are sold first to cover the difference.
CashFlowKind::LiquidationFee { rate, repaid }.If the free balance is short, more assets are sold first. A fee that still cannot be paid takes
freebelow zero, with awarn!.Each forced trade fills as a market order on arrival would, through the venue's fill model and
slippage, as a taker, and against the depth on offer. It is a
TradewithTradeOrigin::Liquidation,StrategyId::unknown(), no client order id and no order snapshot,and it repays the debt of the asset it delivers.
A debt that one pass cannot close (thin depth, no market, too little left to sell) is left owed,
with one
warn!per liquidation. The liquidation continues on every later input, without anothernotice, until the account owes nothing.
While a liquidation is under way the venue lends nothing. An order that would need a loan is
rejected as unaffordable, its reason showing nothing borrowable, and a held order whose fill would
need one is cancelled. An order the account can pay for from its own balance is accepted, and
cancelled on the next input if it is still open. A debt the venue cannot trade out of keeps
lending off until the account repays it.
Configuration checks
with_liquidationreturns aSpotMarginConfigError:LiquidationLevelBelowOne;LiquidationFeeOutOfRange;MarginCallNotAboveLiquidation;LiquidationWithinLeverage, for a level ofL / (L − 1)or more for a maximum leverageL.An account borrowed to the limit is at that margin level, so the venue would liquidate an
account for borrowing what it was allowed to.
CFD positions are not liquidated. The docs say so where CFD accounting is described.
Choices worth a look
is charged once. Each pass's fee comes out of what the account holds after that pass's
repayments, selling for it as for the debt, as a venue takes its fee out of the proceeds of the
liquidation's trades. So a debt a pass leaves owed has that much less to be repaid from.
under auto-repay-at-cancel is not charged the fee.
the quantity is nudged by its last digit until the trade delivers all of what is needed without
costing more than the account holds. If a quantity costs too much, the cap is lowered, so the
nudge cannot climb back to it.
trades nothing (for example, the book has no depth).
shortfall warning.
Also
SimulatedVenue::borrowablenow shares the account valuation (valued) with the liquidationcheck, so the two cannot drift apart.
TradeOrigintable, theAccountNoticeproducers list and theCashFlowKindproducers listnow include the simulated venue.
Tests
exchange/mock/venue/tests/liquidation.rs, 25 tests:order's fill;
margin.rs.rustrade/tests/test_sim_venue_spot_margin.rs: a liquidation fed through the indexer intoEngineStateleaves the position flat, and the engine's balances equal the venue's.