PnL calculation

This document describes the mechanism of calculating profit and loss.

Understanding PnL

PnL (profit and loss) is a metric indicating the overall profitability of trading operations (or incurred losses). It is calculated based on the traded volumes and spreads between closing and opening rates.

This metric allows you to selectively measure performance of each asset and trader. This is important since PnL reflects (both actual and potential) profitability from the standpoint of an exchange user, and not the exchange itself.

Before turning to explain how PnL is measured we should clarify what opening and closing a position stands for.

Essentially, each trade occurs when a sale meets a purchase: closing any position implies that it should have been opened in the first place. Positions may be opened and closed either in the full amount, or partially (when only a portion of a bid or offer is met, based on the order time-in-force settings). Both these cases are accounted for during PnL calculation, in the context of which, “opening a position” means any action after which user funds in some asset increase: be it as a result of depositing funds in this asset, or of buying some asset amount on the exchange (after placing an order), or of any incidental action like airdropping. The term “closing” points to an action after which user funds diminish, which implies a sale or withdrawal.

There are two kinds of PnL which account for actual and potential profit (loss):

  • Realized PnL (also called fixed PnL, or simply PnL) indicates the actual net profit (or loss) calculated each time a position is closed, after which this value is updated and recorded, being no longer affected by any subsequent variation in conversion rates.

  • Unrealized PnL (also called floating PnL) indicates a potential profit (loss) and its hypothetical impact upon Realized PnL that would become manifest if all currently open positions were closed at once. Unrealized PnL is constantly recalculated following an ongoing change in market prices for open positions. This value is not fixed, it “floats”, reflecting the market volatility and the movement of market rates. Unrealised PnL is most helpful for evaluating notional and actual profitability of limit orders.

To evaluate PnL, the exchange engine relies upon a sophisticated algorithm, which is not only capable of speedily handling very large quantities of incoming data, but also accounts for a variety of rare scenarios, such as missing rates in database records or revoking of previously executed trades.

The PnL is calculated individually for each user asset: for example, when ETH is exchanged to LTC, calculations are carried out simultaneously for both these currencies.

The procedure of PnL calculation is based on the following considerations:

  • PnL is calculated, stored and displayed in the platform root asset (typically, this is a fiat currency or stablecoin). In all examples that follow, it is implied that the platform root asset is USD.

  • A deposit operation is treated as a regular asset purchase at the current rate (implying that a position has been opened).

  • A withdrawal operation is treated as a regular asset sale at the current rate (implying that a position has been closed).

  • Trades are accounted for as if their execution involved an intermediate root asset, as in the case when a (base) asset is first sold for USD, and then USD is exchanged for the purchased (quote) asset.

    For example, in the case of buying ETH for BTC, PnL is calculated in the same way as if a user first sold BTC for USD, and then used the purchased USD to buy ETH.

If some asset (such as BTC) cannot be converted to the platform root asset either directly (BTC/USD) or via cross currency (BTC/LTC, and then LTC/USD), PnL for this asset is not calculated and is not affected by any transactions involving the asset (transfer or exchange), regardless of the order side.

Realized PnL calculation

Realized PnL is calculated separately for each user asset, and only after some position has been closed. After the first position is opened for a given asset, only intermediate values are updated to be later used in subsequent PnL calculations.

Upon opening a position, the average price for the asset and current balance are evaluated first, to be later used for Realized PnL calculation:

  1. [Balance In Root Asset] = [Previous Balance In Root Asset] + ([Asset Amount] * [Asset Rate To Root Asset])

  2. [Asset Balance] = [Previous Asset Balance] + [Asset Amount]

… where:

  • Balance In Root Asset — the total cost of assets now available to a user after the recent transaction. This is the current amount remaining in a user’s wallet that holds the traded/transferred asset, in conversion to the platform root asset. The conversion rates are taken as of the time of opening each previous position in this asset.

  • Previous Balance In Root Asset — the previous running Balance In Root Asset value as of before the current transaction, in the platform root asset. This value can be equal to zero if no transactions have previously taken place. It is stored in a database for use in subsequent PnL calculations.

  • Asset Amount — the amount of the recent transaction, in the base asset (after commission, in the case of opening a position).

  • Asset Rate To Root Asset — the current rate of the user asset to the platform root asset. This value continuously changes along with the ongoing rate fluctuations.

  • Asset Balance — the total asset amount in a user wallet after the recent transaction.

  • Previous Asset Balance — the asset amount that was available to a user before the recent transaction. This value can be equal to zero if no transactions have previously taken place. It is stored in a database for use in subsequent PnL calculations.

When a position is closed, Realized PnL is calculated as follows:

  1. [Avg Rate] = [Balance In Root Asset] / [Asset Balance]

  2. [Realized PnL] = [Previous PnL] + [Asset Amount] * ([Asset Rate To Root Asset] – [Avg Rate])

  3. [Balance In Root Asset] = [Previous Balance In Root Asset] – [Asset Amount] * [Avg Rate]

  4. [Asset Balance] = [Previous Asset Balance] – [Asset Amount]

… where:

  • Avg Rate — the average cost of a unit of asset. This value is obtained across the user balance sheet with a reference to historical rates established by the time when each transaction was made.

  • Previous PnL — the Realized PnL value as of before the recent transaction.

Realized PnL is stored and displayed in the same platform root asset, which was used in preceding PnL calculations.

The resulting PnL value cannot be “converted” to another asset, since it would cease being a proper measure of PnL.

The algorithm accounts for variation in the rate of a user asset to the root asset with each transaction.

Unrealized PnL calculation

Unrealized PnL is constantly recalculated, upon receiving continuous data update requests following any change to the current rates:

[Unrealized PnL] = [Asset Balance] * ([Asset Rate To Root Asset] – [Avg Rate])

… where:

  • Asset Balance — the total asset amount in a user wallet.

  • Asset Rate To Root Asset — the current rate of the user asset to the platform root asset. This value continuously changes along with the ongoing rate fluctuations.

  • Avg Rate — the average cost of a unit of asset. This value is obtained across the user balance sheet with a reference to historical rates established by the time when each transaction was made.

Sample PnL calculations

Case study 1

To understand PnL, consider the following example, with a new user arriving on the exchange with zero balance on their freshly opened trading account. All of the user metrics are zero, awaiting the user to make the first investments.

Step 1. The user begins by depositing $6000 to the USD wallet, which for our purposes is the equivalent of opening a position.

To evaluate Balance in Root Asset, the amount deposited is multiplied by one, since we don’t need to convert the deposited currency (USD) to the platform root asset (also USD). The current balance on the user USD account is accordingly updated.

Balance (USD) in Root Asset (USD) = 0 + 6000 * 1 = $6000

Balance (USD) = 0 + 6000 = $6000

At this step, both the PnL values are zero:

Realized PnL = $0

Unrealized PnL = 6000 (1 – 6000/6000) = $0

Step 2. The user spends $1990 to buy 2000 USDT at the rate of 1:0.995.

Balance (USDT) in Root Asset = 0 + (2000 * 0.995) = $1990

Balance (USDT) = 0 + 2000 = ₮2000

PnL values are still zero, since nothing has changed so far, apart from the user opening another position:

Realized PnL (USDT) = $0

Unrealized PnL (USDT) = 2000 * (0.995 – 1990/2000) = $0

Step 3. While the user spends another $1200 to buy 1 ETH (so the ETH/USD rate is 1:1200), the USDT/USD rate changes and becomes 1:0.997.

Balance (USDT) in Root Asset = $1990

Balance USDT = ₮2000

Balance (ETH) in Root Asset = 0 + (1 * 1200) = $1200

Balance (ETH) = Ξ1

Since no positions have been closed at this step, only Unrealized PnL for USDT is updated, reflecting the current change in the USDT/USD rate. Keep in mind that PnL values are always calculated in the platform root asset (which is USD in our example), regardless of the asset being measured.

Realized PnL (USDT) = $0

Unrealized PnL (USDT) = 2000 * (0.997 – 1990/2000) = $4

Realized PnL (ETH) = $0

Unrealized PnL (ETH) = $0

Step 4. The user buys one more ETH, but at this time, the ETH/USD rate has increased and become 1:1400.

Balance (USDT) in Root Asset = $1990

Balance (USDT) = ₮2000

Balance (ETH) in Root Asset = 1200 + (1 * 1400) = $2600

Balance (ETH) = 1 + 1 = Ξ2

This time, Unrealized PnL is updated for ETH, reflecting the change in the ETH/USD rate:

Realized PnL (USDT) = $0

Unrealized PnL (USDT) = 2000 * (0.997 – 1990/2000) = $4

Realized PNL (ETH) = $0

Unrealized PNL (ETH) = 2 * (1400 – (1200 + 1400)/2) = $200

Step 5. The ETH/USD rate is now 1:1500, so the user decides to close two positions by selling 1 ETH at this rate and 1000 USDT at the rate of 1:0.997.

After the trades are executed, the unrealized profit in USDT translates into Realized PnL, while another ₮1000 remaining in the user USDT wallet still awaits its opportunity:

Realized PnL (USDT) = 0 + 1000 * (0.997 – (1990/2000)) = $2

Balance (USDT) in Root Asset = 1990 – 1000 * (1990/2000) = $995

Balance (USDT) = 2000 – 1000 = ₮1000

Unrealized PnL (USDT) = 1000 * (0.997 – 1990/2000) = $2

The same logic applies to PnL calculations for ETH:

Realized PnL (ETH) = 0 + 1 * (1500 – (1200 + 1400)/2) = $200

Balance (ETH) in Root Asset = 2600 – 1 * (1200 + 1400)/2) = $1300

Balance (ETH) = 2 – 1 = Ξ1

Unrealized PnL (ETH) = 1 * (1500 – (1300)/1) = $200

At this step, it is time to check the balance:

Balance (USD) = $1410 + $1500 + $997 = $3907

…where:

  • $1410 — the funds left after purchasing 2000 USDT and 2 ETH (one for $1200 and one for $1400).

  • $1500 — the income after selling 1 ETH

  • $997 — the income after selling 1000 USDT

Case study 2

The following table illustrates a series of sample PnL values produced based on hypothetical user actions and variations in the current rates.

In this table, the Balance and Balance In Root Asset columns display updated values that were obtained as a result of previous transactions (initially, both these values are zero). Realized PnL in each row is calculated using the Balance and Balance In Root Asset values from a preceding row.

As this table illustrates, the Balance to Balance In Root Asset ratio doesn’t change after a position is partially closed, despite the values themselves being updated. This is because a position is actually closed not using some specific portion of previously purchased assets, but by subtracting some portion from a net asset balance, for which the total cost of the assets is known.

Operation:

+ (buy) / (sell)

Rate

Realized PnL

Unrealized PnL

Balance

Balance In Root Asset

+1

10

0

0

1

10

+1

15

0

2 * (15 – 25/2) = 5

2

25

+1

20

0

15

3

45

+1

25

0

30

4

70

+1

30

0

50

5

100

+1

35

0

75

6

135

+1

40

0

105

7

175

–1

40

1 * (40 – 175/7) = 15

90

6

150

–1

35

15 + 1 * (35 – 150/6) = 25

50

5

125

–1

30

25 + 1 * (30 – 125/5) = 30

20

4

100

–1

25

30

0

3

75

–1

20

25

–10

2

50

–1

15

15

–10

1

25

–1

10

0

0

0

0

+1

30

0

0

1

30

+1

40

0

10

2

70