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

# Copy-trade signals

> Build a wallet follow list and identify its recent large trades and Polymarket token IDs.

Build a follow list of S- and A-grade wallets, compare their copy scores, and print recent large trades from those wallets. Each matching trade includes the token ID when 0xinsider has it.

Every trade has already executed on Polymarket. Its price is the wallet's fill price, so copying it later may produce a different result. The API provides analytics; your own Polymarket client handles any orders.

You need a [Pro API key](/authentication) and Python with `requests` installed. Run `python -m pip install requests` if needed. The snippets share the variables defined in step 1.

<span id="1-build-the-follow-list" />

## 1. Build a follow list

[Leaderboard](/api-reference/endpoint/get-leaderboard) lists S-, A-, and B-grade wallets, sorted by score. It has no `min_grade` filter, so keep S and A in your code.

```python theme={null}
import os
from datetime import datetime, timedelta, timezone

import requests

BASE = "https://api.0xinsider.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['OXINSIDER_API_KEY']}"}

def get(path, **params):
    response = requests.get(f"{BASE}{path}", params=params, headers=H, timeout=30)
    response.raise_for_status()
    return response.json()


leaders = get("/leaderboard", limit=100)
follow = [t["address"] for t in leaders["data"] if t["grade"] in ("S", "A")]
```

This example reads the first 100 wallets. To include more, follow `next_cursor` with the same filters. If you receive `cursor_expired`, discard that traversal and start again from the first page.

| Field or filter | Meaning |
| - | - |
| `grade` | The wallet's grade from settled P\&L relative to other tracked wallets. See [Grades](/concepts/grades). |
| `realized_pnl` | P\&L on closed positions, including fees and rebates. |
| `pnl` | P\&L including open positions, which changes with market prices. |
| `strategy` | An optional filter for observable activity, such as `high_activity`. See [Wallet trading styles](/concepts/strategy-types). |

<span id="2-score-each-wallet-on-how-easy-it-is-to-copy" />

## 2. Compare copy scores and freshness

[Batch traders](/api-reference/endpoint/batch-get-traders) reads up to 25 wallets per call. Use `expand: ["quant_metrics"]` to request `copy_score` and `smart_score`.

| Field | Meaning |
| - | - |
| `copy_score` | A score from 0 to 100 for how suitable the wallet's history is to copy. It includes penalties for limited history, concentration, sizing, large losses, and inconsistent edge. |
| `smart_score` | The skill score before copy-specific penalties. |
| `quant_metrics` | An optional block. It is omitted when the stored metrics are at least 6 hours old. |
| `data_quality` | Field-group timestamps and availability. The `ranking` group dates the grade and scores. |
| Item `status` | `ok` or `error` for each input. One failed input does not fail the batch. |

A `fresh` ranking group has a timestamp, but you still need to compare it with your own maximum age. The example accepts rankings no older than 6 hours. It skips undated rankings and sorts wallets without a `copy_score` last.

Step 3 watches only wallets accepted by this freshness check.

```python theme={null}
ranked = []
for start in range(0, len(follow), 25):
    response = requests.post(
        f"{BASE}/traders/batch",
        json={"traders": follow[start:start + 25], "expand": ["quant_metrics"]},
        headers=H,
        timeout=30,
    )
    response.raise_for_status()
    for item in response.json()["data"]:
        if item["status"] != "ok":
            print("skip:", item["input"], item["error"]["message"])
            continue
        trader = item["data"]
        ranking = next(
            (group for group in trader["data_quality"]["field_groups"]
             if group["group"] == "ranking"), None
        )
        if ranking is None or ranking["status"] != "fresh" or "as_of" not in ranking:
            print("skip, grade has no usable timestamp:", trader["address"])
            continue
        as_of = datetime.fromisoformat(ranking["as_of"].replace("Z", "+00:00"))
        if datetime.now(timezone.utc) - as_of > timedelta(hours=6):
            print("skip, grade older than 6 hours:", trader["address"], ranking["as_of"])
            continue
        metrics = trader.get("quant_metrics")
        score = metrics.get("copy_score") if metrics is not None else None
        ranked.append((score, trader["address"], trader.get("username"), trader.get("grade")))

ranked.sort(key=lambda row: (row[0] is not None, row[0] if row[0] is not None else 0), reverse=True)
for score, address, username, grade in ranked:
    print(score, grade, username or address[:10])
```

A missing or `null` score means insufficient or unavailable data; it is not a low score. The [quant metrics](/concepts/quant-metrics) page defines the calculation. SDK users can apply their own freshness limit with `assessDataQuality`, `assess_data_quality`, or `AssessDataQuality`.

<span id="3-watch-for-new-trades" />

## 3. Match recent trades against the list

[Large trades](/api-reference/endpoint/get-large-trades) returns recent trades, newest first. It has no wallet filter, so match `trader.address` against your list.

```python theme={null}
follow_set = {address.lower() for _, address, _, _ in ranked}
seen = set()


def new_moves(min_size=10000):
    page = get("/large-trades", min_grade="A", min_size=min_size, limit=100)
    moves = []
    for tr in page["data"]:
        if tr["id"] in seen:
            continue
        seen.add(tr["id"])
        if tr["trader"]["address"].lower() in follow_set:
            moves.append(tr)
    return moves


for tr in new_moves():
    who = tr["trader"].get("username") or tr["trader"]["address"][:10]
    print(
        who, tr["side"], tr["outcome"], f'${tr["size_usd"]:,.0f}',
        "@", tr["price"], tr["market"]["title"], tr["token_id"],
    )
```

| Field or filter | Meaning |
| - | - |
| `min_grade=A` | Keeps S- and A-grade wallets. |
| `min_size` | Sets the minimum USD trade value. The endpoint default is $5,000; this example uses $10,000. |
| `id` | Identifies the trade. Use it for deduplication. |
| `side` | `BUY` or `SELL`. |
| `outcome` | The outcome label. It is `null` for markets with more than 2 outcomes. |
| `token_id` | The outcome's Polymarket CLOB token ID, or `null` when unavailable. Older recorded trades can also lack it. |

This example reads one page and stores deduplication IDs in memory. It can miss trades during a long pause and repeats them after a restart. For a durable consumer, use the event replay recipe in [Alert bot](/recipes/alert-bot).

To read one wallet's historical large trades, use [Large trades history](/api-reference/endpoint/get-large-trades-history) with `trader`.

<span id="4-read-the-entry-and-exit-history" />

## 4. Inspect entries and exits in one market

Take `market.condition_id` from a matching trade and request [Position timeline](/api-reference/endpoint/get-position-timeline).

```bash theme={null}
curl -H "Authorization: Bearer $OXINSIDER_API_KEY" \
  "https://api.0xinsider.com/api/v1/trader/0x204f72f35326db932158cba6adff0b9a1da95e14/position-timeline?condition_id=0x123..."
```

`running_amount` gives the share balance after each stored fill. `running_avg_price` gives the buy-weighted average price; selling does not change it. Both include earlier pages, so you do not need to recompute them while paging.

The timeline excludes other position-changing operations, such as redemptions. It is available only for closely tracked wallets. A `404` with `trader_not_tracked` means the timeline is unavailable; retrying does not create it.

<span id="what-this-does-not-do" />

<span id="related" />

## Limits

* A wallet's historical grade or score does not guarantee a future result.
* The feed contains recorded large trades, not every fill from a followed wallet.
* Trade prices describe executed fills, not quotes available to you.
* The API does not place, cancel, or read orders.
* The polling example does not provide durable delivery. Use [Alert bot](/recipes/alert-bot) when missing an event is unacceptable.

Use [Review a trader's history](/recipes/backtest) before deciding which wallets to follow.


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