Skip to main content
Profit and loss (PnL) is the core metric that determines a position’s health, liquidation eligibility, and settlement outcome. The Open Nile Protocol computes PnL as the difference between the current forward price and the entry strike, scaled by the position’s notional. Every calculation uses integer arithmetic with no floating point, ensuring deterministic results across all execution environments.

Unrealized PnL Formulas

Unrealized PnL represents the current profit or loss on an open position, computed from the live forward price. LONG position (trader profits when price goes up):
PnL = notional * (currentForwardPrice - entryStrike) / PRICE_PRECISION
SHORT position (trader profits when price goes down):
PnL = notional * (entryStrike - currentForwardPrice) / PRICE_PRECISION
Where:
  • notional is in USDC precision (6 decimals)
  • currentForwardPrice and entryStrike are in price precision (18 decimals)
  • PRICE_PRECISION = 1e18
  • The result is in USDC precision (6 decimals)
  • Positive = profit, negative = loss
The forward price used for unrealized PnL is the live forward price for the position’s specific fixing timestamp, fetched via OracleModule.getForward(pairId, fixingTimestamp). This is different from the fixing price used at maturity settlement.

Position Equity

Position equity determines whether a position can be liquidated:
positionEquity = imLocked + unrealizedPnl
Where imLocked is the margin currently locked against the position (Onchain: stored in the Position struct, mutable via add/remove margin) and unrealizedPnl is signed (Computed: derived from the current forward price and entry strike). Position equity itself is Computed — it is not stored onchain. When equity falls below the maintenance margin threshold (mmThreshold), the position becomes liquidatable. See the Liquidation page for details.

Account Equity

Account equity provides a holistic view of a trader’s financial state across all positions:
accountEquity = collateralBalance + aggregateUnrealizedPnl
Where aggregateUnrealizedPnl is the sum of unrealized PnL across all of the account’s open positions. This metric is used for account-level health monitoring, though the protocol’s isolated margin model means each position is liquidated independently.
Under isolated margin (the only mode in M2), each position’s risk is contained to its own locked margin. A deeply underwater position does not affect other positions or free collateral — only the margin locked against that specific position is at risk.

Dual PnL Tracking

When a position closes (via settlement, liquidation, or early termination), the protocol records two PnL values:
FieldDescriptionCapped?
realizedPnlAccounting PnL used for margin transfersYes — losses capped at marginAtRisk
marketPnlTrue mathematical PnL for analyticsNo
These two values are identical in the common case where the trader’s loss does not exceed their locked margin. They diverge only in bad debt scenarios, when the position’s loss exceeds the margin available to cover it.

When They Diverge

if |pnl| > marginAtRisk:
    realizedPnl = -marginAtRisk     (loss capped at available margin)
    marketPnl   = pnl               (uncapped -- records the true loss)
    badDebt     = |marketPnl| - marginAtRisk  (absorbed by pool equity)
else:
    realizedPnl = pnl               (same as market PnL)
    marketPnl   = pnl               (same as realized PnL)
The realizedPnl drives all USDC transfers. The marketPnl is recorded for accurate analytics and auditing. A BadDebt event is emitted whenever these values diverge.
Bad debt represents a loss that cannot be recovered from the trader because their locked margin is exhausted. The pool absorbs this loss, reducing pool equity and LP share price. This is the fundamental risk that liquidity providers bear in exchange for earning fees.

Worked Examples

Setup:
  • Side: LONG
  • Notional: 1,000 USDC (1,000,000,000 raw)
  • Entry strike: 1.08 (1,080,000,000,000,000,000 in 18 decimals)
  • Current forward price: 1.10 (1,100,000,000,000,000,000)
  • IM locked: 20 USDC
Calculation:
PnL = 1,000,000,000 * (1,100,000,000,000,000,000 - 1,080,000,000,000,000,000) / 1e18
    = 1,000,000,000 * 20,000,000,000,000,000 / 1e18
    = 20,000,000
    = 20 USDC profit
Position equity: 20 (IM) + 20 (PnL) = 40 USDCThe trader is profitable. Both realizedPnl and marketPnl would be +20 USDC if the position were closed at this price.
Setup:
  • Side: SHORT
  • Notional: 1,000 USDC (1,000,000,000 raw)
  • Entry strike: 1.08 (1,080,000,000,000,000,000)
  • Current forward price: 1.06 (1,060,000,000,000,000,000)
  • IM locked: 20 USDC
Calculation:
PnL = 1,000,000,000 * (1,080,000,000,000,000,000 - 1,060,000,000,000,000,000) / 1e18
    = 1,000,000,000 * 20,000,000,000,000,000 / 1e18
    = 20,000,000
    = 20 USDC profit
Position equity: 20 (IM) + 20 (PnL) = 40 USDCThe trader profits because the EUR/USD rate dropped, matching their SHORT bet.
Setup:
  • Side: LONG
  • Notional: 1,000 USDC
  • Entry strike: 1.08
  • Current forward price: 1.055 (significant adverse move)
  • IM locked: 20 USDC
Calculation:
PnL = 1,000 * (1.055 - 1.08) / 1
    = 1,000 * (-0.025)
    = -25 USDC loss
Position equity: 20 (IM) + (-25) (PnL) = -5 USDC (negative)Dual PnL values at close:
  • marginAtRisk = 20 USDC (the locked margin)
  • |pnl| = 25 > marginAtRisk = 20 —> bad debt scenario
  • realizedPnl = -20 USDC (loss capped at margin)
  • marketPnl = -25 USDC (true mathematical loss)
  • Bad debt = 25 - 20 = 5 USDC absorbed by pool
The trader loses their entire 20 USDC locked margin. The remaining 5 USDC loss is bad debt that the pool absorbs, reducing LP share price.

Price Sources for PnL

Different operations use different price sources:
OperationPrice SourceDescription
Unrealized PnL (mark-to-market)Forward priceLive forward price for the position’s fixing timestamp
Maturity settlementFixing pricePyth spot price recorded at 4 PM UTC on maturity day
Early terminationForward priceCurrent forward price at close time
LiquidationForward priceCurrent forward price at liquidation time
Partial reductionForward priceCurrent forward price at reduction time
The forward price is the live price published by the authorized publisher, reflecting the current market view of the EUR/USD rate at the position’s maturity. The fixing price is the Pyth spot price recorded at the exact fixing time (4 PM UTC) and is only used for maturity settlement.

PnL Capping Mechanics

At settlement, losses are capped at the margin available to cover them. This is the isolated margin guarantee:
cappedPnl = max(pnl, -marginAtRisk)
For a full close, marginAtRisk equals the position’s imLocked. For a partial reduction, it is calculated proportionally:
marginAtRisk = (imLocked * reductionNotional) / notional
Profits are never capped — the pool pays the full amount. This asymmetry is by design: the pool’s risk is bounded only by its total equity, not by individual position margins.