diff --git a/piker/clearing/_paper_engine.py b/piker/clearing/_paper_engine.py index c53e02e5..32e829ed 100644 --- a/piker/clearing/_paper_engine.py +++ b/piker/clearing/_paper_engine.py @@ -260,22 +260,16 @@ class PaperBoi(Struct): bsuid=key, ) - # Update in memory ledger per trade - ledger_entry = {oid: t.to_dict()} - - # Store txn in state for PP update - self._txn_dict[oid] = t - self._trade_ledger.update(ledger_entry) - # Write to ledger toml right now - with open_trade_ledger(self.broker, 'paper') as ledger: - ledger.update(self._trade_ledger) - - # Write to pps toml right now - with open_pps(self.broker, 'piker-paper') as table: - table.update_from_trans(self._txn_dict) - # save pps in local state - self._positions = table.pps + with ( + open_trade_ledger(self.broker, 'paper') as ledger, + open_pps(self.broker, 'piker-paper') as table + ): + ledger.update({oid: t.to_dict()}) + # Write to pps toml right now + table.update_from_trans({oid: t}) + # save pps in local state + self._positions.update(table.pps) # Ensure we have the latest positioning data when sending pp_msg pp = self._positions[key] diff --git a/piker/config.py b/piker/config.py index a8f5f494..c11ca3f1 100644 --- a/piker/config.py +++ b/piker/config.py @@ -78,7 +78,8 @@ def get_app_dir(app_name, roaming=True, force_posix=False): # within the tractor runtimes and store testing config data # outside of the users filesystem if "pytest" in sys.modules: - app_name += TEST_CONFIG_DIR_PATH + log.info("TESTING") + os.path.join(app_name, TEST_CONFIG_DIR_PATH) # if WIN: if platform.system() == 'Windows': diff --git a/tests/test_paper.py b/tests/test_paper.py index 943fb9d5..ac916f2c 100644 --- a/tests/test_paper.py +++ b/tests/test_paper.py @@ -1,6 +1,6 @@ -""" +''' Paper-mode testing -""" +''' import trio import math @@ -22,6 +22,7 @@ from piker.config import get_app_dir from piker.log import get_logger from piker.clearing._messages import Order from piker.pp import ( + PpTable, open_trade_ledger, open_pps, ) @@ -36,31 +37,33 @@ from piker.clearing._messages import BrokerdPosition log = get_logger(__name__) -@pytest.fixture(scope="module") + +@pytest.fixture(scope='module') def delete_testing_dir(): '''This fixture removes the temp directory used for storing all config/ledger/pp data created during testing sessions ''' yield - app_dir = Path(get_app_dir("piker")).resolve() + app_dir = Path(get_app_dir('piker')).resolve() if app_dir.is_dir(): rmtree(str(app_dir)) assert not app_dir.is_dir() + def get_fqsn(broker, symbol): - fqsn = f"{symbol}.{broker}" + fqsn = f'{symbol}.{broker}' return (fqsn, symbol, broker) def test_paper_trade(open_test_pikerd: AsyncContextManager): - cleared_price: float - test_exec_mode = "live" - test_account = "paper" + oid = '' + test_exec_mode = 'live' + test_account = 'paper' test_size = 1 - (fqsn, symbol, broker) = get_fqsn("kraken", "xbtusdt") + (fqsn, symbol, broker) = get_fqsn('kraken', 'xbtusdt') brokers = [broker] - test_pp_account = "piker-paper" + test_pp_account = 'piker-paper' positions: dict[ # brokername, acctid tuple[str, str], @@ -69,7 +72,7 @@ def test_paper_trade(open_test_pikerd: AsyncContextManager): async def _async_main( open_pikerd: AsyncContextManager, - action: Literal["buy", "sell"] | None = None, + action: Literal['buy', 'sell'] | None = None, price: int = 30000, assert_entries: bool = False, ) -> None: @@ -78,16 +81,14 @@ def test_paper_trade(open_test_pikerd: AsyncContextManager): that pps from previous trade exists in the ems pps. Finally close the position and ensure that the position in pps.toml is closed. ''' - - oid: str = str(uuid4()) + nonlocal oid book: OrderBook - nonlocal cleared_price nonlocal positions # Set up piker and EMS async with ( open_pikerd() as (_, _, _, services), - open_ems(fqsn, mode="paper") as ( + open_ems(fqsn, mode='paper') as ( book, trades_stream, pps, @@ -97,6 +98,7 @@ def test_paper_trade(open_test_pikerd: AsyncContextManager): ): # Send order to EMS if action: + oid = str(uuid4()) order = Order( exec_mode=test_exec_mode, action=action, @@ -118,17 +120,15 @@ def test_paper_trade(open_test_pikerd: AsyncContextManager): cleared_ledger_entry = {} with open_trade_ledger(broker, test_account) as ledger: cleared_ledger_entry = ledger[oid] - cleared_price = cleared_ledger_entry["price"] - assert list(ledger.keys())[-1] == oid - assert cleared_ledger_entry["size"] == test_size - assert cleared_ledger_entry["fqsn"] == fqsn + assert cleared_ledger_entry['size'] == test_size + assert cleared_ledger_entry['fqsn'] == fqsn with open_pps(broker, test_pp_account) as table: - pp_price = table.conf[broker][test_pp_account][fqsn]["ppu"] + pp_price = table.conf[broker][test_pp_account][fqsn]['ppu'] # Ensure the price-per-unit (breakeven) price is close to our clearing price assert math.isclose( - pp_price, cleared_ledger_entry["size"], rel_tol=1 + pp_price, cleared_ledger_entry['size'], rel_tol=1 ) assert table.brokername == broker assert table.acctid == test_pp_account @@ -140,62 +140,78 @@ def test_paper_trade(open_test_pikerd: AsyncContextManager): # Open piker load pps locally # and ensure last pps price is the same as ledger entry - async def _open_and_assert_pps(): - await _async_main(open_test_pikerd) - assert positions(broker, test_account)[-1] == cleared_price + def _assert_pps(ledger, table): + return ( + positions[(broker, test_account)][-1]['avg_price'] == ledger[oid]['price'] + ) + + def _assert_no_pps(ledger, table): + return len(table.pps) == 0 # Close position and assert empty position in pps - async def _close_pp_and_assert(): - await _async_main(open_test_pikerd, "sell", 1) - with open_pps(broker, test_pp_account) as table: - assert len(table.pps) == 0 - - def _run_test_and_check(exception, fn): + def _run_test_and_check(exception, fn, assert_cb=None): with pytest.raises(exception) as exc_info: trio.run(fn) - for exception in exc_info.value.exceptions: - assert isinstance(exception, KeyboardInterrupt) or isinstance( - exception, ContextCancelled - ) + with ( + open_trade_ledger(broker, test_account) as ledger, + open_pps(broker, test_pp_account) as table, + ): + if assert_cb: + assert assert_cb(ledger, table) - # Send and execute a trade and assert trade + for exception in exc_info.value.exceptions: + assert isinstance(exception, KeyboardInterrupt) or isinstance( + exception, ContextCancelled + ) + + # Setablend and execute a trade and assert trade _run_test_and_check( BaseExceptionGroup, partial( _async_main, open_pikerd=open_test_pikerd, - action="buy", + action='buy', ), ) - _run_test_and_check(BaseExceptionGroup, _open_and_assert_pps) - _run_test_and_check(BaseExceptionGroup, _close_pp_and_assert) - -def test_paper_client( - open_test_pikerd: AsyncContextManager -): - - async def _async_main( - open_pikerd: AsyncContextManager, - ): - (fqsn, symbol, broker) = get_fqsn("kraken", "xbtusdt") - async with ( - open_pikerd() as (_, _, _, services), - open_ems(fqsn, mode="paper") as ( - book, - trades_stream, - pps, - accounts, - dialogs, - ), - ): - async with open_cached_client(broker) as client: - symbol_info = await client.symbol_info() - print(f'client: {symbol_info["XBTUSDT"]}') - - trio.run(partial( - _async_main, - open_pikerd=open_test_pikerd, - ), + _run_test_and_check( + BaseExceptionGroup, + partial(_async_main, open_pikerd=open_test_pikerd), + _assert_pps, ) + + _run_test_and_check( + BaseExceptionGroup, + partial(_async_main, open_pikerd=open_test_pikerd, action='sell', price=1), + _assert_no_pps, + ) + + +# def test_paper_client( +# open_test_pikerd: AsyncContextManager +# ): +# +# async def _async_main( +# open_pikerd: AsyncContextManager, +# ): +# (fqsn, symbol, broker) = get_fqsn('kraken', 'xbtusdt') +# async with ( +# open_pikerd() as (_, _, _, services), +# open_ems(fqsn, mode='paper') as ( +# book, +# trades_stream, +# pps, +# accounts, +# dialogs, +# ), +# ): +# async with open_cached_client(broker) as client: +# symbol_info = await client.symbol_info() +# print(f'client: {symbol_info['XBTUSDT']}') +# +# trio.run(partial( +# _async_main, +# open_pikerd=open_test_pikerd, +# ), +# )