idea105-deltatrace/tests/test_replay_engine.py

99 lines
3.5 KiB
Python

"""Tests for the deterministic replay engine."""
import unittest
from deltatrace.event_graph import Event, EventGraph, EventType
from deltatrace.replay_engine import ReplayEngine
class TestReplayEngine(unittest.TestCase):
def _build_graph(self):
graph = EventGraph()
tick = Event.create(EventType.MD_TICK, {"symbol": "AAPL"})
signal = Event.create(EventType.SIGNAL, {"direction": "buy"})
order = Event.create(EventType.ORDER, {"qty": 100})
fill = Event.create(EventType.FILL, {"price": 185.0})
for ev in [tick, signal, order, fill]:
graph.add_event(ev)
graph.add_edge(tick.id, signal.id)
graph.add_edge(signal.id, order.id)
graph.add_edge(order.id, fill.id)
return graph
def test_replay_all_events(self):
graph = self._build_graph()
engine = ReplayEngine()
result = engine.replay(graph)
self.assertEqual(result.events_replayed, 4)
self.assertEqual(result.fidelity_score, 1.0)
self.assertEqual(len(result.event_log), 4)
def test_replay_deterministic(self):
graph = self._build_graph()
engine = ReplayEngine()
r1 = engine.replay(graph)
r2 = engine.replay(graph)
ids1 = [e["id"] for e in r1.event_log]
ids2 = [e["id"] for e in r2.event_log]
self.assertEqual(ids1, ids2)
def test_replay_with_filter(self):
graph = self._build_graph()
engine = ReplayEngine()
result = engine.replay(graph, filter_types={EventType.MD_TICK, EventType.FILL})
self.assertEqual(result.events_replayed, 2)
types = {e["type"] for e in result.event_log}
self.assertEqual(types, {"md_tick", "fill"})
def test_replay_handlers(self):
graph = self._build_graph()
engine = ReplayEngine()
seen = []
engine.register_handler(EventType.ORDER, lambda e: seen.append(e.id))
engine.replay(graph)
self.assertEqual(len(seen), 1)
def test_replay_from_snapshot(self):
graph = self._build_graph()
snapshot = graph.to_dict()
engine = ReplayEngine()
result = engine.replay_from_snapshot(snapshot)
self.assertEqual(result.events_replayed, 4)
def test_replay_checkpoint_resume(self):
graph = self._build_graph()
engine = ReplayEngine()
# Create a checkpoint up to the SIGNAL event and replay just that prefix
signal = next(e for e in graph.topological_order() if e.event_type == EventType.SIGNAL)
checkpoint = engine.create_checkpoint(graph, upto_event_id=signal.id)
res = engine.replay_from_snapshot(checkpoint)
# tick + signal expected
self.assertEqual(res.events_replayed, 2)
def test_determinism_score(self):
graph = self._build_graph()
engine = ReplayEngine()
# Reference run
r1 = engine.replay(graph)
ref = r1.event_log
# Mutate timestamps in a snapshot to force a different replay ordering
snapshot = graph.to_dict()
# Find the signal event and make its timestamp far later
for e in snapshot["events"]:
if e["type"] == "signal":
e["timestamp_ns"] = e["timestamp_ns"] + 10_000_000_000
break
mutated = EventGraph.from_dict(snapshot)
r2 = engine.replay(mutated, reference_event_log=ref)
# Expect fidelity to be lower than perfect (not equal to 1.0)
self.assertLess(r2.fidelity_score, 1.0)
if __name__ == "__main__":
unittest.main()