Skip to content

Ramairus/MarketSim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

marketsim

A market-simulation activity for a summer school. Student teams write trading bots and run them on their own laptops; a central server runs the game, and each bot opens an outbound WebSocket to it (so it works from behind home NAT with no port-forwarding). The same server also hosts the marketsim client package students pip install.

Two audiences, two sections below:


Students — write and run a bot

1. Requirements

  • Python 3.11 or newer (python --version). Older versions are rejected.
  • That's it — pip install pulls everything else (numpy, pandas, websockets, …).

2. Install the client

Your organizer gives you an install password and a team token. Install the client wheel with one command (swap in the password and host you were given):

pip install "https://install:PASSWORD@marketsim.hoshinofw.net/dist/marketsim-0.1.0-py3-none-any.whl"

That single command installs everything, including pandas for to_dataframe() — there is no extra step or [ml] flag to remember.

Tip: use a fresh virtual environment so it can't clash with other projects:

python -m venv venv && source venv/bin/activate   # Windows: venv\Scripts\activate

3. Write a bot

The wheel already knows the server address (it's baked in at build time). You only supply your team token and your trading logic:

# my_bot.py
from marketsim import Bot, Context

bot = Bot(team_token="YOUR-TEAM-TOKEN")

@bot.on_tick("gold")            # a turn-based market: you're asked once per tick
def trade_gold(ctx: Context):
    # Every value here is in euros; only ctx.price is euros-per-item.
    if ctx.cash > 100 and ctx.position < 50:
        return [ctx.buy(eur=10.0)]      # buy 10 € of gold
    return []                            # return [] to hold

@bot.on_quote("crypto")         # a high-frequency market: a live price stream
def trade_crypto(ctx: Context):
    prices = ctx.prices                  # 1-D numpy array of this asset's history
    if len(prices) > 50 and ctx.price < prices[-50:].min() * 1.001:
        return [ctx.buy(eur=5.0)]
    return []

if __name__ == "__main__":
    bot.run()

Run it:

python my_bot.py

It connects, auto-reconnects if your wifi drops, and keeps your price history across reconnects. Missing a tick (slow wifi, an exception in your handler) simply counts as a hold — it never crashes your bot.

4. The manual "oil" market (a web UI)

One market (oil) is turn-based-with-a-human: each round the server simulates a whole "month" instantly and your client replays it, then you decide. When you run the bot it prints:

Manual trading UI: http://localhost:8765

Open that URL in your browser — the month animates in and you click Buy / Sell / Hold before the countdown ends. Prefer to automate it? Register an @bot.on_decision("oil") handler instead and the UI is skipped. (Run bot.run(ui=False) to use a terminal prompt.)

5. Train on the price history

Each Market is one asset's 1-D price series and doubles as your ML training set:

df = bot.market("gold").to_dataframe()   # columns: tick, time, price  (pandas)

Or download the pre-simulated history over HTTP (no auth needed on /history):

import pandas as pd
df = pd.read_csv("https://marketsim.hoshinofw.net/history/gold.csv")

Handler quick reference (ctx)

Attribute Meaning
ctx.price this asset's current price (€/item)
ctx.prices full history as a 1-D numpy array
ctx.cash your cash in €
ctx.position current € value of your stake in this asset
ctx.holdings dict: asset → current € value of that stake
ctx.buy(eur=x) / ctx.sell(eur=x) build a buy/sell order for x euros
ctx.markets all assets (read another asset's history if you like)

Local testing (optional)

If you run your own server locally (see below), point the client at it:

export MARKETSIM_SERVER_URL="ws://localhost:8000"     # Windows: set MARKETSIM_SERVER_URL=...
pip install "http://install:PASSWORD@localhost:8000/dist/marketsim-0.1.0-py3-none-any.whl"
python my_bot.py

MARKETSIM_SERVER_URL always overrides the baked-in address.


Organizers — run the server

The server is a single-process FastAPI/uvicorn app. It serves the game WebSocket, the client wheel (behind Basic Auth), a live dashboard, and an admin panel. Run one process only — all game state is in memory (never use --workers N / gunicorn).

Option A — Docker (recommended)

Prereqs: Docker + Docker Compose.

# 1. Team tokens: copy the example and edit it (token -> team-id map).
cp deploy/tokens.example.json deploy/tokens.json
#    edit deploy/tokens.json — give each team a unique secret token.

# 2. Set the wheel address for students: edit docker-compose.yml and set
#    build.args.PUBLIC_WS_URL to your real host, e.g.
#      PUBLIC_WS_URL: "wss://marketsim.hoshinofw.net"
#    (this is baked into the wheel students download).

# 3. Passwords come from the environment (required):
export MARKETSIM_INSTALL_PASSWORD="pick-an-install-password"   # students use this to pip install
export MARKETSIM_ADMIN_PASSWORD="pick-an-admin-password"       # you use this for /admin

# 4. Build (runs the test suite as a build gate) and start:
docker compose up -d --build

# 5. Check it's alive:
curl http://localhost:8000/health         # -> {"status":"ok", ...}

The build fails if the test suite is red — a broken server never ships.

Backups and the replay log persist in a Docker volume (marketsim-data/data), so docker compose restart resumes the game (prices and portfolios continue) rather than resetting. Stop everything with docker compose down (the volume, and thus the saved game, survives).

Option B — local dev (no Docker)

pip install -r requirements.txt
pip install -e client                      # so `import marketsim` resolves from source
python scripts/build_client.py             # build the wheel into dist/ (so /dist can serve it)

export MARKETSIM_INSTALL_PASSWORD=changeme-me
export MARKETSIM_ADMIN_PASSWORD=changeme-me
python main.py                             # serves on http://localhost:8000

Run the test suite any time (plain script, prints PASS/FAIL, exits nonzero on failure):

python test.py

Web surfaces

URL What
/ live public dashboard (prices, rankings)
/admin admin panel — log in with MARKETSIM_ADMIN_PASSWORD; pause/resume/schedule markets
/health liveness JSON
/dist/<wheel> the client wheel, behind Basic Auth (install / MARKETSIM_INSTALL_PASSWORD)
/history/<market>.csv that market's pre-simulated training set (no auth)
/ws the game WebSocket (clients connect here with ?token=…)

Session save / resume

The engine snapshots the whole game to /data/backups/session-*.json every MARKETSIM_BACKUP_INTERVAL_S seconds (default 20) and on shutdown. On startup it decides what to load:

  • Attached to a terminal (docker compose run --rm marketsim, or python main.py in a shell): shows an interactive menu — pick a backup to resume, or 0 for a new game.
  • Detached (docker compose up -d, auto-restart): silently resumes the latest backup — this is crash recovery.
  • MARKETSIM_SESSION overrides both: new (fresh game), latest (force resume), or a path to a specific backup file.

Configuration

Layer File / source What
Game/market tuning server/config.toml markets, price models, policies, starting cash, oversell clamp
Project version properties.toml the single source of the version number
Deployment / secrets environment variables see below

Key environment variables (all optional except the two passwords in Docker):

Variable Default Meaning
MARKETSIM_INSTALL_PASSWORD changeme Basic-Auth password for the wheel download
MARKETSIM_ADMIN_PASSWORD changeme admin-panel password
MARKETSIM_HOST / MARKETSIM_PORT 0.0.0.0 / 8000 bind address
MARKETSIM_SESSION (auto) new | latest | <path>
MARKETSIM_BACKUP_INTERVAL_S 20 seconds between session backups
MARKETSIM_BACKUP_KEEP 50 how many backups to retain
MARKETSIM_ROOT_PATH "" set to your proxy prefix only if the proxy forwards it un-stripped
MARKETSIM_TOKENS_PATH tokens.json path to the team-token map

Behind a reverse proxy (TLS)

TLS terminates at your proxy; the container serves plain HTTP/ws on :8000. See nginx.example.conf for a config that reverse-proxies https://marketsim.hoshinofw.net/ to the container at its root (WebSocket upgrade headers included). Students then use wss://marketsim.hoshinofw.net and install from https://marketsim.hoshinofw.net/dist/….

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors