> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Copy Trading

> Lead traders publish a track record; followers mirror their perp fills into a dedicated copy account.

Copy trading lets a follower mirror a lead trader's perpetual futures fills into their own margin account. The follower allocates collateral to a per-leader **copy account**, and every fill the leader makes is mirrored into it in the same sequencer step, as market orders placed under the follower's own account. Leaders earn a share of their followers' realized profit.

## Surface Map

| Surface | Use |
| - | - |
| REST | Public: `GET /api/v1/copy-trading/leaders`, `GET /api/v1/copy-trading/leaders/{leader}`, `GET /api/v1/copy-trading/leaders/{leader}/closed-trades`. Authenticated: `PUT` and `GET /api/v1/copy-trading/leaders/me`, `GET /api/v1/copy-trading/leaders/me/followers`, `GET /api/v1/copy-trading/leaders/{leader}/positions`, `POST` and `GET /api/v1/copy-trading/follows`, `GET /api/v1/copy-trading/follows/{followId}`, `GET /api/v1/copy-trading/follows/{followId}/leader-positions`, `POST /api/v1/copy-trading/follows/preview`, `POST /api/v1/copy-trading/follows/{followId}/stop`, `POST /api/v1/copy-trading/follows/{followId}/position-margin`, `POST /api/v1/copy-trading/follows/{followId}/close-positions`, `POST /api/v1/copy-trading/follows/close-positions` |
| TypeScript SDK | `sdk.copyTrading.*` — see [Copy trading (SDK)](/sdk/typescript/copy-trading) |
| React SDK | `useCopyTrading()`, `useCopyEvents()` — see [useCopyTrading](/sdk/react/hooks/use-copy-trading) |
| WebSocket | The owner-private [`copy` channel](/reference/websockets#copy): follow state changes and skipped mirrors, for the follower |
| gRPC | [`CopyTradingService`](/grpc/protos/copy-trading) |
| MCP | `list_lead_traders`, `get_lead_trader`, `upsert_follow`, `stop_follow`, `add_follow_position_margin` and the other copy trading tools — see [MCP server](/sdk/mcp-server) |

## Concepts

**Lead trader.** A wallet that has registered a lead profile. Leaders are addressed everywhere by their **custom TraderCode handle** (see [TraderCodes](/developers/tradercodes)), and one wallet holds at most one lead profile, so a handle names exactly one leader. Every other user appears by public wallet address plus a display name, never by an internal id. A leader's profile carries a status: `ACTIVE` accepts new follows, `PAUSED` keeps existing follows mirroring but accepts no new ones, and `SUSPENDED` (set by the platform) hides the leader from public reads.

**Follow and copy account.** A follow is one follower's relationship with one leader. Creating it moves the allocation from the follower's margin account into a new **copy account**: a cross risk bucket in the follower's margin account that holds only this follow's collateral and positions. The follow's `copyRiskBucketId` names it, and the account listings label its rows with `copyFollowId` and `copyLeaderHandle`. Isolated copies open isolated risk buckets tied to the same follow.

**Sizing, leverage and margin mode.** Each follow chooses:

* `sizingMode`: `PROPORTIONAL` (default) scales each copy by the copy account's equity over the leader's margin account equity; `FIXED_MARGIN` commits `sizingParam` quote units of margin per leader order.
* `leverageMode`: `FOLLOW` (default) uses the leader's leverage; `FIXED` uses `fixedLeverage` for every copy.
* `marginModePolicy`: `FOLLOW` (default) copies into the leader's margin mode per market; `CROSS` or `ISOLATED` forces one.
* `tradingPairIds`: the markets to copy; empty copies every market the leader trades.

**Skips and close-only.** A leader fill that cannot be mirrored is **skipped** with a reason: `INSUFFICIENT_MARGIN`, `BELOW_MIN`, `LEVERAGE_MISMATCH`, `MARKET_STATE`, `LEADER_EQUITY`, `NO_LIQUIDITY` or `MARGIN_MODE_CONFLICT` (the copy account holds the market in its other margin mode; the skip's `detail` names that mode). Only `INSUFFICIENT_MARGIN` counts toward the follow's `consecutiveSkips` streak. A follow in `CLOSE_ONLY` mirrors reductions only. A copy that would be sized into a `CROSS` risk bucket on an isolated-only market is skipped with `MARKET_STATE` and does not count toward the `consecutiveSkips` streak.

**Stop-loss and stopping.** `slPct` sets a copy stop-loss as a fraction of the allocation, in `(0, 1]`. A follow stops when the follower stops it, the stop-loss triggers, the skip limit is reached, the leader is suspended, or an admin stops it; `stopReason` says which.

**Profit share basis.** Each follow tracks `cumNetRealized` (realized PnL net of fees since the follow started) and its high-water mark `hwm`. These are the basis for the leader's profit share. They are **not** the copy account's balance: on a risk bucket row, `realizedPnl` is the risk bucket's cash balance, which also moves on transfers with no trade.

**Copy orders.** Orders placed for a follow carry `origin: "COPY"` and the follow's id as `copyRelationshipId` on their [order events](/sdk/typescript/websockets#order-event-types).

## Flow for followers

### 1. Find a leader

The leaderboard, a leader's profile and their closed trades are public — no session needed.

```typescript theme={null}
const { leaders, nextPageToken } = await sdk.copyTrading.listLeadTraders({ sort: "roi", window: "30d" });
for (const { profile, followSummary, stats } of leaders) {
  console.log(profile.leader.display, stats.roiPct, stats.maxDrawdownPct, followSummary.followerCount);
}

const leader = await sdk.copyTrading.getLeadTrader("alpha_trader");
const { closedTrades } = await sdk.copyTrading.listLeadTraderClosedTrades("alpha_trader", { pageSize: 50 });
```

Lists are cursor paged: pass the previous response's `nextPageToken` (with the same `sort` and `window` for the leaderboard) until it comes back empty. There are no totals.

### 2. Follow

```typescript theme={null}
const { followId, copyRiskBucketId } = await sdk.copyTrading.upsertFollow({
  leader: "alpha_trader",
  allocation: "1000",
  sizingMode: "PROPORTIONAL",
  slPct: "0.25",
});
```

The allocation must be at or above the platform minimum and the leader's `minAllocation`. Delegated agent and sub-account sessions cannot follow. To also copy the leader's already-open positions into the new copy account, set `copyExisting: true` on the create. Each copy is sized like a mirrored fill. `previewFollow` shows what that would open before you commit, as totals only: the estimated margin the copies would reserve and their fees (rounded up to cents), and how many of the leader's positions would and would not be copied. It names no market, side or reason, since you do not follow the leader yet. It is read-only, applies the same terms rules as a follow (so it refuses terms a follow would refuse), is refused while copy trading is off, and spends the [order-risk preview budget](/developers/rate-limits#reads). It sizes at most 64 positions and sets `truncated` when the leader holds more in the copied markets. `copyExisting` applies only to a create: an edit that sets it is refused, and the follow keeps recording whether it copied at creation.

```typescript theme={null}
const { estimatedMargin, estimatedFee, copiedPositions, skippedPositions } = await sdk.copyTrading.previewFollow({ leader: "alpha_trader", allocation: "1000" });
await sdk.copyTrading.upsertFollow({ leader: "alpha_trader", allocation: "1000", copyExisting: true });
```

### 3. Watch the follow

```typescript theme={null}
const unsubscribe = sdk.ws.copy(
  (event) => {
    if (event.eventType === "copy_skip") {
      console.warn(`skipped ${event.data.requestedQuantity} on ${event.data.tradingPairId}: ${event.data.reason}`);
    } else {
      console.log(`follow ${event.data.relationshipId} is ${event.data.status} (${event.data.change})`);
    }
  },
  (follows) => console.log(`${follows.length} live follows`),
);
```

Current followers can also read the leader's open positions with `listLeadTraderOpenPositions(handle)`, or by follow with `listFollowLeaderPositions(followId)`, which takes the leader from the follow and so keeps working after the leader releases their handle. `getFollow(followId)` returns a follow with its most recent skips and `copyAccountEquity`: the copy account's equity at the engine's marks (its collateral plus the unrealized PnL of its positions). That is a balance, not trading PnL, and it is absent when the engine cannot be read.

### Copy positions are the follow's

The positions in a copy account are managed by the follow:

* TP/SL cannot be attached to a copy position (403).
* A manual close, single or close-all, still works, but it goes out immediate-or-cancel, so no order is left resting in the copy account. Panic close (`batchCloseAll` on positions) closes copy positions along with everything else, one position at a time.
* No order, transfer or simulate accepts a strategy key starting with `copy:` (400 `strategy keys starting with copy: are reserved`). Collateral moves into or out of a copy account only through the follow: its allocation, an edit, or a stop. Within the follow, [adding margin](#4-add-margin-to-a-copy-position) moves copy account collateral into an isolated copy position.

### 4. Add margin to a copy position

`addFollowPositionMargin` moves collateral from the follow's copy account into the margin of its isolated copy position on one trading pair. The position's initial margin requirement rises by exactly `amount` at the current mark (its leverage drops to what that amount buys at this price), the copy account's remaining collateral falls by the same, and your margin account is never touched. It lowers the position's leverage and moves its liquidation price away. It opens no exposure, so it works while copy trading is switched off and on a `CLOSE_ONLY` follow.

```typescript theme={null}
const { positionMargin, copyAccountAvailable } = await sdk.copyTrading.addFollowPositionMargin({
  followId,
  tradingPairId,
  amount: "25",
});
```

Over REST this is a signed `POST /api/v1/copy-trading/follows/{followId}/position-margin`. The follow id goes in the path only, and the body carries the rest; the gateway rejects unknown fields.

| Request field | Type | Meaning |
| - | - | - |
| `tradingPairId` | string (UUID), required | Trading pair of the copy position to add margin to |
| `amount` | string, required | Margin to add, in quote units: a positive decimal, taken from the follow's copy account |

| Response field | Type | Meaning |
| - | - | - |
| `followId` | string (UUID) | The follow |
| `tradingPairId` | string (UUID) | Trading pair of the copy position |
| `riskBucketId` | string (UUID) | The copy position's isolated risk bucket |
| `positionId` | string (UUID) | The copy position |
| `positionMargin` | string | The copy position's stored margin after the add, in quote units at the entry price: it rises by `amount × entry price ÷ mark` |
| `copyAccountAvailable` | string | What the copy account can still put into this position after the add, in quote units |

* There is no margin reduction: a follow exits by being stopped.
* Only a master account session can add margin; a delegated agent or sub-account session gets 403. A follow that is not yours is 404.
* A follow that is stopping, or has stopped, is 409.
* Refusals of the add itself are 400 with `code: "COPY_TRADING_REJECTED"` and a message naming the rule: the follow holds no isolated copy position on the pair (a cross copy lives in the copy account's cross risk bucket, which has no per-position margin), the copy position is being liquidated or is in bad debt (a position waiting for liquidation still takes margin: adding it is the way out), or the amount is more than the copy account can put into the position.
* Switching copy trading off does not block it: the switch halts new exposure, and an add opens none.
* It is refused during an operations block (`OPERATIONS_BLOCKED` over REST, `UNAVAILABLE` over gRPC), like every other margin move.
* It spends the per-user movement budget that withdrawals and collateral transfers share: over it, 429 with `details.retryAfter`.
* It is not idempotent. If a call times out, read the follow (or the position) before retrying: a retry whose first attempt landed adds the amount twice.

### 5. Edit or stop

An edit sends `followId` and **replaces** the follow's settings: every optional setting you leave out reverts to its default, so an edit without `slPct` removes the stop-loss. The leader comes from the stored follow, so `leader` can be omitted.

```typescript theme={null}
await sdk.copyTrading.upsertFollow({ followId, allocation: "1500", slPct: "0.2" });
await sdk.copyTrading.stopFollow(followId);
```

Stopping is never refused for the state of the account or the platform. Only ownership applies, a delegated agent session cannot stop a follow, and stops draw the rate limit below.

Follows, edits and a leader's profile writes share a per-account [settings-write budget](/developers/rate-limits#settings-and-copy-trading-writes) (about one write per second, burst 10). Stops have a budget of their own (about one stop per second, burst 20) that edits and closes never spend, so stopping every follow you hold in a row always goes through. Past either budget the answer is 429 with `Retry-After`.

### 6. Close copy positions and keep following

To close a follow's copy positions without stopping it, close them. The follow stays as it is (ACTIVE or CLOSE\_ONLY) and the leader's next trades keep mirroring; `stopFollow` is the close-and-stop path.

```typescript theme={null}
const { closeOrdersPlaced, positionsNotClosed } = await sdk.copyTrading.closeFollowPositions(followId);
const { follows, stoppingFollowIds, failedFollows } = await sdk.copyTrading.closeAllCopyPositions();
```

* Every position in the copy account closes at market, reduce-only and immediate-or-cancel, in one batch per follow. The resting orders and conditional orders (TP/SL) on its copy risk buckets are cancelled; a TWAP cannot run on a copy risk bucket. `closeAllCopyPositions` does this for every live follow of yours, one batch per follow.
* **A close is not a flatten guarantee.** An immediate-or-cancel close on a thin or halted market can leave part of a position open (`positionsNotClosed`), and because the follow keeps mirroring, the leader's next trade opens copy positions again. Stop the follow to end the copying.
* The proceeds stay in the copy account; nothing returns to your margin account until the follow stops.
* A close is never gated by copy trading being switched off or by an operations block, and it charges no order budget. Only a delegated agent session is refused (403). Both close methods share a small per-account rate limit (about one close per second, burst 5); past it the answer is 429 with `Retry-After`.
* The response comes once the batch is durably logged: close orders placed and positions still open. Each close's fills arrive on the order, position and copy channels. `closeAllCopyPositions` lists every live follow whose stop is not in progress, including one with nothing to close (0 and 0).
* A follow that is not yours is a 404. A stopped follow, or one whose stop is in progress (the stop closes its positions itself), is a 409; `closeAllCopyPositions` lists those in `stoppingFollowIds` instead. A close the engine refuses is a 400, and one that cannot run right now a 503; `closeAllCopyPositions` lists such a follow in `failedFollows` with a fixed reason and still closes the others.

## Flow for leaders

### 1. Register

A registration is checked against the platform's eligibility rules — account equity, trading history, closed positions, and a claimed custom TraderCode handle. A refusal names the rule. A wallet with a live follow cannot register.

```typescript theme={null}
const { created, status, shareBps } = await sdk.copyTrading.upsertLeadTrader({ shareBps: 1000, bio: "BTC momentum" });
```

The profile is replaced as sent: an omitted `bio` or `minAllocation` is cleared and an omitted `status` means `ACTIVE`; an omitted `shareBps` keeps the current share (the platform default on a registration).

### 2. Pause and review followers

```typescript theme={null}
await sdk.copyTrading.upsertLeadTrader({ shareBps: 1000, bio: "BTC momentum", status: "PAUSED" });
const { followers } = await sdk.copyTrading.listMyFollowers({ pageSize: 100 });
const me = await sdk.copyTrading.getMyLeadTrader();
```

## Errors

| Status | When |
| - | - |
| 400 | Invalid request, or refused by a rule (eligibility, allocation minimum, a paused leader, a refused copy position margin add, an allocation with more than 6 decimals: `allocation exceeds supported precision for USDC (6 decimals)`); the message names the rule |
| 401 | An authenticated call without a session |
| 403 | A delegated agent or sub-account session registering, following or adding margin to a copy position; a non-follower reading a leader's open positions; a stopped follow's leader positions; TP/SL on a copy position |
| 404 | An unknown or suspended leader; a follow that is not yours |
| 409 | Copy trading is not available; closing the positions of a stopped follow or of one whose stop is in progress; adding margin to a follow that is stopping or stopped |
| 429 | Too many copy position closes, follow stops, or settings writes (registering, following, editing), or a margin add over the movement budget; retry after the interval the response gives |
| 503 | The matching engine is unavailable, or a close cannot run right now; retry |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.