71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
"""Tests for the rate budgeter and the paper-mode gateway."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from polymaker.config import Config
|
|
from polymaker.domain import Quote, Side
|
|
from polymaker.execution.gateway import ExecutionGateway, _tick_str
|
|
from polymaker.execution.ratelimit import TokenBucket
|
|
|
|
|
|
def test_tick_str_formats():
|
|
assert _tick_str(0.01) == "0.01"
|
|
assert _tick_str(0.001) == "0.001"
|
|
assert _tick_str(0.0025) == "0.0025"
|
|
assert _tick_str(0.1) == "0.1"
|
|
|
|
|
|
async def test_token_bucket_limits_rate():
|
|
bucket = TokenBucket(rate_per_s=100.0, burst=5.0)
|
|
start = time.monotonic()
|
|
# burst of 5 is instant; the next 5 must wait ~ (5/100)s = 50ms
|
|
for _ in range(10):
|
|
await bucket.acquire(1)
|
|
elapsed = time.monotonic() - start
|
|
assert elapsed >= 0.04 # had to wait for refill
|
|
|
|
|
|
async def test_token_bucket_pressure_rises_when_drained():
|
|
bucket = TokenBucket(rate_per_s=10.0, burst=10.0)
|
|
assert bucket.pressure == pytest.approx(0.0, abs=0.01)
|
|
for _ in range(10):
|
|
await bucket.acquire(1)
|
|
assert bucket.pressure > 0.8
|
|
|
|
|
|
async def test_paper_gateway_places_and_cancels_without_wallet(meta):
|
|
cfg = Config() # defaults, no secrets
|
|
gw = ExecutionGateway(cfg, paper=True)
|
|
quotes = [
|
|
Quote(meta.yes.token_id, Side.BUY, 0.49, 100),
|
|
Quote(meta.no.token_id, Side.BUY, 0.48, 100),
|
|
]
|
|
placed = await gw.place(quotes, meta)
|
|
assert len(placed) == 2
|
|
assert all(o.order_id.startswith("paper-") for o in placed)
|
|
# cancel is a no-op in paper mode but must not raise
|
|
await gw.cancel([o.order_id for o in placed])
|
|
assert await gw.open_orders() == []
|
|
|
|
|
|
async def test_paper_gateway_heartbeat_and_cancel_all_noop():
|
|
gw = ExecutionGateway(Config(), paper=True)
|
|
assert await gw.heartbeat() is True # paper: healthy no-op
|
|
assert gw.heartbeat_failures == 0
|
|
await gw.cancel_all() # no client, must not raise
|
|
|
|
|
|
def test_gateway_requires_wallet_for_live_connect():
|
|
from polymaker.config import Secrets
|
|
|
|
# explicitly-empty secrets (don't read a real .env that may exist on disk)
|
|
cfg = Config(secrets=Secrets(_env_file=None))
|
|
gw = ExecutionGateway(cfg, paper=False)
|
|
with pytest.raises(RuntimeError, match="no wallet"):
|
|
asyncio.run(gw.connect())
|