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