import { useState, useEffect, useCallback } from 'react';
export type SignalType = 'WHALE' | 'LONGSHOT' | 'SPIKE' | 'MOMENTUM';
interface SignalBase {
type: SignalType;
strength: number; // 0..1 normalized — directional conviction (scale 1)
marketId: string;
question: string | null;
category: string | null;
detectedAt: string | null;
endDate: string | null; // ISO market resolution time
}
export interface WhaleSignal extends SignalBase {
type: 'WHALE';
imageUrl: string | null;
marketPrice: number;
edgeScore: number;
signal: 'BUY_YES' | 'BUY_NO';
confidence: 'HIGH' | 'MED' | 'LOW';
whaleYesPct: number;
volumeRatio: number;
recommendedSide: 'YES' | 'NO' | null;
impliedProb: number | null; // 0..1 market-implied probability of the recommended side
entryQuality: number; // 0..1 — spread tightness + liquidity (scale 2)
whaleCount: number; // # of qualifying whale trades behind the signal
whaleVolumeUsd: number; // total $ whale volume behind the signal
whaleWr: number | null; // 0..1 avg win-rate of whales in this market, null if unknown
}
export interface LongshotSignal extends SignalBase {
type: 'LONGSHOT';
imageUrl: string | null;
marketPrice: number;
signal: 'BUY_YES' | 'BUY_NO';
confidence: 'HIGH' | 'MED' | 'LOW';
recommendedSide: 'YES' | 'NO' | null;
impliedProb: number | null; // 0..1 market-implied probability of the recommended side
entryQuality: number; // 0..1 (scale 2)
}
export interface SpikeSignal extends SignalBase {
type: 'SPIKE';
score: number; // volume_24h / avg (e.g. 5.0 = 500%)
volume24h: number;
avgVolume: number;
liquidity: number;
}
export interface MomentumSignal extends SignalBase {
type: 'MOMENTUM';
imageUrl: string | null;
marketPrice: number;
momentumFactor: number; // 0..1
confidence: 'HIGH' | 'MED' | 'LOW';
volumeRatio: number;
entryQuality: number; // 0..1 (scale 2)
}
export type SignalCard = WhaleSignal | LongshotSignal | SpikeSignal | MomentumSignal;
export type Horizon = 'fast' | 'mid' | 'long' | '';
interface FeedParams {
limit?: number;
category?: string; // 'All' or empty = no filter
type?: SignalType | ''; // '' = both
minStrength?: number; // 0..1
horizon?: Horizon; // '' = any time-to-resolution
}
export function useSignalsFeed(params: FeedParams = {}) {
const { limit = 40, category, type, minStrength, horizon } = params;
const [signals, setSignals] = useState<SignalCard[]>([]);
const [loading, setLoading] = useState(true);
const fetch_ = useCallback(async () => {
setLoading(true);
try {
const qs = new URLSearchParams({ limit: String(limit) });
if (category && category !== 'All') qs.set('category', category);
if (type) qs.set('type', type);
if (minStrength && minStrength > 0) qs.set('min_strength', String(minStrength));
if (horizon) qs.set('horizon', horizon);
const res = await fetch(`/api/signals/feed?${qs.toString()}`);
const d = await res.json();
if (d.success) {
setSignals(d.data ?? []);
}
} catch { /* ignore */ }
finally { setLoading(false); }
}, [limit, category, type, minStrength, horizon]);
useEffect(() => {
const t = setTimeout(fetch_, 0);
const interval = setInterval(fetch_, 2 * 60_000);
return () => { clearTimeout(t); clearInterval(interval); };
}, [fetch_]);
return { signals, loading, refresh: fetch_ };
}
📜 Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...