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 operator import itemgetter
 | 
			
		||||
import time
 | 
			
		||||
from typing import Tuple
 | 
			
		||||
from typing import Tuple, Optional
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from bidict import bidict
 | 
			
		||||
| 
						 | 
				
			
			@ -32,8 +33,9 @@ from ..data._normalize import iterticks
 | 
			
		|||
 | 
			
		||||
@dataclass
 | 
			
		||||
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
 | 
			
		||||
    requirements.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,13 +61,23 @@ class PaperBoi:
 | 
			
		|||
        price: float,
 | 
			
		||||
        action: str,
 | 
			
		||||
        size: float,
 | 
			
		||||
        brid: Optional[str],
 | 
			
		||||
    ) -> int:
 | 
			
		||||
        """Place an order and return integer request id provided by client.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # the trades stream expects events in the form
 | 
			
		||||
        # {'local_trades': (event_name, msg)}
 | 
			
		||||
        reqid = str(uuid.uuid4())
 | 
			
		||||
 | 
			
		||||
        if brid is None:
 | 
			
		||||
            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':
 | 
			
		||||
            # bypass all fill simulation
 | 
			
		||||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
            action == 'buy' and (clear_price := self.last_ask[0]) <= price
 | 
			
		||||
| 
						 | 
				
			
			@ -116,8 +125,12 @@ class PaperBoi:
 | 
			
		|||
            elif action == 'sell':
 | 
			
		||||
                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))
 | 
			
		||||
            orders.setdefault(symbol, {})[price] = (size, oid, reqid, action)
 | 
			
		||||
            orders.setdefault(symbol, {})[(oid, price)] = (size, reqid, action)
 | 
			
		||||
 | 
			
		||||
        return reqid
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -131,9 +144,9 @@ class PaperBoi:
 | 
			
		|||
        oid, symbol, action, price = self._reqids[reqid]
 | 
			
		||||
 | 
			
		||||
        if action == 'buy':
 | 
			
		||||
            self._buys[symbol].pop(price)
 | 
			
		||||
            self._buys[symbol].pop((oid, price))
 | 
			
		||||
        elif action == 'sell':
 | 
			
		||||
            self._sells[symbol].pop(price)
 | 
			
		||||
            self._sells[symbol].pop((oid, price))
 | 
			
		||||
 | 
			
		||||
        # TODO: net latency model
 | 
			
		||||
        await trio.sleep(0.05)
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +187,8 @@ class PaperBoi:
 | 
			
		|||
        # TODO: net latency model
 | 
			
		||||
        await trio.sleep(0.05)
 | 
			
		||||
 | 
			
		||||
        # the trades stream expects events in the form
 | 
			
		||||
        # {'local_trades': (event_name, msg)}
 | 
			
		||||
        await self._to_trade_stream.send({
 | 
			
		||||
 | 
			
		||||
            'local_trades': ('fill', {
 | 
			
		||||
| 
						 | 
				
			
			@ -267,20 +282,22 @@ async def simulate_fills(
 | 
			
		|||
                    buys = client._buys.get(sym, {})
 | 
			
		||||
 | 
			
		||||
                    # 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:
 | 
			
		||||
 | 
			
		||||
                            # 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
 | 
			
		||||
                            await client.fake_fill(
 | 
			
		||||
                                # todo slippage to determine fill price
 | 
			
		||||
                                tick_price,
 | 
			
		||||
                                size,
 | 
			
		||||
                                action,
 | 
			
		||||
                                reqid,
 | 
			
		||||
                                oid,
 | 
			
		||||
                                price=tick_price,
 | 
			
		||||
                                size=size,
 | 
			
		||||
                                action=action,
 | 
			
		||||
                                reqid=reqid,
 | 
			
		||||
                                oid=oid,
 | 
			
		||||
                            )
 | 
			
		||||
                        else:
 | 
			
		||||
                            # prices are interated in sorted order so
 | 
			
		||||
| 
						 | 
				
			
			@ -297,19 +314,21 @@ async def simulate_fills(
 | 
			
		|||
                    sells = client._sells.get(sym, {})
 | 
			
		||||
 | 
			
		||||
                    # 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:
 | 
			
		||||
 | 
			
		||||
                            # 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
 | 
			
		||||
                            await client.fake_fill(
 | 
			
		||||
                                tick_price,
 | 
			
		||||
                                size,
 | 
			
		||||
                                action,
 | 
			
		||||
                                reqid,
 | 
			
		||||
                                oid,
 | 
			
		||||
                                price=tick_price,
 | 
			
		||||
                                size=size,
 | 
			
		||||
                                action=action,
 | 
			
		||||
                                reqid=reqid,
 | 
			
		||||
                                oid=oid,
 | 
			
		||||
                            )
 | 
			
		||||
                        else:
 | 
			
		||||
                            # prices are interated in sorted order so
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue