Start `piker.pp` module, LIFO pp updates
Start a generic "position related" util mod and bring in the `Position` type from the allocator , convert it to a `msgspec.Struct` and add a `.lifo_update()` method. Implement a WIP pp parser from a trades ledger and use the new lifo method to gather position entries.lifo_pps_ib
parent
725909a94c
commit
1eb7e109e6
|
@ -23,53 +23,11 @@ from typing import Optional
|
||||||
|
|
||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, validator
|
||||||
|
from msgspec import Struct
|
||||||
|
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from ._messages import BrokerdPosition, Status
|
from ._messages import BrokerdPosition, Status
|
||||||
|
from ..pp import Position
|
||||||
|
|
||||||
class Position(BaseModel):
|
|
||||||
'''
|
|
||||||
Basic pp (personal position) model with attached fills history.
|
|
||||||
|
|
||||||
This type should be IPC wire ready?
|
|
||||||
|
|
||||||
'''
|
|
||||||
symbol: Symbol
|
|
||||||
|
|
||||||
# last size and avg entry price
|
|
||||||
size: float
|
|
||||||
avg_price: float # TODO: contextual pricing
|
|
||||||
|
|
||||||
# ordered record of known constituent trade messages
|
|
||||||
fills: list[Status] = []
|
|
||||||
|
|
||||||
def update_from_msg(
|
|
||||||
self,
|
|
||||||
msg: BrokerdPosition,
|
|
||||||
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
# XXX: better place to do this?
|
|
||||||
symbol = self.symbol
|
|
||||||
|
|
||||||
lot_size_digits = symbol.lot_size_digits
|
|
||||||
avg_price, size = (
|
|
||||||
round(msg['avg_price'], ndigits=symbol.tick_size_digits),
|
|
||||||
round(msg['size'], ndigits=lot_size_digits),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.avg_price = avg_price
|
|
||||||
self.size = size
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dsize(self) -> float:
|
|
||||||
'''
|
|
||||||
The "dollar" size of the pp, normally in trading (fiat) unit
|
|
||||||
terms.
|
|
||||||
|
|
||||||
'''
|
|
||||||
return self.avg_price * self.size
|
|
||||||
|
|
||||||
|
|
||||||
_size_units = bidict({
|
_size_units = bidict({
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
# piker: trading gear for hackers
|
||||||
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
||||||
|
|
||||||
|
# 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/>.
|
||||||
|
'''
|
||||||
|
Personal/Private position parsing, calculmating, summarizing in a way
|
||||||
|
that doesn't try to cuk most humans who prefer to not lose their moneys..
|
||||||
|
(looking at you `ib` and shitzy friends)
|
||||||
|
|
||||||
|
'''
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
|
from msgspec import Struct
|
||||||
|
|
||||||
|
from . import config
|
||||||
|
from .clearing._messages import BrokerdPosition, Status
|
||||||
|
from .data._source import Symbol
|
||||||
|
|
||||||
|
|
||||||
|
class Position(Struct):
|
||||||
|
'''
|
||||||
|
Basic pp (personal position) model with attached fills history.
|
||||||
|
|
||||||
|
This type should be IPC wire ready?
|
||||||
|
|
||||||
|
'''
|
||||||
|
symbol: Symbol
|
||||||
|
|
||||||
|
# last size and avg entry price
|
||||||
|
size: float
|
||||||
|
avg_price: float # TODO: contextual pricing
|
||||||
|
|
||||||
|
# ordered record of known constituent trade messages
|
||||||
|
fills: list[Status] = []
|
||||||
|
|
||||||
|
def update_from_msg(
|
||||||
|
self,
|
||||||
|
msg: BrokerdPosition,
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
# XXX: better place to do this?
|
||||||
|
symbol = self.symbol
|
||||||
|
|
||||||
|
lot_size_digits = symbol.lot_size_digits
|
||||||
|
avg_price, size = (
|
||||||
|
round(
|
||||||
|
msg['avg_price'],
|
||||||
|
ndigits=symbol.tick_size_digits
|
||||||
|
),
|
||||||
|
round(
|
||||||
|
msg['size'],
|
||||||
|
ndigits=lot_size_digits
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.avg_price = avg_price
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dsize(self) -> float:
|
||||||
|
'''
|
||||||
|
The "dollar" size of the pp, normally in trading (fiat) unit
|
||||||
|
terms.
|
||||||
|
|
||||||
|
'''
|
||||||
|
return self.avg_price * self.size
|
||||||
|
|
||||||
|
def lifo_update(
|
||||||
|
self,
|
||||||
|
size: float,
|
||||||
|
price: float,
|
||||||
|
|
||||||
|
) -> (float, float):
|
||||||
|
'''
|
||||||
|
Incremental update using a LIFO-style weighted mean.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# "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 = self.size + size
|
||||||
|
|
||||||
|
# old size minus the new size gives us size diff with
|
||||||
|
# +ve -> increase in pp size
|
||||||
|
# -ve -> decrease in pp size
|
||||||
|
size_diff = abs(new_size) - abs(self.size)
|
||||||
|
|
||||||
|
if new_size == 0:
|
||||||
|
self.avg_price = 0
|
||||||
|
|
||||||
|
elif size_diff > 0:
|
||||||
|
# XXX: LOFI incremental update:
|
||||||
|
# only update the "average price" when
|
||||||
|
# the size increases not when it decreases (i.e. the
|
||||||
|
# position is being made smaller)
|
||||||
|
self.avg_price = (
|
||||||
|
abs(size) * price # weight of current exec
|
||||||
|
+
|
||||||
|
self.avg_price * abs(self.size) # weight of previous pp
|
||||||
|
) / abs(new_size)
|
||||||
|
|
||||||
|
self.size = new_size
|
||||||
|
|
||||||
|
return new_size, self.avg_price
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pps(
|
||||||
|
|
||||||
|
brokername: str,
|
||||||
|
acctname: str,
|
||||||
|
|
||||||
|
ledger: Optional[dict[str, Union[str, float]]] = None,
|
||||||
|
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
pps: dict[str, Position] = {}
|
||||||
|
|
||||||
|
if not ledger:
|
||||||
|
with config.open_trade_ledger(
|
||||||
|
brokername,
|
||||||
|
acctname,
|
||||||
|
) as ledger:
|
||||||
|
pass # readonly
|
||||||
|
|
||||||
|
by_date = ledger[brokername]
|
||||||
|
|
||||||
|
for date, by_id in by_date.items():
|
||||||
|
for tid, record in by_id.items():
|
||||||
|
|
||||||
|
# ib specific record parsing
|
||||||
|
# date, time = record['dateTime']
|
||||||
|
# conid = record['condid']
|
||||||
|
# cost = record['cost']
|
||||||
|
# comms = record['ibCommission']
|
||||||
|
symbol = record['symbol']
|
||||||
|
price = record['tradePrice']
|
||||||
|
# action = record['buySell']
|
||||||
|
|
||||||
|
# NOTE: can be -ve on sells
|
||||||
|
size = float(record['quantity'])
|
||||||
|
|
||||||
|
pp = pps.setdefault(
|
||||||
|
symbol,
|
||||||
|
Position(
|
||||||
|
Symbol(key=symbol),
|
||||||
|
size=0.0,
|
||||||
|
avg_price=0.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# LOFI style average price calc
|
||||||
|
pp.lifo_update(size, price)
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
pprint(pps)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parse_pps('ib', 'algopaper')
|
|
@ -19,6 +19,7 @@ Position info and display
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from copy import copy
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import floor, copysign
|
from math import floor, copysign
|
||||||
|
@ -476,7 +477,7 @@ class PositionTracker:
|
||||||
|
|
||||||
self.alloc = alloc
|
self.alloc = alloc
|
||||||
self.startup_pp = startup_pp
|
self.startup_pp = startup_pp
|
||||||
self.live_pp = startup_pp.copy()
|
self.live_pp = copy(startup_pp)
|
||||||
|
|
||||||
view = chart.getViewBox()
|
view = chart.getViewBox()
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,8 @@ log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OrderDialog(BaseModel):
|
class OrderDialog(BaseModel):
|
||||||
'''Trade dialogue meta-data describing the lifetime
|
'''
|
||||||
|
Trade dialogue meta-data describing the lifetime
|
||||||
of an order submission to ``emsd`` from a chart.
|
of an order submission to ``emsd`` from a chart.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
@ -87,7 +88,8 @@ def on_level_change_update_next_order_info(
|
||||||
tracker: PositionTracker,
|
tracker: PositionTracker,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''A callback applied for each level change to the line
|
'''
|
||||||
|
A callback applied for each level change to the line
|
||||||
which will recompute the order size based on allocator
|
which will recompute the order size based on allocator
|
||||||
settings. this is assigned inside
|
settings. this is assigned inside
|
||||||
``OrderMode.line_from_order()``
|
``OrderMode.line_from_order()``
|
||||||
|
|
Loading…
Reference in New Issue