When building a high-leverage trading engine, the most critical component isn't just speed—it’s the Risk Engine. If your liquidation logic fails, the exchange carries the debt, and the system becomes insolvent.
In this first post of my series, I’m diving deep into how I built the liquidation safety net for my Rust-based engine.
1. What is Liquidation?
Liquidation is the automatic closure of a position when a trader's collateral (Margin) is exhausted.
In perpetual trading, leverage is a double-edged sword. For example:
- Margin: $100
- Leverage: 10x
- Position Size: $1,000
If the price moves against you, losses mount quickly. Once your $100 margin can no longer cover the loss, the engine must force-close the position to protect the exchange from losses exceeding your collateral.
2. The Liquidation Price (The Threshold)
The moment a position is opened, the engine calculates a Liquidation Price. This provides the trader with a predictable "point of no return."
The Formula
We use a 5% Maintenance Margin Rate ($0.05$). This means we trigger the close when you have 5% of your buffer remaining.
For Longs:
$$Liquidation Price = Entry Price \times (1 - \frac{0.95}{Leverage})$$
For Shorts:
$$Liquidation Price = Entry Price \times (1 + \frac{0.95}{Leverage})$$
Rust Implementation
Using rust_decimal ensures we avoid the dangerous rounding errors found in standard floating-point math.
let maintenance_buffer = dec!(1.0) - self.maintenance_margin_rate; // 0.95
let liquidation_price = match position_type {
PositionType::Long => {
entry_price * (dec!(1.0) - (maintenance_buffer / leverage))
}
PositionType::Short => {
entry_price * (dec!(1.0) + (maintenance_buffer / leverage))
}
};
3. Real-Time Maintenance Checks
While the Liquidation Price is set at the start, liquidations happen in real-time. On every price update, the engine performs a "Health Check":
Rule: Current Equity (Margin + PnL) <= Maintenance Threshold (Margin * 0.05)
The Logic in Action
fn should_liquidate(&self, position: &Position) -> bool {
let current_equity = position.margin + position.pnl;
let maintenance_threshold = position.margin * self.maintenance_margin_rate;
current_equity <= maintenance_threshold
}
pub fn update_price(&mut self, new_price: Decimal) -> Result<UpdateResult, String> {
// ... update PnL for all positions ...
let to_liquidate: Vec<Uuid> = self
.positions
.values()
.filter(|p| self.should_liquidate(p)) // Check every position
.map(|p| p.id)
.collect();
for id in to_liquidate {
self.close_position(id)?; // Force close
}
Ok(UpdateResult { ... })
}
4. Edge Cases: Beyond the Price
A robust engine must account for more than just price swings.
Edge Case 1: Funding Liquidation
In perpetuals, funding is applied periodically (every hour in my engine). If funding costs are large enough, they can erode a trader's margin and trigger liquidation even if the market price hasn't moved.
pub fn apply_funding(&mut self) -> Result<FundingResult, String> {
// ... apply funding payments ...
// Check for liquidations post-funding
let to_liquidate: Vec<Uuid> = self
.positions
.values()
.filter(|p| self.should_liquidate(p))
.map(|p| p.id)
.collect();
for id in to_liquidate {
self.close_position(id)?;
}
Ok(FundingResult { ... })
}
Edge Case 2: Double-Liquidation Prevention
Race conditions are real. If a price update and a funding payment hit simultaneously, you might try to close the same position twice. Using Result<T, E> and atomic-style state removal handles this gracefully.
pub fn close_position(&mut self, position_id: Uuid) -> Result<Decimal, String> {
let position = self.positions.remove(&position_id)
.ok_or_else(|| format!("Position not found"))?;
// If it's already closed, we return an Error rather than panicking.
// This ensures our engine stays online and consistent.
}
5. Summary Table
| Concept | What | When | Why |
|---|---|---|---|
| Liq Price | Pre-calculated threshold | Position Open | User transparency & risk awareness |
| Maintenance Check | Real-time equity check | Every price update | Catching volatility/flash moves |
| Close Position | Force-close the trade | When check triggers | Preventing negative equity/systemic loss |
| Error Handling |
Result<T, E> logic |
On liquidation | Preventing double-closes & race conditions |
Conclusion
Proper liquidation logic ensures that losses are capped at the user's margin, the exchange stays solvent, and the system remains fair for all traders.
Top comments (0)