Nest nursery scope inside EMS ctx in order mode
Move the `trio.open_nursery()` + `collapse_eg()`
block to be nested inside `open_ems()` instead
of as a sibling — nursery tasks (order handling,
trade processing) depend on the EMS connection
staying alive.
Also,
- Default `'paper'` entry upfront in `accounts`
dict and raise `ConfigurationError` with an
actionable msg when a `brokerd_accounts` fqan
isn't found in `accounts_def`.
- Use `msg.setdefault('brokerd_msg', msg)` for
ems dialog msgs instead of blind assignment;
warn if already set.
- Alias `asynccontextmanager as acm`.
- Use `fqan` var naming for fully-qualified
account names, `broker_name` from `mkt.broker`.
- Tighten log msg formatting (`fqme!r`).
(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
nested_order_mode_block
parent
5387538ba9
commit
e05c258914
|
|
@ -19,7 +19,7 @@ Chart trading, the only way to scalp.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager as acm
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
@ -779,7 +779,7 @@ class OrderMode:
|
||||||
return maybe_dialog
|
return maybe_dialog
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@acm
|
||||||
async def open_order_mode(
|
async def open_order_mode(
|
||||||
feed: Feed,
|
feed: Feed,
|
||||||
godw: GodWidget,
|
godw: GodWidget,
|
||||||
|
|
@ -814,6 +814,7 @@ async def open_order_mode(
|
||||||
|
|
||||||
# spawn EMS actor-service
|
# spawn EMS actor-service
|
||||||
async with (
|
async with (
|
||||||
|
# tractor.trionics.collapse_eg(),
|
||||||
open_ems(
|
open_ems(
|
||||||
fqme,
|
fqme,
|
||||||
loglevel=loglevel,
|
loglevel=loglevel,
|
||||||
|
|
@ -824,11 +825,10 @@ async def open_order_mode(
|
||||||
brokerd_accounts,
|
brokerd_accounts,
|
||||||
ems_dialog_msgs,
|
ems_dialog_msgs,
|
||||||
),
|
),
|
||||||
tractor.trionics.collapse_eg(),
|
|
||||||
trio.open_nursery() as tn,
|
|
||||||
|
|
||||||
):
|
):
|
||||||
log.info(f'Opening order mode for {fqme}')
|
log.info(
|
||||||
|
f'Opening order-mode for {fqme!r}'
|
||||||
|
)
|
||||||
|
|
||||||
# annotations editors
|
# annotations editors
|
||||||
lines = LineEditor(godw=godw)
|
lines = LineEditor(godw=godw)
|
||||||
|
|
@ -841,21 +841,34 @@ async def open_order_mode(
|
||||||
trackers: dict[str, PositionTracker] = {}
|
trackers: dict[str, PositionTracker] = {}
|
||||||
|
|
||||||
# load account names from ``brokers.toml``
|
# load account names from ``brokers.toml``
|
||||||
|
broker_name: str = mkt.broker
|
||||||
accounts_def: bidict[str, str | None] = config.load_accounts(
|
accounts_def: bidict[str, str | None] = config.load_accounts(
|
||||||
providers=[mkt.broker],
|
providers=[broker_name],
|
||||||
)
|
)
|
||||||
|
|
||||||
# XXX: ``brokerd`` delivers a set of account names that it
|
# XXX: ``brokerd`` delivers a set of account names that it
|
||||||
# allows use of but the user also can define the accounts they'd
|
# allows use of but the user also can define the accounts they'd
|
||||||
# like to use, in order, in their `brokers.toml` file.
|
# like to use, in order, in their `brokers.toml` file.
|
||||||
accounts: dict[str, str] = {}
|
accounts: dict[str, str] = {
|
||||||
for name in brokerd_accounts:
|
'paper': 'paper',
|
||||||
# ensure name is in ``brokers.toml``
|
}
|
||||||
accounts[name] = accounts_def[name]
|
fqan: str # ex. 'kraken.spot'
|
||||||
|
for fqan in brokerd_accounts:
|
||||||
|
# ensure fully-qualified-account-name declared in `brokers.toml`
|
||||||
|
|
||||||
# always add a paper entry so that paper cleared
|
try:
|
||||||
|
accounts[fqan] = accounts_def[fqan]
|
||||||
|
except KeyError as ke:
|
||||||
|
raise config.ConfigurationError(
|
||||||
|
f'The {broker_name!r} account {fqan!r} could not be found?\n'
|
||||||
|
f'\n'
|
||||||
|
f'Did you forget to define the correct account name in your `brokers.toml`?\n'
|
||||||
|
f'{ppfmt(accounts_def)}\n'
|
||||||
|
) from ke
|
||||||
|
|
||||||
|
# NOTE, always add a paper entry so that paper cleared
|
||||||
# order dialogs can be tracked in the order mode UIs.
|
# order dialogs can be tracked in the order mode UIs.
|
||||||
accounts['paper'] = 'paper'
|
# accounts['paper'] = 'paper'
|
||||||
|
|
||||||
# first account listed is the one we select at startup
|
# first account listed is the one we select at startup
|
||||||
# (aka order based selection).
|
# (aka order based selection).
|
||||||
|
|
@ -940,110 +953,124 @@ async def open_order_mode(
|
||||||
for name, tracker in trackers.items():
|
for name, tracker in trackers.items():
|
||||||
order_pane.update_account_icons({name: tracker.live_pp})
|
order_pane.update_account_icons({name: tracker.live_pp})
|
||||||
|
|
||||||
# top level abstraction which wraps all this crazyness into
|
|
||||||
# a namespace..
|
|
||||||
mode = OrderMode(
|
|
||||||
godw,
|
|
||||||
feed,
|
|
||||||
chart,
|
|
||||||
hist_chart,
|
|
||||||
tn,
|
|
||||||
client,
|
|
||||||
lines,
|
|
||||||
arrows,
|
|
||||||
multistatus,
|
|
||||||
pane=order_pane,
|
|
||||||
trackers=trackers,
|
|
||||||
|
|
||||||
)
|
|
||||||
# XXX: MUST be set
|
|
||||||
order_pane.order_mode = mode
|
|
||||||
|
|
||||||
# select a pp to track
|
|
||||||
tracker: PositionTracker = trackers[pp_account]
|
|
||||||
mode.current_pp = tracker
|
|
||||||
tracker.nav.show()
|
|
||||||
tracker.nav.hide_info()
|
|
||||||
|
|
||||||
# XXX: would love to not have to do this separate from edit
|
|
||||||
# fields (which are done in an async loop - see below)
|
|
||||||
# connect selection signals (from drop down widgets)
|
|
||||||
# to order sync pane handler
|
|
||||||
for key in ('account', 'size_unit',):
|
|
||||||
w = form.fields[key]
|
|
||||||
w.currentTextChanged.connect(
|
|
||||||
partial(
|
|
||||||
order_pane.on_selection_change,
|
|
||||||
key=key,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# make fill bar and positioning snapshot
|
|
||||||
order_pane.update_status_ui(tracker)
|
|
||||||
|
|
||||||
# TODO: create a mode "manager" of sorts?
|
|
||||||
# -> probably just call it "UxModes" err sumthin?
|
|
||||||
# so that view handlers can access it
|
|
||||||
chart.view.order_mode = mode
|
|
||||||
hist_chart.view.order_mode = mode
|
|
||||||
|
|
||||||
order_pane.on_ui_settings_change('account', pp_account)
|
|
||||||
mode.pane.display_pnl(mode.current_pp)
|
|
||||||
|
|
||||||
# Begin order-response streaming
|
|
||||||
done()
|
|
||||||
|
|
||||||
# Pack position messages by account, should only be one-to-one.
|
|
||||||
# NOTE: requires the backend exactly specifies
|
|
||||||
# the expected symbol key in its positions msg.
|
|
||||||
for (
|
|
||||||
(broker, acctid),
|
|
||||||
pps_by_fqme
|
|
||||||
) in position_msgs.items():
|
|
||||||
for msg in pps_by_fqme.values():
|
|
||||||
await process_trade_msg(
|
|
||||||
mode,
|
|
||||||
client,
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
|
|
||||||
async with (
|
async with (
|
||||||
|
tractor.trionics.collapse_eg(),
|
||||||
# pp pane kb inputs
|
trio.open_nursery() as tn,
|
||||||
open_form_input_handling(
|
|
||||||
form,
|
|
||||||
focus_next=chart.linked.godwidget,
|
|
||||||
on_value_change=order_pane.on_ui_settings_change,
|
|
||||||
),
|
|
||||||
|
|
||||||
):
|
):
|
||||||
# signal to top level symbol loading task we're ready
|
# top level abstraction which wraps all this crazyness into
|
||||||
# to handle input since the ems connection is ready
|
# a namespace..
|
||||||
started.set()
|
mode = OrderMode(
|
||||||
|
godw,
|
||||||
|
feed,
|
||||||
|
chart,
|
||||||
|
hist_chart,
|
||||||
|
tn,
|
||||||
|
client,
|
||||||
|
lines,
|
||||||
|
arrows,
|
||||||
|
multistatus,
|
||||||
|
pane=order_pane,
|
||||||
|
trackers=trackers,
|
||||||
|
|
||||||
for oid, msg in ems_dialog_msgs.items():
|
)
|
||||||
|
# XXX: MUST be set
|
||||||
|
order_pane.order_mode = mode
|
||||||
|
|
||||||
# HACK ALERT: ensure a resp field is filled out since
|
# select a pp to track
|
||||||
# techincally the call below expects a ``Status``. TODO:
|
tracker: PositionTracker = trackers[pp_account]
|
||||||
# parse into proper ``Status`` equivalents ems-side?
|
mode.current_pp = tracker
|
||||||
# msg.setdefault('resp', msg['broker_details']['resp'])
|
tracker.nav.show()
|
||||||
# msg.setdefault('oid', msg['broker_details']['oid'])
|
tracker.nav.hide_info()
|
||||||
msg['brokerd_msg'] = msg
|
|
||||||
|
|
||||||
await process_trade_msg(
|
# XXX: would love to not have to do this separate from edit
|
||||||
mode,
|
# fields (which are done in an async loop - see below)
|
||||||
client,
|
# connect selection signals (from drop down widgets)
|
||||||
msg,
|
# to order sync pane handler
|
||||||
|
for key in ('account', 'size_unit',):
|
||||||
|
w = form.fields[key]
|
||||||
|
w.currentTextChanged.connect(
|
||||||
|
partial(
|
||||||
|
order_pane.on_selection_change,
|
||||||
|
key=key,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
tn.start_soon(
|
# make fill bar and positioning snapshot
|
||||||
process_trades_and_update_ui,
|
order_pane.update_status_ui(tracker)
|
||||||
trades_stream,
|
|
||||||
mode,
|
|
||||||
client,
|
|
||||||
)
|
|
||||||
|
|
||||||
yield mode
|
# TODO: create a mode "manager" of sorts?
|
||||||
|
# -> probably just call it "UxModes" err sumthin?
|
||||||
|
# so that view handlers can access it
|
||||||
|
chart.view.order_mode = mode
|
||||||
|
hist_chart.view.order_mode = mode
|
||||||
|
|
||||||
|
order_pane.on_ui_settings_change('account', pp_account)
|
||||||
|
mode.pane.display_pnl(mode.current_pp)
|
||||||
|
|
||||||
|
# Begin order-response streaming
|
||||||
|
done()
|
||||||
|
|
||||||
|
# Pack position messages by account, should only be one-to-one.
|
||||||
|
# NOTE: requires the backend exactly specifies
|
||||||
|
# the expected symbol key in its positions msg.
|
||||||
|
for (
|
||||||
|
(broker, acctid),
|
||||||
|
pps_by_fqme
|
||||||
|
) in position_msgs.items():
|
||||||
|
for msg in pps_by_fqme.values():
|
||||||
|
await process_trade_msg(
|
||||||
|
mode,
|
||||||
|
client,
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
async with (
|
||||||
|
|
||||||
|
# pp pane kb inputs
|
||||||
|
open_form_input_handling(
|
||||||
|
form,
|
||||||
|
focus_next=chart.linked.godwidget,
|
||||||
|
on_value_change=order_pane.on_ui_settings_change,
|
||||||
|
),
|
||||||
|
|
||||||
|
):
|
||||||
|
# signal to top level symbol loading task we're ready
|
||||||
|
# to handle input since the ems connection is ready
|
||||||
|
started.set()
|
||||||
|
|
||||||
|
for oid, msg in ems_dialog_msgs.items():
|
||||||
|
|
||||||
|
# HACK ALERT: ensure a resp field is filled out since
|
||||||
|
# techincally the call below expects a ``Status``. TODO:
|
||||||
|
# parse into proper ``Status`` equivalents ems-side?
|
||||||
|
# msg.setdefault('resp', msg['broker_details']['resp'])
|
||||||
|
# msg.setdefault('oid', msg['broker_details']['oid'])
|
||||||
|
ya_msg: dict = msg.setdefault(
|
||||||
|
'brokerd_msg',
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
if msg is not ya_msg:
|
||||||
|
log.warning(
|
||||||
|
f'A `.brokerd_msg` was already set for ems-dialog msg?\n'
|
||||||
|
f'oid: {oid!r}\n'
|
||||||
|
f'ya_msg: {ya_msg!r}\n'
|
||||||
|
f'msg: {ya_msg!r}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
await process_trade_msg(
|
||||||
|
mode,
|
||||||
|
client,
|
||||||
|
msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
tn.start_soon(
|
||||||
|
process_trades_and_update_ui,
|
||||||
|
trades_stream,
|
||||||
|
mode,
|
||||||
|
client,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield mode
|
||||||
|
|
||||||
|
|
||||||
async def process_trades_and_update_ui(
|
async def process_trades_and_update_ui(
|
||||||
|
|
@ -1281,7 +1308,8 @@ async def process_trade_msg(
|
||||||
# that should never happen tho?
|
# that should never happen tho?
|
||||||
action: str = (
|
action: str = (
|
||||||
getattr(order, 'action', None)
|
getattr(order, 'action', None)
|
||||||
or order['action']
|
or
|
||||||
|
order['action']
|
||||||
)
|
)
|
||||||
details: dict = msg.brokerd_msg
|
details: dict = msg.brokerd_msg
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue