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.
|
numpy data source coversion helpers.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from decimal import Decimal, ROUND_HALF_EVEN
|
from decimal import (
|
||||||
|
Decimal,
|
||||||
|
ROUND_HALF_EVEN,
|
||||||
|
)
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
|
@ -77,6 +80,10 @@ def mk_fqsn(
|
||||||
def float_digits(
|
def float_digits(
|
||||||
value: float,
|
value: float,
|
||||||
) -> int:
|
) -> int:
|
||||||
|
'''
|
||||||
|
Return the number of precision digits read from a float value.
|
||||||
|
|
||||||
|
'''
|
||||||
if value == 0:
|
if value == 0:
|
||||||
return 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):
|
class Symbol(Struct):
|
||||||
'''
|
'''
|
||||||
I guess this is some kinda container thing for dealing with
|
I guess this is some kinda container thing for dealing with
|
||||||
|
@ -141,10 +198,6 @@ class Symbol(Struct):
|
||||||
suffix: str = ''
|
suffix: str = ''
|
||||||
broker_info: dict[str, dict[str, Any]] = {}
|
broker_info: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
# specifies a "class" of financial instrument
|
|
||||||
# ex. stock, futer, option, bond etc.
|
|
||||||
|
|
||||||
# @validate_arguments
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_broker_info(
|
def from_broker_info(
|
||||||
cls,
|
cls,
|
||||||
|
@ -244,23 +297,23 @@ class Symbol(Struct):
|
||||||
fqsn = '.'.join(map(str.lower, tokens))
|
fqsn = '.'.join(map(str.lower, tokens))
|
||||||
return fqsn
|
return fqsn
|
||||||
|
|
||||||
def iterfqsns(self) -> list[str]:
|
def quantize_size(
|
||||||
keys = []
|
self,
|
||||||
for broker in self.broker_info.keys():
|
size: float,
|
||||||
fqsn = mk_fqsn(self.key, broker)
|
|
||||||
if self.suffix:
|
|
||||||
fqsn += f'.{self.suffix}'
|
|
||||||
keys.append(fqsn)
|
|
||||||
|
|
||||||
return keys
|
) -> Decimal:
|
||||||
|
'''
|
||||||
|
Truncate input ``size: float`` using ``Decimal``
|
||||||
|
and ``.lot_size_digits``.
|
||||||
|
|
||||||
def decimal_quant(self, d: Decimal):
|
'''
|
||||||
digits = self.lot_size_digits
|
digits = self.lot_size_digits
|
||||||
return d.quantize(
|
return Decimal(size).quantize(
|
||||||
Decimal(f'1.{"0".ljust(digits, "0")}'),
|
Decimal(f'1.{"0".ljust(digits, "0")}'),
|
||||||
rounding=ROUND_HALF_EVEN
|
rounding=ROUND_HALF_EVEN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _nan_to_closest_num(array: np.ndarray):
|
def _nan_to_closest_num(array: np.ndarray):
|
||||||
"""Return interpolated values instead of NaN.
|
"""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 __future__ import annotations
|
||||||
from contextlib import contextmanager as cm
|
from contextlib import contextmanager as cm
|
||||||
from pathlib import Path
|
|
||||||
from decimal import Decimal, ROUND_HALF_EVEN
|
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import os
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
|
@ -91,10 +89,12 @@ def open_trade_ledger(
|
||||||
yield cpy
|
yield cpy
|
||||||
finally:
|
finally:
|
||||||
if cpy != ledger:
|
if cpy != ledger:
|
||||||
|
|
||||||
# TODO: show diff output?
|
# TODO: show diff output?
|
||||||
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
# https://stackoverflow.com/questions/12956957/print-diff-of-python-dictionaries
|
||||||
log.info(f'Updating ledger for {tradesfile}:\n')
|
log.info(f'Updating ledger for {tradesfile}:\n')
|
||||||
ledger.update(cpy)
|
ledger.update(cpy)
|
||||||
|
|
||||||
# we write on close the mutated ledger data
|
# we write on close the mutated ledger data
|
||||||
with open(tradesfile, 'w') as cf:
|
with open(tradesfile, 'w') as cf:
|
||||||
toml.dump(ledger, cf)
|
toml.dump(ledger, cf)
|
||||||
|
@ -476,7 +476,7 @@ class Position(Struct):
|
||||||
if self.split_ratio is not None:
|
if self.split_ratio is not None:
|
||||||
size = round(size * self.split_ratio)
|
size = round(size * self.split_ratio)
|
||||||
|
|
||||||
return float(self.symbol.decimal_quant(Decimal(size)))
|
return float(self.symbol.quantize_size(size))
|
||||||
|
|
||||||
def minimize_clears(
|
def minimize_clears(
|
||||||
self,
|
self,
|
||||||
|
@ -934,10 +934,18 @@ def open_pps(
|
||||||
for fqsn, entry in pps.items():
|
for fqsn, entry in pps.items():
|
||||||
bsuid = entry['bsuid']
|
bsuid = entry['bsuid']
|
||||||
symbol = Symbol.from_fqsn(
|
symbol = Symbol.from_fqsn(
|
||||||
fqsn, info={
|
fqsn,
|
||||||
'asset_type': entry['asset_type'],
|
|
||||||
'price_tick_size': entry['price_tick_size'],
|
# NOTE & TODO: right now we fill in the defaults from
|
||||||
'lot_tick_size': entry['lot_tick_size']
|
# `.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']
|
size = entry['size']
|
||||||
|
|
||||||
# TODO: remove but, handle old field name for now
|
# 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')
|
split_ratio = entry.get('split_ratio')
|
||||||
|
|
||||||
expiry = entry.get('expiry')
|
expiry = entry.get('expiry')
|
||||||
|
|
Loading…
Reference in New Issue