A common scene in any copy-trade pipeline that hangs off /v1/kol/coordination: you see "8 KOLs bought PEPECAT in the last hour" — kol_count: 8, coordination_score: 76, peak_kols: 6 — the signal looks tight. You size in. Twenty minutes later you're down 30% because three of those eight KOLs were already net-selling by the time you saw the cluster fire.
The kol_count field is honest: it counts wallets that bought the token in the window. It does not tell you whether they're still long. Without an exit-aware filter, every coordination cluster on a fading move looks identical to a coordination cluster on a forming move.
The exited_count and holders_count fields on the coordination response — both shipped in the v1.1 release — close that gap. This post walks through what they mean, the math behind "exited," and how to wire them into your entry filter.
What "exited" means here
For each KOL in a coordination cluster, the endpoint pulls every buy and sell on the cluster's token within the query period and computes per-wallet aggregates:
buy_sol = SUM(sol_amount) where action = 'buy' AND wallet IN cluster
sell_sol = SUM(sol_amount) where action = 'sell' AND wallet IN cluster
exited = sell_sol > buy_sol
A KOL is flagged exited: true when their SOL out exceeds their SOL in over the window. That's intentionally aggressive: we don't try to compute whether they took a small profit (selling 20% at +2× is still net long); we ask the binary question "have they sold more SOL out than they put in?" If yes, they are no longer long the position in net flow terms.
Per-cluster, the response then surfaces:
{
"token_symbol": "PEPECAT",
"kol_count": 8,
"exited_count": 3,
"holders_count": 5,
"coordination_score": 64,
"kols": [
{ "name": "@alice", "buy_sol": 3.4, "sell_sol": 0.0, "exited": false },
{ "name": "@bob", "buy_sol": 1.8, "sell_sol": 4.2, "exited": true },
{ "name": "@carol", "buy_sol": 2.1, "sell_sol": 0.0, "exited": false },
...
]
}
The same cluster you'd otherwise look at as kol_count: 8 is really 5 holders, 3 already gone. That's a much more honest count to size against.
Why this is in the coordination score
The composite coordination_score (v1, 0-100) already weights exits as a penalty — conviction = (kol_count - exited_count) / kol_count is one of five components, worth 20 points of the score. So a cluster with 3 of 8 exited (conviction = 0.625) loses ~7-8 points off its score relative to one with 0 exits.
But the raw fields are also exposed so you can apply harder filters on your own. Some patterns:
exited_count === 0 — the strictest filter. Nobody has dropped out yet. The cluster is at peak conviction.
exited_count / kol_count < 0.25 — moderate. Allow some early profit-taking but reject clusters that are already half-distributed.
holders_count >= 5 — minimum five wallets still holding. Adapt to your size; for smaller positions, three holders may be enough.
A worked example: filtered copy-trade entry
The basic pattern is a coordination poll filtered by score AND exit ratio, used as the trigger for an entry signal:
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: process.env.MADEONSOL_API_KEY! });
async function freshConvictionClusters() {
const res = await client.kol.coordination({
period: "1h",
min_kols: 4,
window_minutes: 15,
min_score: 70,
include_majors: false,
});
return res.coordination.filter((c) => {
// No exits at all — peak conviction
if (c.exited_count !== 0) return false;
// At least 4 unique holders right now
if ((c.holders_count ?? 0) < 4) return false;
// Cluster formed within a 15-minute peak window
if (c.peak_kols == null || c.peak_kols < 4) return false;
// Strategy diversity — not just 4 sniper bots in lockstep
if (c.unique_strategies < 2) return false;
return true;
});
}
In English: "give me coordination clusters with score ≥70 where nobody has exited yet, at least 4 KOLs piled in within a 15-minute peak window, and they're not all the same strategy type." That's a tight filter. On a typical day, the result set is single digits — but they're meaningfully cleaner than the unfiltered 70+ score list.
When exited_count saves you
The clearest case is a coordination cluster that fired ~30 minutes ago and is now decaying. Suppose the cluster has these parameters:
{
"token_symbol": "FADER",
"kol_count": 7,
"peak_kols": 5,
"peak_window_start": "2026-05-14T11:02:00Z",
"peak_window_end": "2026-05-14T11:17:00Z",
"first_buy_at": "2026-05-14T10:58:00Z",
"last_buy_at": "2026-05-14T11:18:00Z",
"exited_count": 4,
"holders_count": 3,
"coordination_score": 51
}
Read as: 7 KOLs bought, 5 of them within a 15-minute peak window starting at 11:02. By the time you're seeing this at 11:45, 4 of the 7 have already net-sold. The 5 KOLs that piled in together is real history; the relevant question is whether to buy here, and the answer is "no — peak entrants already exited." The coordination_score of 51 is doing the work of telling you this is below threshold, but the raw exited_count: 4 is the underlying reason.
Versus the same cluster pulled 25 minutes earlier (at 11:20):
{
"token_symbol": "FADER",
"kol_count": 5,
"peak_kols": 5,
"first_buy_at": "2026-05-14T10:58:00Z",
"last_buy_at": "2026-05-14T11:18:00Z",
"exited_count": 0,
"holders_count": 5,
"coordination_score": 79
}
Identical token, identical KOLs that bought, but pulled before the exits started. Score 79, zero exits, that's the entry — if your bot was polling on a fast cadence, it would have caught this and not the 11:45 view.
Stacking with peak_kols and time_to_consensus_sec
The three exit-aware fields work together with two existing timing fields:
time_to_consensus_sec — how long it took for the cluster to form (last_buy_at - first_buy_at). Shorter = tighter coordination. Combined with exited_count = 0, a 90-second time-to-consensus is a strong "all the KOLs piled in within a minute and a half, none have left yet" signal.
peak_kols vs kol_count — when these are close, the cluster formed in one dense burst. When peak_kols is meaningfully smaller than kol_count, the cluster spread out over time (the rest are stragglers, not the core).
A tight filter using all of these:
const tight = res.coordination.filter((c) =>
c.exited_count === 0 &&
(c.time_to_consensus_sec ?? 99999) < 1800 && // formed within 30 minutes
(c.peak_kols ?? 0) >= c.kol_count - 1 && // virtually all entered in the peak window
c.coordination_score >= 75
);
That's effectively: "all KOLs who bought formed a single tight cluster within 30 minutes, none have exited, and the composite score backs it." You can stack an entry-cap band on top of this — the SDK 1.6.0 release brought market-cap filtering everywhere, so you can restrict the same query to, say, clusters that formed under $50K MC.