Skip to content

Commit 3e65754

Browse files
committed
Restrict ABIDES player protocol
1 parent 042bcb3 commit 3e65754

7 files changed

Lines changed: 355 additions & 136 deletions

File tree

codeclash/arenas/abides/abides.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@ class ABIDESArena(CodeArena):
1616
submission: str = "abides_agent.py"
1717
description: str = """ABIDES is an agent-based market simulator for financial-market research.
1818
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:
19+
Your bot is a Python file named `abides_agent.py` that defines a function named `decide`.
20+
The function receives a plain observation dictionary and returns a list of order intents:
2121
22-
from agent.ValueAgent import ValueAgent as MyAgent
22+
def decide(observation):
23+
return [{"side": "buy", "quantity": 5, "limit_price": 100000}]
2324
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.
25+
The trusted arena runtime owns the ABIDES exchange, kernel, ledgers, and order objects. Submitted
26+
code only returns declarative order intents. Each round runs several compact ABIDES market
27+
simulations. Every submitted policy is evaluated in identical seeded market worlds with the same
28+
exchange, market maker, and background traders. The objective is to maximize average mark-to-market
29+
profit across all simulations in the round.
2730
"""
2831
default_args: dict = {
2932
"sims_per_round": 3,
3033
"market_minutes": 5,
3134
"background_agents": 3,
3235
"validation_timeout": 10,
36+
"decision_timeout": 3.0,
3337
"player_timeout": 60,
3438
"timeout": 240,
3539
}
@@ -54,29 +58,29 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
5458
import_check = agent.environment.execute(
5559
"python - <<'PY'\n"
5660
"import importlib.util\n"
57-
"import numpy as np\n"
58-
"from agent.TradingAgent import TradingAgent\n"
5961
f"spec = importlib.util.spec_from_file_location('submission_agent', {self.submission!r})\n"
6062
"module = importlib.util.module_from_spec(spec)\n"
6163
"spec.loader.exec_module(module)\n"
62-
"assert hasattr(module, 'MyAgent'), 'MyAgent class not found'\n"
63-
"assert issubclass(module.MyAgent, TradingAgent), 'MyAgent must inherit from an ABIDES TradingAgent class'\n"
64-
"module.MyAgent(\n"
65-
" id=1,\n"
66-
" name='validation',\n"
67-
" type='ValidationAgent',\n"
68-
" symbol='JPM',\n"
69-
" starting_cash=10000000,\n"
70-
" log_orders=False,\n"
71-
" random_state=np.random.RandomState(seed=1),\n"
72-
")\n"
64+
"assert hasattr(module, 'decide'), 'decide function not found'\n"
65+
"assert callable(module.decide), 'decide must be callable'\n"
66+
"observation = {\n"
67+
" 'symbol': 'JPM',\n"
68+
" 'cash': 10000000,\n"
69+
" 'position': 0,\n"
70+
" 'best_bid': None,\n"
71+
" 'best_ask': None,\n"
72+
" 'last_trade': 100000,\n"
73+
" 'market_open': True,\n"
74+
"}\n"
75+
"result = module.decide(observation)\n"
76+
"assert result is None or isinstance(result, (list, tuple, dict)), 'decide must return a list, tuple, dict, or None'\n"
7377
"PY",
7478
timeout=int(self._game_arg("validation_timeout")),
7579
)
7680
if import_check["returncode"] != 0:
7781
return (
7882
False,
79-
f"Could not import and instantiate `MyAgent` from `{self.submission}`:\n{import_check['output']}",
83+
f"Could not import or call `decide` from `{self.submission}`:\n{import_check['output']}",
8084
)
8185

8286
return True, None
@@ -95,6 +99,8 @@ def execute_round(self, agents: list[Player]) -> None:
9599
str(self._game_arg("market_minutes")),
96100
"--background-agents",
97101
str(self._game_arg("background_agents")),
102+
"--decision-timeout",
103+
str(self._game_arg("decision_timeout")),
98104
"--player-timeout",
99105
str(self._game_arg("player_timeout")),
100106
"--output",

codeclash/arenas/abides/runtime/README.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
Edit `abides_agent.py`.
44

5-
Your file must define `MyAgent`, an ABIDES trading-agent class. A safe starting point is:
5+
Your file must define `decide(observation)`. The arena calls this function with a plain dictionary
6+
and expects declarative limit-order intents:
67

78
```python
8-
from agent.ValueAgent import ValueAgent as MyAgent
9+
def decide(observation):
10+
last_trade = observation["last_trade"] or 100_000
11+
if observation["position"] < 20:
12+
return [{"side": "buy", "quantity": 5, "limit_price": last_trade + 50}]
13+
return []
914
```
1015

16+
The trusted CodeClash runtime owns the ABIDES kernel, exchange, ledgers, and order objects. Submitted
17+
code only receives observations and returns intents. The runtime validates and clamps each order
18+
before submitting it to ABIDES.
19+
20+
Observation fields include `symbol`, `cash`, `position`, `best_bid`, `best_ask`, `market_open`, and
21+
`limits`. Supported order fields are `side` (`"buy"` or `"sell"`), `quantity`, and `limit_price`.
22+
1123
The arena runs compact ABIDES market simulations and scores agents by average mark-to-market profit
1224
across identical seeded market worlds.
13-
Scores are computed from exchange execution messages, so editing `self.holdings` directly does not
14-
create scored profit.
15-
Some upstream ABIDES agents keep default behavior behind exact-class checks. If you subclass one of
16-
those agents, override the relevant hooks instead of relying on an empty subclass. Inspect method
17-
signatures before overriding them; common signatures are `wakeup(self, currentTime)`,
18-
`receiveMessage(self, currentTime, msg)`, `kernelStarting(self, startTime)`, and
19-
`kernelStopping(self)`.
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
from agent.ValueAgent import ValueAgent
1+
def decide(observation):
2+
"""Return order intents for the trusted ABIDES runtime to validate and submit."""
3+
position = observation.get("position", 0)
4+
best_bid = observation.get("best_bid")
5+
best_ask = observation.get("best_ask")
6+
last_trade = observation.get("last_trade") or 100_000
27

3-
MyAgent = ValueAgent
8+
orders = []
9+
if best_ask is not None and position < 20:
10+
orders.append({"side": "buy", "quantity": 5, "limit_price": best_ask})
11+
elif position < 20:
12+
orders.append({"side": "buy", "quantity": 5, "limit_price": last_trade + 50})
413

5-
__all__ = ["MyAgent"]
14+
if best_bid is not None and position > -20:
15+
orders.append({"side": "sell", "quantity": 5, "limit_price": best_bid})
16+
elif position > -20:
17+
orders.append({"side": "sell", "quantity": 5, "limit_price": last_trade - 50})
18+
19+
return orders

0 commit comments

Comments
 (0)