Tools
Trading Agent
Autonomous paper-trading OpenClaw plugin for Trading 212 Practice (mean reversion + LLM judge, asymmetric daily band)
Install
npm install
npm
Configuration Example
tickers:
- AAPL
- MSFT
- NVDA
- GOOGL
- AMZN
README
# trading-agent
An autonomous paper-trading OpenClaw plugin for Trading 212 Practice. Runs continuously during NYSE hours, decides trades from a rule-based strategy, has them sanity-checked by an LLM judge, and executes through hard-coded risk caps. Logs every cycle to SQLite.
> **This is paper trading.** No real money is moved. The goal is to *measure* whether a simple agentic strategy can hit a +1%/day target over many sessions โ not to make money.
---
## The "soul"
The agent's behavior is governed by an **asymmetric daily P&L band**:
```
Downside: hard stop at -0.5% โ flatten + halt for the day
Upside: no ceiling โ let winners compound
Target: +1% by EOD โ measured, not enforced
Trailing: once peak โฅ +1%, stop ratchets to break-even (0%)
once peak โฅ +2%, stop trails at (peak - 1%)
```
So a successful day ends โฅ +1%. A bad day ends at -0.5%. A great day can end at +5% with the trailing stop quietly protecting the floor along the way.
Why asymmetric: large-cap stocks routinely swing 0.5โ1% intraday in either direction. Capping the upside would cut off the only way the math works.
---
## Architecture
```
NYSE-aware daemon โโ every 30s โโโ
โผ
โโโโ Strategy (pure code, e.g., mean reversion) โโโ candidate signals
โ โผ
โ โโโโ News (cached headlines)
โ โโโโ LLM judge (Opus 4.7 1M) โโ decisions (APPROVE/REJECT/SHRINK)
โ โผ
โ RiskGuard (caps + asymmetric band) โโ hard-clamped orders
โ โผ
โโโโโโโ Trading 212 Practice (market orders) โโโโโโโ
โผ
SQLite ledger
(every cycle, every decision, every fill)
```
Key boundary rules:
| Module | Responsibility | I/O? |
|---|---|---|
| `strategy/*` | Pure: `(prices, positions) โ signals` | None |
| `riskGuard` | Pure: enforces caps + band | None |
| `judge/*` | Calls LLM, validates JSON | LLM only |
| `trading212Client` | Account / quotes / orders | T212 only |
| `news/headlines` | Fetch + cache | OpenClaw `web_search` only |
| `ledger` | Write every cycle/order/fill | SQLite only |
The LLM **cannot bypass risk caps**. Its output is JSON; RiskGuard runs after and clamps. It also cannot smuggle in non-whitelisted tickers โ strategies only see whitelisted prices, and RiskGuard rejects any order whose ticker isn't in the whitelist.
---
## Quick start
### 1. Prerequisites
- Node.js 22+ (24 recommended; uses built-in `node:sqlite`)
- An OpenClaw install
- A Trading 212 account (Practice mode is enough โ no funding required)
### 2. Install
```bash
git clone https://github.com/<you>/trading-agent.git
cd trading-agent
npm install
npm run build
```
### 3. Get a T212 Practice API key
1. Open the Trading 212 app or web client.
2. Switch the account to **Practice** mode.
3. Settings โ API โ generate a key.
4. Set it as an environment variable:
```powershell
$env:T212_API_KEY = "your-practice-key"
```
On macOS/Linux:
```bash
export T212_API_KEY="your-practice-key"
```
### Trading 212 Public API reference
Trading 212's Public API v0 is currently beta and is available at:
| Environment | Base URL |
|---|---|
| Demo / paper trading | `https://demo.trading212.com/api/v0` |
| Live / real money | `https://live.trading212.com/api/v0` |
Do not commit API credentials. For manual `curl` calls, keep the key and secret in environment variables:
```bash
export T212_API_KEY="your-api-key"
export T212_API_SECRET="your-api-secret"
```
PowerShell:
```powershell
$env:T212_API_KEY = "your-api-key"
$env:T212_API_SECRET = "your-api-secret"
```
The official API uses HTTP Basic auth, with the API key as the username and the API secret as the password:
```bash
curl -s -u "$T212_API_KEY:$T212_API_SECRET" \
"https://demo.trading212.com/api/v0/equity/account/summary"
```
#### Pies API lookup
The Pies endpoints are deprecated by Trading 212, but still documented and operational. The safe read-only flow is:
```bash
# 1. List pies for the account.
curl -s -u "$T212_API_KEY:$T212_API_SECRET" \
"https://live.trading212.com/api/v0/equity/pies"
# 2. Fetch detailed holdings/settings for a returned pie id.
curl -s -u "$T212_API_KEY:$T212_API_SECRET" \
"https://live.trading212.com/api/v0/equity/pies/<PIE_ID>"
```
In the manual lookup that informed this README, the only authenticated account calls used were:
| Method | Endpoint | Purpose |
|---|---|---|
| GET | `/api/v0/equity/pies` | List all pies for the account |
| GET | `/api/v0/equity/pies/{id}` | Fetch detailed instruments/settings for each returned pie |
No order, create, update, duplicate, or delete endpoint was used.
#### Documented Trading 212 endpoints
| Method | Endpoint | Notes |
|---|---|---|
| GET | `/api/v0/equity/account/summary` | Account cash/investment summary |
| GET | `/api/v0/equity/metadata/exchanges` | Exchange metadata |
| GET | `/api/v0/equity/metadata/instruments` | Tradable instruments |
| GET | `/api/v0/equity/orders` | Pending orders |
| GET | `/api/v0/equity/orders/{id}` | Pending order by id |
| DELETE | `/api/v0/equity/orders/{id}` | Cancel pending order |
| POST | `/api/v0/equity/orders/limit` | Place limit order |
| POST | `/api/v0/equity/orders/market` | Place market order |
| POST | `/api/v0/equity/orders/stop` | Place stop order |
| POST | `/api/v0/equity/orders/stop_limit` | Place stop-limit order |
| GET | `/api/v0/equity/positions` | Open positions |
| GET | `/api/v0/equity/history/dividends` | Dividend history |
| GET | `/api/v0/equity/history/exports` | List generated CSV reports |
| POST | `/api/v0/equity/history/exports` | Request CSV report generation |
| GET | `/api/v0/equity/history/orders` | Historical orders |
| GET | `/api/v0/equity/history/transactions` | Account transactions |
| GET | `/api/v0/equity/pies` | List pies; deprecated |
| GET | `/api/v0/equity/pies/{id}` | Pie details; deprecated |
| POST | `/api/v0/equity/pies` | Create pie; deprecated |
| POST | `/api/v0/equity/pies/{id}` | Update pie; deprecated |
| DELETE | `/api/v0/equity/pies/{id}` | Delete pie; deprecated |
| POST | `/api/v0/equity/pies/{id}/duplicate` | Duplicate pie; deprecated |
### 4. Create runtime config
The plugin reads from `~/.openclaw/trading-agent/`:
```bash
mkdir -p ~/.openclaw/trading-agent
cp config.example.yaml ~/.openclaw/trading-agent/config.yaml
cp tickers.example.yaml ~/.openclaw/trading-agent/tickers.yaml
```
Edit `tickers.yaml` to your whitelist:
```yaml
tickers:
- AAPL
- MSFT
- NVDA
- GOOGL
- AMZN
```
(Default `config.yaml` is sensible; tweak only if you want to.)
### 5. Install into OpenClaw
This is a real OpenClaw plugin โ it uses `definePluginEntry` from `openclaw/plugin-sdk/plugin-entry`, registers four tools (`trading_start`, `trading_stop`, `trading_status`, `trading_flatten`), and auto-starts the daemon on OpenClaw boot if a config file is present.
Install the local build into your OpenClaw install:
```bash
openclaw plugins install ./
```
(Run from the repo root; `openclaw plugins install <path>` reads `package.json` `openclaw.extensions` to find the plugin entry.)
After install, restart OpenClaw. The agent boots automatically because `openclaw.plugin.json` declares `"activation": { "onStartup": true }` โ but only if `~/.openclaw/trading-agent/config.yaml` exists. If it's missing, the plugin loads but the daemon stays idle until you call `trading_start`.
### 6. Talk to the agent from chat
Once installed, ask OpenClaw via any configured channel (Telegram, WhatsApp, etc.):
- **"What's my trading P&L today?"** โ calls `trading_status`
- **"Stop the trading agent"** โ calls `trading_stop`
- **"Flatten my trading positions"** โ calls `trading_flatten`
- **"Start the trading agent"** โ calls `trading_start`
The LLM in the loop (the **judge**, not the channel agent) is invoked via OpenClaw's local gateway โ the plugin reads `gateway.auth.token` from `~/.openclaw/openclaw.json` and POSTs to `http://127.0.0.1:18789/v1/chat/completions` with `x-openclaw-model: <cfg.llm.model>`.
---
## Configuration reference
`~/.openclaw/trading-agent/config.yaml`:
```yaml
mode: paper # 'paper' or 'live' โ live requires explicit confirmation, currently refused
strategy: meanReversion # meanReversion | momentum | dualMA
tick_seconds: 30 # daemon loop interval
# Risk caps
max_position_pct: 20 # max % of equity in any single ticker
max_concurrent_positions: 5 # cap on open positions
cash_buffer_pct: 10 # never go below 10% cash
# Daily band โ the "soul" parameters
daily_loss_stop_pct: -0.5 # hard downside stop (% of equity)
daily_profit_floor_pct: 1.0 # KPI / target โ measured, not enforced
trailing_protection:
break_even_at: 1.0 # peak โฅ +1% โ stop ratchets to 0%
trail_distance_at: 2.0 # peak โฅ +2% โ stop = peak - 1%
# Order behavior
order_type: market # T212 Practice supports market only
allow_shorting: false
allow_leverage: false
# News input to the LLM judge
news:
enabled: true
refresh_seconds: 300 # cache TTL per ticker
max_headlines_per_ticker: 5
# LLM judge
llm:
model: copilot/claude-opus-4.7-1m-internal
max_tokens: 4000
json_retry_count: 1 # retries on malformed JSON; then REJECT-all fallback
# Broker
trading212:
base_url: https://demo.trading212.com/api/v0
api_key_env: T212_API_KEY # name of env var holding the key
# Reporting
report_channel: telegram # OpenClaw channel for EOD summary
```
---
## How the daily band works (worked example)
Start of day, equit
... (truncated)
tools
Comments
Sign in to leave a comment