Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ The winner is the LM agent who wins the most rounds.
## 🧩 Available Arenas

CodeClash includes competitive programming games and simulation-backed arenas, including BattleSnake,
CoreWar, CybORG, Halite, HuskyBench, RoboCode, RobotRumble, and SCML.
ABIDES, CoreWar, CybORG, Halite, HuskyBench, RoboCode, RobotRumble, and SCML.

ABIDES is a financial-market simulation arena based on the Agent-Based Interactive Discrete Event
Simulation environment. Agents edit a Python `abides_agent.py` implementation and compete to
maximize mark-to-market profit across compact simulated limit-order-book markets.

SCML is a supply-chain negotiation arena based on the ANAC Supply Chain Management League OneShot
track. Agents edit a Python `scml_agent.py` implementation and compete to maximize average profit
Expand Down
2 changes: 2 additions & 0 deletions codeclash/arenas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from codeclash.arenas.abides.abides import ABIDESArena
from codeclash.arenas.arena import CodeArena
from codeclash.arenas.battlecode23.battlecode23 import BattleCode23Arena
from codeclash.arenas.battlecode24.battlecode24 import BattleCode24Arena
Expand All @@ -19,6 +20,7 @@
from codeclash.arenas.scml.scml import SCMLOneShotArena

ARENAS = [
ABIDESArena,
BattleCode23Arena,
BattleCode24Arena,
BattleCode25Arena,
Expand Down
30 changes: 30 additions & 0 deletions codeclash/arenas/abides/ABIDES.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM python:3.11-slim-bookworm

ENV DEBIAN_FRONTEND=noninteractive \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PYTHONPATH=/opt/abides

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates git build-essential jq \
&& rm -rf /var/lib/apt/lists/*

COPY codeclash/arenas/abides/constraints.txt /tmp/abides-constraints.txt

RUN python -m pip install pip==26.1.1 \
&& git clone https://github.com/abides-sim/abides.git /opt/abides \
&& cd /opt/abides \
&& git checkout c4bf157678928934417aba6073eb0651aeaf6d15 \
&& 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'))" \
&& python -m pip install -e /opt/abides -c /tmp/abides-constraints.txt

WORKDIR /workspace

COPY codeclash/arenas/abides/runtime/ /workspace/

RUN git init \
&& git config user.email "[email protected]" \
&& git config user.name "Player" \
&& git add . \
&& git commit -m "Initial ABIDES workspace"
3 changes: 3 additions & 0 deletions codeclash/arenas/abides/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from codeclash.arenas.abides.abides import ABIDESArena

__all__ = ["ABIDESArena"]
150 changes: 150 additions & 0 deletions codeclash/arenas/abides/abides.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import json
import shlex
import subprocess

from codeclash.agents.player import Player
from codeclash.arenas.arena import CodeArena, RoundStats
from codeclash.constants import RESULT_TIE
from codeclash.utils.environment import assert_zero_exit_code

RESULTS_JSON = "abides_results.json"
CRASH_SCORE = -1_000_000.0


class ABIDESArena(CodeArena):
name: str = "ABIDES"
submission: str = "abides_agent.py"
description: str = """ABIDES is an agent-based market simulator for financial-market research.

Your bot is a Python file named `abides_agent.py` that defines a class named `MyAgent`.
`MyAgent` should be an ABIDES trading agent class, for example:

from agent.ValueAgent import ValueAgent as MyAgent

Each round runs several compact ABIDES market simulations. Every submitted agent is evaluated in
identical seeded market worlds with the same exchange, market maker, and background traders. The
objective is to maximize average mark-to-market profit across all simulations in the round.
"""
default_args: dict = {
"sims_per_round": 3,
"market_minutes": 5,
"background_agents": 3,
"timeout": 240,
}

def _game_arg(self, key: str):
return self.game_config.get("args", {}).get(key, self.game_config.get(key, self.default_args[key]))

def validate_code(self, agent: Player) -> tuple[bool, str | None]:
quoted_submission = shlex.quote(self.submission)
file_check = agent.environment.execute(f"test -f {quoted_submission} && echo exists")
if "exists" not in file_check["output"]:
return False, f"Submission file `{self.submission}` not found in the workspace root"

content = agent.environment.execute(f"cat {quoted_submission}")["output"]
if not content.strip():
return False, f"`{self.submission}` is empty"

syntax_check = agent.environment.execute(f"python -m py_compile {quoted_submission}")
if syntax_check["returncode"] != 0:
return False, f"Python syntax error in `{self.submission}`:\n{syntax_check['output']}"

import_check = agent.environment.execute(
"python - <<'PY'\n"
"import importlib.util\n"
"import numpy as np\n"
"from agent.TradingAgent import TradingAgent\n"
f"spec = importlib.util.spec_from_file_location('submission_agent', {self.submission!r})\n"
"module = importlib.util.module_from_spec(spec)\n"
"spec.loader.exec_module(module)\n"
"assert hasattr(module, 'MyAgent'), 'MyAgent class not found'\n"
"assert issubclass(module.MyAgent, TradingAgent), 'MyAgent must inherit from an ABIDES TradingAgent class'\n"
"module.MyAgent(\n"
" id=1,\n"
" name='validation',\n"
" type='ValidationAgent',\n"
" symbol='JPM',\n"
" starting_cash=10000000,\n"
" log_orders=False,\n"
" random_state=np.random.RandomState(seed=1),\n"
")\n"
"PY"
)
if import_check["returncode"] != 0:
return (
False,
f"Could not import and instantiate `MyAgent` from `{self.submission}`:\n{import_check['output']}",
)

return True, None

def execute_round(self, agents: list[Player]) -> None:
agent_args = []
for agent in agents:
agent_args.extend(["--agent", f"{agent.name}=/{agent.name}/{self.submission}"])

cmd = [
"python",
"run_abides.py",
"--sims",
str(self.game_config.get("sims_per_round", self.default_args["sims_per_round"])),
"--market-minutes",
str(self._game_arg("market_minutes")),
"--background-agents",
str(self._game_arg("background_agents")),
"--output",
str(self.log_env / RESULTS_JSON),
*agent_args,
]
full_cmd = " ".join(shlex.quote(part) for part in cmd)
self.logger.info(f"Running game: {full_cmd}")
try:
response = self.environment.execute(full_cmd, timeout=int(self._game_arg("timeout")))
except subprocess.TimeoutExpired as exc:
raise RuntimeError("ABIDES round timed out") from exc
assert_zero_exit_code(response, logger=self.logger)

def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
result_file = self.log_round(round_num) / RESULTS_JSON
if not result_file.exists():
self.logger.error(f"Missing result file: {result_file}")
stats.winner = RESULT_TIE
for agent in agents:
stats.scores[agent.name] = 0.0
stats.player_stats[agent.name].score = 0.0
return

with open(result_file) as f:
result = json.load(f)

scores = {agent.name: 0.0 for agent in agents}
for player, score in result.get("average_scores", {}).items():
if player in scores:
scores[player] = float(score)
missing_players = sorted(set(scores) - set(result.get("average_scores", {})))
for player in missing_players:
scores[player] = CRASH_SCORE
stats.details.append(
json.dumps(
{
"player": player,
"score": CRASH_SCORE,
"status": "error",
"error": "missing ABIDES score",
},
sort_keys=True,
)
)

stats.scores = scores
stats.details.extend(result.get("details", []))
for player, score in scores.items():
stats.player_stats[player].score = score

if not scores:
stats.winner = RESULT_TIE
return

top_score = max(scores.values())
winners = [player for player, score in scores.items() if score == top_score]
stats.winner = winners[0] if len(winners) == 1 else RESULT_TIE
21 changes: 21 additions & 0 deletions codeclash/arenas/abides/constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
contourpy==1.3.3
cycler==0.12.1
fonttools==4.62.1
joblib==1.5.3
jsons==1.6.3
kiwisolver==1.5.0
matplotlib==3.10.9
numpy==2.4.4
packaging==26.2
pandas==3.0.2
pillow==12.2.0
pprofile==2.2.0
psutil==7.2.2
pyparsing==3.3.2
python-dateutil==2.9.0.post0
pytz==2026.2
scipy==1.17.1
seaborn==0.13.2
six==1.17.0
tqdm==4.67.3
typish==1.9.3
4 changes: 4 additions & 0 deletions codeclash/arenas/abides/runtime/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__/
*.pyc
log/
logs/
14 changes: 14 additions & 0 deletions codeclash/arenas/abides/runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ABIDES CodeClash Workspace

Edit `abides_agent.py`.

Your file must define `MyAgent`, an ABIDES trading-agent class. A safe starting point is:

```python
from agent.ValueAgent import ValueAgent as MyAgent
```

The arena runs compact ABIDES market simulations and scores agents by average mark-to-market profit
across identical seeded market worlds.
Some upstream ABIDES agents keep default behavior behind exact-class checks. If you subclass one of
those agents, override the relevant hooks instead of relying on an empty subclass.
5 changes: 5 additions & 0 deletions codeclash/arenas/abides/runtime/abides_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from agent.ValueAgent import ValueAgent

MyAgent = ValueAgent

__all__ = ["MyAgent"]
Loading
Loading