Flatten the brokerd-dialog relay loop using `match:`
parent
46c51b55f7
commit
2309e7ab05
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue