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:

๐Ÿ” Authenticate
โ†’
๐Ÿ“Œ Place bid + ask
โ†’
โ™ป๏ธ Reprice loop
โ†’
๐Ÿ—‘๏ธ Cancel when done
โ„น๏ธ
All liquidity orders are linked in pairs. Cancelling or filling one side automatically cancels the other. This is by design โ€” the platform enforces two-sided markets.

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

POST /api/auth/login

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.

POST /api/trading/orders ๐Ÿ”‘ Auth required

Request Body

FieldTypeRequiredDescription
symbolstringrequirede.g. "BTCUSDT"
sidestringrequired"buy" or "sell"
typestringrequiredMust be "liquidity"
quantitynumberrequiredOrder size in base asset
leveragenumberoptionalDefault: 1
liquidityConfig.levelOffsetnumberrequiredDepth level (1โ€“19). Use 1 for tightest spread.
liquidityConfig.ttlSecondsnumberoptionalOrder lifetime in seconds. Default: 300
priceModestringrequiredPricing mode โ€” see below
pricePercentnumberif percentagee.g. 0.5 = ยฑ0.5% from market
priceOffsetnumberif fixedOffsete.g. 500 = ยฑ500 USDT from market
metastring (JSON)requiredMust contain pairId

Pricing Modes

priceModeDescriptionRequired field
percentageMarket price ยฑ N%pricePercent
fixedOffsetMarket price ยฑ N USDTpriceOffset
levelOffsetOrderbook depth slot NliquidityConfig.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.

POST /api/liquidity/reprice ๐Ÿ”‘ Auth required

Request Body

FieldTypeRequiredDescription
pairIdstringrequiredThe pair ID used when placing orders
newBidPricenumberrequiredNew absolute buy price (USDT). Must be < newAskPrice.
newAskPricenumberrequiredNew absolute sell price (USDT). Must be > newBidPrice.

Response

{
  "success": true,
  "buyOrderId": "new-buy-order-uuid",
  "sellOrderId": "new-sell-order-uuid",
  "newBidPrice": 68000,
  "newAskPrice": 72000
}
โš ๏ธ
After a successful reprice, the old order IDs are no longer valid. Save the new 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.

DELETE /api/trading/orders/:orderId ๐Ÿ”‘ Auth required
curl -s -X DELETE http://HOST:3001/api/trading/orders/$BUY_ORDER_ID \
  -H "Authorization: Bearer $TOKEN"

Pair Lifecycle

EventBuy sideSell side
Normal placementpendingpending
Buy side filledfilledcancelled (auto)
Sell side filledcancelled (auto)filled
TTL expiredcancelledcancelled
Manual cancelcancelledcancelled (auto)
Reprice calledcancelled โ†’ new ordercancelled โ†’ new order
๐Ÿ’ก
If a pair is filled before your reprice call arrives, the reprice will return a 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

pairId management

Margin management

Error handling


Error Reference

HTTPError messageCause
400pairId, newBidPrice, newAskPrice ๅฟ…ๅกซMissing required fields
400newBidPrice ๅฟ…้ ˆๅฐๆ–ผ newAskPriceInverted spread
400ๆ‰พไธๅˆฐ pairId=โ€ฆ ็š„ pending ๆตๅ‹•ๆ€ง่จ‚ๅ–ฎPair already filled, cancelled, or invalid pairId
400pairId=โ€ฆ ็š„่จ‚ๅ–ฎไธๅฎŒๆ•ดOnly one side found (should not happen in normal flow)
400ๅฏ็”จไฟ่ฏ้‡‘ไธ่ถณInsufficient margin for new orders after reprice
401Sessionๆ— ๆ•ˆๆˆ–ๅทฒ่ฟ‡ๆœŸToken expired or revoked โ€” re-authenticate
500LiquidityOrderManager ๆœชๅˆๅง‹ๅŒ–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.

POST /api/broker/price-feed

Request Body

symbolstringSymbol code (e.g. XAUUSD)
pricenumberLast 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);
Throttling: You can push at any frequency. The platform automatically throttles WebSocket broadcast to 1 price update/sec per symbol and order matching checks to once every 5 sec. K-line candles are updated on every tick in memory.

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).

POST /api/broker/symbols

Request Body

symbolstringSymbol code, e.g. XAUUSD
displayNamestringDisplay name, e.g. ้ปƒ้‡‘/็พŽๅ…ƒ
pricePrecisionnumberDecimal places for price (default: 2)
qtyPrecisionnumberDecimal places for quantity (default: 4)
minOrderSizenumberMinimum 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
  }'