Get roundtrip alert uuids workin; stage order book api

basic_alerts
Tyler Goodlet 2021-01-05 08:02:14 -05:00
parent d0a3deae09
commit 24536ad769
3 changed files with 103 additions and 21 deletions

View File

@ -23,8 +23,9 @@ from typing import (
AsyncIterator, List, Dict, Callable, Tuple, AsyncIterator, List, Dict, Callable, Tuple,
Any, Any,
) )
import uuid # import uuid
import pyqtgraph as pg
import trio import trio
from trio_typing import TaskStatus from trio_typing import TaskStatus
import tractor import tractor
@ -32,12 +33,14 @@ import tractor
from . import data from . import data
from .log import get_logger from .log import get_logger
from .data._source import Symbol from .data._source import Symbol
from .ui._style import hcolor
log = get_logger(__name__) log = get_logger(__name__)
_to_router: trio.abc.SendChannel = None _to_router: trio.abc.SendChannel = None
_from_ui: trio.abc.ReceiveChannel = None _from_ui: trio.abc.ReceiveChannel = None
_lines = {}
_local_book = {} _local_book = {}
@ -54,6 +57,21 @@ class OrderBook:
orders: Dict[str, dict] = field(default_factory=dict) orders: Dict[str, dict] = field(default_factory=dict)
_cmds_from_ui: trio.abc.ReceiveChannel = _from_ui _cmds_from_ui: trio.abc.ReceiveChannel = _from_ui
async def alert(self, price: float) -> str:
...
async def buy(self, price: float) -> str:
...
async def sell(self, price: float) -> str:
...
async def modify(self, oid: str, price) -> bool:
...
async def cancel(self, oid: str) -> bool:
...
_orders: OrderBook = None _orders: OrderBook = None
@ -86,8 +104,11 @@ async def send_order_cmds():
symbol = lc.symbol symbol = lc.symbol
tp = order['type'] tp = order['type']
price = order['price'] price = order['price']
oid = order['oid']
oid = str(uuid.uuid4()) print(f'oid: {oid}')
# TODO
# oid = str(uuid.uuid4())
cmd = { cmd = {
'price': price, 'price': price,
@ -134,7 +155,7 @@ def mk_check(trigger_price, known_last) -> Callable[[float, float], bool]:
else: else:
return False return False
return check_gt return check_gt, 'gt'
elif trigger_price <= known_last: elif trigger_price <= known_last:
@ -144,7 +165,7 @@ def mk_check(trigger_price, known_last) -> Callable[[float, float], bool]:
else: else:
return False return False
return check_lt return check_lt, 'lt'
@dataclass @dataclass
@ -233,17 +254,23 @@ async def exec_orders(
if not execs: if not execs:
continue continue
for pred, action in tuple(execs): for oid, pred, action in tuple(execs):
# push trigger msg back to parent as an "alert" # push trigger msg back to parent as an "alert"
# (mocking for eg. a "fill") # (mocking for eg. a "fill")
if pred(price): if pred(price):
res = action(price) name = action(price)
await ctx.send_yield({ await ctx.send_yield({
'type': 'alert', 'type': 'alert',
'price': price, 'price': price,
# current shm array index
'index': feed.shm._last.value - 1,
'name': name,
'oid': oid,
}) })
execs.remove((pred, action)) execs.remove((oid, pred, action))
print(f"GOT ALERT FOR {exec_price} @ \n{tick}") print(
f"GOT ALERT FOR {exec_price} @ \n{tick}\n")
print(f'execs are {execs}')
# feed teardown # feed teardown
@ -264,10 +291,17 @@ async def stream_and_route(ctx, ui_name):
async for cmd in await portal.run(send_order_cmds): async for cmd in await portal.run(send_order_cmds):
action = cmd.pop('action')
if action == 'cancel':
pass
tp = cmd.pop('type') tp = cmd.pop('type')
trigger_price = cmd['price'] trigger_price = cmd['price']
sym = cmd['symbol'] sym = cmd['symbol']
brokers = cmd['brokers'] brokers = cmd['brokers']
oid = cmd['oid']
if tp == 'alert': if tp == 'alert':
log.info(f'Alert {cmd} received in {actor.uid}') log.info(f'Alert {cmd} received in {actor.uid}')
@ -295,18 +329,20 @@ async def stream_and_route(ctx, ui_name):
# should be based on the current first price received from the # should be based on the current first price received from the
# feed, instead of being like every other shitty tina platform # feed, instead of being like every other shitty tina platform
# that makes the user choose the predicate operator. # that makes the user choose the predicate operator.
pred = mk_check(trigger_price, last) pred, name = mk_check(trigger_price, last)
# create list of executions on first entry # create list of executions on first entry
book.orders.setdefault((broker, sym), []).append( book.orders.setdefault((broker, sym), []).append(
(pred, lambda p: p) (oid, pred, lambda p: name)
) )
# continue and wait on next order cmd # continue and wait on next order cmd
async def spawn_router_stream_alerts( async def spawn_router_stream_alerts(
chart,
symbol: Symbol, symbol: Symbol,
# lines: 'LinesEditor',
task_status: TaskStatus[str] = trio.TASK_STATUS_IGNORED, task_status: TaskStatus[str] = trio.TASK_STATUS_IGNORED,
) -> None: ) -> None:
"""Spawn an EMS daemon and begin sending orders and receiving """Spawn an EMS daemon and begin sending orders and receiving
@ -314,7 +350,7 @@ async def spawn_router_stream_alerts(
""" """
# setup local ui event streaming channels # setup local ui event streaming channels
global _from_ui, _to_router global _from_ui, _to_router, _lines
_to_router, _from_ui = trio.open_memory_channel(100) _to_router, _from_ui = trio.open_memory_channel(100)
actor = tractor.current_actor() actor = tractor.current_actor()
@ -337,6 +373,27 @@ async def spawn_router_stream_alerts(
async for alert in stream: async for alert in stream:
yb = pg.mkBrush(hcolor('alert_yellow'))
angle = 90 if alert['name'] == 'lt' else -90
arrow = pg.ArrowItem(
angle=angle,
baseAngle=0,
headLen=5,
headWidth=2,
tailLen=None,
brush=yb,
)
arrow.setPos(alert['index'], alert['price'])
chart.plotItem.addItem(arrow)
# delete the line from view
oid = alert['oid']
print(f'_lines: {_lines}')
print(f'deleting line with oid: {oid}')
_lines.pop(oid).delete()
# TODO: this in another task? # TODO: this in another task?
# not sure if this will ever be a bottleneck, # not sure if this will ever be a bottleneck,
# we probably could do graphics stuff first tho? # we probably could do graphics stuff first tho?
@ -345,10 +402,10 @@ async def spawn_router_stream_alerts(
result = await trio.run_process( result = await trio.run_process(
[ [
'notify-send', 'notify-send',
'piker',
f'Alert: {alert}',
'-u', 'normal', '-u', 'normal',
'-t', '10000', '-t', '10000',
'piker',
f'alert: {alert}',
], ],
) )
log.runtime(result) log.runtime(result)

View File

@ -937,6 +937,7 @@ async def _async_main(
# spawn EMS actor-service # spawn EMS actor-service
router_send_chan = await n.start( router_send_chan = await n.start(
spawn_router_stream_alerts, spawn_router_stream_alerts,
chart,
symbol, symbol,
) )

View File

@ -17,7 +17,9 @@
""" """
UX interaction customs. UX interaction customs.
""" """
from dataclasses import dataclass
from typing import Optional from typing import Optional
import uuid
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph import ViewBox, Point, QtCore, QtGui from pyqtgraph import ViewBox, Point, QtCore, QtGui
@ -27,6 +29,7 @@ import numpy as np
from ..log import get_logger from ..log import get_logger
from ._style import _min_points_to_show, hcolor, _font from ._style import _min_points_to_show, hcolor, _font
from ._graphics._lines import level_line from ._graphics._lines import level_line
from .._ems import _lines
log = get_logger(__name__) log = get_logger(__name__)
@ -195,13 +198,30 @@ class SelectRect(QtGui.QGraphicsRectItem):
self.hide() self.hide()
@dataclass
class LinesEditor:
view: 'ChartView'
chart: 'ChartPlotWidget'
active_line: 'LevelLine'
def stage_line(self) -> 'LevelLine':
...
def commit_line(self) -> 'LevelLine':
...
def remove_line(self, line) -> None:
...
class ChartView(ViewBox): class ChartView(ViewBox):
"""Price chart view box with interaction behaviors you'd expect from """Price chart view box with interaction behaviors you'd expect from
any interactive platform: any interactive platform:
- zoom on mouse scroll that auto fits y-axis - zoom on mouse scroll that auto fits y-axis
- no vertical scrolling - vertical scrolling on y-axis
- zoom to a "fixed point" on the y-axis - zoom on x to most recent in view datum
- zoom on right-click-n-drag to cursor position
""" """
def __init__( def __init__(
self, self,
@ -306,9 +326,7 @@ class ChartView(ViewBox):
# Scale or translate based on mouse button # Scale or translate based on mouse button
if button & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): if button & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton):
# print(f'left click drag pos {pos}') # zoom y-axis ONLY when click-n-drag on it
# zoom only y-axis when click-n-drag on it
if axis == 1: if axis == 1:
# set a static y range special value on chart widget to # set a static y range special value on chart widget to
# prevent sizing to data in view. # prevent sizing to data in view.
@ -407,11 +425,16 @@ class ChartView(ViewBox):
# XXX: should make this an explicit attr # XXX: should make this an explicit attr
# it's assigned inside ``.add_plot()`` # it's assigned inside ``.add_plot()``
self.linked_charts._to_router.send_nowait({ lc = self.linked_charts
'symbol': chart.name, oid = str(uuid.uuid4())
'brokers': ['kraken'], lc._to_router.send_nowait({
'chart': lc,
'type': 'alert', 'type': 'alert',
'price': y, 'price': y,
'oid': oid,
# 'symbol': lc.chart.name,
# 'brokers': lc.symbol.brokers,
# 'price': y,
}) })
line = level_line( line = level_line(
@ -419,6 +442,7 @@ class ChartView(ViewBox):
level=y, level=y,
color='alert_yellow', color='alert_yellow',
) )
_lines[oid] = line
log.info(f'clicked {pos}') log.info(f'clicked {pos}')
def keyReleaseEvent(self, ev): def keyReleaseEvent(self, ev):