5
$\begingroup$

I'm trying to calculate the average entry price for perpetual swap contracts for use in back-testing a trading strategy, as per Bitmex's documentation:

A Perpetual Contract is a derivative product that is similar to a traditional Futures Contract, but has a few differing specifications:

There is no expiry or settlement. Perpetual Contracts mimic a margin-based spot market and hence trade close to the underlying reference Index Price. This is in contrast to a Futures Contract which may trade at significantly different prices due to basis. The primary mechanism to tether to spot price is Funding

This contract specification is found on numerous exchanges, Bitmex, Okex, Binance etc. The average entry price is used in calculating the realized PNL, the following describes the context in which it is used.

John is long 1,000 XBTUSD contracts with an average entry price of $1,000. The mark price of XBTUSD is currently $1,250.

John’s unrealised PNL is based on the difference between his average entry price and the mark price.

Unrealised Profit = ($1/$1,000 - $1/$1,250) * 1,000 = 0.20 XBT

The last price of XBTUSD is $1,500. However for the calculation of unrealised PNL, the mark price is used not the last price. To understand why, please read Fair Price Marking.

John decides to sell 500 XBTUSD contracts at $1,500 and realise some profit.

John’s realised PNL is based on the difference between his average entry price and the price at which he sells XBTUSD.

Realised Profit = ($1/1,000 - $1/$1,500) * 500 = 0.17 XBT

Realised PNL is based on where you can actually buy or sell your position, which in most cases is not the mark price. If John had sold his 500 contracts at the mark price of $1,250, he would have a realised profit of 0.10 XBT.

I have taken note of the answers located here.

total_quantity / ((quantity_1 / price_1) + (quantity_2 / price_2)) = entry_price

and have appended each execution to an array then run a summation before dividing the current position by the summed executions i.e.

entries = []
position = 0
orders = [
{"size":-50, "price":10000, "side":"sell"},
{"size":5, "price":10000, "side":"sell"},
]
for order in orders:
   entries.append(abs(order["size"])/order["price"])
   position += order["size"]
average_entry_price = abs(position)/sum(entries)
print(average_entry_price) # 8181.818181818182

However in this instance it returns a presumably incorrect average entry price of 8181.818181818182.This happens in numerous cases.

I then after contacting support was given the following protocol for deriving the average entry price:

  1. The execCost of each entry order is added together. For our contracts, execCost is calculated as round(1e8/price) * number of contracts.
  2. The total execCost is divided by the total number of entry contracts. This is the 'average satoshi price' of the position.
  3. For long positions, floor() the average satoshi price. For short position, round() the average satoshi price.
  4. Divide 1e8 by the average satoshi price to get the average USD price. This is then rounded to 4 decimal places for the API, and rounded to the nearest tick for the front-end.

And thereafter implemented a simple "proof of concept" implementation:

orders = [
    {"price":10000, "size": -100},
    {"price":10000, "size": -100},
    {"price":10000, "size": 100},
    {"price":10000, "size": 100},
    {"price":9000, "size": 100},
]

execCosts = 0
totalEntry = 0
position = 0
for order in orders:
    next_position = position+order["size"]
    if position * next_position <0:
        #Current position is closed and opposite position is opened
        execCosts = 0
        totalEntry = 0
        execCosts += (round(1e8/order["price"]) * order["size"])
        totalEntry += abs(order["size"])
    elif abs(position) > abs(next_position):
        # The current position is reduced
        pass
    else:
        #The current position is increased
        execCosts += (round(1e8/order["price"]) * order["size"])
        totalEntry += abs(order["size"])
    position = next_position

 averageSatoshiPrice = abs(execCosts/totalEntry)
 print(averageSatoshiPrice) # 2963.0 ???

However this again seemed to be an insufficient implementation. Could someone please advise me on how to calculate the Average Entry Price. Your guidance is truly appreciated.

$\endgroup$
3
  • $\begingroup$ I think the answer from bitmex is wrong, you should not round the stuff. But I also got some trouble, cannot match the result of mine with the result in bitmex web ui. Did you solve it? $\endgroup$ Commented Nov 28, 2019 at 19:44
  • $\begingroup$ I think the correct formula to use is the one you mentioned: total_quantity / ((quantity_1 / price_1) + (quantity_2 / price_2)) = entry_price. But in your implementation of this formula, you use abs() on order size and position total. I think if you remove abs, it should work. $\endgroup$
    – Keith
    Commented Dec 17, 2019 at 12:56
  • $\begingroup$ @Natalie Williams did you get a solution? I am stuck trying to figure out the same. $\endgroup$
    – José D.
    Commented Feb 19 at 9:12

1 Answer 1

0
$\begingroup$

To calculate the average entry price you need to know the position cost AND the current profit loss. Pseudocode to get the contract value in satoshi:

heldContractValue = (executionCost + realisedGrossPNL) / nContracts

You can then calculate the price from the contract value. This differs between contracts, but for a quanto contract the formula where satoshiPerDollar is defined in the contract specification:

price = heldContractValue / satoshiPerDollar

Whereas for the inverse contracts it's

price = 100_000_000 / heldContractValue

I think BitMEX just using integer division under the hood, causing rounding errors.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.