Copy-trading a Solana KOL with a 30-second entry lag and a 15-minute exit lag is not copy-trading. It's buying from them and selling to them. Most "copy bots" advertise speed they don't actually deliver, and most users never measure the gap between their fills and the wallet they're following.
The /v1/kol/{wallet}/timing endpoint gives you the KOL side of that equation: how long does this specific wallet hold, when in the day is it active, what percentage of positions does it close inside the first hour. Combined with your own fill timestamps, it tells you exactly where you're bleeding.
This post walks through what the endpoint returns, how to compute your own lag against it, and how to use the numbers to decide whether a KOL is worth copying at all.
The endpoint
GET /v1/kol/{wallet}/timing?period=30d
Returns a behavioral timing profile for a single KOL wallet over the past 7 or 30 days. The full (PRO/ULTRA) response looks like this:
{
"kol": { "name": "Mezoteric", "wallet": "7xKX..." },
"timing": {
"tokens_traded": 142,
"positions_closed": 128,
"avg_hold_minutes": 47.3,
"median_hold_minutes": 18.5,
"pct_closed_1h": 0.61,
"pct_closed_6h": 0.84,
"pct_closed_24h": 0.94,
"avg_buy_size_sol": 3.2,
"avg_sell_size_sol": 3.8,
"most_active_hours": [14, 15, 16, 21, 22],
"hour_distribution": { "0": 2, "1": 1, "14": 19, "15": 22, ... }
},
"period": "30d"
}
Each field is a different dimension of how this wallet trades:
avg_hold_minutes / median_hold_minutes — how long between entry and exit on average. Huge divergence (avg 47m, median 18m) means a few long holds are pulling up the mean. For copy-trade sizing, the median is usually more representative.
pct_closed_1h / 6h / 24h — cumulative distribution of hold times. A wallet with pct_closed_1h=0.61 closes 61% of positions inside the first hour. If you're copying with a 15-minute entry lag, you're already behind on two-thirds of their exits.
avg_buy_size_sol / avg_sell_size_sol — position sizing context. If avg_sell > avg_buy, the wallet is typically scaling in then exiting larger, which matters for how you model copy fills.
most_active_hours — UTC hours where the wallet posts the most trades. Use this to schedule polling frequency rather than hitting the API constantly.
hour_distribution — full 24-hour count histogram for heatmap rendering.
What "good" looks like
Different strategies produce very different timing profiles. A few archetypes to calibrate against:
Snipers — median_hold_minutes under 10, pct_closed_1h above 0.75, trades concentrated in a 4–6 hour daily window. Very hard to copy profitably unless your infrastructure is gRPC-tier and your TX sending is sub-500ms.
Swing traders — median_hold_minutes 60–240, pct_closed_6h 0.6–0.8, trades spread across more hours. The copy-trade sweet spot: their entries decay slowly enough that a 30-second lag still captures most of the move.
Bag holders — median_hold_minutes over 1440 (24h+), pct_closed_24h below 0.5. These are the easiest to copy from a latency standpoint but the hardest to profit from — their edge is usually conviction on small, slow-moving bets rather than momentum.
Bots you shouldn't copy — median_hold_minutes under 2 and pct_closed_1h above 0.95. Not a human. Infrastructure alpha, not wallet alpha. Copying these is a losing bet unless you have matching infra.
Before copy-trading any wallet, pull its timing profile and ask: is the exit window slow enough that my entry lag fits inside it?
Calculating your actual copy-trade lag
The timing endpoint tells you how the KOL trades. To measure your lag, you join it against your own fills.
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: process.env.MADEONSOL_API_KEY! });
interface MyFill {
wallet_followed: string;
token_mint: string;
side: "buy" | "sell";
filled_at: string; // ISO timestamp
}
async function measureLag(kolWallet: string, myFills: MyFill[]) {
// 1. Get the KOL's trades for the same mints
const trades = await client.wallet.trades(kolWallet, { limit: 500 });
const kolTradesByMint = new Map<string, Array<{ action: string; traded_at: string }>>();
for (const t of trades.trades) {
const arr = kolTradesByMint.get(t.token_mint) ?? [];
arr.push({ action: t.action, traded_at: t.traded_at });
kolTradesByMint.set(t.token_mint, arr);
}
// 2. For each of my fills, find the matching KOL trade and compute delta
const lags: Array<{ side: string; lag_sec: number; mint: string }> = [];
for (const mine of myFills.filter((f) => f.wallet_followed === kolWallet)) {
const kolTrades = kolTradesByMint.get(mine.token_mint) ?? [];
const match = kolTrades
.filter((k) => k.action === mine.side)
.map((k) => ({ t: new Date(k.traded_at).getTime(), ...k }))
.sort((a, b) => a.t - b.t)[0];
if (!match) continue;
const lag = (new Date(mine.filled_at).getTime() - match.t) / 1000;
lags.push({ side: mine.side, lag_sec: lag, mint: mine.token_mint });
}
// 3. Summary
const buys = lags.filter((l) => l.side === "buy").map((l) => l.lag_sec);
const sells = lags.filter((l) => l.side === "sell").map((l) => l.lag_sec);
const median = (xs: number[]) =>
xs.sort((a, b) => a - b)[Math.floor(xs.length / 2)] ?? 0;
console.log(`Median buy lag: ${median(buys).toFixed(1)}s over ${buys.length} fills`);
console.log(`Median sell lag: ${median(sells).toFixed(1)}s over ${sells.length} fills`);
// 4. Context from the timing endpoint
const { timing } = await client.kol.timing(kolWallet, { period: "30d" });
const exitWindow = (timing.median_hold_minutes ?? 0) * 60;
console.log(
`KOL median hold: ${timing.median_hold_minutes}m. Your sell lag eats ${(
(median(sells) / exitWindow) * 100
).toFixed(1)}% of their typical hold window.`
);
}
The last line is the important one. If a KOL's median hold is 18 minutes (1080 seconds) and your median sell lag is 240 seconds, you're absorbing 22% of their hold window as pure latency. On a fast-moving memecoin that can flip a +40% exit into a +10% one.
Using timing data to gate which KOLs you copy
Timing profile is the single cheapest filter for building a copy-trade whitelist. A simple rule that works in practice:
- Require
median_hold_minutes >= 15. Anything faster and infra lag eats your edge.
- Require
pct_closed_24h >= 0.70. You want wallets that actually exit, not bagholders.