( trades: WhaleTrade[], markets: ProcessedMarket[], hoursBack = 6 )
| 48 | } |
| 49 | |
| 50 | export function detectSignals( |
| 51 | trades: WhaleTrade[], |
| 52 | markets: ProcessedMarket[], |
| 53 | hoursBack = 6 |
| 54 | ): SmartSignal[] { |
| 55 | const signals: SmartSignal[] = []; |
| 56 | const now = Date.now(); |
| 57 | const cutoff = now - hoursBack * 3600_000; |
| 58 | const cutoff2h = now - 2 * 3600_000; |
| 59 | const cutoff3h = now - 3 * 3600_000; |
| 60 | |
| 61 | // Filter smart trades within timeframe |
| 62 | const recentTrades = trades.filter( |
| 63 | (t) => t.isSmartWallet && new Date(t.timestamp).getTime() >= cutoff |
| 64 | ); |
| 65 | |
| 66 | if (recentTrades.length === 0) return []; |
| 67 | |
| 68 | // Group trades by slug |
| 69 | const bySlug = new Map<string, WhaleTrade[]>(); |
| 70 | for (const t of recentTrades) { |
| 71 | if (!bySlug.has(t.slug)) bySlug.set(t.slug, []); |
| 72 | bySlug.get(t.slug)!.push(t); |
| 73 | } |
| 74 | |
| 75 | for (const [slug, slugTrades] of bySlug) { |
| 76 | const market = findMarket(slug, markets); |
| 77 | if (!market) continue; |
| 78 | |
| 79 | const marketInfo = { title: market.title, slug: market.slug, prob: market.prob }; |
| 80 | |
| 81 | // 1. Whale Accumulation: same wallet, same market, >=3 buys, total >= $5000 |
| 82 | const byWallet = new Map<string, WhaleTrade[]>(); |
| 83 | for (const t of slugTrades) { |
| 84 | if (!byWallet.has(t.wallet)) byWallet.set(t.wallet, []); |
| 85 | byWallet.get(t.wallet)!.push(t); |
| 86 | } |
| 87 | |
| 88 | for (const [wallet, walletTrades] of byWallet) { |
| 89 | const buys = walletTrades.filter((t) => t.side === "BUY"); |
| 90 | const sells = walletTrades.filter((t) => t.side === "SELL"); |
| 91 | |
| 92 | const buyVol = buys.reduce((s, t) => s + t.usdcSize, 0); |
| 93 | const sellVol = sells.reduce((s, t) => s + t.usdcSize, 0); |
| 94 | |
| 95 | if (buys.length >= 3 && buyVol >= 5000) { |
| 96 | const strength = rateStrength(buys.length, buyVol); |
| 97 | signals.push({ |
| 98 | id: makeId("whale_accumulation", slug, now), |
| 99 | type: "whale_accumulation", |
| 100 | strength, |
| 101 | market: marketInfo, |
| 102 | wallets: [{ address: wallet, username: walletTrades[0].username ?? null }], |
| 103 | direction: "bullish", |
| 104 | summary: `Whale accumulating ${market.title} — ${buys.length} buys totaling $${(buyVol / 1000).toFixed(1)}k`, |
| 105 | timestamp: Math.max(...buys.map((t) => new Date(t.timestamp).getTime())), |
| 106 | details: { totalVolume: buyVol, tradeCount: buys.length, priceAtSignal: market.prob ?? undefined }, |
| 107 | }); |
no test coverage detected