Use `MktPair.price_tick: Decimal` in dark triggers

This was actually incorrect prior, we were rounding triggered limit
orders with the `.size_tick` value's digits when we should have been
using the `.price_tick` (facepalm). So fix that and compute the rounding
number of digits (as passed to the round(<value>, ndigits=<here>)`
builtin) and store it in the `DarkBook.triggers` tuples so that at
trigger/match time the round call is done *just prior* to msg send to
`brokerd` given the last known live L1 queue price.
basic_buy_bot
Tyler Goodlet 2023-06-19 13:05:34 -04:00
parent a149e71fb1
commit 80461e18a5
1 changed files with 41 additions and 25 deletions

View File

@ -24,6 +24,7 @@ from collections import (
# ChainMap, # ChainMap,
) )
from contextlib import asynccontextmanager as acm from contextlib import asynccontextmanager as acm
from decimal import Decimal
from math import isnan from math import isnan
from pprint import pformat from pprint import pformat
import time import time
@ -49,7 +50,7 @@ from ._util import (
from ..data._normalize import iterticks from ..data._normalize import iterticks
from ..accounting._mktinfo import ( from ..accounting._mktinfo import (
unpack_fqme, unpack_fqme,
float_digits, dec_digits,
) )
from ..ui._notify import notify_from_ems_status_msg from ..ui._notify import notify_from_ems_status_msg
from ..data.types import Struct from ..data.types import Struct
@ -130,11 +131,16 @@ class DarkBook(Struct):
triggers: dict[ triggers: dict[
str, # symbol str, # symbol
dict[ dict[
str, # uuid str, # uuid for triggerable execution
tuple[ tuple[
Callable[[float], bool], # predicate Callable[[float], bool], # predicate
str, # name tuple[str, ...], # tickfilter
dict, # cmd / msg type dict | Order, # cmd / msg type
# live submission constraint parameters
float, # percent_away max price diff
float, # abs_diff_away max price diff
int, # min_tick_digits to round the clearable price
] ]
] ]
] = {} ] = {}
@ -177,7 +183,8 @@ async def clear_dark_triggers(
async for quotes in quote_stream: async for quotes in quote_stream:
# start = time.time() # start = time.time()
for sym, quote in quotes.items(): for sym, quote in quotes.items():
execs = book.triggers.get(sym, {}) # TODO: make this a msg-compat struct
execs: tuple = book.triggers.get(sym, {})
for tick in iterticks( for tick in iterticks(
quote, quote,
# dark order price filter(s) # dark order price filter(s)
@ -200,7 +207,8 @@ async def clear_dark_triggers(
# TODO: send this msg instead? # TODO: send this msg instead?
cmd, cmd,
percent_away, percent_away,
abs_diff_away abs_diff_away,
price_tick_digits,
) in ( ) in (
tuple(execs.items()) tuple(execs.items())
): ):
@ -233,8 +241,11 @@ async def clear_dark_triggers(
size=size, size=size,
): ):
bfqme: str = symbol.replace(f'.{broker}', '') bfqme: str = symbol.replace(f'.{broker}', '')
submit_price = price + abs_diff_away submit_price: float = round(
resp = 'triggered' # hidden on client-side price + abs_diff_away,
ndigits=price_tick_digits,
)
resp: str = 'triggered' # hidden on client-side
log.info( log.info(
f'Dark order triggered for price {price}\n' f'Dark order triggered for price {price}\n'
@ -264,11 +275,11 @@ async def clear_dark_triggers(
) )
# remove exec-condition from set # remove exec-condition from set
log.info(f'removing pred for {oid}') log.info(f'Removing trigger for {oid}')
pred = execs.pop(oid, None) trigger: tuple | None = execs.pop(oid, None)
if not pred: if not trigger:
log.warning( log.warning(
f'pred for {oid} was already removed!?' f'trigger for {oid} was already removed!?'
) )
# update actives # update actives
@ -1215,14 +1226,15 @@ async def process_client_order_cmds(
and status.resp == 'dark_open' and status.resp == 'dark_open'
): ):
# remove from dark book clearing # remove from dark book clearing
entry = dark_book.triggers[fqme].pop(oid, None) entry: tuple | None = dark_book.triggers[fqme].pop(oid, None)
if entry: if entry:
( (
pred, pred,
tickfilter, tickfilter,
cmd, cmd,
percent_away, percent_away,
abs_diff_away abs_diff_away,
min_tick_digits,
) = entry ) = entry
# tell client side that we've cancelled the # tell client side that we've cancelled the
@ -1357,33 +1369,36 @@ async def process_client_order_cmds(
# TODO: make this configurable from our top level # TODO: make this configurable from our top level
# config, prolly in a .clearing` section? # config, prolly in a .clearing` section?
spread_slap: float = 5 spread_slap: float = 5
min_tick = float(flume.mkt.size_tick) min_tick = Decimal(flume.mkt.price_tick)
min_tick_digits = float_digits(min_tick) min_tick_digits: int = dec_digits(min_tick)
tickfilter: tuple[str, ...]
percent_away: float
if action == 'buy': if action == 'buy':
tickfilter = ('ask', 'last', 'trade') tickfilter = ('ask', 'last', 'trade')
percent_away = 0.005 percent_away: float = 0.005
# TODO: we probably need to scale this based # TODO: we probably need to scale this based
# on some near term historical spread # on some near term historical spread
# measure? # measure?
abs_diff_away = round( abs_diff_away = float(round(
spread_slap * min_tick, spread_slap * min_tick,
ndigits=min_tick_digits, ndigits=min_tick_digits,
) ))
elif action == 'sell': elif action == 'sell':
tickfilter = ('bid', 'last', 'trade') tickfilter = ('bid', 'last', 'trade')
percent_away = -0.005 percent_away: float = -0.005
abs_diff_away = round( abs_diff_away: float = float(round(
-spread_slap * min_tick, -spread_slap * min_tick,
ndigits=min_tick_digits, ndigits=min_tick_digits,
) ))
else: # alert else: # alert
tickfilter = ('trade', 'utrade', 'last') tickfilter = ('trade', 'utrade', 'last')
percent_away = 0 percent_away: float = 0
abs_diff_away = 0 abs_diff_away: float = 0
# submit execution/order to EMS scan loop # submit execution/order to EMS scan loop
# NOTE: this may result in an override of an existing # NOTE: this may result in an override of an existing
@ -1395,7 +1410,8 @@ async def process_client_order_cmds(
tickfilter, tickfilter,
req, req,
percent_away, percent_away,
abs_diff_away abs_diff_away,
min_tick_digits,
) )
resp = 'dark_open' resp = 'dark_open'