nautechsystems / nautilus_trader

A high-performance algorithmic trading platform and event-driven backtester
https://nautilustrader.io
GNU Lesser General Public License v3.0
2.2k stars 504 forks source link

Position in base currency #55

Closed scoriiu closed 4 years ago

scoriiu commented 4 years ago

Currently the position is represented in quote currency. I find this confusing, hence I propose to change it to base currency.

cjdsellers commented 4 years ago

This definitely needs to be changed and your solution is probably good. So that we can reach a more stable API as fast as possible, let's look ahead to some of the fields on the execution events being returned from the exchanges we intend to integrate with, and CCXT.

I'll do some research myself but also feel free to comment.

scoriiu commented 4 years ago

Excepting the quotation part I think the API of the position is pretty good already. I would also add ROI and margin related information.

Bitmex position update:

{
  "table": "position",
  "action": "partial",
  "keys": [
    "account",
    "symbol"
  ],
  "types": {
    "account": "long",
    "symbol": "symbol",
    "currency": "symbol",
    "underlying": "symbol",
    "quoteCurrency": "symbol",
    "commission": "float",
    "initMarginReq": "float",
    "maintMarginReq": "float",
    "riskLimit": "long",
    "leverage": "float",
    "crossMargin": "boolean",
    "deleveragePercentile": "float",
    "rebalancedPnl": "long",
    "prevRealisedPnl": "long",
    "prevUnrealisedPnl": "long",
    "prevClosePrice": "float",
    "openingTimestamp": "timestamp",
    "openingQty": "long",
    "openingCost": "long",
    "openingComm": "long",
    "openOrderBuyQty": "long",
    "openOrderBuyCost": "long",
    "openOrderBuyPremium": "long",
    "openOrderSellQty": "long",
    "openOrderSellCost": "long",
    "openOrderSellPremium": "long",
    "execBuyQty": "long",
    "execBuyCost": "long",
    "execSellQty": "long",
    "execSellCost": "long",
    "execQty": "long",
    "execCost": "long",
    "execComm": "long",
    "currentTimestamp": "timestamp",
    "currentQty": "long",
    "currentCost": "long",
    "currentComm": "long",
    "realisedCost": "long",
    "unrealisedCost": "long",
    "grossOpenCost": "long",
    "grossOpenPremium": "long",
    "grossExecCost": "long",
    "isOpen": "boolean",
    "markPrice": "float",
    "markValue": "long",
    "riskValue": "long",
    "homeNotional": "float",
    "foreignNotional": "float",
    "posState": "symbol",
    "posCost": "long",
    "posCost2": "long",
    "posCross": "long",
    "posInit": "long",
    "posComm": "long",
    "posLoss": "long",
    "posMargin": "long",
    "posMaint": "long",
    "posAllowance": "long",
    "taxableMargin": "long",
    "initMargin": "long",
    "maintMargin": "long",
    "sessionMargin": "long",
    "targetExcessMargin": "long",
    "varMargin": "long",
    "realisedGrossPnl": "long",
    "realisedTax": "long",
    "realisedPnl": "long",
    "unrealisedGrossPnl": "long",
    "longBankrupt": "long",
    "shortBankrupt": "long",
    "taxBase": "long",
    "indicativeTaxRate": "float",
    "indicativeTax": "long",
    "unrealisedTax": "long",
    "unrealisedPnl": "long",
    "unrealisedPnlPcnt": "float",
    "unrealisedRoePcnt": "float",
    "simpleQty": "float",
    "simpleCost": "float",
    "simpleValue": "float",
    "simplePnl": "float",
    "simplePnlPcnt": "float",
    "avgCostPrice": "float",
    "avgEntryPrice": "float",
    "breakEvenPrice": "float",
    "marginCallPrice": "float",
    "liquidationPrice": "float",
    "bankruptPrice": "float",
    "timestamp": "timestamp",
    "lastPrice": "float",
    "lastValue": "long"
  },
  "foreignKeys": {
    "symbol": "instrument"
  },
  "attributes": {
    "account": "sorted",
    "symbol": "grouped",
    "underlying": "grouped"
  },
  "filter": {
    "account": 1513111
  },
  "data": [

  ]
}
cjdsellers commented 4 years ago

Ok, so I know where this has come from now. Its taken from the Currency field of the ExecutionReport FIX4.4 message, which is the currency used for the prices. https://www.onixs.biz/fix-dictionary/4.4/tagNum_15.html

I think this link explains the transaction vs settlement currency well. https://ibkr.info/node/295

So actually the use of the quote_currency is correct right now, as in the examples using USD/JPY the USD is the transaction currency, the position is settled in JPY which is the currency used to determine PNL (then converted into the account base currency). This is obviously FX centric though. There is already a SecurityType enum available, which may be good to add to the OrderFill events if it could help us sort out the logic...

I agree though we can be clearer about this and even provide all the fields so there is no confusion. For instance I see BitMEX returns both the currency and settlCurrency. For XBt\USD the settlement currency is on the other side (XBt), I think you were alluding to this before?

How do you propose we should change things?

cjdsellers commented 4 years ago

It seems BitMEX provides some PNL info calculated from their side.

Just brainstorming, we could always push this upstream away from the Position object and place it into the OrderFilled event instead. The Position just captures the data. ExecutionEngine could handle this sort of calculation for instance. Then the Position object doesn't need knowledge about the specifics of the account currency - which simplifies things...

scoriiu commented 4 years ago

Interesting. I am more used with seeing the position in the base transaction currency by default but I think it would be better to provide all the fields for more clarity, like you propose.

The same goes with submitting an order, where currently the quantity is expressed in quote currency. All the APIs I worked so far requires for the quantity to be expressed in base currency. Some APIs allow to provide the quantity in quote currency, but this is optional.

As for tracking the PNLs I'm not sure what is best. My preference is also to drop the PNLs upstream and calculate them ourselves. This would mean that we have to track all the fills in the position in order to be able to calculate the realized PNL. So basically being able to construct the position based on the upstream trades.

scoriiu commented 4 years ago

I think it would be useful if not too complicated to switch the quantity to base currency when submitting orders. This would mean that also the Position.quantity and PNLs has to be in line with this change and be expressed in the based currency by default.

Did you have the time to trade live on a test account inverse and direct swaps on a testnet account to get the feeling of how the positions are represented? If you will, we can setup a video call and show you a couple of these examples.

cjdsellers commented 4 years ago

I think that would be useful to have a live discussion and demo. Information transfer will be faster 👍🏻

I have had a chance to play around with a testnet, it seems they're using a really simple netting type OMS. BitMEX have huge capacity hurdles so I suppose it's as simple as possible.

cjdsellers commented 4 years ago

The quantity is for the left hand side of any currency pair or number of contracts, that's definitely the intention for quantity in the orders. I think we need to rewrite how PNL is being calculated basically, but I agree it doesn't need to happen upstream, I think the Position objects will be able to continue to calculate the PNLs - it just needs to be perfectly correct.

cjdsellers commented 4 years ago

I rewrote the ExecutionEngine. The client position ids have been removed in favour of just a PositionId. So there's no longer a need to specify a PositionId when submitting an order. If one does however then the system will need to have a record of that position id already, because its clearly meant to be an order to modify an existing position. This keeps things consistent with any position ids the exchange may or may not be generating.

If a broker/exchange is using hedging type order management then their own position id will be captured and indexed for that order.

If not and the order management is netting (BitMEX), then the system will automatically generate and track a position id for new positions.

The ExecutionEngine is much simpler and more robust now - it still needs to be refactored some more. Theres scope for more order validation logic too.

We still need to decide if a position is allowed to be 'flipped' with a single order. My preference is the trader should be required to close out the original position first before going in the order direction with an additional order... It avoids the need for complex logic splitting the original OrderFill event into two new events (one to close the original position and one to create the new position).

cjdsellers commented 4 years ago

Position flipping logic has been implemented inside ExecutionEngine. Lets open a new issue for further enhancements/changes to Position.