Re-factor pnl display logic into settings pane
parent
21e6bee39b
commit
ef6594cfc4
|
@ -21,7 +21,7 @@ Position info and display
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import floor
|
from math import floor, copysign
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +32,10 @@ from ._anchors import (
|
||||||
pp_tight_and_right, # wanna keep it straight in the long run
|
pp_tight_and_right, # wanna keep it straight in the long run
|
||||||
gpath_pin,
|
gpath_pin,
|
||||||
)
|
)
|
||||||
from ..calc import humanize
|
from ..calc import humanize, pnl
|
||||||
from ..clearing._allocate import Allocator, Position
|
from ..clearing._allocate import Allocator, Position
|
||||||
|
from ..data._normalize import iterticks
|
||||||
|
from ..data.feed import Feed
|
||||||
from ._label import Label
|
from ._label import Label
|
||||||
from ._lines import LevelLine, order_line
|
from ._lines import LevelLine, order_line
|
||||||
from ._style import _font
|
from ._style import _font
|
||||||
|
@ -41,6 +43,70 @@ from ._forms import FieldsForm, FillStatusBar, QLabel
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
_pnl_tasks: dict[str, bool] = {}
|
||||||
|
|
||||||
|
|
||||||
|
async def display_pnl(
|
||||||
|
|
||||||
|
feed: Feed,
|
||||||
|
order_mode: OrderMode, # noqa
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
'''Real-time display the current pp's PnL in the appropriate label.
|
||||||
|
|
||||||
|
``ValueError`` if this task is spawned where there is a net-zero pp.
|
||||||
|
|
||||||
|
'''
|
||||||
|
global _pnl_tasks
|
||||||
|
|
||||||
|
pp = order_mode.current_pp
|
||||||
|
live = pp.live_pp
|
||||||
|
key = live.symbol.key
|
||||||
|
|
||||||
|
if live.size < 0:
|
||||||
|
types = ('ask', 'last', 'last', 'utrade')
|
||||||
|
|
||||||
|
elif live.size > 0:
|
||||||
|
types = ('bid', 'last', 'last', 'utrade')
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError('No pp?!?!')
|
||||||
|
|
||||||
|
# real-time update pnl on the status pane
|
||||||
|
try:
|
||||||
|
async with feed.stream.subscribe() as bstream:
|
||||||
|
# last_tick = time.time()
|
||||||
|
async for quotes in bstream:
|
||||||
|
|
||||||
|
# now = time.time()
|
||||||
|
# period = now - last_tick
|
||||||
|
|
||||||
|
for sym, quote in quotes.items():
|
||||||
|
|
||||||
|
for tick in iterticks(quote, types):
|
||||||
|
# print(f'{1/period} Hz')
|
||||||
|
|
||||||
|
size = order_mode.current_pp.live_pp.size
|
||||||
|
if size == 0:
|
||||||
|
# terminate this update task since we're
|
||||||
|
# no longer in a pp
|
||||||
|
order_mode.pane.pnl_label.format(pnl=0)
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
# compute and display pnl status
|
||||||
|
order_mode.pane.pnl_label.format(
|
||||||
|
pnl=copysign(1, size) * pnl(
|
||||||
|
# live.avg_price,
|
||||||
|
order_mode.current_pp.live_pp.avg_price,
|
||||||
|
tick['price'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# last_tick = time.time()
|
||||||
|
finally:
|
||||||
|
assert _pnl_tasks[key]
|
||||||
|
assert _pnl_tasks.pop(key)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -116,7 +182,7 @@ class SettingsPane:
|
||||||
tracker.show()
|
tracker.show()
|
||||||
tracker.hide_info()
|
tracker.hide_info()
|
||||||
|
|
||||||
mode.display_pnl(tracker)
|
self.display_pnl(tracker)
|
||||||
|
|
||||||
# load the new account's allocator
|
# load the new account's allocator
|
||||||
alloc = tracker.alloc
|
alloc = tracker.alloc
|
||||||
|
@ -201,6 +267,52 @@ class SettingsPane:
|
||||||
min(used, slots)
|
min(used, slots)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def display_pnl(
|
||||||
|
self,
|
||||||
|
tracker: PositionTracker,
|
||||||
|
|
||||||
|
) -> bool:
|
||||||
|
'''Display the PnL for the current symbol and personal positioning (pp).
|
||||||
|
|
||||||
|
If a position is open start a background task which will
|
||||||
|
real-time update the pnl label in the settings pane.
|
||||||
|
|
||||||
|
'''
|
||||||
|
mode = self.order_mode
|
||||||
|
sym = mode.chart.linked.symbol
|
||||||
|
size = tracker.live_pp.size
|
||||||
|
feed = mode.quote_feed
|
||||||
|
global _pnl_tasks
|
||||||
|
|
||||||
|
if (
|
||||||
|
size and
|
||||||
|
sym.key not in _pnl_tasks
|
||||||
|
):
|
||||||
|
_pnl_tasks[sym.key] = True
|
||||||
|
|
||||||
|
# immediately compute and display pnl status from last quote
|
||||||
|
self.pnl_label.format(
|
||||||
|
pnl=copysign(1, size) * pnl(
|
||||||
|
tracker.live_pp.avg_price,
|
||||||
|
# last historical close price
|
||||||
|
feed.shm.array[-1][['close']][0],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
f'Starting pnl display for {tracker.alloc.account_name()}')
|
||||||
|
self.order_mode.nursery.start_soon(
|
||||||
|
display_pnl,
|
||||||
|
feed,
|
||||||
|
mode,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
# set 0% pnl
|
||||||
|
self.pnl_label.format(pnl=0)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def position_line(
|
def position_line(
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ Chart trading, the only way to scalp.
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import copysign
|
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Dict, Callable, Any
|
from typing import Optional, Dict, Callable, Any
|
||||||
|
@ -32,14 +31,12 @@ import tractor
|
||||||
import trio
|
import trio
|
||||||
|
|
||||||
from .. import config
|
from .. import config
|
||||||
from ..calc import pnl
|
|
||||||
from ..clearing._client import open_ems, OrderBook
|
from ..clearing._client import open_ems, OrderBook
|
||||||
from ..clearing._allocate import (
|
from ..clearing._allocate import (
|
||||||
mk_allocator,
|
mk_allocator,
|
||||||
Position,
|
Position,
|
||||||
)
|
)
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from ..data._normalize import iterticks
|
|
||||||
from ..data.feed import Feed
|
from ..data.feed import Feed
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._editors import LineEditor, ArrowEditor
|
from ._editors import LineEditor, ArrowEditor
|
||||||
|
@ -507,49 +504,6 @@ class OrderMode:
|
||||||
|
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
def display_pnl(
|
|
||||||
self,
|
|
||||||
tracker: PositionTracker,
|
|
||||||
|
|
||||||
) -> bool:
|
|
||||||
'''Display the PnL for the current symbol and personal positioning (pp).
|
|
||||||
|
|
||||||
If a position is open start a background task which will
|
|
||||||
real-time update the pnl label in the settings pane.
|
|
||||||
|
|
||||||
'''
|
|
||||||
sym = self.chart.linked.symbol
|
|
||||||
size = tracker.live_pp.size
|
|
||||||
global _pnl_tasks
|
|
||||||
if (
|
|
||||||
size and
|
|
||||||
sym.key not in _pnl_tasks
|
|
||||||
):
|
|
||||||
_pnl_tasks[sym.key] = True
|
|
||||||
|
|
||||||
# immediately compute and display pnl status from last quote
|
|
||||||
self.pane.pnl_label.format(
|
|
||||||
pnl=copysign(1, size) * pnl(
|
|
||||||
tracker.live_pp.avg_price,
|
|
||||||
# last historical close price
|
|
||||||
self.quote_feed.shm.array[-1][['close']][0],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info(
|
|
||||||
f'Starting pnl display for {tracker.alloc.account_name()}')
|
|
||||||
self.nursery.start_soon(
|
|
||||||
display_pnl,
|
|
||||||
self.quote_feed,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
else:
|
|
||||||
# set 0% pnl
|
|
||||||
self.pane.pnl_label.format(pnl=0)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def open_order_mode(
|
async def open_order_mode(
|
||||||
|
@ -740,8 +694,6 @@ async def open_order_mode(
|
||||||
|
|
||||||
# make fill bar and positioning snapshot
|
# make fill bar and positioning snapshot
|
||||||
order_pane.on_ui_settings_change('limit', tracker.alloc.limit())
|
order_pane.on_ui_settings_change('limit', tracker.alloc.limit())
|
||||||
# order_pane.on_ui_settings_change('account', pp_account)
|
|
||||||
|
|
||||||
order_pane.update_status_ui(pp=tracker)
|
order_pane.update_status_ui(pp=tracker)
|
||||||
|
|
||||||
# TODO: create a mode "manager" of sorts?
|
# TODO: create a mode "manager" of sorts?
|
||||||
|
@ -749,8 +701,8 @@ async def open_order_mode(
|
||||||
# so that view handlers can access it
|
# so that view handlers can access it
|
||||||
view.order_mode = mode
|
view.order_mode = mode
|
||||||
|
|
||||||
mode.display_pnl(mode.current_pp)
|
|
||||||
order_pane.on_ui_settings_change('account', pp_account)
|
order_pane.on_ui_settings_change('account', pp_account)
|
||||||
|
mode.pane.display_pnl(mode.current_pp)
|
||||||
|
|
||||||
# Begin order-response streaming
|
# Begin order-response streaming
|
||||||
done()
|
done()
|
||||||
|
@ -784,72 +736,6 @@ async def open_order_mode(
|
||||||
yield mode
|
yield mode
|
||||||
|
|
||||||
|
|
||||||
_pnl_tasks: dict[str, bool] = {}
|
|
||||||
|
|
||||||
|
|
||||||
async def display_pnl(
|
|
||||||
|
|
||||||
feed: Feed,
|
|
||||||
order_mode: OrderMode,
|
|
||||||
|
|
||||||
) -> None:
|
|
||||||
'''Real-time display the current pp's PnL in the appropriate label.
|
|
||||||
|
|
||||||
Error if this task is spawned where there is a net-zero pp.
|
|
||||||
|
|
||||||
'''
|
|
||||||
global _pnl_tasks
|
|
||||||
|
|
||||||
pp = order_mode.current_pp
|
|
||||||
live = pp.live_pp
|
|
||||||
key = live.symbol.key
|
|
||||||
|
|
||||||
if live.size < 0:
|
|
||||||
types = ('ask', 'last', 'last', 'utrade')
|
|
||||||
|
|
||||||
elif live.size > 0:
|
|
||||||
types = ('bid', 'last', 'last', 'utrade')
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise RuntimeError('No pp?!?!')
|
|
||||||
|
|
||||||
# real-time update pnl on the status pane
|
|
||||||
try:
|
|
||||||
async with feed.stream.subscribe() as bstream:
|
|
||||||
# last_tick = time.time()
|
|
||||||
async for quotes in bstream:
|
|
||||||
|
|
||||||
# now = time.time()
|
|
||||||
# period = now - last_tick
|
|
||||||
|
|
||||||
for sym, quote in quotes.items():
|
|
||||||
|
|
||||||
for tick in iterticks(quote, types):
|
|
||||||
# print(f'{1/period} Hz')
|
|
||||||
|
|
||||||
size = order_mode.current_pp.live_pp.size
|
|
||||||
if size == 0:
|
|
||||||
# terminate this update task since we're
|
|
||||||
# no longer in a pp
|
|
||||||
order_mode.pane.pnl_label.format(pnl=0)
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
|
||||||
# compute and display pnl status
|
|
||||||
order_mode.pane.pnl_label.format(
|
|
||||||
pnl=copysign(1, size) * pnl(
|
|
||||||
# live.avg_price,
|
|
||||||
order_mode.current_pp.live_pp.avg_price,
|
|
||||||
tick['price'],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# last_tick = time.time()
|
|
||||||
finally:
|
|
||||||
assert _pnl_tasks[key]
|
|
||||||
assert _pnl_tasks.pop(key)
|
|
||||||
|
|
||||||
|
|
||||||
async def process_trades_and_update_ui(
|
async def process_trades_and_update_ui(
|
||||||
|
|
||||||
n: trio.Nursery,
|
n: trio.Nursery,
|
||||||
|
@ -884,7 +770,7 @@ async def process_trades_and_update_ui(
|
||||||
# update order pane widgets
|
# update order pane widgets
|
||||||
mode.pane.update_status_ui(tracker)
|
mode.pane.update_status_ui(tracker)
|
||||||
|
|
||||||
mode.display_pnl(tracker)
|
mode.pane.display_pnl(tracker)
|
||||||
# short circuit to next msg to avoid
|
# short circuit to next msg to avoid
|
||||||
# unnecessary msg content lookups
|
# unnecessary msg content lookups
|
||||||
continue
|
continue
|
||||||
|
|
Loading…
Reference in New Issue