TICKERALL!
← All posts
August 27, 20265 min read

Trade Many MetaTrader 5 Accounts at Once with the Bulk API

Place, close, modify, cancel, and read across many MetaTrader 5 accounts in a single request. Here's how the TickerAll Bulk API fans one call out to your whole roster — with TypeScript and Python examples.

MetaTrader 5REST APITypeScriptPythonTrading

If you run more than one MetaTrader 5 account — a desk of funded accounts, a handful of demos under test, the same strategy spread across brokers — doing everything one account at a time gets old fast. Placing the same order on ten accounts is ten round trips, ten awaits, and ten places for something to go half-wrong. Reading them all for a dashboard is another ten.

The Bulk API collapses that into one call. You hand it a list, it fans the request out to every account you named, and it returns a per-account result plus a summary. Six verbs cover the surface: place, close positions, modify positions, cancel pending, modify pending, and read.

Your code bulk.place([…]) Bulk API one request Acct A Acct B Acct C
One call fans out to every account you name. Each returns its own result — A and B filled, C failed — and the response carries a summary. A partial failure is data, not a thrown exception.

Bulk is a Pro / Enterprise feature. Every /v1/bulk/* call requires a Pro or Enterprise plan; on Free and Trader it returns 403. Read state for your whole roster or fan a trade out to all of it — same key, one request.

The one contract to internalize: partial success

A bulk op runs across many independent broker sessions, so it is not atomic. There is no rollback of a real fill or a real close — if account C's broker rejects the order after A and B already filled, you can't un-fill A and B. So the Bulk API doesn't pretend: every method returns a results array with one entry per account plus a summary, and a per-account failure is data (status: 'failed' with a code / reason), never a thrown exception.

The rule that follows: inspect every result. A call that returns without throwing has not necessarily succeeded everywhere — it has told you, per account, what happened.

Install

npm install @tickerall/sdk    # TypeScript / Node 18+
pip install tickerall         # Python 3.9+

Both wrap the same REST endpoints, so if you'd rather hit the API directly the shapes below map one-to-one onto the JSON.

Place an order on many accounts

Each element is an ordinary place-order body plus the accountId it targets. An account may appear at most once. type defaults to market; a limit / stop order needs a price.

import { Tickerall } from '@tickerall/sdk'

const client = new Tickerall({ apiKey: process.env.TICKERALL_API_KEY! })

const res = await client.bulk.place([
  { accountId: 'acc_8Kd3f2', symbol: 'BTCUSD', side: 'BUY', volume: 0.10, stopLoss: 58000, takeProfit: 72000 },
  { accountId: 'acc_9Lm4Xs', symbol: 'BTCUSD', side: 'BUY', volume: 0.05 },
  { accountId: 'acc_2Rt7Bv', type: 'limit', symbol: 'BTCUSD', side: 'BUY', volume: 0.10, price: 60000 },
])

console.log(res.summary)  // { total: 3, filled: 2, failed: 1 }

for (const r of res.results) {
  if (r.status === 'filled') console.log(r.accountId, 'ticket', r.ticket, '@', r.price)
  else                       console.log(r.accountId, 'FAILED', r.code, r.reason)
}

The same in Python — dict keys are accepted in snake_case or camelCase:

from tickerall import Tickerall

client = Tickerall(api_key="cf_live_...")

res = client.bulk.place([
    {"account_id": "acc_8Kd3f2", "symbol": "BTCUSD", "side": "BUY", "volume": 0.10, "stop_loss": 58000, "take_profit": 72000},
    {"account_id": "acc_9Lm4Xs", "symbol": "BTCUSD", "side": "BUY", "volume": 0.05},
    {"account_id": "acc_2Rt7Bv", "type": "limit", "symbol": "BTCUSD", "side": "BUY", "volume": 0.10, "price": 60000},
])

print(res.summary.total, res.summary.filled, res.summary.failed)   # 3 2 1

for r in res.results:
    if r.status == "filled":
        print(r.account_id, "ticket", r.ticket, "@", r.price)
    else:
        print(r.account_id, "FAILED", r.code, r.reason)

Each results entry carries accountId, status ('filled' or 'failed'), and on a fill a ticket, price, and symbol; on a failure a code and reason. The summary is { total, filled, failed }.

Close positions — explicit list, or per-account intent

Closing takes one of two shapes. Pass exactly one:

  • items — an explicit per-account list, { accountId, ticket, volume? }. Omit volume for a full close; pass a smaller volume to scale out.
  • targets — a per-account intent, { accountId, symbol?, side? }. Each target flattens every open position on that account matching its own symbol / side (all open positions when neither is given).
// Explicit: close these exact positions (the second is a partial scale-out)
await client.bulk.closePositions({
  items: [
    { accountId: 'acc_8Kd3f2', ticket: 4072808150 },
    { accountId: 'acc_9Lm4Xs', ticket: 4072809002, volume: 0.05 },
  ],
})

// Intent: flatten BTC longs on A, everything on B
const res = await client.bulk.closePositions({
  targets: [
    { accountId: 'acc_8Kd3f2', symbol: 'BTCUSD', side: 'BUY' },
    { accountId: 'acc_9Lm4Xs' },
  ],
})
// res.results[] -> { accountId, ticket, status: 'ok' | 'failed', closed, symbol, side, volume, ... }
# Explicit
client.bulk.close_positions(items=[
    {"account_id": "acc_8Kd3f2", "ticket": 4072808150},
    {"account_id": "acc_9Lm4Xs", "ticket": 4072809002, "volume": 0.05},
])

# Intent
res = client.bulk.close_positions(targets=[
    {"account_id": "acc_8Kd3f2", "symbol": "BTCUSD", "side": "BUY"},
    {"account_id": "acc_9Lm4Xs"},
])

The reason targets is per-account and not one global filter is mixed brokers. Symbols aren't portable — the same instrument is BTCUSDm on one broker and BTCUSD on another, EURUSDm vs EURUSD. Because each target carries its own broker-native symbol, you can flatten a roster that spans brokers in one call:

await client.bulk.closePositions({
  targets: [
    { accountId: 'acc_exness', symbol: 'EURUSDm' },   // Exness naming
    { accountId: 'acc_xm',     symbol: 'EURUSD'  },   // XM naming
  ],
})

Modify positions, cancel pending, modify pending

Modifies and pending cancels follow the same per-account-list pattern.

// Set / change SL / TP across accounts (at least one of stopLoss / takeProfit)
await client.bulk.modifyPositions([
  { accountId: 'acc_8Kd3f2', ticket: 4072808150, stopLoss: 60000, takeProfit: 85000 },
  { accountId: 'acc_9Lm4Xs', ticket: 4072809002, takeProfit: 84000 },
])

// Cancel resting pending orders — explicit tickets, or per-account intent
await client.bulk.cancelPending({
  items: [
    { accountId: 'acc_8Kd3f2', ticket: 4072809988 },
    { accountId: 'acc_9Lm4Xs', ticket: 4072809991 },
  ],
})
await client.bulk.cancelPending({
  targets: [{ accountId: 'acc_8Kd3f2', symbol: 'BTCUSD' }, { accountId: 'acc_9Lm4Xs' }],
})

// Change a pending order's trigger price / SL / TP
await client.bulk.modifyPending([
  { accountId: 'acc_8Kd3f2', ticket: 4072809988, price: 60000, stopLoss: 58000 },
])
client.bulk.modify_positions([
    {"account_id": "acc_8Kd3f2", "ticket": 4072808150, "stop_loss": 60000, "take_profit": 85000},
    {"account_id": "acc_9Lm4Xs", "ticket": 4072809002, "take_profit": 84000},
])

client.bulk.cancel_pending(items=[
    {"account_id": "acc_8Kd3f2", "ticket": 4072809988},
    {"account_id": "acc_9Lm4Xs", "ticket": 4072809991},
])
client.bulk.cancel_pending(targets=[
    {"account_id": "acc_8Kd3f2", "symbol": "BTCUSD"},
    {"account_id": "acc_9Lm4Xs"},
])

client.bulk.modify_pending([
    {"account_id": "acc_8Kd3f2", "ticket": 4072809988, "price": 60000, "stop_loss": 58000},
])

Every write verb returns the same BulkWriteResponse: a results array of { accountId, ticket, status, ... } (with closed / cancelled / modified set as the verb dictates) and a summary of { total, ok, failed }.

Read your whole roster in one call

readAccounts is the dashboard verb. Omit ids to read every account on your key, or pass a subset; include trims the payload ('account' financials and/or 'positions'). It's a plain read — Pro-gated, but never demo-gated, and no idempotency key.

const state = await client.bulk.readAccounts({
  include: ['account', 'positions'],   // omit `ids` for the whole roster
})

for (const a of state.accounts) {
  console.log(a.id, a.status, a.hot, a.account ? (a.account as any).balance : '—')
}
console.log(state.summary)  // { total, online, offline }
state = client.bulk.read_accounts(include=["account", "positions"])

for a in state.accounts:
    bal = a.account.get("balance") if a.account else None
    print(a.id, a.status, a.hot, bal)

print(state.summary.total, state.summary.online, state.summary.offline)

Each account comes back as { id, broker, server, accountNumber, isDemo, hot, status, ... }, plus account and positions when you asked for them and the account is online. If you passed ids, any that aren't yours land in not_found.

Retries are safe — one key for the whole fan-out

The write verbs carry a single Idempotency-Key that covers the entire batch, so a retried bulk call is recognized as the same call and won't double-execute. If a transient connectivity blip surfaces as a TickerallServiceUnavailableError, you can retry the same request without fear of placing a second round of orders. (Pass your own idempotencyKey in the options if you want to control the value.)

Before you fan a write out to real money

Bulk multiplies both convenience and blast radius — one call can touch every account you have. Validate your fan-out logic on demo accounts first: confirm the right accounts, the right symbols per broker, and the right sizes, and read the results array back to see exactly what each account did. Once it's boring and predictable, point it wherever you like.

Where to go next

Bulk covers "do the same thing to many accounts." If what you actually want is "whatever the master does, mirror it to the followers," that's Copy Trading — a master account and a set of scaled, risk-clamped followers. And if you're new to the SDK, the TypeScript quickstart walks a single account end to end.

Full API reference: tickerall.com/docs

Was this useful?

Comments

Leave a comment

Comments are public.
Trade Many MetaTrader 5 Accounts at Once with the Bulk API · Ticker All!