2023-02-24 22:23:08 +00:00
|
|
|
"""
|
2023-02-23 20:21:10 +00:00
|
|
|
Paper-mode testing
|
2023-02-24 22:23:08 +00:00
|
|
|
"""
|
2023-02-23 20:21:10 +00:00
|
|
|
|
2023-02-09 19:53:57 +00:00
|
|
|
import trio
|
|
|
|
import math
|
2023-02-14 22:06:48 +00:00
|
|
|
from shutil import rmtree
|
2023-02-23 20:21:10 +00:00
|
|
|
from exceptiongroup import BaseExceptionGroup
|
2023-02-09 23:14:41 +00:00
|
|
|
from typing import (
|
|
|
|
AsyncContextManager,
|
2023-02-12 22:04:49 +00:00
|
|
|
Literal,
|
2023-02-09 23:14:41 +00:00
|
|
|
)
|
2023-02-23 20:21:10 +00:00
|
|
|
from pathlib import Path
|
2023-02-25 20:59:14 +00:00
|
|
|
from operator import attrgetter
|
2023-02-23 20:21:10 +00:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
import tractor
|
|
|
|
from tractor._exceptions import ContextCancelled
|
|
|
|
from uuid import uuid4
|
2023-02-09 23:14:41 +00:00
|
|
|
from functools import partial
|
2023-02-23 20:21:10 +00:00
|
|
|
|
|
|
|
from piker.config import get_app_dir
|
|
|
|
from piker.log import get_logger
|
|
|
|
from piker.clearing._messages import Order
|
2023-02-09 19:53:57 +00:00
|
|
|
from piker.pp import (
|
2023-02-24 18:42:44 +00:00
|
|
|
PpTable,
|
2023-02-09 19:53:57 +00:00
|
|
|
open_trade_ledger,
|
2023-02-12 22:04:49 +00:00
|
|
|
open_pps,
|
2023-02-09 19:53:57 +00:00
|
|
|
)
|
|
|
|
from piker.clearing import (
|
|
|
|
open_ems,
|
|
|
|
)
|
|
|
|
from piker.clearing._client import (
|
|
|
|
OrderBook,
|
|
|
|
)
|
2023-02-23 20:21:10 +00:00
|
|
|
from piker._cacheables import open_cached_client
|
|
|
|
from piker.clearing._messages import BrokerdPosition
|
2023-02-14 22:06:48 +00:00
|
|
|
|
2023-02-12 22:04:49 +00:00
|
|
|
log = get_logger(__name__)
|
2023-02-09 19:53:57 +00:00
|
|
|
|
2023-02-24 18:42:44 +00:00
|
|
|
|
2023-02-24 23:05:17 +00:00
|
|
|
@pytest.fixture(scope="session")
|
2023-02-23 20:21:10 +00:00
|
|
|
def delete_testing_dir():
|
2023-02-24 22:23:08 +00:00
|
|
|
"""This fixture removes the temp directory
|
2023-02-23 20:21:10 +00:00
|
|
|
used for storing all config/ledger/pp data
|
|
|
|
created during testing sessions
|
2023-02-24 22:23:08 +00:00
|
|
|
"""
|
2023-02-14 22:06:48 +00:00
|
|
|
yield
|
2023-02-24 22:23:08 +00:00
|
|
|
app_dir = Path(get_app_dir("piker")).resolve()
|
2023-02-23 20:21:10 +00:00
|
|
|
if app_dir.is_dir():
|
|
|
|
rmtree(str(app_dir))
|
|
|
|
assert not app_dir.is_dir()
|
|
|
|
|
2023-02-24 18:42:44 +00:00
|
|
|
|
2023-02-23 20:21:10 +00:00
|
|
|
def get_fqsn(broker, symbol):
|
2023-02-24 22:23:08 +00:00
|
|
|
fqsn = f"{symbol}.{broker}"
|
2023-02-23 20:21:10 +00:00
|
|
|
return (fqsn, symbol, broker)
|
|
|
|
|
2023-02-12 22:04:49 +00:00
|
|
|
|
2023-02-24 23:05:17 +00:00
|
|
|
def test_paper_trade(open_test_pikerd: AsyncContextManager, delete_testing_dir):
|
2023-02-24 22:23:08 +00:00
|
|
|
oid = ""
|
|
|
|
test_exec_mode = "live"
|
|
|
|
test_account = "paper"
|
|
|
|
(fqsn, symbol, broker) = get_fqsn("kraken", "xbtusdt")
|
2023-02-23 20:21:10 +00:00
|
|
|
brokers = [broker]
|
2023-02-25 22:45:21 +00:00
|
|
|
account = "paper"
|
2023-02-12 22:04:49 +00:00
|
|
|
positions: dict[
|
|
|
|
# brokername, acctid
|
|
|
|
tuple[str, str],
|
|
|
|
list[BrokerdPosition],
|
|
|
|
]
|
2023-02-09 23:14:41 +00:00
|
|
|
|
2023-02-12 22:04:49 +00:00
|
|
|
async def _async_main(
|
2023-02-24 22:23:08 +00:00
|
|
|
action: Literal["buy", "sell"] | None = None,
|
2023-02-12 22:04:49 +00:00
|
|
|
price: int = 30000,
|
2023-02-09 23:14:41 +00:00
|
|
|
assert_entries: bool = False,
|
2023-02-25 00:09:36 +00:00
|
|
|
assert_pps: bool = False,
|
|
|
|
assert_zeroed_pps: bool = False,
|
2023-02-25 22:45:21 +00:00
|
|
|
assert_msg: bool = True,
|
2023-02-25 20:59:14 +00:00
|
|
|
executions: int = 1,
|
|
|
|
size: float = 0.01,
|
2023-02-14 17:07:42 +00:00
|
|
|
) -> None:
|
2023-02-25 20:59:14 +00:00
|
|
|
"""Start piker, place a trade and assert entries are present
|
2023-02-23 20:21:10 +00:00
|
|
|
in both trade ledger and pps tomls. Then restart piker and ensure
|
|
|
|
that pps from previous trade exists in the ems pps.
|
|
|
|
Finally close the position and ensure that the position in pps.toml is closed.
|
2023-02-24 22:23:08 +00:00
|
|
|
"""
|
2023-02-24 18:42:44 +00:00
|
|
|
nonlocal oid
|
2023-02-23 20:21:10 +00:00
|
|
|
nonlocal positions
|
2023-02-25 20:59:14 +00:00
|
|
|
book: OrderBook
|
2023-02-25 22:45:21 +00:00
|
|
|
msg = {}
|
2023-02-12 22:04:49 +00:00
|
|
|
# Set up piker and EMS
|
2023-02-09 19:53:57 +00:00
|
|
|
async with (
|
2023-02-24 23:05:17 +00:00
|
|
|
open_test_pikerd() as (_, _, _, services),
|
2023-02-24 22:23:08 +00:00
|
|
|
open_ems(fqsn, mode="paper") as (
|
2023-02-09 19:53:57 +00:00
|
|
|
book,
|
|
|
|
trades_stream,
|
|
|
|
pps,
|
|
|
|
accounts,
|
|
|
|
dialogs,
|
|
|
|
),
|
|
|
|
):
|
2023-02-12 22:04:49 +00:00
|
|
|
# Send order to EMS
|
2023-02-23 20:21:10 +00:00
|
|
|
if action:
|
2023-02-25 20:59:14 +00:00
|
|
|
for x in range(executions):
|
|
|
|
print(f"Sending {action} order num {x}")
|
|
|
|
oid = str(uuid4())
|
|
|
|
order = Order(
|
|
|
|
exec_mode=test_exec_mode,
|
|
|
|
action=action,
|
|
|
|
oid=oid,
|
|
|
|
account=test_account,
|
|
|
|
size=size,
|
|
|
|
symbol=fqsn,
|
|
|
|
price=price,
|
|
|
|
brokers=brokers,
|
|
|
|
)
|
|
|
|
# This is actually a syncronous call to push a message
|
|
|
|
# to the async ems clue - hence why we call trio.sleep afterwards
|
|
|
|
book.send(order)
|
|
|
|
|
|
|
|
async for msg in trades_stream:
|
|
|
|
msg = await trades_stream.receive()
|
|
|
|
try:
|
|
|
|
if msg["name"] == "position":
|
|
|
|
break
|
|
|
|
except (NameError, AttributeError):
|
|
|
|
pass
|
|
|
|
# Do nothing, message isn't a position
|
2023-02-25 21:09:16 +00:00
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
await trio.sleep(1)
|
2023-02-12 22:04:49 +00:00
|
|
|
# Assert entries are made in both ledger and PPS
|
2023-02-25 22:45:21 +00:00
|
|
|
if assert_entries or assert_pps or assert_zeroed_pps or assert_msg:
|
2023-02-25 20:59:14 +00:00
|
|
|
_assert(
|
|
|
|
assert_entries,
|
|
|
|
assert_pps,
|
|
|
|
assert_zeroed_pps,
|
2023-02-25 22:45:21 +00:00
|
|
|
assert_msg,
|
2023-02-25 20:59:14 +00:00
|
|
|
pps,
|
|
|
|
msg,
|
2023-02-25 22:45:21 +00:00
|
|
|
size,
|
2023-02-25 20:59:14 +00:00
|
|
|
)
|
2023-02-12 22:04:49 +00:00
|
|
|
|
|
|
|
# Close piker like a user would
|
|
|
|
raise KeyboardInterrupt
|
2023-02-23 20:21:10 +00:00
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
def _assert(
|
2023-02-25 22:45:21 +00:00
|
|
|
assert_entries, assert_pps, assert_zerod_pps, assert_msg, pps, msg, size
|
2023-02-25 20:59:14 +00:00
|
|
|
):
|
2023-02-24 18:42:44 +00:00
|
|
|
with (
|
|
|
|
open_trade_ledger(broker, test_account) as ledger,
|
2023-02-25 22:45:21 +00:00
|
|
|
open_pps(broker, test_account) as table,
|
2023-02-24 18:42:44 +00:00
|
|
|
):
|
2023-02-25 20:59:14 +00:00
|
|
|
# TODO: Assert between msg and pp, ledger and pp, ledger and message
|
|
|
|
# for proper values
|
|
|
|
print(f"assertion msg: {msg}")
|
2023-02-25 00:09:36 +00:00
|
|
|
# assert that entires are have been written
|
|
|
|
if assert_entries:
|
2023-02-25 20:59:14 +00:00
|
|
|
latest_ledger_entry = ledger[oid]
|
|
|
|
latest_position = pps[(broker, test_account)][-1]
|
2023-02-25 22:45:21 +00:00
|
|
|
pp_price = table.conf[broker][account][fqsn]["ppu"]
|
2023-02-25 20:59:14 +00:00
|
|
|
# assert most
|
|
|
|
assert list(ledger.keys())[-1] == oid
|
|
|
|
assert latest_ledger_entry["size"] == test_size
|
|
|
|
assert latest_ledger_entry["fqsn"] == fqsn
|
|
|
|
|
2023-02-25 00:09:36 +00:00
|
|
|
# Ensure the price-per-unit (breakeven) price is close to our clearing price
|
2023-02-25 20:59:14 +00:00
|
|
|
assert math.isclose(pp_price, latest_ledger_entry["size"], rel_tol=1)
|
2023-02-25 00:09:36 +00:00
|
|
|
assert table.brokername == broker
|
2023-02-25 22:45:21 +00:00
|
|
|
assert table.acctid == account
|
2023-02-25 00:09:36 +00:00
|
|
|
|
|
|
|
# assert that the last pps price is the same as the ledger price
|
|
|
|
if assert_pps:
|
2023-02-25 20:59:14 +00:00
|
|
|
latest_ledger_entry = ledger[oid]
|
|
|
|
latest_position = pps[(broker, test_account)][-1]
|
|
|
|
assert latest_position["avg_price"] == latest_ledger_entry["price"]
|
2023-02-23 20:21:10 +00:00
|
|
|
|
2023-02-25 00:09:36 +00:00
|
|
|
if assert_zerod_pps:
|
2023-02-25 22:45:21 +00:00
|
|
|
# assert that positions are not present
|
2023-02-25 20:59:14 +00:00
|
|
|
assert not bool(table.pps)
|
2023-02-25 00:09:36 +00:00
|
|
|
|
2023-02-25 22:45:21 +00:00
|
|
|
if assert_msg and msg["name"] == "position":
|
|
|
|
latest_position = pps[(broker, test_account)][-1]
|
|
|
|
breakpoint()
|
|
|
|
assert msg["broker"] == broker
|
|
|
|
assert msg["account"]== test_account
|
|
|
|
assert msg["symbol"] == fqsn
|
|
|
|
assert msg["avg_price"]== latest_position["avg_price"]
|
|
|
|
|
2023-02-25 00:09:36 +00:00
|
|
|
# Close position and assert empty position in pps
|
|
|
|
def _run_test_and_check(exception, fn):
|
|
|
|
with pytest.raises(exception) as exc_info:
|
|
|
|
trio.run(fn)
|
|
|
|
|
|
|
|
for exception in exc_info.value.exceptions:
|
2023-02-25 20:59:14 +00:00
|
|
|
assert (
|
|
|
|
isinstance(exception, KeyboardInterrupt)
|
|
|
|
or isinstance(exception, ContextCancelled)
|
|
|
|
or isinstance(exception, KeyError)
|
2023-02-25 00:09:36 +00:00
|
|
|
)
|
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
# Enter a trade and assert entries are made in pps and ledger files
|
2023-02-23 20:21:10 +00:00
|
|
|
_run_test_and_check(
|
|
|
|
BaseExceptionGroup,
|
2023-02-25 00:09:36 +00:00
|
|
|
partial(_async_main, action="buy", assert_entries=True),
|
|
|
|
)
|
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
# Open ems and assert existence of pps entries
|
2023-02-25 00:09:36 +00:00
|
|
|
_run_test_and_check(
|
|
|
|
BaseExceptionGroup,
|
|
|
|
partial(_async_main, assert_pps=True),
|
2023-02-23 20:21:10 +00:00
|
|
|
)
|
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
# Sell position
|
2023-02-24 18:42:44 +00:00
|
|
|
_run_test_and_check(
|
|
|
|
BaseExceptionGroup,
|
2023-02-25 20:59:14 +00:00
|
|
|
partial(_async_main, action="sell", price=1),
|
2023-02-23 20:21:10 +00:00
|
|
|
)
|
2023-02-24 18:42:44 +00:00
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
# Ensure pps are zeroed
|
2023-02-24 18:42:44 +00:00
|
|
|
_run_test_and_check(
|
|
|
|
BaseExceptionGroup,
|
2023-02-25 20:59:14 +00:00
|
|
|
partial(_async_main, assert_zeroed_pps=True),
|
2023-02-24 18:42:44 +00:00
|
|
|
)
|
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
# Make 5 market limit buy orders
|
|
|
|
_run_test_and_check(
|
|
|
|
BaseExceptionGroup, partial(_async_main, action="buy", executions=5)
|
|
|
|
)
|
2023-02-24 18:42:44 +00:00
|
|
|
|
2023-02-25 20:59:14 +00:00
|
|
|
# Sell 5 slots at the same price, assert cleared positions
|
|
|
|
_run_test_and_check(
|
|
|
|
BaseExceptionGroup,
|
|
|
|
partial(
|
|
|
|
_async_main, action="sell", executions=5, price=1, assert_zeroed_pps=True
|
|
|
|
),
|
|
|
|
)
|