101 lines
2.9 KiB
Python
101 lines
2.9 KiB
Python
|
|
'''
|
||
|
|
Offline replay provider contract tests.
|
||
|
|
|
||
|
|
'''
|
||
|
|
from datetime import (
|
||
|
|
UTC,
|
||
|
|
datetime,
|
||
|
|
)
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import pytest
|
||
|
|
import trio
|
||
|
|
|
||
|
|
from piker.brokers import DataUnavailable
|
||
|
|
from piker.brokers import replay
|
||
|
|
|
||
|
|
|
||
|
|
INPUTS: Path = (
|
||
|
|
Path(__file__).parents[1]
|
||
|
|
/ '_inputs'
|
||
|
|
/ 'replay'
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_versioned_scenario_normalizes_market_data() -> None:
|
||
|
|
'''
|
||
|
|
Reject fixture drift before a datad actor obscures its cause.
|
||
|
|
|
||
|
|
Replay scenarios are durable test inputs rather than loose mock
|
||
|
|
dictionaries. Decode the versioned basic scenario through the
|
||
|
|
production loader and prove its market identity, Decimal fields,
|
||
|
|
contiguous event IDs, and normalized tick records survive typed
|
||
|
|
decoding. These assertions catch schema or symbology changes at
|
||
|
|
the provider boundary without starting services or using network
|
||
|
|
resources.
|
||
|
|
|
||
|
|
'''
|
||
|
|
scenario: replay.ReplayScenario = replay.load_scenario(
|
||
|
|
INPUTS / 'basic-v1.json'
|
||
|
|
)
|
||
|
|
|
||
|
|
assert scenario.version == 1
|
||
|
|
assert scenario.scenario_id == 'basic-v1'
|
||
|
|
assert scenario.markets[0].fqme == 'btcusd.test.replay'
|
||
|
|
assert str(scenario.markets[0].price_tick) == '0.01'
|
||
|
|
assert [quote.sequence for quote in scenario.quotes] == [
|
||
|
|
1,
|
||
|
|
2,
|
||
|
|
3,
|
||
|
|
4,
|
||
|
|
]
|
||
|
|
assert scenario.quotes[1].ticks[0].type == 'trade'
|
||
|
|
|
||
|
|
|
||
|
|
def test_history_queries_are_bounded_and_repeatable(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> None:
|
||
|
|
'''
|
||
|
|
Keep history replay finite, offline, and repeatable.
|
||
|
|
|
||
|
|
A history fixture which mutates a cursor per request can make the
|
||
|
|
two concurrent 1-second and 1-minute backfill tasks race,
|
||
|
|
while an unbounded latest frame can make datad backfill
|
||
|
|
forever. Select the same 1-minute frame twice, then request
|
||
|
|
data ending at its first timestamp. Equal arrays prove calls
|
||
|
|
are immutable and the explicit `DataUnavailable` proves
|
||
|
|
reverse backfill terminates at the fixture boundary without
|
||
|
|
a clock delay or external request.
|
||
|
|
|
||
|
|
'''
|
||
|
|
scenario_path: Path = INPUTS / 'basic-v1.json'
|
||
|
|
monkeypatch.setenv(
|
||
|
|
'PIKER_REPLAY_SCENARIO',
|
||
|
|
str(scenario_path),
|
||
|
|
)
|
||
|
|
scenario: replay.ReplayScenario = replay.load_scenario(
|
||
|
|
scenario_path
|
||
|
|
)
|
||
|
|
mkt = scenario.markets[0]
|
||
|
|
|
||
|
|
async def main() -> None:
|
||
|
|
async with replay.open_history_client(mkt) as (
|
||
|
|
get_hist,
|
||
|
|
config,
|
||
|
|
):
|
||
|
|
first, start, end = await get_hist(60)
|
||
|
|
second, second_start, second_end = await get_hist(60)
|
||
|
|
np.testing.assert_array_equal(first, second)
|
||
|
|
assert (start, end) == (second_start, second_end)
|
||
|
|
assert config == {'erlangs': 1, 'rate': 1}
|
||
|
|
|
||
|
|
boundary: datetime = datetime.fromtimestamp(
|
||
|
|
int(first['time'][0]),
|
||
|
|
tz=UTC,
|
||
|
|
)
|
||
|
|
with pytest.raises(DataUnavailable):
|
||
|
|
await get_hist(60, end_dt=boundary)
|
||
|
|
|
||
|
|
trio.run(main)
|