Market Maker API
Complete guide for automated market makers integrating with the sim-trading platform.
Overview
The Market Maker API lets automated trading systems place and dynamically reprice two-sided liquidity orders (bid + ask) as a single atomic pair. The core workflow is:
Authentication
All API calls require a Bearer token in the Authorization header.
Authorization: Bearer <token>
Tokens do not expire by session but may be revoked. Re-authenticate if you receive 401.
Base URL
http://<host>:3001
All paths in this document are relative to the base URL.
Step 1 โ Authenticate
Request
{
"username": "your_username",
"password": "your_password"
}
Response
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { "id": "...", "username": "...", "role": "..." }
}
Step 2 โ Place a Liquidity Pair
Place two orders with the same pairId in meta.
The pairId is a string you generate (e.g. a UUID). Use it to reprice or cancel the pair later.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
symbol | string | required | e.g. "BTCUSDT" |
side | string | required | "buy" or "sell" |
type | string | required | Must be "liquidity" |
quantity | number | required | Order size in base asset |
leverage | number | optional | Default: 1 |
liquidityConfig.levelOffset | number | required | Depth level (1โ19). Use 1 for tightest spread. |
liquidityConfig.ttlSeconds | number | optional | Order lifetime in seconds. Default: 300 |
priceMode | string | required | Pricing mode โ see below |
pricePercent | number | if percentage | e.g. 0.5 = ยฑ0.5% from market |
priceOffset | number | if fixedOffset | e.g. 500 = ยฑ500 USDT from market |
meta | string (JSON) | required | Must contain pairId |
Pricing Modes
| priceMode | Description | Required field |
|---|---|---|
percentage | Market price ยฑ N% | pricePercent |
fixedOffset | Market price ยฑ N USDT | priceOffset |
levelOffset | Orderbook depth slot N | liquidityConfig.levelOffset |
Example โ place a ยฑ0.5% pair
# 1. Generate a pair ID
PAIR_ID="pair_$(date +%s)_$(head /dev/urandom | tr -dc a-z0-9 | head -c6)"
# 2. Place buy side
curl -s -X POST http://HOST:3001/api/trading/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"symbol\": \"BTCUSDT\",
\"side\": \"buy\",
\"type\": \"liquidity\",
\"quantity\": 0.01,
\"leverage\": 1,
\"liquidityConfig\": { \"levelOffset\": 1, \"ttlSeconds\": 300 },
\"priceMode\": \"percentage\",
\"pricePercent\": 0.5,
\"meta\": \"{\\\"pairId\\\": \\\"$PAIR_ID\\\"}\"
}"
# 3. Place sell side (same pairId)
curl -s -X POST http://HOST:3001/api/trading/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"symbol\": \"BTCUSDT\",
\"side\": \"sell\",
\"type\": \"liquidity\",
\"quantity\": 0.01,
\"leverage\": 1,
\"liquidityConfig\": { \"levelOffset\": 1, \"ttlSeconds\": 300 },
\"priceMode\": \"percentage\",
\"pricePercent\": 0.5,
\"meta\": \"{\\\"pairId\\\": \\\"$PAIR_ID\\\"}\"
}"
const BASE = 'http://HOST:3001';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
};
const pairId = `pair_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
async function placeLiquidityPair({ symbol, quantity, pricePercent, ttlSeconds = 300 }) {
const base = { symbol, type: 'liquidity', quantity, leverage: 1,
liquidityConfig: { levelOffset: 1, ttlSeconds },
priceMode: 'percentage', pricePercent,
meta: JSON.stringify({ pairId }),
};
const [buyRes, sellRes] = await Promise.all([
fetch(`${BASE}/api/trading/orders`, { method: 'POST', headers, body: JSON.stringify({ ...base, side: 'buy' }) }),
fetch(`${BASE}/api/trading/orders`, { method: 'POST', headers, body: JSON.stringify({ ...base, side: 'sell' }) }),
]);
const [buy, sell] = await Promise.all([buyRes.json(), sellRes.json()]);
return { pairId, buyOrderId: buy.order.id, sellOrderId: sell.order.id };
}
const pair = await placeLiquidityPair({
symbol: 'BTCUSDT',
quantity: 0.01,
pricePercent: 0.5,
});
Step 3 โ Reprice
Call this to atomically cancel the existing pair and re-place at new absolute prices. Margin is released and re-frozen automatically.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
pairId | string | required | The pair ID used when placing orders |
newBidPrice | number | required | New absolute buy price (USDT). Must be < newAskPrice. |
newAskPrice | number | required | New absolute sell price (USDT). Must be > newBidPrice. |
Response
{
"success": true,
"buyOrderId": "new-buy-order-uuid",
"sellOrderId": "new-sell-order-uuid",
"newBidPrice": 68000,
"newAskPrice": 72000
}
buyOrderId / sellOrderId from the response if you need to track individual orders.
The pairId changes too โ use the new buyOrderId's meta to get the new pairId,
or generate a new pairId yourself and pass it (coming in a future version).
Example
curl -s -X POST http://HOST:3001/api/liquidity/reprice \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pairId": "pair_1742123456_abc123",
"newBidPrice": 68000,
"newAskPrice": 72000
}'
async function reprice(pairId, newBidPrice, newAskPrice) {
const res = await fetch(`${BASE}/api/liquidity/reprice`, {
method: 'POST',
headers,
body: JSON.stringify({ pairId, newBidPrice, newAskPrice }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error);
}
return res.json();
}
// Reprice every 10 seconds based on mid price
setInterval(async () => {
const mid = await getMidPrice('BTCUSDT'); // your price feed
const spread = mid * 0.005; // 0.5% spread
await reprice(currentPairId, mid - spread, mid + spread);
}, 10_000);
Cancel a Pair
Cancel either order by ID. The other side is automatically cancelled too.
curl -s -X DELETE http://HOST:3001/api/trading/orders/$BUY_ORDER_ID \
-H "Authorization: Bearer $TOKEN"
Pair Lifecycle
| Event | Buy side | Sell side |
|---|---|---|
| Normal placement | pending | pending |
| Buy side filled | filled | cancelled (auto) |
| Sell side filled | cancelled (auto) | filled |
| TTL expired | cancelled | cancelled |
| Manual cancel | cancelled | cancelled (auto) |
| Reprice called | cancelled โ new order | cancelled โ new order |
400
error (pairId has no pending orders). Always handle this case in your bot logic โ
it means a trade occurred and you should re-evaluate your inventory before placing a new pair.
Best Practices
Reprice frequency
- Recommended minimum interval: 1 second between reprice calls for the same pair.
- Rapid repricing (<500ms) is not blocked but increases margin churn and server load.
- For volatile markets, consider wider spreads instead of faster repricing.
pairId management
- Generate a new
pairIdfor each pair placement (UUID or timestamp + random). - After a reprice, the server generates a new internal pairId for the new orders. Your original
pairIdis consumed. - Store the new
buyOrderId/sellOrderIdfrom the reprice response to track the live pair.
Margin management
- Both sides of a pair require margin. A pair of 0.01 BTC at 70,000 USDT with leverage=1 freezes ~1,400 USDT total.
- Reprice releases old margin and freezes new margin atomically โ net change is typically <1 USDT (market price drift).
- Monitor
availableBalanceviaGET /api/accounts/meto avoid margin failures.
Error handling
- Always handle
400on reprice (pair already filled or cancelled). - Re-authenticate on
401. - Back off on
500โ do not retry in a tight loop.
Error Reference
| HTTP | Error message | Cause |
|---|---|---|
| 400 | pairId, newBidPrice, newAskPrice ๅฟ ๅกซ | Missing required fields |
| 400 | newBidPrice ๅฟ ้ ๅฐๆผ newAskPrice | Inverted spread |
| 400 | ๆพไธๅฐ pairId=โฆ ็ pending ๆตๅๆง่จๅฎ | Pair already filled, cancelled, or invalid pairId |
| 400 | pairId=โฆ ็่จๅฎไธๅฎๆด | Only one side found (should not happen in normal flow) |
| 400 | ๅฏ็จไฟ่ฏ้ไธ่ถณ | Insufficient margin for new orders after reprice |
| 401 | Sessionๆ ๆๆๅทฒ่ฟๆ | Token expired or revoked โ re-authenticate |
| 500 | LiquidityOrderManager ๆชๅๅงๅ | Server startup issue โ contact platform admin |
Market Maker API v1.0 ยท sim-trading-pro ยท Questions? Contact the platform operator.
External Symbol Price Feed
Push a reference price (last trade price) for a custom symbol. The price is broadcast to all connected clients and used for chart display and order matching. bid/ask spreads are formed by your liquidity orders, not this endpoint.
Request Body
symbol | string | Symbol code (e.g. XAUUSD) |
price | number | Last trade price (reference price) |
Legacy format: { bid, ask } is also accepted โ mid price is used automatically.
# Single push
curl -X POST http://HOST:3001/api/broker/price-feed \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"symbol":"XAUUSD","price":3275.50}'
# Continuous push script (bash)
TOKEN="YOUR_TOKEN"
while true; do
PRICE=3275.50 # replace with your data source
curl -s -X POST http://HOST:3001/api/broker/price-feed \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"symbol\":\"XAUUSD\",\"price\":$PRICE}" > /dev/null
sleep 1
done
import requests, time
BASE = "http://HOST:3001"
TOKEN = "YOUR_TOKEN"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
def push_price(symbol: str, price: float):
requests.post(f"{BASE}/api/broker/price-feed",
headers=HEADERS,
json={"symbol": symbol, "price": price})
# Example: push every tick from your data source
while True:
price = get_price_from_your_source() # replace with your API
push_price("XAUUSD", price)
time.sleep(0.1) # push every 100ms - backend throttles WS broadcast to 1/sec
const BASE = "http://HOST:3001";
const TOKEN = "YOUR_TOKEN";
async function pushPrice(symbol, price) {
await fetch(`${BASE}/api/broker/price-feed`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ symbol, price }),
});
}
// Push every tick - platform throttles broadcast automatically
setInterval(async () => {
const price = await getFromYourSource(); // replace with your feed
await pushPrice("XAUUSD", price);
}, 100);
Create Custom Symbol
Register a new tradeable symbol on the platform. After creation, the symbol appears in the market list immediately (page refresh required on client).
Request Body
symbol | string | Symbol code, e.g. XAUUSD |
displayName | string | Display name, e.g. ้ป้/็พๅ
|
pricePrecision | number | Decimal places for price (default: 2) |
qtyPrecision | number | Decimal places for quantity (default: 4) |
minOrderSize | number | Minimum order size (default: 0.01) |
curl -X POST http://HOST:3001/api/broker/symbols \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"symbol": "XAUUSD",
"displayName": "้ป้/็พๅ
",
"pricePrecision": 2,
"qtyPrecision": 2,
"minOrderSize": 0.01
}'