import { useState, useEffect, useCallback, useRef } from 'react';
export interface WhaleProfile {
address: string;
label: string | null;
totalTrades: number;
totalVolume: number;
avgTradeSize: number;
largestTrade: number;
winRate: number | null;
topCategory: string | null;
firstSeen: string | null;
lastSeen: string | null;
totalPnl: number | null;
avgBuy: number | null;
sellRate: number | null;
resolvedTrades: number;
}
export interface WhaleProfileTrade {
id: string;
marketId: string;
marketQuestion: string | null;
marketSlug: string | null;
side: string;
outcome: string;
size: number;
price: number;
amount: number;
timestamp: string;
status: 'won' | 'lost' | 'open' | 'resolved';
pnl: number | null; // realized PnL (resolved BUY only), null otherwise
won: boolean | null;
}
export interface WhaleTopMarket {
marketId: string;
marketQuestion: string | null;
trades: number;
volume: number;
}
export interface WhaleEquityPoint {
t: string; // ISO timestamp of the resolved trade
pnl: number; // realized PnL of that trade
cum: number; // running cumulative realized PnL
}
interface WhaleProfileData {
profile: WhaleProfile;
trades: WhaleProfileTrade[];
topMarkets: WhaleTopMarket[];
equity: WhaleEquityPoint[];
}
export function useWhaleProfile(address: string | null, tradeLimit = 50, offset = 0) {
const [data, setData] = useState<WhaleProfileData | null>(null);
const [loading, setLoading] = useState(false);
const [backfilling, setBackfilling] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchProfile = useCallback(async () => {
if (!address) return;
setLoading(true);
setError(null);
try {
const res = await fetch(
`/api/signals/whale/${encodeURIComponent(address)}?limit=${tradeLimit}&offset=${offset}`
);
const d = await res.json();
if (d.success) {
setData(d.data ?? null);
} else {
setError(d.error ?? 'Whale not found');
setData(null);
}
} catch {
setError('Failed to fetch whale profile');
setData(null);
} finally {
setLoading(false);
}
}, [address, tradeLimit, offset]);
useEffect(() => {
const t = setTimeout(fetchProfile, 0);
return () => { clearTimeout(t); };
}, [fetchProfile]);
// Keep a stable ref to the latest fetchProfile so the backfill effect can
// refetch the current page without re-running on every page change.
const fetchRef = useRef(fetchProfile);
useEffect(() => { fetchRef.current = fetchProfile; }, [fetchProfile]);
// On open (address change only), backfill this whale's full history (Data API
// user filter) so the profile shows hundreds of trades + a meaningful win-rate
// instead of the few caught globally. Then refetch the current page.
useEffect(() => {
if (!address) return;
let cancelled = false;
(async () => {
setBackfilling(true);
try {
await fetch(
`/api/signals/whale/${encodeURIComponent(address)}/backfill`,
{ method: 'POST' }
);
if (!cancelled) await fetchRef.current();
} catch { /* ignore — keep showing whatever we already have */ }
finally { if (!cancelled) setBackfilling(false); }
})();
return () => { cancelled = true; };
}, [address]);
return {
profile: data?.profile ?? null,
trades: data?.trades ?? [],
topMarkets: data?.topMarkets ?? [],
equity: data?.equity ?? [],
loading,
backfilling,
error,
refresh: fetchProfile,
};
}
📜 Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...