Skip to content

Commit bbc23f7

Browse files
committed
Add ABIDES arena
1 parent 7fcc206 commit bbc23f7

14 files changed

Lines changed: 931 additions & 1 deletion

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ The winner is the LM agent who wins the most rounds.
9898
## 🧩 Available Arenas
9999

100100
CodeClash includes competitive programming games and simulation-backed arenas, including BattleSnake,
101-
CoreWar, CybORG, Halite, HuskyBench, RoboCode, RobotRumble, and SCML.
101+
ABIDES, CoreWar, CybORG, Halite, HuskyBench, RoboCode, RobotRumble, and SCML.
102+
103+
ABIDES is a financial-market simulation arena based on the Agent-Based Interactive Discrete Event
104+
Simulation environment. Agents edit a Python `abides_agent.py` implementation and compete to
105+
maximize mark-to-market profit across compact simulated limit-order-book markets.
102106

103107
SCML is a supply-chain negotiation arena based on the ANAC Supply Chain Management League OneShot
104108
track. Agents edit a Python `scml_agent.py` implementation and compete to maximize average profit

codeclash/arenas/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from codeclash.arenas.abides.abides import ABIDESArena
12
from codeclash.arenas.arena import CodeArena
23
from codeclash.arenas.battlecode23.battlecode23 import BattleCode23Arena
34
from codeclash.arenas.battlecode24.battlecode24 import BattleCode24Arena
@@ -19,6 +20,7 @@
1920
from codeclash.arenas.scml.scml import SCMLOneShotArena
2021

2122
ARENAS = [
23+
ABIDESArena,
2224
BattleCode23Arena,
2325
BattleCode24Arena,
2426
BattleCode25Arena,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM python:3.11-slim-bookworm
2+
3+
ENV DEBIAN_FRONTEND=noninteractive \
4+
PYTHONDONTWRITEBYTECODE=1 \
5+
PIP_NO_CACHE_DIR=1 \
6+
PYTHONPATH=/opt/abides
7+
8+
RUN apt-get update \
9+
&& apt-get install -y --no-install-recommends \
10+
ca-certificates git build-essential jq \
11+
&& rm -rf /var/lib/apt/lists/*
12+
13+
COPY codeclash/arenas/abides/constraints.txt /tmp/abides-constraints.txt
14+
15+
RUN python -m pip install pip==26.1.1 \
16+
&& git clone https://github.com/abides-sim/abides.git /opt/abides \
17+
&& cd /opt/abides \
18+
&& git checkout c4bf157678928934417aba6073eb0651aeaf6d15 \
19+
&& python -c "from pathlib import Path; p = Path('/opt/abides/util/OrderBook.py'); s = p.read_text(); p.write_text(s.replace('from pandas.io.json import json_normalize', 'from pandas import json_normalize'))" \
20+
&& python -m pip install -e /opt/abides -c /tmp/abides-constraints.txt
21+
22+
WORKDIR /workspace
23+
24+
COPY codeclash/arenas/abides/runtime/ /workspace/
25+
26+
RUN git init \
27+
&& git config user.email "player@codeclash.com" \
28+
&& git config user.name "Player" \
29+
&& git add . \
30+
&& git commit -m "Initial ABIDES workspace"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from codeclash.arenas.abides.abides import ABIDESArena
2+
3+
__all__ = ["ABIDESArena"]

codeclash/arenas/abides/abides.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import json
2+
import shlex
3+
import subprocess
4+
5+
from codeclash.agents.player import Player
6+
from codeclash.arenas.arena import CodeArena, RoundStats
7+
from codeclash.constants import RESULT_TIE
8+
from codeclash.utils.environment import assert_zero_exit_code
9+
10+
RESULTS_JSON = "abides_results.json"
11+
CRASH_SCORE = -1_000_000.0
12+
13+
14+
class ABIDESArena(CodeArena):
15+
name: str = "ABIDES"
16+
submission: str = "abides_agent.py"
17+
description: str = """ABIDES is an agent-based market simulator for financial-market research.
18+
19+
Your bot is a Python file named `abides_agent.py` that defines a class named `MyAgent`.
20+
`MyAgent` should be an ABIDES trading agent class, for example:
21+
22+
from agent.ValueAgent import ValueAgent as MyAgent
23+
24+
Each round runs several compact ABIDES market simulations. Every submitted agent is evaluated in
25+
identical seeded market worlds with the same exchange, market maker, and background traders. The
26+
objective is to maximize average mark-to-market profit across all simulations in the round.
27+
"""
28+
default_args: dict = {
29+
"sims_per_round": 3,
30+
"market_minutes": 5,
31+
"background_agents": 3,
32+
"timeout": 240,
33+
}
34+
35+
def _game_arg(self, key: str):
36+
return self.game_config.get("args", {}).get(key, self.game_config.get(key, self.default_args[key]))
37+
38+
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
39+
quoted_submission = shlex.quote(self.submission)
40+
file_check = agent.environment.execute(f"test -f {quoted_submission} && echo exists")
41+
if "exists" not in file_check["output"]:
42+
return False, f"Submission file `{self.submission}` not found in the workspace root"
43+
44+
content = agent.environment.execute(f"cat {quoted_submission}")["output"]
45+
if not content.strip():
46+
return False, f"`{self.submission}` is empty"
47+
48+
syntax_check = agent.environment.execute(f"python -m py_compile {quoted_submission}")
49+
if syntax_check["returncode"] != 0:
50+
return False, f"Python syntax error in `{self.submission}`:\n{syntax_check['output']}"
51+
52+
import_check = agent.environment.execute(
53+
"python - <<'PY'\n"
54+
"import importlib.util\n"
55+
"import numpy as np\n"
56+
"from agent.TradingAgent import TradingAgent\n"
57+
f"spec = importlib.util.spec_from_file_location('submission_agent', {self.submission!r})\n"
58+
"module = importlib.util.module_from_spec(spec)\n"
59+
"spec.loader.exec_module(module)\n"
60+
"assert hasattr(module, 'MyAgent'), 'MyAgent class not found'\n"
61+
"assert issubclass(module.MyAgent, TradingAgent), 'MyAgent must inherit from an ABIDES TradingAgent class'\n"
62+
"module.MyAgent(\n"
63+
" id=1,\n"
64+
" name='validation',\n"
65+
" type='ValidationAgent',\n"
66+
" symbol='JPM',\n"
67+
" starting_cash=10000000,\n"
68+
" log_orders=False,\n"
69+
" random_state=np.random.RandomState(seed=1),\n"
70+
")\n"
71+
"PY"
72+
)
73+
if import_check["returncode"] != 0:
74+
return (
75+
False,
76+
f"Could not import and instantiate `MyAgent` from `{self.submission}`:\n{import_check['output']}",
77+
)
78+
79+
return True, None
80+
81+
def execute_round(self, agents: list[Player]) -> None:
82+
agent_args = []
83+
for agent in agents:
84+
agent_args.extend(["--agent", f"{agent.name}=/{agent.name}/{self.submission}"])
85+
86+
cmd = [
87+
"python",
88+
"run_abides.py",
89+
"--sims",
90+
str(self.game_config.get("sims_per_round", self.default_args["sims_per_round"])),
91+
"--market-minutes",
92+
str(self._game_arg("market_minutes")),
93+
"--background-agents",
94+
str(self._game_arg("background_agents")),
95+
"--output",
96+
str(self.log_env / RESULTS_JSON),
97+
*agent_args,
98+
]
99+
full_cmd = " ".join(shlex.quote(part) for part in cmd)
100+
self.logger.info(f"Running game: {full_cmd}")
101+
try:
102+
response = self.environment.execute(full_cmd, timeout=int(self._game_arg("timeout")))
103+
except subprocess.TimeoutExpired as exc:
104+
raise RuntimeError("ABIDES round timed out") from exc
105+
assert_zero_exit_code(response, logger=self.logger)
106+
107+
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
108+
result_file = self.log_round(round_num) / RESULTS_JSON
109+
if not result_file.exists():
110+
self.logger.error(f"Missing result file: {result_file}")
111+
stats.winner = RESULT_TIE
112+
for agent in agents:
113+
stats.scores[agent.name] = 0.0
114+
stats.player_stats[agent.name].score = 0.0
115+
return
116+
117+
with open(result_file) as f:
118+
result = json.load(f)
119+
120+
scores = {agent.name: 0.0 for agent in agents}
121+
for player, score in result.get("average_scores", {}).items():
122+
if player in scores:
123+
scores[player] = float(score)
124+
missing_players = sorted(set(scores) - set(result.get("average_scores", {})))
125+
for player in missing_players:
126+
scores[player] = CRASH_SCORE
127+
stats.details.append(
128+
json.dumps(
129+
{
130+
"player": player,
131+
"score": CRASH_SCORE,
132+
"status": "error",
133+
"error": "missing ABIDES score",
134+
},
135+
sort_keys=True,
136+
)
137+
)
138+
139+
stats.scores = scores
140+
stats.details.extend(result.get("details", []))
141+
for player, score in scores.items():
142+
stats.player_stats[player].score = score
143+
144+
if not scores:
145+
stats.winner = RESULT_TIE
146+
return
147+
148+
top_score = max(scores.values())
149+
winners = [player for player, score in scores.items() if score == top_score]
150+
stats.winner = winners[0] if len(winners) == 1 else RESULT_TIE
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
contourpy==1.3.3
2+
cycler==0.12.1
3+
fonttools==4.62.1
4+
joblib==1.5.3
5+
jsons==1.6.3
6+
kiwisolver==1.5.0
7+
matplotlib==3.10.9
8+
numpy==2.4.4
9+
packaging==26.2
10+
pandas==3.0.2
11+
pillow==12.2.0
12+
pprofile==2.2.0
13+
psutil==7.2.2
14+
pyparsing==3.3.2
15+
python-dateutil==2.9.0.post0
16+
pytz==2026.2
17+
scipy==1.17.1
18+
seaborn==0.13.2
19+
six==1.17.0
20+
tqdm==4.67.3
21+
typish==1.9.3
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__pycache__/
2+
*.pyc
3+
log/
4+
logs/
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ABIDES CodeClash Workspace
2+
3+
Edit `abides_agent.py`.
4+
5+
Your file must define `MyAgent`, an ABIDES trading-agent class. A safe starting point is:
6+
7+
```python
8+
from agent.ValueAgent import ValueAgent as MyAgent
9+
```
10+
11+
The arena runs compact ABIDES market simulations and scores agents by average mark-to-market profit
12+
across identical seeded market worlds.
13+
Some upstream ABIDES agents keep default behavior behind exact-class checks. If you subclass one of
14+
those agents, override the relevant hooks instead of relying on an empty subclass.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from agent.ValueAgent import ValueAgent
2+
3+
MyAgent = ValueAgent
4+
5+
__all__ = ["MyAgent"]

0 commit comments

Comments
 (0)