Add per session paper position tracking
Generate and maintain position messages in the paper engine for each `pikerd` session. We no longer tear down the engine on each client disconnect. Ensure -ve size on sells to make the math work.pause_feeds_on_sym_switch
							parent
							
								
									908678da84
								
							
						
					
					
						commit
						449c4210e4
					
				|  | @ -35,7 +35,7 @@ from ..data._normalize import iterticks | |||
| from ..log import get_logger | ||||
| from ._messages import ( | ||||
|     BrokerdCancel, BrokerdOrder, BrokerdOrderAck, BrokerdStatus, | ||||
|     BrokerdFill, | ||||
|     BrokerdFill, BrokerdPosition, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -60,6 +60,7 @@ class PaperBoi: | |||
|     _buys: bidict | ||||
|     _sells: bidict | ||||
|     _reqids: bidict | ||||
|     _positions: dict[str, BrokerdPosition] | ||||
| 
 | ||||
|     # init edge case L1 spread | ||||
|     last_ask: Tuple[float, float] = (float('inf'), 0)  # price, size | ||||
|  | @ -101,6 +102,9 @@ class PaperBoi: | |||
|         # in the broker trades event processing loop | ||||
|         await trio.sleep(0.05) | ||||
| 
 | ||||
|         if action == 'sell': | ||||
|             size = -size | ||||
| 
 | ||||
|         msg = BrokerdStatus( | ||||
|             status='submitted', | ||||
|             reqid=reqid, | ||||
|  | @ -118,7 +122,7 @@ class PaperBoi: | |||
|             ) or ( | ||||
|             action == 'sell' and (clear_price := self.last_bid[0]) >= price | ||||
|         ): | ||||
|             await self.fake_fill(clear_price, size, action, reqid, oid) | ||||
|             await self.fake_fill(symbol, clear_price, size, action, reqid, oid) | ||||
| 
 | ||||
|         else: | ||||
|             # register this submissions as a paper live order | ||||
|  | @ -170,6 +174,8 @@ class PaperBoi: | |||
| 
 | ||||
|     async def fake_fill( | ||||
|         self, | ||||
| 
 | ||||
|         symbol: str, | ||||
|         price: float, | ||||
|         size: float, | ||||
|         action: str,  # one of {'buy', 'sell'} | ||||
|  | @ -232,6 +238,39 @@ class PaperBoi: | |||
|             ) | ||||
|             await self.ems_trades_stream.send(msg.dict()) | ||||
| 
 | ||||
|         # lookup any existing position | ||||
|         token = f'{symbol}.{self.broker}' | ||||
|         pp_msg = self._positions.setdefault( | ||||
|             token, | ||||
|             BrokerdPosition( | ||||
|                 broker=self.broker, | ||||
|                 account='paper', | ||||
|                 symbol=symbol, | ||||
|                 # TODO: we need to look up the asset currency from | ||||
|                 # broker info. i guess for crypto this can be | ||||
|                 # inferred from the pair? | ||||
|                 currency='', | ||||
|                 size=0.0, | ||||
|                 avg_price=0, | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         # "avg position price" calcs | ||||
|         # TODO: eventually it'd be nice to have a small set of routines | ||||
|         # to do this stuff from a sequence of cleared orders to enable | ||||
|         # so called "contextual positions". | ||||
| 
 | ||||
|         new_size = size + pp_msg.size | ||||
| 
 | ||||
|         if new_size != 0: | ||||
|             pp_msg.avg_price = (size*price + pp_msg.avg_price) / new_size | ||||
|         else: | ||||
|             pp_msg.avg_price = 0 | ||||
| 
 | ||||
|         pp_msg.size = new_size | ||||
| 
 | ||||
|         await self.ems_trades_stream.send(pp_msg.dict()) | ||||
| 
 | ||||
| 
 | ||||
| async def simulate_fills( | ||||
|     quote_stream: 'tractor.ReceiveStream',  # noqa | ||||
|  | @ -255,6 +294,7 @@ async def simulate_fills( | |||
| 
 | ||||
|     # this stream may eventually contain multiple symbols | ||||
|     async for quotes in quote_stream: | ||||
| 
 | ||||
|         for sym, quote in quotes.items(): | ||||
| 
 | ||||
|             for tick in iterticks( | ||||
|  | @ -274,6 +314,7 @@ async def simulate_fills( | |||
|                     ) | ||||
| 
 | ||||
|                     orders = client._buys.get(sym, {}) | ||||
| 
 | ||||
|                     book_sequence = reversed( | ||||
|                         sorted(orders.keys(), key=itemgetter(1))) | ||||
| 
 | ||||
|  | @ -307,6 +348,7 @@ async def simulate_fills( | |||
| 
 | ||||
|                         # clearing price would have filled entirely | ||||
|                         await client.fake_fill( | ||||
|                             symbol=sym, | ||||
|                             # todo slippage to determine fill price | ||||
|                             price=tick_price, | ||||
|                             size=size, | ||||
|  | @ -411,6 +453,9 @@ async def trades_dialogue( | |||
|                 _sells={}, | ||||
| 
 | ||||
|                 _reqids={}, | ||||
| 
 | ||||
|                 # TODO: load paper positions from ``positions.toml`` | ||||
|                 _positions={}, | ||||
|             ) | ||||
| 
 | ||||
|             n.start_soon(handle_order_requests, client, ems_stream) | ||||
|  | @ -452,10 +497,5 @@ async def open_paperboi( | |||
|                 loglevel=loglevel, | ||||
| 
 | ||||
|         ) as (ctx, first): | ||||
|             try: | ||||
|                 yield ctx, first | ||||
| 
 | ||||
|             finally: | ||||
|                 # be sure to tear down the paper service on exit | ||||
|                 with trio.CancelScope(shield=True): | ||||
|                     await portal.cancel_actor() | ||||
|             yield ctx, first | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue