Install
openclaw skills install ctrader-commanderPlace and manage cTrader orders (market, limit, stop), check open positions, fetch live quotes and OHLC candles, and query account balance and equity via a l...
openclaw skills install ctrader-commanderUse when the user wants to place trades, check positions or balance, get live prices, fetch candles, or manage orders on a cTrader account.
All calls go to http://localhost:9009 — credentials live in .env on the server, never passed by callers.
Proxy repo: https://github.com/LogicalSapien/ctrader-openapi-proxy Clone it, add your
.env, and runmake runto start the proxy before using this skill.
Full reference: {baseDir}/endpoints.md
curl -s "http://localhost:9009/get-data?command=ProtoOAVersionReq"
If it fails, start the proxy: cd ~/ctrader-openapi-proxy && make run
Symbol IDs are broker-specific — look them up before placing orders or fetching data:
curl -s "http://localhost:9009/get-data?command=ProtoOASymbolsListReq"
Returns symbol[] with symbolId and symbolName. Note the ID for your instrument.
curl -s -X POST http://localhost:9009/api/market-order \
-H "Content-Type: application/json" \
-d '{"symbolId": 158, "orderType": "MARKET", "tradeSide": "BUY", "volume": 1000}'
Volume is in units: 1000 = 0.01 lot · 10000 = 0.1 lot · 100000 = 1 lot.
Add "relativeStopLoss": 200, "relativeTakeProfit": 350 (pips, market orders only).
curl -s -X POST http://localhost:9009/api/market-order \
-H "Content-Type: application/json" \
-d '{"symbolId": 158, "orderType": "LIMIT", "tradeSide": "BUY", "volume": 1000, "price": 0.62500}'
orderType: MARKET · LIMIT · STOP — tradeSide: BUY · SELL
NOW_MS=$(python3 -c "import time; print(int(time.time()*1000))")
FROM_MS=$(python3 -c "import time; print(int(time.time()*1000) - 3600000)")
curl -s -X POST http://localhost:9009/api/trendbars \
-H "Content-Type: application/json" \
-d "{\"fromTimestamp\": $FROM_MS, \"toTimestamp\": $NOW_MS, \"period\": \"M5\", \"symbolId\": 158}"
Periods: M1 M2 M3 M4 M5 M10 M15 M30 H1 H4 H12 D1 W1 MN1
curl -s -X POST http://localhost:9009/api/live-quote \
-H "Content-Type: application/json" \
-d '{"symbolId": 158, "quoteType": "BID", "timeDeltaInSeconds": 60}'
quoteType: BID or ASK
curl -s "http://localhost:9009/get-data?command=ProtoOAReconcileReq"
curl -s "http://localhost:9009/get-data?command=ClosePosition%20123456%201000"
# ClosePosition <positionId> <volumeInUnits>
curl -s "http://localhost:9009/get-data?command=CancelOrder%20789"
curl -s "http://localhost:9009/get-data?command=ProtoOATraderReq"
A local HTTP proxy (localhost:9009) that wraps the cTrader OpenAPI Protobuf connection and exposes it as a REST API. No credentials are passed at call time — they are loaded from .env on the server.
Full endpoint reference: {baseDir}/endpoints.md
Python usage examples: {baseDir}/examples.md
The proxy must be running before any call. If unsure, check:
curl -s "http://localhost:9009/get-data?command=ProtoOAVersionReq"
If it returns JSON, the proxy is up. If it fails, start it:
cd ~/ctrader-openapi-proxy && make run
Always look up the symbol ID before placing orders or fetching candle/tick data. Symbol IDs differ between brokers and between demo and live accounts.
curl -s "http://localhost:9009/get-data?command=ProtoOASymbolsListReq"
Response contains symbol[] with symbolId and symbolName. Find your instrument and note its symbolId.
POST /api/trendbars
{
"fromTimestamp": 1700000000000,
"toTimestamp": 1700086400000,
"period": "M5",
"symbolId": 158
}
period options: M1 M2 M3 M4 M5 M10 M15 M30 H1 H4 H12 D1 W1 MN1
For current time in ms (macOS):
NOW_MS=$(python3 -c "import time; print(int(time.time()*1000))")
FROM_MS=$(python3 -c "import time; print(int(time.time()*1000) - 3600000)")
POST /api/live-quote
{
"symbolId": 158,
"quoteType": "BID",
"timeDeltaInSeconds": 60
}
quoteType: "BID" or "ASK"
POST /api/market-order
{
"symbolId": 158,
"orderType": "MARKET",
"tradeSide": "BUY",
"volume": 1000,
"comment": "my trade",
"relativeStopLoss": 200,
"relativeTakeProfit": 350
}
orderType values: "MARKET" "LIMIT" "STOP"
tradeSide values: "BUY" "SELL"
For LIMIT and STOP orders, include "price": 0.62500.
relativeStopLoss / relativeTakeProfit are in pips and apply to MARKET orders only.
Volume units (NOT lots):
volume | Lots | Notes |
|---|---|---|
1000 | 0.01 | Micro lot — typical minimum |
10000 | 0.1 | Mini lot |
100000 | 1 | Standard lot |
GET /get-data?command=ProtoOAReconcileReq
Returns position[] and order[]. Each position has positionId, symbolId, tradeSide, volume, price.
GET /get-data?command=ClosePosition <positionId> <volumeInUnits>
Example — close position 123456 with 1000 units (0.01 lot):
curl -s "http://localhost:9009/get-data?command=ClosePosition%20123456%201000"
GET /get-data?command=CancelOrder <orderId>
curl -s "http://localhost:9009/get-data?command=CancelOrder%20789"
Account is auto-authorised from .env on startup. Only call this to switch accounts at runtime:
curl -s -X POST http://localhost:9009/api/set-account
To switch to a different account pass {"accountId": 12345678} as JSON body.
Any cTrader API command can be called via:
GET /get-data?command=COMMAND_NAME arg1 arg2
No token required — credentials are read from .env on the server.
Full list of supported commands: {baseDir}/endpoints.md
curl -s "http://localhost:9009/get-data?command=ProtoOASymbolsListReq" | python3 -c "
import sys, json
data = json.load(sys.stdin)
[print(s['symbolId'], s['symbolName']) for s in data.get('symbol', []) if 'EURUSD' in s['symbolName']]
"
curl -s "http://localhost:9009/get-data?command=ProtoOATraderReq"
curl -s -X POST http://localhost:9009/api/market-order \
-H "Content-Type: application/json" \
-d '{"symbolId": 1, "orderType": "MARKET", "tradeSide": "BUY", "volume": 1000}'
curl -s "http://localhost:9009/get-data?command=ProtoOAReconcileReq"