Building a Solana Portfolio Alert System: Price Drops, KOL Exits, and Recovery Signals (2026)
Combine price dip webhooks, KOL coordination WebSocket streams, and wallet tracker events into a unified Solana alert system. TypeScript code for multi-signal trading decisions — sell when smart money exits, buy when they accumulate.
MadeOnSol·· 12 min read
Disclosure: This article contains affiliate links. If you sign up through them, MadeOnSol may earn a commission at no extra cost to you. This never affects our rankings, ratings, or reviews.
Building a product on Solana data?
Skip the Geyser pipeline — embed KOL flow, deployer reputation, and the all-DEX firehose over REST, WebSocket, or webhooks. The Business tier (€400/mo) is the self-serve embed license at 500k calls/day — Enterprise adds white-label & redistribution.
This is where the streams converge into decisions:
function evaluateSignals(mint: string) {
const ts = getState(mint);
const now = Date.now();
const fiveMinAgo = now - 5 * 60 * 1000;
const recentDip = ts.lastPriceDrop && ts.lastPriceDrop.at.getTime() > fiveMinAgo;
const kolExiting = ts.kolActivity && ts.kolActivity.exited >= 3 &&
ts.kolActivity.at.getTime() > fiveMinAgo;
const kolAccumulating = ts.kolActivity && ts.kolActivity.entered >= 3 &&
ts.kolActivity.exited === 0 && ts.kolActivity.at.getTime() > fiveMinAgo;
// STRONG SELL: price dropping AND KOLs are exiting
if (recentDip && kolExiting) {
triggerAction("SELL", mint, `Price down ${ts.lastPriceDrop!.dropPct}% + ${ts.kolActivity!.exited} KOLs exited`);
return;
}
// BUY THE DIP: price dropping BUT KOLs are still accumulating
if (recentDip && kolAccumulating) {
triggerAction("BUY", mint, `Price down ${ts.lastPriceDrop!.dropPct}% but ${ts.kolActivity!.entered} KOLs buying`);
return;
}
// WATCH: price dropping, no KOL signal yet
if (recentDip) {
triggerAction("WATCH", mint, `Price down ${ts.lastPriceDrop!.dropPct}% — waiting for KOL signal`);
}
}
The key insight is temporal overlap. A price dip alone is ambiguous. A price dip combined with three KOL exits in the same five-minute window is a much stronger sell signal. A price dip with KOLs actively buying is the opposite — it suggests smart money views the dip as an opportunity.
The five-minute window (fiveMinAgo) is doing the temporal-overlap work here. Each signal carries an at timestamp, and the evaluation only counts events that landed inside the window. This is what prevents the system from acting on a stale combination — a KOL exit from twenty minutes ago should not be paired with a fresh price dip as if they happened together. Tune the window to the timescale you trade on: shorter windows demand tighter coordination and fire less often; longer windows catch slower-developing setups but admit more loosely related events. The thresholds (exited >= 3, entered >= 3) play the same role on the count axis — they are the minimum number of corroborating KOL moves you require before a price dip becomes actionable. Raise them to cut noise, lower them to react earlier at the cost of more false positives.
Note the ordering of the checks: the strongest, most specific combinations are evaluated first and return early, so a clear SELL or BUY signal short-circuits before the system falls through to the weaker WATCH case. If you add more rules, keep them ordered from most to least specific for the same reason.
Recovery Signals
When the price bounces back (via the price_alert:recovery webhook) and new KOL entries appear on the coordination stream simultaneously, that is a re-entry signal. The recovery handler checks for this:
function handleRecovery(mint: string, payload: any) {
const ts = getState(mint);
const recentKolEntry = ts.kolActivity && ts.kolActivity.entered >= 2 &&
ts.kolActivity.at.getTime() > Date.now() - 10 * 60 * 1000;
if (recentKolEntry) {
triggerAction("RE-ENTER", mint,
`Recovery from $${payload.dip_low_mc_usd} + ${ts.kolActivity!.entered} new KOL entries`);
}
}
Tools mentioned
Compare features and read reviews.
Live health scores, average ratings, and direct links on MadeOnSol.
MadeOnSol isn't an RPC or another generic token API — it's Solana memecoin intelligence that's painful to build in-house, pulled from dual-region gRPC shred streams: sub-second from the on-chain event to your app.
Signals you'd otherwise build
KOL & smart-money flow from 1,000+ labeled wallets, deployer reputation, coordination clusters, linked-wallet entity resolution, and an all-DEX firehose.
Embed it in your product
The Business tier (€400/mo, self-serve) licenses you to display MadeOnSol data inside your own product — 500k calls/day, 10 WS + 5 firehose connections. Enterprise above adds white-label & redistribution rights and custom endpoints.
Evaluate first, commit later
Test everything on a free ULTRA key — no commitment. If it fits, we scope volume or white-label pricing.
A single data point rarely tells the full story on Solana memecoins. A 30% price drop could mean a healthy correction before continuation, a whale dumping to reload lower, or the start of a terminal decline. The difference between these scenarios often shows up in other signals — what KOLs are doing, whether smart wallets are exiting or accumulating, and whether the drop triggers recovery patterns.
A proper alert system combines multiple signal types and evaluates them together. This guide builds one using three MadeOnSol data streams: price alert webhooks, KOL coordination WebSocket events, and wallet tracker notifications. If you're new to the SDK, our overview of building Solana trading tools with the MadeOnSol SDK covers the setup these examples assume.
Why Combine Signals Instead of Using One
It is tempting to build an alert system around a single trigger — a price drop, a KOL sell, a whale exit — because each one is simple to wire up on its own. The problem is that each signal in isolation has a high false-positive rate on memecoins. Price moves constantly, KOLs rotate in and out of positions for reasons that have nothing to do with the token's trajectory, and individual wallets rebalance. Acting on any one of them by itself means reacting to noise most of the time.
The approach in this guide treats signals as evidence rather than commands. No single event triggers an action directly. Instead, each stream writes into a shared per-token state object, and a separate evaluation step looks at the combination of recent events to decide whether the evidence is strong enough to act. A price dip with three KOLs exiting in the same window is a different event than a price dip on its own, even though the price data is identical. The whole point of running three streams is to let them confirm or contradict each other.
This also keeps the system honest about latency. Price alerts and the two WebSocket channels each push events the moment they happen, so the state object reflects what is true right now rather than what was true at your last poll. The cost is that you maintain three live connections and reason about events that arrive out of order — which is what the architecture and reconnect logic below are designed to absorb. If you only ever want one of these signals, you do not need this architecture; a single webhook receiver (covered in the dip-buying webhook guide) is simpler and sufficient.
The Three Signal Types
1. Price Dip Alerts
Price alerts fire when a token's market capdrops below a threshold you define. You create them via the API with a drop_pct parameter, and they deliver via webhook or WebSocket. Each alert captures the token's current MC as a baseline and watches for deviations.
Useful for: detecting rapid price movements that require immediate decisions.
2. KOL Coordination Events
The /ws/kol-coordination WebSocket channel streams real-time coordination signals — when multiple tracked KOLs are buying or selling the same token within a short window. The exited_count field tells you how many KOLs have sold, and entered_count tells you how many have bought.
Useful for: understanding whether smart money is bailing or loading up, independent of price action.
3. Wallet Tracker Events
The /ws/wallet-tracker channel lets you track specific wallets (your own, known whales, deployer wallets) and receive push notifications when they execute trades. Each event includes the token, direction (buy/sell), and size. For the full REST and webhook surface behind this stream, see our guide on how to monitor Solana wallet activity with the Wallet Tracker API.
Useful for: tracking specific addresses you trust or fear, and detecting portfolio-relevant moves in real time.
Architecture
The system runs three concurrent connections and funnels all events into a single handler that evaluates the combined signal state:
All three streams update a shared state object per token. The handler evaluates the combined state on every new event and decides whether to alert, buy, sell, or wait.
The shared state map is the heart of the design. Each stream is a producer that knows nothing about the others; it only updates its slice of the per-token record and then calls evaluateSignals. This decoupling matters because the three streams are independent connections that can drop and reconnect at different times. Keeping each producer narrow — the KOL handler only touches kolActivity, the wallet handler only appends to walletEvents — means a reconnect on one stream never corrupts the state the others depend on. The setTimeout(connect…, 5000) reconnect calls on each close event are deliberate: WebSocket connections to the KOL tracker and wallet streams will drop periodically, and a five-second backoff re-establishes them without hammering the server.
Setting Up the Streams
import MadeOnSol from "madeonsol";
import WebSocket from "ws";
import express from "express";
const client = new MadeOnSol({ apiKey: process.env.MADEONSOL_API_KEY! });
// Shared state per token
interface TokenState {
lastPriceDrop: { dropPct: number; mcUsd: number; at: Date } | null;
kolActivity: { entered: number; exited: number; at: Date } | null;
walletEvents: Array<{ wallet: string; direction: "buy" | "sell"; at: Date }>;
}
const state = new Map<string, TokenState>();
function getState(mint: string): TokenState {
if (!state.has(mint)) {
state.set(mint, { lastPriceDrop: null, kolActivity: null, walletEvents: [] });
}
return state.get(mint)!;
}
Not all combinations are equal. The strongest signals come from:
Price dip + high KOL exit count: reliable sell signal, especially if exited KOLs are S-tier or A-tier rated
Price dip + KOL accumulation: strongest buy-the-dip signal, particularly when the accumulating KOLs have high win rates
Recovery + new KOL entries: solid re-entry signal, but verify the recovery is sustained (wait for the second tick)
Wallet tracker sell + no KOL signal: weak signal alone, since individual wallet moves can be rebalancing rather than conviction exits
The system works best when you feed it tokens you already hold or are watching. Running it across thousands of tokens generates noise. Focus on a curated list of 10-50 positions where combined signals actually inform decisions you are ready to execute.
Common Pitfalls
A few mistakes show up repeatedly when wiring up multi-stream systems like this:
Treating reconnects as no-ops. When a WebSocket drops and reconnects, you start from an empty event history on that channel. Any state the stream held before the drop is gone unless you persisted it. The shared state map above survives reconnects because it lives outside the connection, but be aware that the first events after a reconnect may not reflect activity that happened while you were disconnected.
Letting state grow unbounded. The wallet event array is capped at 50 entries per token for a reason. Without a cap, a busy token accumulates events indefinitely and the map leaks memory. The price and KOL slices overwrite rather than append, so they are bounded by design.
Not skewing for KOL quality. The evaluation treats every KOL exit as equal. In practice an exit from a consistently profitable KOL carries more weight than one from a tracked wallet with a poor record. The signals surface and the underlying KOL ratings let you weight by win rate rather than raw count.
Acting on the first recovery tick. A recovery event can fire on a dead-cat bounce. The re-entry logic deliberately checks for fresh KOL entries alongside the recovery, but you should still confirm the bounce holds before committing size.
How to Extend This
The three-stream skeleton generalizes well. A few directions to take it:
Add a fourth signal. Deployer behavior is a strong contextual filter — if the token's deployer is dumping or has a history of rugs, that should down-weight any buy signal. The Deployer Hunter data slots into the same state-and-evaluate pattern: write deployer events into a new slice of TokenState and reference it inside evaluateSignals.
Persist state across restarts. Right now the state map is in-memory and resets on process restart. Backing it with a small datastore lets the evaluator reason about signals that span a restart.
Score instead of branch. The current logic is a decision tree. For more positions, replace the if-chain with a weighted score per token — each signal contributes points, and you act when the score crosses a threshold. This scales better than enumerating combinations by hand.
Drive execution off the actions.triggerAction currently just labels a decision. Swapping in a Jupiter execution call (with the same HMAC-verified, slippage-aware approach from the dip-buying guide) turns the alert system into an automated trader.
FAQ
Do I need all three streams? No. The architecture degrades gracefully — drop any stream and the evaluator simply never sees that slice of state. The combined signals get weaker, but the system still functions on whatever streams you connect.
Why WebSockets for KOL and wallet data but a webhook for price? Price alerts are discrete, infrequent events tied to a threshold you defined, which fits the push-once-on-trigger webhook model. KOL coordination and wallet activity are continuous streams where you want every event, which fits a persistent WebSocket. You can also receive price alerts over WebSocket if you prefer not to run a public endpoint.
How many tokens can one process handle? The bottleneck is the number of live WebSocket connections and your tier limits, not the evaluation logic, which is cheap. The curated 10-50 position range keeps both the connection count and the signal-to-noise ratio reasonable.
How does this relate to a plain dip-buying bot or whale tracking on its own? If you only need one signal, building a Solana dip-buying bot covers the simpler single-stream price-alert version, and Solana whale alert strategies explains how to interpret the wallet-tracker signal by itself before you combine it here.
This article is for educational and informational purposes only. Nothing in this article constitutes financial, investment, or trading advice. Automated trading carries extreme risk — bots can lose money faster than manual trading. Always test with small amounts and never risk more than you can afford to lose.