Fix double cancel bug!

Not sure how this lasted so long without complaint (literally since we
added history 1m OHLC it seems; guess it means most backends are pretty
tolerant XD ) but we've been sending 2 cancels per order (dialog) due to
the mirrored lines on each chart: 1s and 1m. This fixes that by
reworking the `OrderMode` methods to be a bit more sane and less
conflated with the graphics (lines) layer.

Deatz:
- add new methods:
  - `.oids_from_lines()` line -> oid extraction,
  - `.cancel_orders()` which makes the order client cancel requests from
    a `oids: list[str]`.
- re-impl `.cancel_all_orders()` and `.cancel_orders_under_cursor()` to
  use the above methods thus fixing the original bug B)
basic_buy_bot
Tyler Goodlet 2023-06-17 16:50:56 -04:00
parent 84613cd596
commit b28b38afab
1 changed files with 55 additions and 38 deletions

View File

@ -31,6 +31,7 @@ from typing import (
) )
import uuid import uuid
from bidict import bidict
import tractor import tractor
import trio import trio
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
@ -601,50 +602,65 @@ class OrderMode:
) )
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(
self.oids_from_lines(
self.lines.lines_under_cursor() self.lines.lines_under_cursor()
) )
def cancel_all_orders(self) -> list[str]:
'''
Cancel all orders for the current chart.
'''
return self.cancel_orders_from_lines(
self.lines.all_lines()
) )
def cancel_orders_from_lines( def oids_from_lines(
self, self,
lines: list[LevelLine], lines: list[LevelLine],
) -> list[str]: ) -> list[Dialog]:
ids: list = [] oids: set[str] = set()
if lines: for line in lines:
dialog: Dialog = getattr(line, 'dialog', None)
oid: str = dialog.uuid
if (
dialog
and oid not in oids
):
oids.add(oid)
return oids
def cancel_orders(
self,
oids: list[str],
) -> None:
'''
Cancel all orders from a list of order ids: `oids`.
'''
key = self.multistatus.open_status( key = self.multistatus.open_status(
f'cancelling {len(lines)} orders', f'cancelling {len(oids)} orders',
final_msg=f'cancelled {len(lines)} orders', final_msg=f'cancelled orders:\n{oids}',
group_key=True group_key=True
) )
for oid in oids:
# cancel all active orders and triggers dialog: Dialog = self.dialogs[oid]
for line in lines: self.client.cancel_nowait(uuid=oid)
dialog = getattr(line, 'dialog', None)
if dialog:
oid = dialog.uuid
cancel_status_close = self.multistatus.open_status( cancel_status_close = self.multistatus.open_status(
f'cancelling order {oid}', f'cancelling order {oid}',
group_key=key, group_key=key,
) )
dialog.last_status_close = cancel_status_close dialog.last_status_close = cancel_status_close
ids.append(oid) def cancel_all_orders(self) -> None:
self.client.cancel_nowait(uuid=oid) '''
Cancel all unique orders / executions by extracting unique
order ids from all order lines and then submitting cancel
requests for each dialog.
return ids '''
return self.cancel_orders(
self.oids_from_lines(
self.lines.all_lines()
)
)
def load_unknown_dialog_from_msg( def load_unknown_dialog_from_msg(
self, self,
@ -750,7 +766,7 @@ 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``
accounts_def = config.load_accounts( accounts_def: bidict[str, str | None] = config.load_accounts(
providers=[mkt.broker], providers=[mkt.broker],
) )
@ -1135,8 +1151,9 @@ async def process_trade_msg(
action = order.action action = order.action
details = msg.brokerd_msg details = msg.brokerd_msg
# TODO: put the actual exchange timestamp? # TODO: state tracking:
# TODO: some kinda progress system? # - put the actual exchange timestamp?
# - some kinda progress system?
# NOTE: currently the ``kraken`` openOrders sub # NOTE: currently the ``kraken`` openOrders sub
# doesn't deliver their engine timestamp as part of # doesn't deliver their engine timestamp as part of