Fix size quantization and closed position popping..
Turns out we actually had further pp entry bugs due to *not quantizing* the size inside `.minimize_clears()` method calcs; fix that using `Position.sys.mkt.quantize()` as is done in `Position.calc_size()`. Fix `PpTable.write_config()` to drop from the TOML config any `closed: dict[str, Position]` entries delivered by `.dump_active()`. Add a more detailed doc string for our position type and a little todo for the `.bep` B)rekt_pps
parent
bba1ee43ff
commit
f106472bcb
|
@ -63,19 +63,42 @@ log = get_logger(__name__)
|
|||
|
||||
class Position(Struct):
|
||||
'''
|
||||
Basic pp (personal/piker position) model with attached clearing
|
||||
transaction history.
|
||||
An asset "position" model with attached clearing transaction history.
|
||||
|
||||
A financial "position" in `piker` terms is a summary of accounting
|
||||
metrics computed from a transaction ledger; generally it describes
|
||||
some acumulative "size" and "average price" from the summarized
|
||||
underlying transaction set.
|
||||
|
||||
In piker we focus on the `.ppu` (price per unit) and the `.bep`
|
||||
(break even price) including all transaction entries and exits since
|
||||
the last "net-zero" size of the destination asset's holding.
|
||||
|
||||
This interface serves as an object API for computing and tracking
|
||||
positions as well as supports serialization for storage in the local
|
||||
file system (in TOML) and to interchange as a msg over IPC.
|
||||
|
||||
'''
|
||||
symbol: Symbol | MktPair
|
||||
|
||||
@property
|
||||
def mkt(self) -> MktPair:
|
||||
return self.symbol
|
||||
|
||||
# can be +ve or -ve for long/short
|
||||
size: float
|
||||
|
||||
# "breakeven price" above or below which pnl moves above and below
|
||||
# zero for the entirety of the current "trade state".
|
||||
# "price-per-unit price" above or below which pnl moves above and
|
||||
# below zero for the entirety of the current "trade state". The ppu
|
||||
# is only modified on "increases of" the absolute size of a position
|
||||
# in one of a long/short "direction" (i.e. abs(.size_i) > 0 after
|
||||
# the next transaction given .size was > 0 before that tx, and vice
|
||||
# versa for -ve sized positions).
|
||||
ppu: float
|
||||
|
||||
# TODO: break-even-price support!
|
||||
# bep: float
|
||||
|
||||
# unique "backend system market id"
|
||||
bs_mktid: str
|
||||
|
||||
|
@ -164,7 +187,8 @@ class Position(Struct):
|
|||
inline_table = toml.TomlDecoder().get_empty_inline_table()
|
||||
|
||||
# serialize datetime to parsable `str`
|
||||
inline_table['dt'] = str(data['dt'])
|
||||
dtstr = inline_table['dt'] = str(data['dt'])
|
||||
assert 'Datetime' not in dtstr
|
||||
|
||||
# insert optional clear fields in column order
|
||||
for k in ['ppu', 'accum_size']:
|
||||
|
@ -191,7 +215,9 @@ class Position(Struct):
|
|||
|
||||
'''
|
||||
clears = list(self.clears.values())
|
||||
self.first_clear_dt = min(list(entry['dt'] for entry in clears))
|
||||
self.first_clear_dt = min(
|
||||
list(entry['dt'] for entry in clears)
|
||||
)
|
||||
last_clear = clears[-1]
|
||||
|
||||
csize = self.calc_size()
|
||||
|
@ -413,15 +439,21 @@ class Position(Struct):
|
|||
asset using the clears/trade event table; zero if expired.
|
||||
|
||||
'''
|
||||
size: float = 0
|
||||
size: float = 0.
|
||||
|
||||
# time-expired pps (normally derivatives) are "closed"
|
||||
# and have a zero size.
|
||||
if self.expired():
|
||||
return 0
|
||||
return 0.
|
||||
|
||||
for tid, entry in self.clears.items():
|
||||
size += entry['size']
|
||||
# XXX: do we need it every step?
|
||||
# no right since rounding is an LT?
|
||||
# size = self.mkt.quantize(
|
||||
# size + entry['size'],
|
||||
# quantity_type='size',
|
||||
# )
|
||||
|
||||
if self.split_ratio is not None:
|
||||
size = round(size * self.split_ratio)
|
||||
|
@ -450,7 +482,9 @@ class Position(Struct):
|
|||
# scan for the last "net zero" position by iterating
|
||||
# transactions until the next net-zero size, rinse, repeat.
|
||||
for tid, clear in self.clears.items():
|
||||
size += clear['size']
|
||||
size = float(
|
||||
self.mkt.quantize(size + clear['size'])
|
||||
)
|
||||
clears_since_zero.append((tid, clear))
|
||||
|
||||
if size == 0:
|
||||
|
@ -504,8 +538,6 @@ class PpTable(Struct):
|
|||
trans: dict[str, Transaction],
|
||||
cost_scalar: float = 2,
|
||||
|
||||
force_mkt: MktPair | None = None,
|
||||
|
||||
) -> dict[str, Position]:
|
||||
|
||||
pps = self.pps
|
||||
|
@ -523,15 +555,7 @@ class PpTable(Struct):
|
|||
|
||||
# template the mkt-info presuming a legacy market ticks
|
||||
# if no info exists in the transactions..
|
||||
mkt: MktPair | Symbol | None = force_mkt or t.sys
|
||||
if not mkt:
|
||||
mkt = MktPair.from_fqme(
|
||||
fqme,
|
||||
price_tick='0.01',
|
||||
size_tick='0.0',
|
||||
bs_mktid=bs_mktid,
|
||||
)
|
||||
|
||||
mkt: MktPair = t.sys
|
||||
pp = pps.get(bs_mktid)
|
||||
if not pp:
|
||||
# if no existing pp, allocate fresh one.
|
||||
|
@ -633,14 +657,18 @@ class PpTable(Struct):
|
|||
|
||||
def to_toml(
|
||||
self,
|
||||
active: dict[str, Position] | None = None,
|
||||
|
||||
) -> dict[str, Any]:
|
||||
|
||||
active, closed = self.dump_active()
|
||||
if active is None:
|
||||
active, _ = self.dump_active()
|
||||
|
||||
# ONLY dict-serialize all active positions; those that are
|
||||
# closed we don't store in the ``pps.toml``.
|
||||
to_toml_dict = {}
|
||||
|
||||
pos: Position
|
||||
for bs_mktid, pos in active.items():
|
||||
|
||||
# keep the minimal amount of clears that make up this
|
||||
|
@ -650,6 +678,8 @@ class PpTable(Struct):
|
|||
|
||||
# serialize to pre-toml form
|
||||
fqme, asdict = pos.to_pretoml()
|
||||
|
||||
# assert 'Datetime' not in asdict['dt']
|
||||
log.info(f'Updating active pp: {fqme}')
|
||||
|
||||
# XXX: ugh, it's cuz we push the section under
|
||||
|
@ -667,7 +697,9 @@ class PpTable(Struct):
|
|||
# TODO: show diff output?
|
||||
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
||||
# active, closed_pp_objs = table.dump_active()
|
||||
pp_entries = self.to_toml()
|
||||
|
||||
active, closed = self.dump_active()
|
||||
pp_entries = self.to_toml(active=active)
|
||||
if pp_entries:
|
||||
log.info(
|
||||
f'Updating positions in ``{self.conf_path}``:\n'
|
||||
|
@ -688,6 +720,12 @@ class PpTable(Struct):
|
|||
|
||||
self.conf.update(pp_entries)
|
||||
|
||||
# drop any entries that are computed as net-zero
|
||||
# we don't care about storing in the pps file.
|
||||
if closed:
|
||||
for fqme in closed:
|
||||
self.conf.pop(fqme, None)
|
||||
|
||||
# if there are no active position entries according
|
||||
# to the toml dump output above, then clear the config
|
||||
# file of all entries.
|
||||
|
|
Loading…
Reference in New Issue