# Place one order across many MT5 accounts

URL: https://docs.fxapis.com/guides/multi-account-orders

> Send the same MetaTrader 5 trade to many accounts in one API call — up to 500 on Enterprise (Starter 10, Scale 100): per-account volumes, barrier policies for…



A multi-account order places **one trade on many MetaTrader 5 accounts at the
same moment** — a signal service entering for every subscriber, a fund manager
trading a set of managed accounts, a copier fanning out a master's trade. You
make one request; every account gets its own order, sent together.

In the API these are called **execution waves** (`/v1/execution-waves`).

```
POST /v1/execution-waves            one trade, many accounts → 201 at once
GET  /v1/execution-waves/{id}       poll until state = settled
GET  /v1/orders/{orderId}           any one account's order, in full
```

<Callout type="warn" title="Test with demo accounts">
  Every key reaches real brokers — `fx_test_` is only a label. Build and test with broker demo
  accounts first.
</Callout>

## The request [#the-request]

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    curl -sS -X POST "https://api.fxapis.com/v1/execution-waves" \
      -H "Authorization: Bearer $FXAPIS_KEY" \
      -H "Idempotency-Key: wave:signal_9931" \
      -H "Content-Type: application/json" \
      -d '{
        "accountIds": [
          "0d7c1f5e-4b6a-4c1e-9f0a-2d9e8c7b6a51",
          "5b2e8a10-93c4-4f7d-8e61-0a9c2b4d6e13",
          "c41d7e22-6f0b-4a95-b3e8-7d1f5a2c9b80"
        ],
        "symbol": "EURUSD",
        "side": "buy",
        "volume": "0.01",
        "weights": { "5b2e8a10-93c4-4f7d-8e61-0a9c2b4d6e13": "0.05" },
        "stopLoss": "1.12900",
        "takeProfit": "1.14200",
        "barrierPolicy": "release-ready",
        "label": "London open — EURUSD long",
        "clientWaveId": "signal_9931"
      }'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    wave = session.post(f"{API}/v1/execution-waves",
        headers={"Idempotency-Key": f"wave:signal_{signal.id}"},
        json={
            "accountIds": [m.fxapis_account_id for m in members],
            "symbol": "EURUSD",
            "side": "buy",
            "volume": "0.01",
            "weights": {m.fxapis_account_id: m.lot_size for m in members if m.lot_size != "0.01"},
            "stopLoss": "1.12900",
            "takeProfit": "1.14200",
            "barrierPolicy": "release-ready",
            "clientWaveId": f"signal_{signal.id}",
        },
    ).json()["data"]
    ```
  </CodeBlockTab>
</CodeBlockTabs>

| Field                              | Meaning                                                                                                                                                                                                                                                        |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accountIds`                       | 1 to 500 account ids — up to your plan's accounts per multi-account order: Starter 10, Scale 100, Enterprise 500. An id listed twice is traded once. Every id must belong to your workspace, or the whole request is refused with **404** `ACCOUNT_NOT_FOUND`. |
| `symbol`, `side`                   | The trade, as for a single order. Every account must know the symbol by that name.                                                                                                                                                                             |
| `volume`                           | Lots **per account**, as a string.                                                                                                                                                                                                                             |
| `weights`                          | Optional. Per-account volume in lots, keyed by account id, overriding `volume` for those accounts.                                                                                                                                                             |
| `stopLoss`, `takeProfit`           | Optional absolute prices, applied to every account's order.                                                                                                                                                                                                    |
| `barrierPolicy`                    | What to do about accounts that are not online in time. Default `release-ready`.                                                                                                                                                                                |
| `executeAt`                        | Optional RFC 3339 time at which to send. Omit it to send as soon as every account is online — waiting up to 60 seconds for accounts still logging in.                                                                                                          |
| `expiresAt`                        | Optional time after which to stop waiting for accounts.                                                                                                                                                                                                        |
| `label`, `clientWaveId`, `comment` | Your own references; `comment` goes on each MT5 order.                                                                                                                                                                                                         |

The response is **201** straight away, with the order in state `planned` and
one entry per account in `legs`. The work happens in the background: poll
`GET /v1/execution-waves/{id}`. Reference:
[Place one trade on many accounts](/api-reference/waves/place-one-trade-on-many-accounts).

Multi-account orders are a plan feature; a plan without them answers **402**
`FEATURE_NOT_IN_PLAN`, and a request larger than the plan allows answers
**402** `QUOTA_EXCEEDED` before anything is created.

## Sizing with weights [#sizing-with-weights]

`weights` values are **volumes, not multipliers**. With `"volume": "0.01"` and
`"weights": {"B": "0.05"}`, account B trades 0.05 lots and every other account
trades 0.01.

A common way to size by account balance is to compute each account's lots on
your side and send them all as weights:

```python
from decimal import Decimal, ROUND_DOWN

def lots_for(balance: Decimal, reference_balance: Decimal, reference_lots: Decimal) -> str:
    raw = reference_lots * balance / reference_balance
    lots = raw.quantize(Decimal("0.01"), rounding=ROUND_DOWN)   # the symbol's volume step
    return str(max(lots, Decimal("0.01")))                      # at least the minimum
```

Use `Decimal` (or your language's decimal type) and send strings. Round **down**
to the symbol's volume step — rounding up quietly increases every account's
risk. A weight that is not a usable volume is refused with **400**
`INVALID_REQUEST` naming the account.

## Bring the accounts online first [#bring-the-accounts-online-first]

Each account's order can only be sent while that account is online, and a
broker login takes about ten seconds in our tests. A multi-account order brings offline
accounts online itself: with no `executeAt`, it waits for them — up to 60
seconds — and sends to everyone together as soon as they are all ready (or as
soon as the only ones left need a person, such as a wrong password). After the
minute, the barrier policy decides about the stragglers: skipped
(`release-ready`), waited for until `expiresAt` (`wait`), or the whole order
held (`all-or-nothing`).

When the moment matters — a signal whose price will not wait a minute — bring
the accounts online **before** you create the order, and wait until they are
`ready`:

```python
session.post(f"{API}/v1/accounts/prepare", json={"accountIds": account_ids})   # up to 200 per call
# poll GET /v1/accounts/{id}/status until "ready" for each, as in the language guides
```

Then create the multi-account order with `executeAt` at the moment you want to
enter, or without it to send as soon as every account is ready. See
[Signals → prepare](/signals#2-when-a-member-opens-a-signal-prepare-their-account). For
accounts that must act at any moment, the `always_on` mode keeps them online
all the time — see [Accounts → Modes](/accounts#modes).

## Barrier policies [#barrier-policies]

The *barrier* is the moment the orders are released. `barrierPolicy` decides
what happens when some accounts are not online at that moment:

| Policy           | Behaviour                                                                       | Use it when                                                                                      |
| ---------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `release-ready`  | Send to the accounts that are online; skip the rest (`skipped`).                | The entry is time-sensitive. A partial entry beats a late one. This is the default.              |
| `all-or-nothing` | Send to none unless every account is online.                                    | The strategy only makes sense across the whole set — a hedge split across accounts, for example. |
| `wait`           | Keep waiting past `executeAt` until every account is online, up to `expiresAt`. | Coverage matters more than the price.                                                            |

`all-or-nothing` is about **sending**, not about the result. A broker can still
reject one account's order after all were sent — for margin, say — and no API
can make a hundred independent brokers fill as one transaction.

If `expiresAt` passes and the policy still will not send, the order ends
`abandoned` with nothing sent. With `release-ready` or `wait`, reaching
`expiresAt` sends to whichever accounts are ready.

## Following it to the end [#following-it-to-the-end]

```python
import time

def follow(wave_id, timeout=300):
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        wave = session.get(f"{API}/v1/execution-waves/{wave_id}").json()["data"]
        if wave["state"] in ("settled", "cancelled", "abandoned"):
            return wave
        time.sleep(1)
    raise TimeoutError(wave_id)

wave = follow(wave["id"])
print(wave["state"], wave["summary"], f"spread {wave['dispatchSpreadMs']} ms")
for leg in wave["legs"]:
    print(leg["accountId"], leg["state"], leg["volume"], leg["orderId"], leg["stateDetail"])
```

The order moves `planned → preparing → armed → releasing → settled`: accepted,
bringing accounts online, waiting for the moment, sending, and every account has
a result. `cancelled` and `abandoned` mean nothing was sent.

```json
{
  "state": "settled",
  "dispatchSpreadMs": 30,
  "summary": { "total": 100, "filled": 97, "rejected": 2, "skipped": 0, "unresolved": 1 }
}
```

A multi-account order **never reports a single success or failure**. Each leg
has its own state:

| Leg state                                     | Meaning                                                                              | What to do                                                             |
| --------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| `filled`                                      | That account's order filled.                                                         | Fetch `GET /v1/orders/{orderId}` for its price and `brokerPositionId`. |
| `rejected`                                    | That broker refused it. `stateDetail` says why.                                      | Usually margin or a closed market on that account. Tell its owner.     |
| `skipped`                                     | Not sent — the account was not online at the barrier, or trading is disabled for it. | Place a single order later if it still makes sense.                    |
| `unresolved`                                  | We do not know yet whether it executed.                                              | **Do not resend.** Poll the order; we confirm it with the broker.      |
| `pending`, `preparing`, `ready`, `dispatched` | Still in progress.                                                                   | Keep polling.                                                          |

Each leg's order is an ordinary order: it appears in `GET /v1/orders`, is
confirmed against the broker like any other, and is closed like any other —
with [`POST /v1/accounts/{id}/positions/{ticket}/close`](/api-reference/trading/close-a-position),
account by account. There is no multi-account close.

## Scheduling with executeAt and expiresAt [#scheduling-with-executeat-and-expiresat]

```json
{
  "executeAt": "2026-10-01T07:00:00Z",
  "expiresAt": "2026-10-01T07:00:30Z",
  "barrierPolicy": "release-ready"
}
```

The orders are sent at `executeAt`, not before — even if every account is
ready early. Bring the accounts online first, as above, so they are ready when
the moment comes. Times are RFC 3339; use UTC. A `warm_on_demand` account goes
offline after 15 idle minutes, so for an `executeAt` further ahead than that,
prepare the accounts shortly before it, or use `always_on` for them.

## Cancelling [#cancelling]

```bash
curl -sS -X POST "https://api.fxapis.com/v1/execution-waves/$WAVE_ID/cancel" \
  -H "Authorization: Bearer $FXAPIS_KEY"
```

Possible only **before anything is sent**: a scheduled order waiting for its
`executeAt`, or one waiting for accounts. Once orders are going out, cancelling
returns **409** `WAVE_ALREADY_RELEASING` — an order that has left for a broker
cannot be recalled, and `cancelled` would claim that nothing was sent.

## Dispatch spread [#dispatch-spread]

`dispatchSpreadMs` is the time between the first account's order being sent
and the last. It is the number to watch: as it grows, your accounts are getting
different prices. Every multi-account order reports its own; these are from our
benchmark against an instant broker, so a real broker adds its own latency on top:

| Accounts | Spread |
| -------- | ------ |
| 10       | 17 ms  |
| 50       | 26 ms  |
| 100      | 30 ms  |

Measured on our side against a broker answering instantly. Each broker adds its
own latency on top — about 100 ms for a round trip to a London broker — and
brokers fill at their own prices, so fills across accounts are close but never
identical.

## Retrying safely [#retrying-safely]

Send an `Idempotency-Key` with every multi-account order. Sending the same key
again returns the order that key created — **200** with `idempotent-replay: true`
— rather than creating a second one, which would be a duplicate trade on every
account at once. Use one key per decision, such as `wave:signal_9931`. See
[Idempotent MT5 orders](/guides/idempotency).

Creating multi-account orders has its own rate limit per workspace — 10 a minute
with a burst of 2 — separate from single orders and reads; a **429**
`RATE_LIMITED` carries `retry-after`. See [Errors](/errors#rate-limits).

## Related [#related]

* [Build an MT5 trade copier](/guides/copy-trading) — multi-account orders as the fan-out.
* [Signals and click-to-trade](/signals) — when each member approves their own trade instead.
* [Trading → Multi-account orders](/trading#multi-account-orders) and the
  [API reference](/api-reference/waves/place-one-trade-on-many-accounts).
