|
| 1 | +import importlib.util |
1 | 2 | import json |
2 | 3 | import subprocess |
| 4 | +import sys |
| 5 | +import types |
3 | 6 | from pathlib import Path |
4 | 7 |
|
| 8 | +import pytest |
| 9 | + |
5 | 10 | from codeclash.arenas.abides.abides import CRASH_SCORE, ABIDESArena |
6 | 11 | from codeclash.arenas.arena import RoundStats |
7 | 12 | from codeclash.constants import RESULT_TIE |
8 | 13 |
|
9 | 14 | from .conftest import MockEnvironment, MockPlayer |
10 | 15 |
|
11 | 16 |
|
| 17 | +def load_runtime_module(monkeypatch): |
| 18 | + class FakeTradingAgent: |
| 19 | + pass |
| 20 | + |
| 21 | + fake_modules = { |
| 22 | + "agent": types.ModuleType("agent"), |
| 23 | + "agent.ExchangeAgent": types.ModuleType("agent.ExchangeAgent"), |
| 24 | + "agent.market_makers": types.ModuleType("agent.market_makers"), |
| 25 | + "agent.market_makers.MarketMakerAgent": types.ModuleType("agent.market_makers.MarketMakerAgent"), |
| 26 | + "agent.TradingAgent": types.ModuleType("agent.TradingAgent"), |
| 27 | + "agent.ZeroIntelligenceAgent": types.ModuleType("agent.ZeroIntelligenceAgent"), |
| 28 | + "Kernel": types.ModuleType("Kernel"), |
| 29 | + "util": types.ModuleType("util"), |
| 30 | + "util.oracle": types.ModuleType("util.oracle"), |
| 31 | + "util.oracle.SparseMeanRevertingOracle": types.ModuleType("util.oracle.SparseMeanRevertingOracle"), |
| 32 | + "util.order": types.ModuleType("util.order"), |
| 33 | + } |
| 34 | + fake_modules["agent.ExchangeAgent"].ExchangeAgent = type("ExchangeAgent", (), {}) |
| 35 | + fake_modules["agent.market_makers.MarketMakerAgent"].MarketMakerAgent = type("MarketMakerAgent", (), {}) |
| 36 | + fake_modules["agent.TradingAgent"].TradingAgent = FakeTradingAgent |
| 37 | + fake_modules["agent.ZeroIntelligenceAgent"].ZeroIntelligenceAgent = type("ZeroIntelligenceAgent", (), {}) |
| 38 | + fake_modules["Kernel"].Kernel = type("Kernel", (), {}) |
| 39 | + fake_modules["util"].util = types.SimpleNamespace(silent_mode=False) |
| 40 | + fake_modules["util.oracle.SparseMeanRevertingOracle"].SparseMeanRevertingOracle = type( |
| 41 | + "SparseMeanRevertingOracle", (), {} |
| 42 | + ) |
| 43 | + fake_modules["util.order"].LimitOrder = type("LimitOrder", (), {"silent_mode": False}) |
| 44 | + for name, module in fake_modules.items(): |
| 45 | + monkeypatch.setitem(sys.modules, name, module) |
| 46 | + |
| 47 | + runtime_path = Path(__file__).parents[2] / "codeclash/arenas/abides/runtime/run_abides.py" |
| 48 | + spec = importlib.util.spec_from_file_location("run_abides_test", runtime_path) |
| 49 | + module = importlib.util.module_from_spec(spec) |
| 50 | + spec.loader.exec_module(module) |
| 51 | + return module |
| 52 | + |
| 53 | + |
12 | 54 | class TestABIDESValidation: |
13 | 55 | def test_valid_agent(self, mock_player_factory): |
14 | 56 | arena = ABIDESArena.__new__(ABIDESArena) |
@@ -252,3 +294,27 @@ def execute(self, cmd, cwd=None, timeout=None): |
252 | 294 | assert "--agent Alice=/Alice/abides_agent.py" in cmd |
253 | 295 | assert "--agent Bob=/Bob/abides_agent.py" in cmd |
254 | 296 | assert arena.environment.timeout == 17 |
| 297 | + |
| 298 | + |
| 299 | +class TestABIDESRuntimeProtocol: |
| 300 | + def test_normalize_order_intents_rejects_boolean_numeric_fields(self, monkeypatch): |
| 301 | + runtime = load_runtime_module(monkeypatch) |
| 302 | + |
| 303 | + with pytest.raises(ValueError, match="quantity"): |
| 304 | + runtime.normalize_order_intents({"side": "buy", "quantity": True, "limit_price": 100_000}) |
| 305 | + |
| 306 | + with pytest.raises(ValueError, match="limit_price"): |
| 307 | + runtime.normalize_order_intents({"side": "buy", "quantity": 1, "limit_price": False}) |
| 308 | + |
| 309 | + def test_normalize_order_intents_accepts_whole_number_floats_and_clamps(self, monkeypatch): |
| 310 | + runtime = load_runtime_module(monkeypatch) |
| 311 | + |
| 312 | + assert runtime.normalize_order_intents({"side": "buy", "quantity": 25.0, "limit_price": 2_000_000.0}) == [ |
| 313 | + {"side": "buy", "quantity": runtime.MAX_ORDER_QUANTITY, "limit_price": runtime.MAX_LIMIT_PRICE} |
| 314 | + ] |
| 315 | + |
| 316 | + def test_normalize_order_intents_rejects_non_integral_floats(self, monkeypatch): |
| 317 | + runtime = load_runtime_module(monkeypatch) |
| 318 | + |
| 319 | + with pytest.raises(ValueError, match="quantity"): |
| 320 | + runtime.normalize_order_intents({"side": "buy", "quantity": 1.5, "limit_price": 100_000}) |
0 commit comments