AML-Sim is an experiment layer on top of the StockSim market simulator. The
goal is to study market decision making, robustness, and behavioral finance in
synthetic multi-agent markets while keeping scenario orchestration outside the
StockSim submodule.
simulators/StockSim is a git submodule. Changes inside StockSim are committed
and pushed from that directory, then the parent AML-Sim repository commits the
updated submodule pointer.
AML-Sim builds on top of
StockSim, which is included as a git
submodule under simulators/StockSim. StockSim provides the core market
simulation engine, exchange agents, trader framework, RabbitMQ-based
coordination, and YAML-driven simulation launcher.
This repository adds AML-specific scenario orchestration and synthetic market participants for studying market behavior, robustness, and decision making.
AML-Sim/
├── aml_runner.py # AML scenario runner
├── aml_sim/
│ ├── launcher.py # AML-owned StockSim component launcher
│ ├── reporting.py # AML-owned report orchestration
│ ├── runs.py # AML run directory/artifact helpers
│ ├── scenario.py # AML scenario loading/validation
│ └── agents/ # AML-specific trader agents
│ ├── base.py # Shared fast/slow-loop AML agent base
│ ├── market_maker_trader.py # AML market-maker agent
│ ├── retail_trader.py # AML retail trader agent
│ ├── institutional_trader.py # AML institutional trader agent
│ ├── informed_trader.py # AML informed trader agent
│ ├── liquidity_taker.py # AML liquidity-taking flow agent
│ ├── shock_agent.py # AML scenario shock/event broadcaster
│ ├── models/
│ │ ├── profile.py # Stable role/personality profile models
│ │ └── state.py # Role-specific strategy state models
│ ├── context/
│ │ ├── observation.py # LLM/slow-loop observation context
│ │ └── memory.py # Local memory + future Zep hook
│ └── strategy/
│ ├── llm_slow_strategy.py # LLM-shaped slow-loop strategist
│ └── validator.py # Strategy state bounds validation
├── scenarios/
│ └── aml_orderbook_replay.yaml # Current AML smoke scenario
└── simulators/
└── StockSim/ # StockSim submodule
├── main_launcher.py # StockSim entrypoint
└── docker-compose.yml # RabbitMQ + StockSim services
AML-Sim is split into two layers:
- Run orchestration: AML-Sim reads scenarios, creates run artifacts, and starts StockSim engine components.
- Agent behavior: AML-Sim owns the market-maker, retail, and institutional
trader behavior while keeping those agents compatible with StockSim's
TraderAgent.
aml_runner.pyreads an AML scenario YAML file.- The scenario's
stocksim_configsection is extracted and written to.aml_runs/<run-id>/stocksim_config.yaml. - AML-Sim also archives the original scenario as
.aml_runs/<run-id>/scenario.yamland writes run metadata to.aml_runs/<run-id>/metadata.json. - AML-Sim imports StockSim exchange/base-trader/simulation-clock classes and
starts those component processes itself. The AML agent behavior classes live
under
aml_sim/agents/.simulators/StockSim/main_launcher.pyremains StockSim's standalone CLI entrypoint. - AML-Sim starts the exchange agents, trader agents, and simulation clock.
- Components communicate through RabbitMQ.
- Logs for AML-launched runs are written under
.aml_runs/<run-id>/logs.
The scenario YAML is the experiment definition. It contains AML-level metadata
such as name, description, and rabbitmq_host, plus the stocksim_config
mapping that is passed directly into StockSim after generation. In other words,
the YAML file is where you configure instruments, exchange mode, agents,
simulation times, and environment settings for a StockSim run.
The AML agent layer currently includes these synthetic market participants:
AML_Market_Maker: posts bid/ask limit orders around a configurable fair price and adjusts quotes with an inventory skew.AML_Retail_Trader: submits occasional small noisy market orders with a configurable buy bias and trade probability.AML_Institutional_Trader: works toward target positions using sliced child orders.AML_Informed_Trader: trades from a private/fundamental value signal when the signal is strong enough.AML_Liquidity_Taker: submits directional flow against available liquidity with bounded size and inventory exposure.AML_Shock_Agent: emits scheduled, announced, and random AML shock/event messages to target agents.aml_orderbook_replay.yaml: runs a short synthetic AAPL order book scenario with one market maker, five retail traders, and one institutional trader.aml_agent_infra_smoke.yamlandaml_one_hour_live.yaml: exercise the broader AML agent set, including informed flow, liquidity-taking flow, and scheduled shock events.
These AML agents live in aml_sim/agents/. They still inherit StockSim's
TraderAgent and use StockSim's order/message primitives, but AML-Sim owns
their behavior and maps YAML types such as AML_Market_Maker to these classes.
AML agents now use a shared fast-loop / slow-loop architecture:
BaseAMLAgentinherits from StockSim'sTraderAgentand keeps the shared AML agent plumbing in one place.- StockSim still owns execution, messaging, portfolio/accounting state, order state, and RabbitMQ integration.
- AML-Sim owns behavioral strategy state, observation packaging, memory hooks, strategy validation, slow-loop strategy updates, and role-specific fast execution behavior.
action_intervalcontrols how often the fast loop is allowed to submit orders.slow_loop_intervalcontrols how often the slow loop updates the agent's strategy state.
The fast loop is role-specific and runs from the currently validated strategy state:
- Market maker fast loop refreshes bid/ask quotes using fair price, spread, quote size, target inventory, and inventory skew.
- Retail fast loop submits small probabilistic market orders using trade probability, buy bias, and max order size.
- Institutional fast loop works toward target positions using child order size, order type, and execution style.
The slow loop uses aml_sim/agents/strategy/llm_slow_strategy.py. By default it
uses fixed role-specific JSON responses so the control flow can be tested
without spending API credits. Agents can opt into real OpenAI calls through
scenario YAML by setting slow_strategist.type: openai.
Role-specific strategy states live in aml_sim/agents/models/state.py:
MarketMakerStrategyStateRetailStrategyStateInstitutionalStrategyStateInformedStrategyStateLiquidityTakerStrategyState
Before a strategy proposal is applied, aml_sim/agents/strategy/validator.py
checks bounds such as trade probability, buy bias, quote size, spread, child
order size, confidence, and risk mode. If validation fails, the agent keeps its
previous strategy state and logs the rejection.
Every AML strategy state defaults to risk_mode: normal. Experiments can set a
different initial posture in an agent's YAML parameters:
parameters:
risk_mode: conservativeFor an OpenAI slow strategist with an explicit allowed_strategy_fields
allowlist, include risk_mode to let the LLM change the posture dynamically:
slow_strategist:
type: openai
allowed_strategy_fields:
- risk_modeRisk modes map to a normalized risk-aversion value, gamma:
| Risk mode | gamma |
|---|---|
risk_off |
3.00 |
conservative |
1.50 |
normal |
1.00 |
opportunistic |
0.75 |
aggressive |
0.50 |
The shared fast-loop policy derives these values from gamma:
- participation multiplier:
clamp(1 / gamma, 0.25, 1.50) - order-size multiplier:
clamp(1 / sqrt(gamma), 0.40, 1.40) - position-limit multiplier:
clamp(1 / gamma, 0.25, 1.00) - signal-threshold multiplier:
clamp(sqrt(gamma), 0.70, 2.00)
clamp(value, minimum, maximum) restricts a result to the stated range.
normal therefore produces 1.0 multipliers and preserves existing behavior.
The policy is composed with, rather than substituted for, event pressure:
effective fast-loop behavior =
configured strategy × event pressure × risk-mode policy
The common preference produces role-specific behavior:
- market makers adjust quote size, maximum inventory, spread, and inventory-based price skew;
- retail traders adjust participation probability and maximum order size;
- informed traders adjust participation, order size, position limit, and the signal strength required to trade;
- liquidity takers adjust participation, order size, and inventory limit;
- institutional traders use smaller child orders when adding exposure, but higher risk aversion accelerates child orders that reduce existing exposure.
The relationships are informed by Merton-style portfolio choice, Avellaneda-Stoikov market making, and Almgren-Chriss execution. The mode values are normalized simulation categories because those source models use differently scaled risk parameters.
Slow-loop memory records strategy_before and strategy_after, including
risk_mode. Submitted, rejected, and executed order artifacts record both the
strategy state and the derived risk_policy, so experiments can verify which
policy was active for each action.
The observation processor in aml_sim/agents/context/observation.py builds the
structured context package used by the slow loop. Today that package includes:
- agent id
- current simulation time
- latest market snapshot
- cash, portfolio value, and per-instrument inventory
- pending orders
- recent fills
- current strategy state
- memory context
- active shock/event context and known future/scheduled event context
AML_Shock_Agent is the scenario-level event broadcaster for market stress,
news, policy, liquidity, and other uncertainty injections. It supports:
- Scheduled shocks with
tick/time, optionalnotice_ticksorannounce_tick, and separate announcement vs active phases. - Unexpected shocks through
random_eventstemplates with deterministicrandom_seed, per-tick probability, max event count, and severity ranges. - Systematic and non-systematic taxonomy through fields such as
shock_class,scope,trigger_type,visibility,surprise, andexpected_probability. - Cross-asset effects through
affected_instruments,affected_asset_classes,asset_class_effects, andper_instrument_effects. - Central market state through
initial_market_state,market_state, andmarket_state_delta. Temporary impacts revert after their event duration; structural changes can usestate_persistence: permanent.
Supported effect fields include fundamental_price_shift,
order_arrival_multiplier, risk_limit_multiplier, liquidity_multiplier,
volatility_multiplier, spread_multiplier, price_impact_multiplier,
rate_shift_bps, yield_shift_bps, funding_spread_bps,
credit_spread_bps, sentiment_shift, and risk_aversion_shift. Current
agents react mainly through price/fundamental pressure, order-arrival pressure,
risk-limit pressure, liquidity withdrawal, spread/volatility widening, and
sentiment shifts. The same event payload is included in the observation context
so LLM slow loops can reason over active shocks and announced future events.
The observation also carries the current central market state and its baseline,
which the fast loops use for ongoing rate, funding, credit, liquidity, and risk
conditions after an individual shock has expired.
Clone the repo with submodules in one step:
git clone --recurse-submodules <AML-Sim repo URL>
cd AML-SimOr clone normally, then initialize the StockSim submodule afterward:
git clone <AML-Sim repo URL>
cd AML-Sim
git submodule update --init --recursiveCreate and activate a Python environment from the AML-Sim root:
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txtFor the current synthetic order book scenario, no Polygon, Alpha Vantage, or OpenAI API key is required. RabbitMQ is required.
Optional root .env:
RABBITMQ_HOST=localhost
LOG_DIR=logs
OPENAI_API_KEY=sk-...aml_runner.py will also set LOG_DIR to the run-specific log directory and
will pass rabbitmq_host from the scenario to StockSim.
OpenAI is only used for scenarios or agents that explicitly configure
slow_strategist.type: openai.
The easiest route is to use the StockSim Docker Compose file and start only RabbitMQ:
cd simulators/StockSim
docker compose up -d rabbitmq
cd ../..Before running a scenario, make sure the RabbitMQ container is actually up:
docker ps | grep rabbitmqThis should print the running RabbitMQ container, usually named
stocksim-rabbitmq. If it prints nothing, RabbitMQ is not running and StockSim
agents will fail to connect to the message broker, usually with a connection
refused or AMQP connection error.
From the AML-Sim root, first check that the scenario can generate a valid StockSim config:
python aml_runner.py scenarios/aml_orderbook_replay.yaml --dry-runThis creates a run directory under .aml_runs/ and writes:
.aml_runs/<run-id>/scenario.yaml
.aml_runs/<run-id>/stocksim_config.yaml
.aml_runs/<run-id>/metadata.json
.aml_runs/<run-id>/logs/
.aml_runs/<run-id>/charts/
.aml_runs/<run-id>/reports/
Completed AML runs also write trader action artifacts under:
.aml_runs/<run-id>/reports/agents/
.aml_runs/<run-id>/reports/trader_actions.json
The combined trader_actions.json report contains submitted orders, rejected
orders, trade executions, strategy state at the time of the action, and
portfolio/share state before and after the action.
Then run the full scenario with RabbitMQ running:
python aml_runner.py scenarios/aml_orderbook_replay.yamlTo call StockSim's post-simulation artifact generator and save reports/charts
inside the AML run directory, add --reports:
python aml_runner.py scenarios/aml_orderbook_replay.yaml --reportsFor the current synthetic order book scenario, this writes the StockSim summary
JSON under .aml_runs/<run-id>/reports/. Future AML-specific reports should use
the same run-local reports/ and charts/ folders, but can add synthetic
orderbook/trade HTML views instead of relying only on external candle data.
You can set a stable run directory name while iterating:
python aml_runner.py scenarios/aml_orderbook_replay.yaml --run-id smoke_orderbookUse a new --run-id each time, because the runner intentionally refuses to
overwrite an existing .aml_runs/<run-id> directory.
scenarios/aml_llm_api_smoke.yaml enables real OpenAI slow-loop calls for only
three agent groups to keep API usage small:
python aml_runner.py scenarios/aml_llm_api_smoke.yaml --dry-run
python aml_runner.py scenarios/aml_llm_api_smoke.yaml --run-id llm_api_smokeOpenAI defaults live at the AML scenario level because the LLM call belongs to AML-Sim, not StockSim:
aml_config:
llm:
provider: openai
model: gpt-5.4
api_key_env: OPENAI_API_KEY
temperature: 0.2
timeout_seconds: 30
max_retries: 2Each configured agent can opt into those defaults like this:
slow_strategist:
type: openaiAgent-level slow_strategist values override aml_config.llm, so one agent can
use a different model or temperature for an experiment without changing the
global defaults.
The OpenAI slow strategist receives profile, memory, observation, and current strategy state, then returns JSON strategy updates. It does not place orders directly; strategy updates still pass through AML validation before the fast loop can use them.
For local iteration, the dashboard can serve the UI and launch a simulation from
one small Python server. If Docker Desktop is installed and the docker
command works in your terminal, use:
python dashboard_server.py --start-rabbitmq --run --run-id one_hour_liveIf Docker is not installed or is not on your PATH, start RabbitMQ manually on
localhost:5672 first, then run the dashboard without --start-rabbitmq:
python dashboard_server.py --run --run-id one_hour_liveOpen the printed URL, or go directly to:
http://127.0.0.1:8765/dashboard.html?run=one_hour_live
The Run Simulation button in dashboard.html works only when the page is
served by dashboard_server.py, because plain python3 -m http.server 8765
cannot start local Python processes. If RabbitMQ is already running and you only
want the UI/API server, use:
python dashboard_server.pyWhile a simulation is running, the dashboard streams run artifacts from
/api/live and updates the price chart, order book, trade tape, shock monitor,
participant activity, and top-line stats as the StockSim logs are written. Final
report files are still loaded after shutdown for completed-run metrics.
Runs are finite by default. The scenario YAML controls the simulated clock with
simulation.start_time, simulation.end_time, and simulation.tick_interval.
For example, scenarios/aml_one_hour_live.yaml runs from 09:30 to 10:30 with
30-second ticks. The dashboard streams updates while this run is active; after
the scenario clock reaches end_time, the run stops and the final reports are
loaded. StockSim currently sleeps for roughly 5 wall-clock seconds per tick, so
this one simulated hour usually takes about 10 wall-clock minutes plus
startup/reporting overhead. Longer live scenarios should set
simulation.max_wall_time_seconds high enough for the wall-clock runtime; the
one-hour dashboard scenario uses 900 seconds.
When editing files under simulators/StockSim, commit and push those changes
from inside the submodule:
cd simulators/StockSim
git status
git add .
git commit -m "Update AML StockSim agents"
git pushThen commit the updated submodule pointer from the parent repo:
cd ../..
git status
git add simulators/StockSim
git commit -m "Update StockSim submodule"
git pushPush the StockSim commit first. The parent repo only stores a pointer to a specific StockSim commit, so other users need that commit to exist on the StockSim remote.