Skip to content

Commit 54128a6

Browse files
committed
Add ABIDES arena
1 parent 7fcc206 commit 54128a6

14 files changed

Lines changed: 802 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 --upgrade pip \
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: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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+
12+
13+
class ABIDESArena(CodeArena):
14+
name: str = "ABIDES"
15+
submission: str = "abides_agent.py"
16+
description: str = """ABIDES is an agent-based market simulator for financial-market research.
17+
18+
Your bot is a Python file named `abides_agent.py` that defines a class named `MyAgent`.
19+
`MyAgent` should be an ABIDES trading agent class, for example:
20+
21+
from agent.ValueAgent import ValueAgent as MyAgent
22+
23+
Each round runs several compact ABIDES market simulations. Your agent trades in the same simulated
24+
limit-order-book market as the other submitted agents. The objective is to maximize average
25+
mark-to-market profit across all simulations in the round.
26+
"""
27+
default_args: dict = {
28+
"sims_per_round": 3,
29+
"market_minutes": 5,
30+
"background_agents": 3,
31+
"timeout": 240,
32+
}
33+
34+
def _game_arg(self, key: str):
35+
return self.game_config.get("args", {}).get(key, self.game_config.get(key, self.default_args[key]))
36+
37+
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
38+
quoted_submission = shlex.quote(self.submission)
39+
file_check = agent.environment.execute(f"test -f {quoted_submission} && echo exists")
40+
if "exists" not in file_check["output"]:
41+
return False, f"Submission file `{self.submission}` not found in the workspace root"
42+
43+
content = agent.environment.execute(f"cat {quoted_submission}")["output"]
44+
if not content.strip():
45+
return False, f"`{self.submission}` is empty"
46+
47+
syntax_check = agent.environment.execute(f"python -m py_compile {quoted_submission}")
48+
if syntax_check["returncode"] != 0:
49+
return False, f"Python syntax error in `{self.submission}`:\n{syntax_check['output']}"
50+
51+
import_check = agent.environment.execute(
52+
"python - <<'PY'\n"
53+
"import importlib.util\n"
54+
"import numpy as np\n"
55+
f"spec = importlib.util.spec_from_file_location('submission_agent', {self.submission!r})\n"
56+
"module = importlib.util.module_from_spec(spec)\n"
57+
"spec.loader.exec_module(module)\n"
58+
"assert hasattr(module, 'MyAgent'), 'MyAgent class not found'\n"
59+
"from agent.TradingAgent import TradingAgent\n"
60+
"assert issubclass(module.MyAgent, TradingAgent), 'MyAgent must inherit from an ABIDES TradingAgent class'\n"
61+
"module.MyAgent(\n"
62+
" id=1,\n"
63+
" name='validation',\n"
64+
" type='ValidationAgent',\n"
65+
" symbol='JPM',\n"
66+
" starting_cash=10000000,\n"
67+
" log_orders=False,\n"
68+
" random_state=np.random.RandomState(seed=1),\n"
69+
")\n"
70+
"PY"
71+
)
72+
if import_check["returncode"] != 0:
73+
return (
74+
False,
75+
f"Could not import and instantiate `MyAgent` from `{self.submission}`:\n{import_check['output']}",
76+
)
77+
78+
return True, None
79+
80+
def execute_round(self, agents: list[Player]) -> None:
81+
agent_args = []
82+
for agent in agents:
83+
agent_args.extend(["--agent", f"{agent.name}=/{agent.name}/{self.submission}"])
84+
85+
cmd = [
86+
"python",
87+
"run_abides.py",
88+
"--sims",
89+
str(self.game_config.get("sims_per_round", self.default_args["sims_per_round"])),
90+
"--market-minutes",
91+
str(self._game_arg("market_minutes")),
92+
"--background-agents",
93+
str(self._game_arg("background_agents")),
94+
"--output",
95+
str(self.log_env / RESULTS_JSON),
96+
*agent_args,
97+
]
98+
full_cmd = " ".join(shlex.quote(part) for part in cmd)
99+
self.logger.info(f"Running game: {full_cmd}")
100+
try:
101+
response = self.environment.execute(full_cmd, timeout=int(self._game_arg("timeout")))
102+
except subprocess.TimeoutExpired as exc:
103+
raise RuntimeError("ABIDES round timed out") from exc
104+
assert_zero_exit_code(response, logger=self.logger)
105+
106+
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
107+
result_file = self.log_round(round_num) / RESULTS_JSON
108+
if not result_file.exists():
109+
self.logger.error(f"Missing result file: {result_file}")
110+
stats.winner = RESULT_TIE
111+
for agent in agents:
112+
stats.scores[agent.name] = 0.0
113+
stats.player_stats[agent.name].score = 0.0
114+
return
115+
116+
with open(result_file) as f:
117+
result = json.load(f)
118+
119+
scores = {agent.name: 0.0 for agent in agents}
120+
for player, score in result.get("average_scores", {}).items():
121+
if player in scores:
122+
scores[player] = float(score)
123+
124+
stats.scores = scores
125+
stats.details = result.get("details", [])
126+
for player, score in scores.items():
127+
stats.player_stats[player].score = score
128+
129+
if not scores:
130+
stats.winner = RESULT_TIE
131+
return
132+
133+
top_score = max(scores.values())
134+
winners = [player for player, score in scores.items() if score == top_score]
135+
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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
Some upstream ABIDES agents keep default behavior behind exact-class checks. If you subclass one of
13+
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)