Skip to content

Commit

Permalink
Backward-compat: don't require 'lot_tick_size'
Browse files Browse the repository at this point in the history
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 #467.
  • Loading branch information
goodboy committed Mar 3, 2023
1 parent 6be96a9 commit 3a4794e
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 24 deletions.
83 changes: 68 additions & 15 deletions piker/data/_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -244,23 +297,23 @@ def front_fqsn(self) -> str:
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.
Expand Down
30 changes: 21 additions & 9 deletions piker/pp.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
'''
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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -476,7 +476,7 @@ def calc_size(self) -> float:
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,
Expand Down Expand Up @@ -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),
}
)

Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 3a4794e

Please sign in to comment.