# Build a trade copier

> Copy trades from one account to many, with idempotency keys so a retry never fills twice, and handle the ones that can't be confirmed.

Source: https://tickwire.io/docs/guides/copier/

Copiers fail in two ways: copying a trade twice, or silently not copying it. Here's how to avoid
both.

> Copy only into accounts whose owners have authorised it. Copying into someone else's account is
> banned by our [acceptable use policy](https://tickwire.io/legal/acceptable-use/) and by most prop firms.

## Listen to the source

Subscribe to the source account's stream and act on position changes:

```js
if (f.type === "positionUpdated" && isNew(f.position)) fanOut(f.position);
if (f.type === "positionRemoved") closeCopies(f.positionId);
```

## Fan out with deterministic keys

Derive the idempotency key from the source position and the follower. A crashed and restarted
worker then produces the same key and can't double-copy.

```python
import requests

def copy_open(follower, src):
    return requests.post(
        f"{TW}/users/current/accounts/{follower}/trade",
        headers={
            "auth-token": KEY,
            "Idempotency-Key": f"open-{src['id']}-{follower}",
            "X-Actor": "copier",
            "X-Reason": f"copy of {src['id']}",
        },
        json={
            "actionType": "ORDER_TYPE_BUY" if src["type"] == "POSITION_TYPE_BUY" else "ORDER_TYPE_SELL",
            "symbol": map_symbol(follower, src["symbol"]),
            "volume": size_for(follower, src),
            "clientId": f"cp_{src['id']}",
        },
        timeout=30,
    ).json()
```

`clientId` is stored with the order, so you can find each copy later by the source id.

## Symbols differ per broker

`EURUSD` on one broker is `EURUSDm` or `EURUSD.r` on another. Build a map per follower from
`GET …/symbols`, and check `symbols/{symbol}/specification` for `minVolume` and `volumeStep` before
sizing. Cent accounts need very different volumes.

## Many followers in one call

`POST /v1/orders` sends up to 20 legs across accounts in one idempotent request and returns a
status per leg.

## Handle "unknown"

If a leg comes back `unknown`, don't send a new order. Retry with the same key, or look for the
`clientId` in the follower's `positions`. See [Idempotency](https://tickwire.io/docs/idempotency/).

## Skip followers you can't reach

Check health before copying. If a follower is `disconnected`, record the missed copy and decide
deliberately (catch up at market, or skip) when it comes back, rather than letting requests time
out.
