Backward-compat: don't require `'lot_tick_size'`
In order to support existing `pps.toml` files in the wild which don't have the `asset_type, price_tick_size, lot_tick_size` fields, we need to only optionally read them and instead expect that backends will write the fields going forward (coming in follow patches). Further this makes some small asset-size (vlm accounting) quantization related adjustments: - rename `Symbol.decimal_quant()` -> `.quantize_size()` since that is explicitly what this method is doing. - and expect an input `size: float` which we cast to decimal instead of doing it inside the `.calc_size()` caller code. - drop `Symbol.iterfqsns()` which wasn't being used anywhere at all.. Additionally, this drafts out a new replacement market-trading-pair data type to eventually replace `.data._source.Symbol` -> `MktPair` which we aren't using yet, but serves as the documentation-driven motivator ;) and, it relates to https://github.com/pikers/piker/issues/467.backward_compat_trans_with_symbolinfo
parent
6be96a96aa
commit
3a4794e9d1
|
@ -18,7 +18,10 @@
|
|||
numpy data source coversion helpers.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from decimal import Decimal, ROUND_HALF_EVEN
|
||||
from decimal import (
|
||||
Decimal,
|
||||
ROUND_HALF_EVEN,
|
||||
)
|
||||
from typing import Any
|
||||
|
||||
from bidict import bidict
|
||||
|
@ -77,6 +80,10 @@ def mk_fqsn(
|
|||
def float_digits(
|
||||
value: float,
|
||||
) -> int:
|
||||
'''
|
||||
Return the number of precision digits read from a float value.
|
||||
|
||||
'''
|
||||
if value == 0:
|
||||
return 0
|
||||
|
||||
|
@ -127,6 +134,56 @@ def unpack_fqsn(fqsn: str) -> tuple[str, str, str]:
|
|||
)
|
||||
|
||||
|
||||
class MktPair(Struct, frozen=True):
|
||||
|
||||
src: str # source asset name being used to buy
|
||||
src_type: str # source asset's financial type/classification name
|
||||
# ^ specifies a "class" of financial instrument
|
||||
# egs. stock, futer, option, bond etc.
|
||||
|
||||
dst: str # destination asset name being bought
|
||||
dst_type: str # destination asset's financial type/classification name
|
||||
|
||||
price_tick: float # minimum price increment value increment
|
||||
price_tick_digits: int # required decimal digits for above
|
||||
|
||||
size_tick: float # minimum size (aka vlm) increment value increment
|
||||
size_tick_digits: int # required decimal digits for above
|
||||
|
||||
venue: str | None = None # market venue provider name
|
||||
expiry: str | None = None # for derivs, expiry datetime parseable str
|
||||
|
||||
# for derivs, info describing contract, egs.
|
||||
# strike price, call or put, swap type, exercise model, etc.
|
||||
contract_info: str | None = None
|
||||
|
||||
@classmethod
|
||||
def from_msg(
|
||||
self,
|
||||
msg: dict[str, Any],
|
||||
|
||||
) -> MktPair:
|
||||
'''
|
||||
Constructor for a received msg-dict normally received over IPC.
|
||||
|
||||
'''
|
||||
...
|
||||
|
||||
# fqa, fqma, .. etc. see issue:
|
||||
# https://github.com/pikers/piker/issues/467
|
||||
@property
|
||||
def fqsn(self) -> str:
|
||||
'''
|
||||
Return the fully qualified market (endpoint) name for the
|
||||
pair of transacting assets.
|
||||
|
||||
'''
|
||||
...
|
||||
|
||||
|
||||
# TODO: rework the below `Symbol` (which was originally inspired and
|
||||
# derived from stuff in quantdom) into a simpler, ipc msg ready, market
|
||||
# endpoint meta-data container type as per the drafted interace above.
|
||||
class Symbol(Struct):
|
||||
'''
|
||||
I guess this is some kinda container thing for dealing with
|
||||
|
@ -141,10 +198,6 @@ class Symbol(Struct):
|
|||
suffix: str = ''
|
||||
broker_info: dict[str, dict[str, Any]] = {}
|
||||
|
||||
# specifies a "class" of financial instrument
|
||||
# ex. stock, futer, option, bond etc.
|
||||
|
||||
# @validate_arguments
|
||||
@classmethod
|
||||
def from_broker_info(
|
||||
cls,
|
||||
|
@ -244,23 +297,23 @@ class Symbol(Struct):
|
|||
fqsn = '.'.join(map(str.lower, tokens))
|
||||
return fqsn
|
||||
|
||||
def iterfqsns(self) -> list[str]:
|
||||
keys = []
|
||||
for broker in self.broker_info.keys():
|
||||
fqsn = mk_fqsn(self.key, broker)
|
||||
if self.suffix:
|
||||
fqsn += f'.{self.suffix}'
|
||||
keys.append(fqsn)
|
||||
def quantize_size(
|
||||
self,
|
||||
size: float,
|
||||
|
||||
return keys
|
||||
) -> Decimal:
|
||||
'''
|
||||
Truncate input ``size: float`` using ``Decimal``
|
||||
and ``.lot_size_digits``.
|
||||
|
||||
def decimal_quant(self, d: Decimal):
|
||||
'''
|
||||
digits = self.lot_size_digits
|
||||
return d.quantize(
|
||||
return Decimal(size).quantize(
|
||||
Decimal(f'1.{"0".ljust(digits, "0")}'),
|
||||
rounding=ROUND_HALF_EVEN
|
||||
)
|
||||
|
||||
|
||||
def _nan_to_closest_num(array: np.ndarray):
|
||||
"""Return interpolated values instead of NaN.
|
||||
|
||||
|
|
30
piker/pp.py
30
piker/pp.py
|
@ -22,8 +22,6 @@ that doesn't try to cuk most humans who prefer to not lose their moneys..
|
|||
'''
|
||||
from __future__ import annotations
|
||||
from contextlib import contextmanager as cm
|
||||
from pathlib import Path
|
||||
from decimal import Decimal, ROUND_HALF_EVEN
|
||||
from pprint import pformat
|
||||
import os
|
||||
from os import path
|
||||
|
@ -91,10 +89,12 @@ def open_trade_ledger(
|
|||
yield cpy
|
||||
finally:
|
||||
if cpy != ledger:
|
||||
|
||||
# TODO: show diff output?
|
||||
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
||||
log.info(f'Updating ledger for {tradesfile}:\n')
|
||||
ledger.update(cpy)
|
||||
ledger.update(cpy)
|
||||
|
||||
# we write on close the mutated ledger data
|
||||
with open(tradesfile, 'w') as cf:
|
||||
toml.dump(ledger, cf)
|
||||
|
@ -476,7 +476,7 @@ class Position(Struct):
|
|||
if self.split_ratio is not None:
|
||||
size = round(size * self.split_ratio)
|
||||
|
||||
return float(self.symbol.decimal_quant(Decimal(size)))
|
||||
return float(self.symbol.quantize_size(size))
|
||||
|
||||
def minimize_clears(
|
||||
self,
|
||||
|
@ -934,10 +934,18 @@ def open_pps(
|
|||
for fqsn, entry in pps.items():
|
||||
bsuid = entry['bsuid']
|
||||
symbol = Symbol.from_fqsn(
|
||||
fqsn, info={
|
||||
'asset_type': entry['asset_type'],
|
||||
'price_tick_size': entry['price_tick_size'],
|
||||
'lot_tick_size': entry['lot_tick_size']
|
||||
fqsn,
|
||||
|
||||
# NOTE & TODO: right now we fill in the defaults from
|
||||
# `.data._source.Symbol` but eventually these should always
|
||||
# either be already written to the pos table or provided at
|
||||
# write time to ensure always having these values somewhere
|
||||
# and thus allowing us to get our pos sizing precision
|
||||
# correct!
|
||||
info={
|
||||
'asset_type': entry.get('asset_type', '<unknown>'),
|
||||
'price_tick_size': entry.get('price_tick_size', 0.01),
|
||||
'lot_tick_size': entry.get('lot_tick_size', 0.0),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -977,7 +985,11 @@ def open_pps(
|
|||
size = entry['size']
|
||||
|
||||
# TODO: remove but, handle old field name for now
|
||||
ppu = entry.get('ppu', entry.get('be_price', 0))
|
||||
ppu = entry.get(
|
||||
'ppu',
|
||||
entry.get('be_price', 0),
|
||||
)
|
||||
|
||||
split_ratio = entry.get('split_ratio')
|
||||
|
||||
expiry = entry.get('expiry')
|
||||
|
|
Loading…
Reference in New Issue