Flatten the brokerd-dialog relay loop using `match:`

the_ems_flattening
Tyler Goodlet 2022-08-04 15:21:15 -04:00
parent 46c51b55f7
commit 2309e7ab05
1 changed files with 156 additions and 148 deletions

View File

@ -23,7 +23,11 @@ from dataclasses import dataclass, field
from math import isnan from math import isnan
from pprint import pformat from pprint import pformat
import time import time
from typing import AsyncIterator, Callable from typing import (
AsyncIterator,
Any,
Callable,
)
from bidict import bidict from bidict import bidict
import trio import trio
@ -572,22 +576,25 @@ async def translate_and_relay_brokerd_events(
assert relay.brokerd_dialogue == brokerd_trades_stream assert relay.brokerd_dialogue == brokerd_trades_stream
brokerd_msg: dict[str, Any]
async for brokerd_msg in brokerd_trades_stream: async for brokerd_msg in brokerd_trades_stream:
name = brokerd_msg['name']
log.info( log.info(
f'Received broker trade event:\n' f'Received broker trade event:\n'
f'{pformat(brokerd_msg)}' f'{pformat(brokerd_msg)}'
) )
match brokerd_msg:
if name == 'position': # BrokerdPosition
case {
'name': 'position',
'symbol': sym,
'broker': broker,
}:
pos_msg = BrokerdPosition(**brokerd_msg) pos_msg = BrokerdPosition(**brokerd_msg)
# XXX: this will be useful for automatic strats yah? # XXX: this will be useful for automatic strats yah?
# keep pps per account up to date locally in ``emsd`` mem # keep pps per account up to date locally in ``emsd`` mem
sym, broker = pos_msg.symbol, pos_msg.broker # sym, broker = pos_msg.symbol, pos_msg.broker
relay.positions.setdefault( relay.positions.setdefault(
# NOTE: translate to a FQSN! # NOTE: translate to a FQSN!
@ -610,60 +617,16 @@ async def translate_and_relay_brokerd_events(
continue continue
# Get the broker (order) request id, this **must** be normalized # BrokerdOrderAck
# into messaging provided by the broker backend case {
reqid = brokerd_msg['reqid'] 'name': 'ack',
'reqid': reqid, # brokerd generated order-request id
# all piker originated requests will have an ems generated oid field 'oid': oid, # ems order-dialog id
oid = brokerd_msg.get( } if (
'oid', entry := book._ems_entries.get(oid)
book._ems2brokerd_ids.inverse.get(reqid) ):
)
if oid is None:
# XXX: paper clearing special cases
# paper engine race case: ``Client.submit_limit()`` hasn't
# returned yet and provided an output reqid to register
# locally, so we need to retreive the oid that was already
# packed at submission since we already know it ahead of
# time
paper = brokerd_msg['broker_details'].get('paper_info')
ext = brokerd_msg['broker_details'].get('external')
if paper:
# paperboi keeps the ems id up front
oid = paper['oid']
elif ext:
# may be an order msg specified as "external" to the
# piker ems flow (i.e. generated by some other
# external broker backend client (like tws for ib)
log.error(f"External trade event {ext}")
continue
else:
# something is out of order, we don't have an oid for
# this broker-side message.
log.error(
f'Unknown oid: {oid} for msg:\n'
f'{pformat(brokerd_msg)}\n'
'Unable to relay message to client side!?'
)
else:
# check for existing live flow entry
entry = book._ems_entries.get(oid)
old_reqid = entry.reqid
if old_reqid and old_reqid != reqid:
log.warning(
f'Brokerd order id change for {oid}:\n'
f'{old_reqid} -> {reqid}'
)
# initial response to brokerd order request # initial response to brokerd order request
if name == 'ack': # if name == 'ack':
# register the brokerd request id (that was generated # register the brokerd request id (that was generated
# / created internally by the broker backend) with our # / created internally by the broker backend) with our
@ -697,60 +660,53 @@ async def translate_and_relay_brokerd_events(
# update the flow with the ack msg # update the flow with the ack msg
book._ems_entries[oid] = BrokerdOrderAck(**brokerd_msg) book._ems_entries[oid] = BrokerdOrderAck(**brokerd_msg)
# no msg to client necessary
continue continue
# a live flow now exists # BrokerdOrderError
oid = entry.oid case {
'name': 'error',
'oid': oid, # ems order-dialog id
'reqid': reqid, # brokerd generated order-request id
'symbol': sym,
'broker_details': details,
# 'reason': reason,
}:
msg = BrokerdError(**brokerd_msg)
resp = 'broker_errored'
log.error(pformat(msg)) # XXX make one when it's blank?
# TODO: instead this should be our status set.
# ack, open, fill, closed, cancelled'
resp = None
broker_details = {}
if name in (
'error',
):
# TODO: figure out how this will interact with EMS clients # TODO: figure out how this will interact with EMS clients
# for ex. on an error do we react with a dark orders # for ex. on an error do we react with a dark orders
# management response, like cancelling all dark orders? # management response, like cancelling all dark orders?
# This looks like a supervision policy for pending orders on # This looks like a supervision policy for pending orders on
# some unexpected failure - something we need to think more # some unexpected failure - something we need to think more
# about. In most default situations, with composed orders # about. In most default situations, with composed orders
# (ex. brackets), most brokers seem to use a oca policy. # (ex. brackets), most brokers seem to use a oca policy.
msg = BrokerdError(**brokerd_msg)
# XXX should we make one when it's blank? # BrokerdStatus
log.error(pformat(msg)) case {
'name': 'status',
'status': status,
'reqid': reqid, # brokerd generated order-request id
# TODO: feels like the wrong msg for this field?
'remaining': remaining,
# TODO: getting this bs, prolly need to handle status messages } if (
# 'Market data farm connection is OK:usfarm.nj' oid := book._ems2brokerd_ids.inverse.get(reqid)
# another stupid ib error to handle
# if 10147 in message: cancel
resp = 'broker_errored'
broker_details = msg
# don't relay message to order requester client
# continue
elif name in (
'status',
): ):
msg = BrokerdStatus(**brokerd_msg) msg = BrokerdStatus(**brokerd_msg)
if msg.status == 'cancelled': # TODO: should we flatten out these cases and/or should
# they maybe even eventually be separate messages?
if status == 'cancelled':
log.info(f'Cancellation for {oid} is complete!') log.info(f'Cancellation for {oid} is complete!')
if msg.status == 'filled': if status == 'filled':
# conditional execution is fully complete, no more # conditional execution is fully complete, no more
# fills for the noted order # fills for the noted order
if not msg.remaining: if not remaining:
resp = 'broker_executed' resp = 'broker_executed'
@ -766,23 +722,75 @@ async def translate_and_relay_brokerd_events(
# one of {submitted, cancelled} # one of {submitted, cancelled}
resp = 'broker_' + msg.status resp = 'broker_' + msg.status
# pass the BrokerdStatus msg inside the broker details field # BrokerdFill
broker_details = msg case {
'name': 'fill',
elif name in ( 'reqid': reqid, # brokerd generated order-request id
'fill', # 'symbol': sym, # paper engine doesn't have this, nbd?
} if (
oid := book._ems2brokerd_ids.inverse.get(reqid)
): ):
msg = BrokerdFill(**brokerd_msg)
# proxy through the "fill" result(s) # proxy through the "fill" result(s)
msg = BrokerdFill(**brokerd_msg)
resp = 'broker_filled' resp = 'broker_filled'
broker_details = msg
log.info(f'\nFill for {oid} cleared with:\n{pformat(resp)}') log.info(f'\nFill for {oid} cleared with:\n{pformat(resp)}')
# unknown valid message case?
case {
'name': name,
'symbol': sym,
'reqid': reqid, # brokerd generated order-request id
# 'oid': oid, # ems order-dialog id
'broker_details': details,
} if (
book._ems2brokerd_ids.inverse.get(reqid) is None
):
# TODO: pretty sure we can drop this now?
# XXX: paper clearing special cases
# paper engine race case: ``Client.submit_limit()`` hasn't
# returned yet and provided an output reqid to register
# locally, so we need to retreive the oid that was already
# packed at submission since we already know it ahead of
# time
paper = details.get('paper_info')
ext = details.get('external')
if paper:
# paperboi keeps the ems id up front
oid = paper['oid']
elif ext:
# may be an order msg specified as "external" to the
# piker ems flow (i.e. generated by some other
# external broker backend client (like tws for ib)
log.error(f"External trade event {name}@{ext}")
else: else:
# something is out of order, we don't have an oid for
# this broker-side message.
log.error(
f'Unknown oid: {oid} for msg {name}:\n'
f'{pformat(brokerd_msg)}\n'
'Unable to relay message to client side!?'
)
continue
case _:
raise ValueError(f'Brokerd message {brokerd_msg} is invalid') raise ValueError(f'Brokerd message {brokerd_msg} is invalid')
# retrieve existing live flow
entry = book._ems_entries[oid]
assert entry.oid == oid
old_reqid = entry.reqid
if old_reqid and old_reqid != reqid:
log.warning(
f'Brokerd order id change for {oid}:\n'
f'{old_reqid} -> {reqid}'
)
# Create and relay response status message # Create and relay response status message
# to requesting EMS client # to requesting EMS client
try: try:
@ -793,7 +801,7 @@ async def translate_and_relay_brokerd_events(
resp=resp, resp=resp,
time_ns=time.time_ns(), time_ns=time.time_ns(),
broker_reqid=reqid, broker_reqid=reqid,
brokerd_msg=broker_details, brokerd_msg=msg,
) )
) )
except KeyError: except KeyError: