Support simulated live order modification in paper engine
							parent
							
								
									919ecab732
								
							
						
					
					
						commit
						0ade7daebc
					
				| 
						 | 
					@ -19,8 +19,9 @@ Fake trading for forward testing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from operator import itemgetter
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
from typing import Tuple
 | 
					from typing import Tuple, Optional
 | 
				
			||||||
import uuid
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from bidict import bidict
 | 
					from bidict import bidict
 | 
				
			||||||
| 
						 | 
					@ -32,8 +33,9 @@ from ..data._normalize import iterticks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class PaperBoi:
 | 
					class PaperBoi:
 | 
				
			||||||
    """Emulates a broker order client providing the same API and
 | 
					    """
 | 
				
			||||||
    order-event response event stream format but with methods for
 | 
					    Emulates a broker order client providing the same API and
 | 
				
			||||||
 | 
					    delivering an order-event response stream but with methods for
 | 
				
			||||||
    triggering desired events based on forward testing engine
 | 
					    triggering desired events based on forward testing engine
 | 
				
			||||||
    requirements.
 | 
					    requirements.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,14 +61,24 @@ class PaperBoi:
 | 
				
			||||||
        price: float,
 | 
					        price: float,
 | 
				
			||||||
        action: str,
 | 
					        action: str,
 | 
				
			||||||
        size: float,
 | 
					        size: float,
 | 
				
			||||||
 | 
					        brid: Optional[str],
 | 
				
			||||||
    ) -> int:
 | 
					    ) -> int:
 | 
				
			||||||
        """Place an order and return integer request id provided by client.
 | 
					        """Place an order and return integer request id provided by client.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        # the trades stream expects events in the form
 | 
					
 | 
				
			||||||
        # {'local_trades': (event_name, msg)}
 | 
					        if brid is None:
 | 
				
			||||||
            reqid = str(uuid.uuid4())
 | 
					            reqid = str(uuid.uuid4())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # register order internally
 | 
				
			||||||
 | 
					            self._reqids[reqid] = (oid, symbol, action, price)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # order is already existing, this is a modify
 | 
				
			||||||
 | 
					            (oid, symbol, action, old_price) = self._reqids[brid]
 | 
				
			||||||
 | 
					            assert old_price != price
 | 
				
			||||||
 | 
					            reqid = brid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if action == 'alert':
 | 
					        if action == 'alert':
 | 
				
			||||||
            # bypass all fill simulation
 | 
					            # bypass all fill simulation
 | 
				
			||||||
            return reqid
 | 
					            return reqid
 | 
				
			||||||
| 
						 | 
					@ -95,9 +107,6 @@ class PaperBoi:
 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # register order internally
 | 
					 | 
				
			||||||
        self._reqids[reqid] = (oid, symbol, action, price)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # if we're already a clearing price simulate an immediate fill
 | 
					        # if we're already a clearing price simulate an immediate fill
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            action == 'buy' and (clear_price := self.last_ask[0]) <= price
 | 
					            action == 'buy' and (clear_price := self.last_ask[0]) <= price
 | 
				
			||||||
| 
						 | 
					@ -116,8 +125,12 @@ class PaperBoi:
 | 
				
			||||||
            elif action == 'sell':
 | 
					            elif action == 'sell':
 | 
				
			||||||
                orders = self._sells
 | 
					                orders = self._sells
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # set the simulated order in the respective table for lookup
 | 
				
			||||||
 | 
					            # and trigger by the simulated clearing task normally
 | 
				
			||||||
 | 
					            # running ``simulate_fills()``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # buys/sells: (symbol  -> (price -> order))
 | 
					            # buys/sells: (symbol  -> (price -> order))
 | 
				
			||||||
            orders.setdefault(symbol, {})[price] = (size, oid, reqid, action)
 | 
					            orders.setdefault(symbol, {})[(oid, price)] = (size, reqid, action)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return reqid
 | 
					        return reqid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,9 +144,9 @@ class PaperBoi:
 | 
				
			||||||
        oid, symbol, action, price = self._reqids[reqid]
 | 
					        oid, symbol, action, price = self._reqids[reqid]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if action == 'buy':
 | 
					        if action == 'buy':
 | 
				
			||||||
            self._buys[symbol].pop(price)
 | 
					            self._buys[symbol].pop((oid, price))
 | 
				
			||||||
        elif action == 'sell':
 | 
					        elif action == 'sell':
 | 
				
			||||||
            self._sells[symbol].pop(price)
 | 
					            self._sells[symbol].pop((oid, price))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO: net latency model
 | 
					        # TODO: net latency model
 | 
				
			||||||
        await trio.sleep(0.05)
 | 
					        await trio.sleep(0.05)
 | 
				
			||||||
| 
						 | 
					@ -174,6 +187,8 @@ class PaperBoi:
 | 
				
			||||||
        # TODO: net latency model
 | 
					        # TODO: net latency model
 | 
				
			||||||
        await trio.sleep(0.05)
 | 
					        await trio.sleep(0.05)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # the trades stream expects events in the form
 | 
				
			||||||
 | 
					        # {'local_trades': (event_name, msg)}
 | 
				
			||||||
        await self._to_trade_stream.send({
 | 
					        await self._to_trade_stream.send({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            'local_trades': ('fill', {
 | 
					            'local_trades': ('fill', {
 | 
				
			||||||
| 
						 | 
					@ -267,20 +282,22 @@ async def simulate_fills(
 | 
				
			||||||
                    buys = client._buys.get(sym, {})
 | 
					                    buys = client._buys.get(sym, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # iterate book prices descending
 | 
					                    # iterate book prices descending
 | 
				
			||||||
                    for our_bid in reversed(sorted(buys.keys())):
 | 
					                    for oid, our_bid in reversed(
 | 
				
			||||||
 | 
					                        sorted(buys.keys(), key=itemgetter(1))
 | 
				
			||||||
 | 
					                    ):
 | 
				
			||||||
                        if tick_price < our_bid:
 | 
					                        if tick_price < our_bid:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            # retreive order info
 | 
					                            # retreive order info
 | 
				
			||||||
                            (size, oid, reqid, action) = buys.pop(our_bid)
 | 
					                            (size, reqid, action) = buys.pop((oid, our_bid))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            # clearing price would have filled entirely
 | 
					                            # clearing price would have filled entirely
 | 
				
			||||||
                            await client.fake_fill(
 | 
					                            await client.fake_fill(
 | 
				
			||||||
                                # todo slippage to determine fill price
 | 
					                                # todo slippage to determine fill price
 | 
				
			||||||
                                tick_price,
 | 
					                                price=tick_price,
 | 
				
			||||||
                                size,
 | 
					                                size=size,
 | 
				
			||||||
                                action,
 | 
					                                action=action,
 | 
				
			||||||
                                reqid,
 | 
					                                reqid=reqid,
 | 
				
			||||||
                                oid,
 | 
					                                oid=oid,
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                        else:
 | 
					                        else:
 | 
				
			||||||
                            # prices are interated in sorted order so
 | 
					                            # prices are interated in sorted order so
 | 
				
			||||||
| 
						 | 
					@ -297,19 +314,21 @@ async def simulate_fills(
 | 
				
			||||||
                    sells = client._sells.get(sym, {})
 | 
					                    sells = client._sells.get(sym, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # iterate book prices ascending
 | 
					                    # iterate book prices ascending
 | 
				
			||||||
                    for our_ask in sorted(sells.keys()):
 | 
					                    for oid, our_ask in sorted(
 | 
				
			||||||
 | 
					                        sells.keys(), key=itemgetter(1)
 | 
				
			||||||
 | 
					                    ):
 | 
				
			||||||
                        if tick_price > our_ask:
 | 
					                        if tick_price > our_ask:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            # retreive order info
 | 
					                            # retreive order info
 | 
				
			||||||
                            (size, oid, reqid, action) = sells.pop(our_ask)
 | 
					                            (size, reqid, action) = sells.pop((oid, our_ask))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            # clearing price would have filled entirely
 | 
					                            # clearing price would have filled entirely
 | 
				
			||||||
                            await client.fake_fill(
 | 
					                            await client.fake_fill(
 | 
				
			||||||
                                tick_price,
 | 
					                                price=tick_price,
 | 
				
			||||||
                                size,
 | 
					                                size=size,
 | 
				
			||||||
                                action,
 | 
					                                action=action,
 | 
				
			||||||
                                reqid,
 | 
					                                reqid=reqid,
 | 
				
			||||||
                                oid,
 | 
					                                oid=oid,
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                        else:
 | 
					                        else:
 | 
				
			||||||
                            # prices are interated in sorted order so
 | 
					                            # prices are interated in sorted order so
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue