Merge pull request #218 from pikers/paper_pp_tracking

Paper pp tracking
pause_feeds_on_sym_switch
goodboy 2021-09-06 09:27:38 -04:00 committed by GitHub
commit 86cb8421d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 241 additions and 159 deletions

View File

@ -1419,6 +1419,7 @@ async def trades_dialogue(
# - short-sale but securities haven't been located, in this # - short-sale but securities haven't been located, in this
# case we should probably keep the order in some kind of # case we should probably keep the order in some kind of
# weird state or cancel it outright? # weird state or cancel it outright?
# status='PendingSubmit', message=''), # status='PendingSubmit', message=''),
# status='Cancelled', message='Error 404, # status='Cancelled', message='Error 404,
# reqId 1550: Order held while securities are located.'), # reqId 1550: Order held while securities are located.'),

View File

@ -35,7 +35,7 @@ from ..data._normalize import iterticks
from ..log import get_logger from ..log import get_logger
from ._messages import ( from ._messages import (
BrokerdCancel, BrokerdOrder, BrokerdOrderAck, BrokerdStatus, BrokerdCancel, BrokerdOrder, BrokerdOrderAck, BrokerdStatus,
BrokerdFill, BrokerdFill, BrokerdPosition,
) )
@ -60,6 +60,7 @@ class PaperBoi:
_buys: bidict _buys: bidict
_sells: bidict _sells: bidict
_reqids: bidict _reqids: bidict
_positions: dict[str, BrokerdPosition]
# init edge case L1 spread # init edge case L1 spread
last_ask: Tuple[float, float] = (float('inf'), 0) # price, size last_ask: Tuple[float, float] = (float('inf'), 0) # price, size
@ -101,6 +102,9 @@ class PaperBoi:
# in the broker trades event processing loop # in the broker trades event processing loop
await trio.sleep(0.05) await trio.sleep(0.05)
if action == 'sell':
size = -size
msg = BrokerdStatus( msg = BrokerdStatus(
status='submitted', status='submitted',
reqid=reqid, reqid=reqid,
@ -118,7 +122,7 @@ class PaperBoi:
) or ( ) or (
action == 'sell' and (clear_price := self.last_bid[0]) >= price action == 'sell' and (clear_price := self.last_bid[0]) >= price
): ):
await self.fake_fill(clear_price, size, action, reqid, oid) await self.fake_fill(symbol, clear_price, size, action, reqid, oid)
else: else:
# register this submissions as a paper live order # register this submissions as a paper live order
@ -170,6 +174,8 @@ class PaperBoi:
async def fake_fill( async def fake_fill(
self, self,
symbol: str,
price: float, price: float,
size: float, size: float,
action: str, # one of {'buy', 'sell'} action: str, # one of {'buy', 'sell'}
@ -181,6 +187,7 @@ class PaperBoi:
# remaining lots to fill # remaining lots to fill
order_complete: bool = True, order_complete: bool = True,
remaining: float = 0, remaining: float = 0,
) -> None: ) -> None:
"""Pretend to fill a broker order @ price and size. """Pretend to fill a broker order @ price and size.
@ -232,6 +239,49 @@ class PaperBoi:
) )
await self.ems_trades_stream.send(msg.dict()) await self.ems_trades_stream.send(msg.dict())
# lookup any existing position
token = f'{symbol}.{self.broker}'
pp_msg = self._positions.setdefault(
token,
BrokerdPosition(
broker=self.broker,
account='paper',
symbol=symbol,
# TODO: we need to look up the asset currency from
# broker info. i guess for crypto this can be
# inferred from the pair?
currency='',
size=0.0,
avg_price=0,
)
)
# "avg position price" calcs
# TODO: eventually it'd be nice to have a small set of routines
# to do this stuff from a sequence of cleared orders to enable
# so called "contextual positions".
new_size = size + pp_msg.size
# old size minus the new size gives us size differential with
# +ve -> increase in pp size
# -ve -> decrease in pp size
size_diff = abs(new_size) - abs(pp_msg.size)
if new_size == 0:
pp_msg.avg_price = 0
elif size_diff > 0:
# only update the "average position price" when the position
# size increases not when it decreases (i.e. the position is
# being made smaller)
pp_msg.avg_price = (
abs(size) * price + pp_msg.avg_price * abs(pp_msg.size)
) / abs(new_size)
pp_msg.size = new_size
await self.ems_trades_stream.send(pp_msg.dict())
async def simulate_fills( async def simulate_fills(
quote_stream: 'tractor.ReceiveStream', # noqa quote_stream: 'tractor.ReceiveStream', # noqa
@ -255,6 +305,7 @@ async def simulate_fills(
# this stream may eventually contain multiple symbols # this stream may eventually contain multiple symbols
async for quotes in quote_stream: async for quotes in quote_stream:
for sym, quote in quotes.items(): for sym, quote in quotes.items():
for tick in iterticks( for tick in iterticks(
@ -274,6 +325,7 @@ async def simulate_fills(
) )
orders = client._buys.get(sym, {}) orders = client._buys.get(sym, {})
book_sequence = reversed( book_sequence = reversed(
sorted(orders.keys(), key=itemgetter(1))) sorted(orders.keys(), key=itemgetter(1)))
@ -307,6 +359,7 @@ async def simulate_fills(
# clearing price would have filled entirely # clearing price would have filled entirely
await client.fake_fill( await client.fake_fill(
symbol=sym,
# todo slippage to determine fill price # todo slippage to determine fill price
price=tick_price, price=tick_price,
size=size, size=size,
@ -411,6 +464,9 @@ async def trades_dialogue(
_sells={}, _sells={},
_reqids={}, _reqids={},
# TODO: load paper positions from ``positions.toml``
_positions={},
) )
n.start_soon(handle_order_requests, client, ems_stream) n.start_soon(handle_order_requests, client, ems_stream)
@ -452,10 +508,5 @@ async def open_paperboi(
loglevel=loglevel, loglevel=loglevel,
) as (ctx, first): ) as (ctx, first):
try:
yield ctx, first
finally: yield ctx, first
# be sure to tear down the paper service on exit
with trio.CancelScope(shield=True):
await portal.cancel_actor()

View File

@ -26,9 +26,11 @@ import numpy as np
def mk_marker( def mk_marker(
style, style,
size: float = 20.0, size: float = 20.0,
use_qgpath: bool = True, use_qgpath: bool = True,
) -> QGraphicsPathItem: ) -> QGraphicsPathItem:
"""Add a marker to be displayed on the line wrapped in a ``QGraphicsPathItem`` """Add a marker to be displayed on the line wrapped in a ``QGraphicsPathItem``
ready to be placed using scene coordinates (not view). ready to be placed using scene coordinates (not view).

View File

@ -40,13 +40,13 @@ from ._axes import (
PriceAxis, PriceAxis,
YAxisLabel, YAxisLabel,
) )
from ._graphics._cursor import ( from ._cursor import (
Cursor, Cursor,
ContentsLabel, ContentsLabel,
) )
from ._l1 import L1Labels from ._l1 import L1Labels
from ._graphics._ohlc import BarItems from ._ohlc import BarItems
from ._graphics._curve import FastAppendCurve from ._curve import FastAppendCurve
from ._style import ( from ._style import (
hcolor, hcolor,
CHART_MARGINS, CHART_MARGINS,
@ -296,7 +296,7 @@ class LinkedSplits(QtWidgets.QWidget):
def set_split_sizes( def set_split_sizes(
self, self,
prop: float = 0.28 # proportion allocated to consumer subcharts prop: float = 0.375 # proportion allocated to consumer subcharts
) -> None: ) -> None:
"""Set the proportion of space allocated for linked subcharts. """Set the proportion of space allocated for linked subcharts.
""" """
@ -1317,7 +1317,7 @@ async def run_fsp(
# graphics.curve.setFillLevel(50) # graphics.curve.setFillLevel(50)
if fsp_func_name == 'rsi': if fsp_func_name == 'rsi':
from ._graphics._lines import level_line from ._lines import level_line
# add moveable over-[sold/bought] lines # add moveable over-[sold/bought] lines
# and labels only for the 70/30 lines # and labels only for the 70/30 lines
level_line(chart, 20) level_line(chart, 20)

View File

@ -27,13 +27,13 @@ import pyqtgraph as pg
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPointF, QRectF from PyQt5.QtCore import QPointF, QRectF
from .._style import ( from ._style import (
_xaxis_at, _xaxis_at,
hcolor, hcolor,
_font_small, _font_small,
) )
from .._axes import YAxisLabel, XAxisLabel from ._axes import YAxisLabel, XAxisLabel
from ...log import get_logger from ..log import get_logger
log = get_logger(__name__) log = get_logger(__name__)

View File

@ -23,7 +23,7 @@ from typing import Tuple
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from ..._profile import pg_profile_enabled from .._profile import pg_profile_enabled
# TODO: got a feeling that dropping this inheritance gets us even more speedups # TODO: got a feeling that dropping this inheritance gets us even more speedups

View File

@ -28,7 +28,7 @@ from PyQt5.QtCore import QPointF
import numpy as np import numpy as np
from ._style import hcolor, _font from ._style import hcolor, _font
from ._graphics._lines import order_line, LevelLine from ._lines import order_line, LevelLine
from ..log import get_logger from ..log import get_logger
@ -237,7 +237,6 @@ class LineEditor:
log.warning(f'No line for {uuid} could be found?') log.warning(f'No line for {uuid} could be found?')
return return
else: else:
assert line.oid == uuid
line.show_labels() line.show_labels()
# TODO: other flashy things to indicate the order is active # TODO: other flashy things to indicate the order is active
@ -260,18 +259,16 @@ class LineEditor:
self, self,
line: LevelLine = None, line: LevelLine = None,
uuid: str = None, uuid: str = None,
) -> LevelLine:
"""Remove a line by refernce or uuid. ) -> Optional[LevelLine]:
'''Remove a line by refernce or uuid.
If no lines or ids are provided remove all lines under the If no lines or ids are provided remove all lines under the
cursor position. cursor position.
""" '''
if line:
uuid = line.oid
# try to look up line from our registry # try to look up line from our registry
line = self._order_lines.pop(uuid, None) line = self._order_lines.pop(uuid, line)
if line: if line:
# if hovered remove from cursor set # if hovered remove from cursor set
@ -284,8 +281,13 @@ class LineEditor:
# just because we never got a un-hover event # just because we never got a un-hover event
cursor.show_xhair() cursor.show_xhair()
log.debug(f'deleting {line} with oid: {uuid}')
line.delete() line.delete()
return line
else:
log.warning(f'Could not find line for {line}')
return line
class SelectRect(QtGui.QGraphicsRectItem): class SelectRect(QtGui.QGraphicsRectItem):

View File

@ -1,20 +0,0 @@
# piker: trading gear for hackers
# Copyright (C) 2018-present Tyler Goodlet (in stewardship of piker0)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
Internal custom graphics mostly built for low latency and reuse.
"""

View File

@ -26,9 +26,9 @@ from pyqtgraph import Point, functions as fn
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPointF from PyQt5.QtCore import QPointF
from .._annotate import mk_marker, qgo_draw_markers from ._annotate import mk_marker, qgo_draw_markers
from .._label import Label, vbr_left, right_axis from ._label import Label, vbr_left, right_axis
from .._style import hcolor, _font from ._style import hcolor, _font
# TODO: probably worth investigating if we can # TODO: probably worth investigating if we can
@ -352,6 +352,21 @@ class LevelLine(pg.InfiniteLine):
return up_to_l1_sc return up_to_l1_sc
def marker_right_points(self) -> (float, float, float):
chart = self._chart
l1_len = chart._max_l1_line_len
ryaxis = chart.getAxis('right')
r_axis_x = ryaxis.pos().x()
up_to_l1_sc = r_axis_x - l1_len
size = self._default_mkr_size
marker_right = up_to_l1_sc - (1.375 * 2*size)
line_end = marker_right - (6/16 * size)
return line_end, marker_right, r_axis_x
def paint( def paint(
self, self,
p: QtGui.QPainter, p: QtGui.QPainter,
@ -366,26 +381,14 @@ class LevelLine(pg.InfiniteLine):
# these are in viewbox coords # these are in viewbox coords
vb_left, vb_right = self._endPoints vb_left, vb_right = self._endPoints
chart = self._chart
l1_len = chart._max_l1_line_len
ryaxis = chart.getAxis('right')
r_axis_x = ryaxis.pos().x()
up_to_l1_sc = r_axis_x - l1_len
vb = self.getViewBox() vb = self.getViewBox()
size = self._default_mkr_size line_end, marker_right, r_axis_x = self.marker_right_points()
marker_right = up_to_l1_sc - (1.375 * 2*size)
line_end = marker_right - (6/16 * size)
if self.show_markers and self.markers: if self.show_markers and self.markers:
size = self.markers[0][2]
p.setPen(self.pen) p.setPen(self.pen)
size = qgo_draw_markers( qgo_draw_markers(
self.markers, self.markers,
self.pen.color(), self.pen.color(),
p, p,
@ -438,9 +441,8 @@ class LevelLine(pg.InfiniteLine):
path: QtWidgets.QGraphicsPathItem, path: QtWidgets.QGraphicsPathItem,
) -> None: ) -> None:
# chart = self._chart # add path to scene
vb = self.getViewBox() self.getViewBox().scene().addItem(path)
vb.scene().addItem(path)
self._marker = path self._marker = path
@ -758,27 +760,31 @@ def position_line(
vr = vb.state['viewRange'] vr = vb.state['viewRange']
ymn, ymx = vr[1] ymn, ymx = vr[1]
level = line.value() level = line.value()
path = line._marker
if gt := level > ymx or (lt := level < ymn): # provide "nav hub" like indicator for where
# the position is on the y-dimension
# print(path._height)
# print(vb.shape())
# print(vb.boundingRect())
# print(vb.height())
_, marker_right, _ = line.marker_right_points()
if chartview.mode.name == 'order': if level > ymx: # pin to top of view
path.setPos(
QPointF(
marker_right,
2 + path._height,
)
)
# provide "nav hub" like indicator for where elif level < ymn: # pin to bottom of view
# the position is on the y-dimension path.setPos(
if gt: QPointF(
# pin to top of view since position is above current marker_right,
# y-range vb.height() - 16 + path._height,
pass )
)
elif lt:
# pin to bottom of view since position is above
# below y-range
pass
else:
# order mode is not active
# so hide the pp market
line._marker.hide()
else: else:
# pp line is viewable so show marker # pp line is viewable so show marker
@ -812,6 +818,10 @@ def position_line(
style = '>|' style = '>|'
arrow_path = mk_marker(style, size=arrow_size) arrow_path = mk_marker(style, size=arrow_size)
# monkey-cache height for sizing on pp nav-hub
arrow_path._height = arrow_path.boundingRect().height()
# XXX: uses new marker drawing approach # XXX: uses new marker drawing approach
line.add_marker(arrow_path) line.add_marker(arrow_path)
line.set_level(level) line.set_level(level)

View File

@ -27,8 +27,8 @@ from PyQt5.QtCore import QLineF, QPointF
# from numba import types as ntypes # from numba import types as ntypes
# from ..data._source import numba_ohlc_dtype # from ..data._source import numba_ohlc_dtype
from ..._profile import pg_profile_enabled from .._profile import pg_profile_enabled
from .._style import hcolor from ._style import hcolor
def _mk_lines_array( def _mk_lines_array(

View File

@ -29,7 +29,7 @@ import pyqtgraph as pg
from pydantic import BaseModel from pydantic import BaseModel
import trio import trio
from ._graphics._lines import LevelLine, position_line from ._lines import LevelLine, position_line
from ._editors import LineEditor, ArrowEditor from ._editors import LineEditor, ArrowEditor
from ._window import MultiStatus, main_window from ._window import MultiStatus, main_window
from ..clearing._client import open_ems, OrderBook from ..clearing._client import open_ems, OrderBook
@ -41,12 +41,31 @@ log = get_logger(__name__)
class Position(BaseModel): class Position(BaseModel):
'''Basic pp representation with attached fills history.
'''
symbol: Symbol symbol: Symbol
size: float size: float
avg_price: float avg_price: float # TODO: contextual pricing
fills: Dict[str, Any] = {} fills: Dict[str, Any] = {}
class OrderDialog(BaseModel):
'''Trade dialogue meta-data describing the lifetime
of an order submission to ``emsd`` from a chart.
'''
uuid: str
line: LevelLine
last_status_close: Callable = lambda: None
msgs: dict[str, dict] = {}
fills: Dict[str, Any] = {}
class Config:
arbitrary_types_allowed = True
underscore_attrs_are_private = False
@dataclass @dataclass
class OrderMode: class OrderMode:
'''Major mode for placing orders on a chart view. '''Major mode for placing orders on a chart view.
@ -60,8 +79,8 @@ class OrderMode:
Current manual: Current manual:
a -> alert a -> alert
s/ctrl -> submission type modifier {on: live, off: dark} s/ctrl -> submission type modifier {on: live, off: dark}
f (fill) -> buy limit order f (fill) -> 'buy' limit order
d (dump) -> sell limit order d (dump) -> 'sell' limit order
c (cancel) -> cancel order under cursor c (cancel) -> cancel order under cursor
cc -> cancel all submitted orders on chart cc -> cancel all submitted orders on chart
mouse click and drag -> modify current order under cursor mouse click and drag -> modify current order under cursor
@ -85,8 +104,7 @@ class OrderMode:
_position: Dict[str, Any] = field(default_factory=dict) _position: Dict[str, Any] = field(default_factory=dict)
_position_line: dict = None _position_line: dict = None
_pending_submissions: dict[str, (LevelLine, Callable)] = field( dialogs: dict[str, OrderDialog] = field(default_factory=dict)
default_factory=dict)
def on_position_update( def on_position_update(
self, self,
@ -139,33 +157,34 @@ class OrderMode:
action=action, action=action,
) )
def on_submit(self, uuid: str) -> dict: def on_submit(self, uuid: str) -> OrderDialog:
"""On order submitted event, commit the order line '''Order submitted status event handler.
and registered order uuid, store ack time stamp.
TODO: annotate order line with submission type ('live' vs. Commit the order line and registered order uuid, store ack time stamp.
'dark').
""" '''
line = self.lines.commit_line(uuid) line = self.lines.commit_line(uuid)
pending = self._pending_submissions.get(uuid) # a submission is the start of a new order dialog
if pending: dialog = self.dialogs[uuid]
order_line, func = pending dialog.line = line
assert order_line is line dialog.last_status_close()
func()
return line return dialog
def on_fill( def on_fill(
self, self,
uuid: str, uuid: str,
price: float, price: float,
arrow_index: float, arrow_index: float,
pointing: Optional[str] = None pointing: Optional[str] = None,
# delete_line: bool = False,
) -> None: ) -> None:
line = self.lines._order_lines.get(uuid) dialog = self.dialogs[uuid]
line = dialog.line
if line: if line:
self.arrows.add( self.arrows.add(
uuid, uuid,
@ -174,6 +193,8 @@ class OrderMode:
pointing=pointing, pointing=pointing,
color=line.color color=line.color
) )
else:
log.warn("No line for order {uuid}!?")
async def on_exec( async def on_exec(
self, self,
@ -181,11 +202,6 @@ class OrderMode:
msg: Dict[str, Any], msg: Dict[str, Any],
) -> None: ) -> None:
# only once all fills have cleared and the execution
# is complet do we remove our "order line"
line = self.lines.remove_line(uuid=uuid)
log.debug(f'deleting {line} with oid: {uuid}')
# DESKTOP NOTIFICATIONS # DESKTOP NOTIFICATIONS
# #
# TODO: this in another task? # TODO: this in another task?
@ -212,10 +228,9 @@ class OrderMode:
self.lines.remove_line(uuid=uuid) self.lines.remove_line(uuid=uuid)
self.chart.linked.cursor.show_xhair() self.chart.linked.cursor.show_xhair()
pending = self._pending_submissions.pop(uuid, None) dialog = self.dialogs.pop(uuid, None)
if pending: if dialog:
order_line, func = pending dialog.last_status_close()
func()
else: else:
log.warning( log.warning(
f'Received cancel for unsubmitted order {pformat(msg)}' f'Received cancel for unsubmitted order {pformat(msg)}'
@ -225,7 +240,7 @@ class OrderMode:
self, self,
size: Optional[float] = None, size: Optional[float] = None,
) -> LevelLine: ) -> OrderDialog:
"""Send execution order to EMS return a level line to """Send execution order to EMS return a level line to
represent the order on a chart. represent the order on a chart.
@ -234,7 +249,7 @@ class OrderMode:
# to be displayed when above order ack arrives # to be displayed when above order ack arrives
# (means the line graphic doesn't show on screen until the # (means the line graphic doesn't show on screen until the
# order is live in the emsd). # order is live in the emsd).
uid = str(uuid.uuid4()) oid = str(uuid.uuid4())
size = size or self._size size = size or self._size
@ -246,9 +261,46 @@ class OrderMode:
action = self._action action = self._action
# TODO: update the line once an ack event comes back
# from the EMS!
# TODO: place a grey line in "submission" mode
# which will be updated to it's appropriate action
# color once the submission ack arrives.
# make line graphic if order push was sucessful
line = self.lines.create_order_line(
oid,
level=y,
chart=chart,
size=size,
action=action,
)
dialog = OrderDialog(
uuid=oid,
line=line,
last_status_close=self.status_bar.open_status(
f'submitting {self._exec_mode}-{action}',
final_msg=f'submitted {self._exec_mode}-{action}',
clear_on_next=True,
)
)
# TODO: create a new ``OrderLine`` with this optional var defined
line.dialog = dialog
# enter submission which will be popped once a response
# from the EMS is received to move the order to a different# status
self.dialogs[oid] = dialog
# hook up mouse drag handlers
line._on_drag_start = self.order_line_modify_start
line._on_drag_end = self.order_line_modify_complete
# send order cmd to ems # send order cmd to ems
self.book.send( self.book.send(
uuid=uid, uuid=oid,
symbol=symbol.key, symbol=symbol.key,
brokers=symbol.brokers, brokers=symbol.brokers,
price=y, price=y,
@ -257,36 +309,7 @@ class OrderMode:
exec_mode=self._exec_mode, exec_mode=self._exec_mode,
) )
# TODO: update the line once an ack event comes back return dialog
# from the EMS!
# make line graphic if order push was
# sucessful
line = self.lines.create_order_line(
uid,
level=y,
chart=chart,
size=size,
action=action,
)
line.oid = uid
# enter submission which will be popped once a response
# from the EMS is received to move the order to a different# status
self._pending_submissions[uid] = (
line,
self.status_bar.open_status(
f'submitting {self._exec_mode}-{action}',
final_msg=f'submitted {self._exec_mode}-{action}',
clear_on_next=True,
)
)
# hook up mouse drag handlers
line._on_drag_start = self.order_line_modify_start
line._on_drag_end = self.order_line_modify_complete
return line
def cancel_orders_under_cursor(self) -> list[str]: def cancel_orders_under_cursor(self) -> list[str]:
return self.cancel_orders_from_lines( return self.cancel_orders_from_lines(
@ -317,16 +340,16 @@ class OrderMode:
# cancel all active orders and triggers # cancel all active orders and triggers
for line in lines: for line in lines:
oid = getattr(line, 'oid', None) dialog = getattr(line, 'dialog', None)
if oid: if dialog:
self._pending_submissions[oid] = ( oid = dialog.uuid
line,
self.status_bar.open_status( cancel_status_close = self.status_bar.open_status(
f'cancelling order {oid[:6]}', f'cancelling order {oid[:6]}',
group_key=key, group_key=key,
),
) )
dialog.last_status_close = cancel_status_close
ids.append(oid) ids.append(oid)
self.book.cancel(uuid=oid) self.book.cancel(uuid=oid)
@ -338,16 +361,20 @@ class OrderMode:
def order_line_modify_start( def order_line_modify_start(
self, self,
line: LevelLine, line: LevelLine,
) -> None: ) -> None:
print(f'Line modify: {line}') print(f'Line modify: {line}')
# cancel original order until new position is found # cancel original order until new position is found
def order_line_modify_complete( def order_line_modify_complete(
self, self,
line: LevelLine, line: LevelLine,
) -> None: ) -> None:
self.book.update( self.book.update(
uuid=line.oid, uuid=line.dialog.uuid,
# TODO: should we round this to a nearest tick here? # TODO: should we round this to a nearest tick here?
price=line.value(), price=line.value(),
@ -464,6 +491,10 @@ async def start_order_mode(
resp = msg['resp'] resp = msg['resp']
oid = msg['oid'] oid = msg['oid']
dialog = order_mode.dialogs[oid]
# record message to dialog tracking
dialog.msgs[oid] = msg
# response to 'action' request (buy/sell) # response to 'action' request (buy/sell)
if resp in ( if resp in (
'dark_submitted', 'dark_submitted',
@ -496,16 +527,21 @@ async def start_order_mode(
order_mode.on_fill( order_mode.on_fill(
oid, oid,
price=msg['trigger_price'], price=msg['trigger_price'],
arrow_index=get_index(time.time()) arrow_index=get_index(time.time()),
) )
order_mode.lines.remove_line(uuid=oid)
await order_mode.on_exec(oid, msg) await order_mode.on_exec(oid, msg)
# response to completed 'action' request for buy/sell # response to completed 'action' request for buy/sell
elif resp in ( elif resp in (
'broker_executed', 'broker_executed',
): ):
# right now this is just triggering a system alert
await order_mode.on_exec(oid, msg) await order_mode.on_exec(oid, msg)
if msg['brokerd_msg']['remaining'] == 0:
order_mode.lines.remove_line(uuid=oid)
# each clearing tick is responded individually # each clearing tick is responded individually
elif resp in ('broker_filled',): elif resp in ('broker_filled',):