> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PnL Calculation

> PnL = notional × (currentPrice − entryStrike) / PRICE_PRECISION for LONG, inverted for SHORT — losses capped at locked margin, integer arithmetic with no floating point

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

<Note>
  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.
</Note>

## 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](/protocol/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.

<Info>
  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.
</Info>

## Dual PnL Tracking

When a position closes (via settlement, liquidation, or early termination), the protocol records two PnL values:

| Field         | Description                              | Capped?                                |
| ------------- | ---------------------------------------- | -------------------------------------- |
| `realizedPnl` | Accounting PnL used for margin transfers | Yes -- losses capped at `marginAtRisk` |
| `marketPnl`   | True mathematical PnL for analytics      | No                                     |

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.

<Warning>
  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.
</Warning>

## Worked Examples

<AccordionGroup>
  <Accordion title="Example 1: LONG Position in Profit">
    **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 USDC

    The trader is profitable. Both `realizedPnl` and `marketPnl` would be +20 USDC if the position were closed at this price.
  </Accordion>

  <Accordion title="Example 2: SHORT Position in Profit">
    **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 USDC

    The trader profits because the EUR/USD rate dropped, matching their SHORT bet.
  </Accordion>

  <Accordion title="Example 3: Bad Debt Scenario">
    **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.
  </Accordion>
</AccordionGroup>

## Price Sources for PnL

Different operations use different price sources:

| Operation                       | Price Source  | Description                                            |
| ------------------------------- | ------------- | ------------------------------------------------------ |
| Unrealized PnL (mark-to-market) | Forward price | Live forward price for the position's fixing timestamp |
| Maturity settlement             | Fixing price  | Pyth spot price recorded at 4 PM UTC on maturity day   |
| Early termination               | Forward price | Current forward price at close time                    |
| Liquidation                     | Forward price | Current forward price at liquidation time              |
| Partial reduction               | Forward price | Current forward price at reduction time                |

<Tip>
  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.
</Tip>

## 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.
