''' `piker.accounting` mgmt calculations for - positioning - ledger updates - config file IO ''' from decimal import Decimal from pathlib import Path from types import ModuleType import tomllib import pytest from piker import config from piker.accounting import ( Account, Asset, calc, open_account, load_account, load_account_from_ledger, MktPair, open_trade_ledger, Position, TransactionLedger, ) from piker.data._symcache import SymbologyCache import tractor def test_root_conf_networking_section( root_conf: dict, ): conf, path = config.load( 'conf', touch_if_dne=True, ) assert conf['network']['tsdb'] def test_account_file_default_empty( tmpconfdir: Path, ): conf, path = load_account( 'kraken', 'paper', ) # ensure the account file empty but created # and in the correct place in the filesystem! assert not conf assert path.parent.is_dir() assert path.parent.name == 'accounting' def test_paper_ledger_fuzzy_qualifies_mktmap_keys( tmp_path: Path, ): ''' Qualify legacy paper-ledger FQMEs via symcache string keys. ``SymbologyCache.search()`` previously passed its ``mktmaps`` mapping directly to RapidFuzz. RapidFuzz searches mapping values, so a paper fill with an unqualified FQME made RapidFuzz call ``len()`` on a ``MktPair`` while ``TransactionLedger.write_config()`` exited. This test installs both native-ID and FQME keys for one market, proves search preserves the matched alias, then writes the same unqualified paper transaction shape. The persisted fully qualified keys prove the ledger exit path resolves the market without treating ``MktPair`` values as fuzzy-search sequences. ''' usdt = Asset( name='usdt', atype='crypto', tx_tick=Decimal('0.00000001'), ) nvda = Asset( name='nvda', atype='stock', tx_tick=Decimal('0.00000001'), ) mkt = MktPair( dst=nvda, src=usdt, price_tick=Decimal('0.01'), size_tick=Decimal('0.001'), bs_mktid='NVDAUSDT', broker='binance', venue='usdtm', expiry='perp', ) symcache = SymbologyCache( mod=ModuleType('binance'), fp=tmp_path / 'binance.symcache.toml', mktmaps={ mkt.bs_mktid: mkt, mkt.fqme: mkt, }, ) matches = symcache.search(mkt.bs_mktid) assert next(iter(matches)) == mkt.bs_mktid assert matches[mkt.bs_mktid] is mkt ledger_path = tmp_path / 'trades_binance_paper.toml' unqualified_fqme = 'nvdausdt.usdtm.perp' ledger = TransactionLedger( ledger_dict={ 'fill-id': { 'fqme': unqualified_fqme, 'bs_mktid': unqualified_fqme, }, }, file_path=ledger_path, account='paper', mod=symcache.mod, tx_sort=lambda txns: txns.items(), symcache=symcache, ) ledger.write_config() with ledger_path.open('rb') as ledger_file: saved = tomllib.load(ledger_file)['fill-id'] assert saved['fqme'] == mkt.fqme assert saved['bs_mktid'] == mkt.fqme @pytest.mark.parametrize( 'fq_acnt', [ ('binance', 'paper'), ], ) def test_paper_ledger_position_calcs( fq_acnt: tuple[str, str], debug_mode: bool, ): broker: str acnt_name: str broker, acnt_name = fq_acnt accounts_path: Path = ( config.repodir() / 'tests' / '_inputs' # tests-local-subdir ) ldr: TransactionLedger with ( open_trade_ledger( broker, acnt_name, allow_from_sync_code=True, _fp=accounts_path, ) as ldr, # open `polars` acnt dfs Bo calc.open_ledger_dfs( broker, acnt_name, ledger=ldr, _fp=accounts_path, debug_mode=debug_mode, ) as (dfs, ledger), ): acnt: Account = load_account_from_ledger( broker, acnt_name, ledger=ldr, _fp=accounts_path, ) # do manual checks on expected pos calcs based on input # ledger B) # xrpusdt should have a net-zero size xrp: str = 'xrpusdt.spot.binance' pos: Position = acnt.pps[xrp] # XXX: turns out our old dict-style-processing # get's this wrong i think due to dt-sorting.. # lcum: float = pos.cumsize df = dfs[xrp] assert df['cumsize'][-1] == 0 assert pos.cumsize == 0 @pytest.mark.parametrize( 'fq_acnt', [ ('ib', 'algopaper'), ], ) def test_ib_account_with_duplicated_mktids( fq_acnt: tuple[str, str], debug_mode: bool, ): # ?TODO, once we start symcache-incremental-update-support? # from piker.data import ( # open_symcache, # ) # # async def main(): # async with ( # # TODO: do this as part of `open_account()`!? # open_symcache( # 'ib', # only_from_memcache=True, # ) as symcache, # ): from piker.brokers.ib.ledger import ( tx_sort, # ?TODO, once we want to pull lowlevel txns and process them? # norm_trade_records, # update_ledger_from_api_trades, ) broker: str acnt_id: str = 'algopaper' broker, acnt_id = fq_acnt accounts_def = config.load_accounts([broker]) assert accounts_def[f'{broker}.{acnt_id}'] ledger: TransactionLedger acnt: Account with ( tractor.devx.maybe_open_crash_handler(pdb=debug_mode), open_trade_ledger( 'ib', acnt_id, tx_sort=tx_sort, # TODO, eventually incrementally updated for IB.. # symcache=symcache, symcache=None, allow_from_sync_code=True, ) as ledger, open_account( 'ib', acnt_id, write_on_exit=True, ) as acnt, ): # per input params symcache = ledger.symcache assert not ( symcache.pairs or symcache.pairs or symcache.mktmaps ) # re-compute all positions that have changed state. # TODO: likely we should change the API to return the # position updates from `.update_from_ledger()`? active, closed = acnt.dump_active() # breakpoint() # TODO, (see above imports as well) incremental update from # (updated) ledger? # -[ ] pull some code from `.ib.broker` content.