Skip to content

Latest commit

 

History

History
336 lines (222 loc) · 8.84 KB

File metadata and controls

336 lines (222 loc) · 8.84 KB

Mathematical Model

Complete derivation of all formulas used by the Flash Risk Engine. Every formula references its source in the Flash Trade reference implementation or SDK.


Unit System

All arithmetic is performed with arbitrary-precision integers (BN.js). No intermediate computation uses floating-point. The following scales are used throughout:

Scale Name Value Usage
USD_DECIMALS 10^6 All USD amounts: size, collateral, margin, PnL, fees
ORACLE_PRICE_SCALE 10^9 All oracle prices (entry, exit, liquidation, current)
BPS_DECIMALS 10^4 Leverage ratios, fee ratios, max_leverage thresholds
RATE_DECIMALS 10^9 Borrow rates, utilization, lock fee accumulators
Trade spread 10^4 * 100 = 10^6 Spread values are in 100ths of a basis point

Token Amount to USD Conversion

usd = token_amount * oracle_price / 10^(token_decimals + 3)

The +3 derives from: |ORACLE_EXPONENT_SCALE| - USD_DECIMALS = 9 - 6 = 3.

Source: flash-sdk-rust/programs/flash-read/src/states.rs -> impl OraclePrice -> fn get_asset_amount_usd


Oracle Price Model

Price Range (Divergence Banding)

The protocol uses a dual-oracle model. The Custom Oracle account provides spot price, EMA price, and a confidence interval. The price range is determined by comparing spot-EMA divergence against a custody-specific threshold:

divergence = |spot_price - ema_price| * BPS_POWER / ema_price

if divergence <= max_divergence_bps:
    min_price = spot_price
    max_price = spot_price
else:
    min_price = max(0, spot_price - confidence)
    max_price = spot_price + confidence

When divergence is low, the engine uses a single price (no confidence spread). When divergence is high, the confidence band widens the price range.

Source: flash-sdk-rust/programs/flash-read/src/states.rs -> impl OraclePrice -> fn fetch_from_oracle

Exit Price (Pessimistic Pricing)

The exit price is the worst-case price for the trader, adjusted by the trade spread:

spread = trade_spread_min + (trade_spread_max - trade_spread_min)
         * size_usd / max_position_size_usd

scale = BPS_POWER * 100    // = 1,000,000

Long exit:   exit_price = min_price * (scale - spread) / scale
Short exit:  exit_price = max_price * (scale + spread) / scale

Long positions exit at the lower price minus spread. Short positions exit at the higher price plus spread.

Source: flash-perpetuals/programs/perpetuals/src/state/pool.rs -> fn get_exit_price


PnL Computation

Price-Based PnL

Long:
    price_diff_profit = max(0, exit_price - entry_price)
    price_diff_loss   = max(0, entry_price - exit_price)

Short:
    price_diff_profit = max(0, entry_price - exit_price)
    price_diff_loss   = max(0, exit_price - entry_price)

profit_usd = size_usd * price_diff_profit / entry_price
loss_usd   = size_usd * price_diff_loss   / entry_price

Fee-Inclusive PnL

The total loss includes borrow interest and close fee:

total_loss   = price_loss + interest_usd + close_fee_usd
total_profit = min(price_profit, locked_value_usd)    // profit cap

Profit is capped at the position's locked collateral value in USD.

Net PnL

if total_profit > total_loss:
    final_profit = total_profit - total_loss
    final_loss   = 0
else:
    final_profit = 0
    final_loss   = total_loss - total_profit

Source: flash-perpetuals/programs/perpetuals/src/state/pool.rs -> fn get_pnl_usd


Margin and Leverage

Margin

current_margin_usd = collateral_usd + final_profit - final_loss
margin = max(0, current_margin_usd)    // clamp to zero

Source: flash-perpetuals/programs/perpetuals/src/state/pool.rs -> fn get_leverage

Leverage

if margin == 0:
    leverage = MAX_SAFE_INTEGER    // sentinel for infinite leverage
else:
    leverage = size_usd * BPS_POWER / margin

Result is in BPS. 10x leverage = 100,000 BPS.

Source: flash-perpetuals/programs/perpetuals/src/state/pool.rs -> fn get_leverage


Liquidation

Liquidation Condition

A position is liquidatable when:

current_leverage > max_leverage

Where max_leverage is custody.pricing.maxLeverage (in BPS, e.g., 1,000,000 = 100x).

Source: flash-perpetuals/programs/perpetuals/src/state/pool.rs -> fn check_leverage

Liquidation Price Derivation

Starting from the liquidation condition and solving for price:

leverage > max_leverage
size_usd * BPS_POWER / margin > max_leverage
margin < size_usd * BPS_POWER / max_leverage

Let min_margin = size_usd * BPS_POWER / max_leverage

At the liquidation price, margin equals min_margin. Substituting the margin formula:

collateral + PnL(liq_price) - fees = min_margin

For a long position, PnL at liquidation price P_liq:

PnL = size_usd * (P_liq - entry_price) / entry_price

Solving for P_liq:

buffer = collateral - fees - min_margin

Long:   P_liq = entry_price * (size_usd - buffer) / size_usd
Short:  P_liq = entry_price * (size_usd + buffer) / size_usd

Where fees = interest_usd + close_fee_usd.

For longs, the liquidation price is below entry (price must drop). For shorts, it is above entry (price must rise).


Borrow Rate Model

Two-Slope Kink Model

The borrow rate is a function of utilization, with a kink at the optimal utilization point:

utilization_rate = locked * RATE_POWER / owned    // in RATE_DECIMALS

if utilization_rate < optimal_utilization:
    // Below kink: linear interpolation from base_rate to base_rate + slope1
    variable_rate = utilization_rate * slope1 / optimal_utilization
    rate = base_rate + variable_rate
else:
    // Above kink: steep increase from base_rate + slope1 toward base_rate + slope1 + slope2
    excess = utilization_rate - optimal_utilization
    remaining = RATE_POWER - optimal_utilization
    variable_rate = slope1 + excess * slope2 / remaining
    rate = base_rate + variable_rate

Maximum rate (at 100% utilization): base_rate + slope1 + slope2.

Source: flash-perpetuals/programs/perpetuals/src/state/custody.rs -> fn update_borrow_rate

Interest Accumulation

The cumulative lock fee is updated using ceiling division:

time_delta = current_time - last_update_time    // seconds
accrual = time_delta * current_rate
increment = ceil(accrual / 3600)                // ceiling division
cumulative_lock_fee += increment

Ceiling division formula: ceil(a / b) = (a + b - 1) / b

This ensures borrowers are never undercharged due to integer truncation.

Source: flash-perpetuals/programs/perpetuals/src/state/custody.rs -> fn get_cumulative_interest

Position Interest

delta = cumulative_lock_fee_now - position_cumulative_snapshot
interest_usd = delta * locked_usd / RATE_POWER

When locked_usd == 0, interest is zero (multiplication by zero).

Source: flash-perpetuals/programs/perpetuals/src/state/custody.rs -> fn get_interest_amount_usd

Close Fee

close_fee_usd = size_usd * close_position_fee / RATE_POWER

Where close_position_fee is in RATE_DECIMALS (10^9).


Worked Example

Consider a long SOL position with the following parameters:

entry_price      = 150,000,000,000    (= $150.00 at exponent -9)
current_price    = 148,000,000,000    (= $148.00)
size_usd         = 10,000,000,000     (= $10,000.00 at 10^6)
collateral_usd   = 1,000,000,000      (= $1,000.00)
interest_usd     = 5,000,000          (= $5.00)
close_fee        = 1,000,000          (= $1.00)
max_leverage     = 1,000,000          (= 100x in BPS)

Step 1: PnL

Exit price approximately equals current price (assuming no spread for simplicity):

price_diff_loss = 150,000,000,000 - 148,000,000,000 = 2,000,000,000
loss_usd = 10,000,000,000 * 2,000,000,000 / 150,000,000,000 = 133,333,333

Loss = $133.33

Step 2: Total loss including fees

total_loss = 133,333,333 + 5,000,000 + 1,000,000 = 139,333,333

Total loss = $139.33

Step 3: Margin

margin = 1,000,000,000 + 0 - 139,333,333 = 860,666,667

Margin = $860.67

Step 4: Leverage

leverage = 10,000,000,000 * 10,000 / 860,666,667 = 116,187

Leverage = 11.62x (116,187 BPS)

Step 5: Liquidation check

116,187 < 1,000,000 → NOT liquidatable

The position is safe (11.62x < 100x).

Step 6: Liquidation price

min_margin = 10,000,000,000 * 10,000 / 1,000,000 = 100,000,000
fees = 5,000,000 + 1,000,000 = 6,000,000
buffer = 1,000,000,000 - 6,000,000 - 100,000,000 = 894,000,000

liq_price = 150,000,000,000 * (10,000,000,000 - 894,000,000) / 10,000,000,000
          = 150,000,000,000 * 9,106,000,000 / 10,000,000,000
          = 136,590,000,000

Liquidation price = $136.59

Step 7: Distance to liquidation

distance = |148,000,000,000 - 136,590,000,000| / 148,000,000,000 * 100
         = 11,410,000,000 / 148,000,000,000 * 100
         = 7.71%

The price must drop 7.71% from $148.00 to reach liquidation at $136.59.