piker/tests/test_accounting.py

279 lines
6.6 KiB
Python

'''
`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.