From 582cf99c8bd77f7be4e0cac855330ee911b49546 Mon Sep 17 00:00:00 2001 From: agent-14fd4b738639d573 Date: Fri, 24 Apr 2026 19:40:43 +0200 Subject: [PATCH] build(agent): melter#14fd4b iteration --- crossvenue_arbx/fuzz.py | 66 ++++++++++++++++++++++++++++++++++++++++ tests/test_delta_fuzz.py | 33 ++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 crossvenue_arbx/fuzz.py create mode 100644 tests/test_delta_fuzz.py diff --git a/crossvenue_arbx/fuzz.py b/crossvenue_arbx/fuzz.py new file mode 100644 index 0000000..0d770bd --- /dev/null +++ b/crossvenue_arbx/fuzz.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import random +from typing import Iterable, List, Tuple + +from .core import LocalArbProblem, SharedSignals +from .coordinator import CentralCoordinator + + +class ScenarioGenerator: + """Deterministic scenario generator for delta-fuzzing. + + Produces a replayable sequence of (LocalArbProblem, SharedSignals) that can + be fed into CentralCoordinator.ingest_local. Uses an explicit seed to make + runs deterministic. + """ + + def __init__(self, seed: int = 0) -> None: + self._seed = seed + + def generate(self, steps: int = 10) -> List[Tuple[LocalArbProblem, SharedSignals]]: + rnd = random.Random(self._seed) + venues = ["VenueA", "VenueB"] + assets = ["AAA", "BBB"] + out: List[Tuple[LocalArbProblem, SharedSignals]] = [] + for i in range(steps): + venue = venues[rnd.randrange(len(venues))] + problem = LocalArbProblem( + id=f"prob-{venue}-{i}", + venue=venue, + assets=list(assets), + target_misprice=rnd.uniform(0.0, 0.01), + max_exposure=rnd.choice([100.0, 1000.0, 10000.0]), + latency_budget=rnd.uniform(0.01, 1.0), + ) + # deterministic but varied price deltas and liquidity + price_deltas = {a: rnd.uniform(-0.005, 0.005) for a in assets} + # occasionally inject a shock + if rnd.random() < 0.15: + shock_asset = rnd.choice(assets) + price_deltas[shock_asset] = rnd.choice([-1.0, 1.0]) * rnd.uniform(0.001, 0.02) + signals = SharedSignals( + version=i, + price_delta_by_asset=price_deltas, + cross_corr={("AAA","BBB"): rnd.uniform(-1.0,1.0)}, + liquidity_estimates={a: rnd.uniform(10.0, 10000.0) for a in assets}, + ) + out.append((problem, signals)) + return out + + +class ReplayRunner: + """Runs a generated scenario through a fresh CentralCoordinator and + returns the replay log (list of PlanDelta signatures) and the final plan + signature. + """ + + def __init__(self) -> None: + self.coordinator = CentralCoordinator() + + def run(self, events: Iterable[Tuple[LocalArbProblem, SharedSignals]]): + for problem, sig in events: + self.coordinator.ingest_local(problem, sig) + # call reconcile to flush pending actions + self.coordinator.reconcile() + return list(self.coordinator.replay_log), (self.coordinator.last_plan.signature if self.coordinator.last_plan else None) diff --git a/tests/test_delta_fuzz.py b/tests/test_delta_fuzz.py new file mode 100644 index 0000000..fb80e23 --- /dev/null +++ b/tests/test_delta_fuzz.py @@ -0,0 +1,33 @@ +from crossvenue_arbx.fuzz import ScenarioGenerator, ReplayRunner + + +def test_replay_determinism_same_seed(): + gen = ScenarioGenerator(seed=42) + events = gen.generate(steps=20) + + runner1 = ReplayRunner() + log1, final1 = runner1.run(events) + + runner2 = ReplayRunner() + # regenerate to ensure fresh objects but same seed + gen2 = ScenarioGenerator(seed=42) + events2 = gen2.generate(steps=20) + log2, final2 = runner2.run(events2) + + assert log1 == log2, "Replay logs must be identical for same seed" + assert final1 == final2, "Final plan signature must match for same seed" + + +def test_replay_variation_different_seed(): + gen = ScenarioGenerator(seed=1) + events = gen.generate(steps=20) + runner = ReplayRunner() + log1, final1 = runner.run(events) + + genb = ScenarioGenerator(seed=2) + eventsb = genb.generate(steps=20) + runnerb = ReplayRunner() + log2, final2 = runnerb.run(eventsb) + + # usually different seeds will produce different logs or final plan + assert (log1 != log2) or (final1 != final2)