← Back
import { useState, useEffect, useCallback } from 'react';

export interface Trader {
  address: string;
  label: string | null;
  totalTrades: number;
  totalVolume: number;
  avgTradeSize: number;
  largestTrade: number;
  totalPnl: number | null;
  winRate: number | null;
  topCategory: string | null;
  firstSeen: string | null;
  lastSeen: string | null;
  avgBuy: number | null;
  sellRate: number | null;
  avatar: string | null;
  // Phase B: honest positional stats (only resolved/closed positions).
  winRatePos: number | null;
  realizedPnlPos: number | null;
  closedPositions: number | null;
  openPositions: number | null;
  // Phase «Флаги»: copy-fit quality gate (Phase D).
  qualityFlag: number | null;
  backtestPf: number | null;
  activeDays: number | null;
  // Chunk B: realized PnL on resolved BUYs in the last 7 days (momentum). NULL if none.
  pnl7d: number | null;
  // Chunk B: JSON array of last-16 resolved-BUY per-trade PnLs (oldest→newest) for a sparkline.
  spark: string | null;
}

const FOLLOW_KEY = 'sz_following';

export function useTopTraders(sort = 'volume', limit = 30) {
  const [traders, setTraders] = useState<Trader[]>([]);
  const [loading, setLoading] = useState(true);

  const fetchTraders = useCallback(async () => {
    setLoading(true);
    try {
      const res = await fetch(`/api/signals/top-traders?sort=${sort}&limit=${limit}`);
      const d = await res.json();
      if (d.success) setTraders(d.data ?? []);
    } catch { /* ignore */ }
    setLoading(false);
  }, [sort, limit]);

  useEffect(() => {
    const t = setTimeout(fetchTraders, 0);
    return () => { clearTimeout(t); };
  }, [fetchTraders]);

  return { traders, loading, refresh: fetchTraders };
}

// Follow/unfollow persistence (localStorage)
export function useFollowing() {
  const [following, setFollowing] = useState<Set<string>>(() => {
    try {
      const stored = localStorage.getItem(FOLLOW_KEY);
      return stored ? new Set(JSON.parse(stored)) : new Set();
    } catch { return new Set(); }
  });

  const toggle = useCallback((address: string) => {
    setFollowing(prev => {
      const next = new Set(prev);
      if (next.has(address)) next.delete(address);
      else next.add(address);
      try { localStorage.setItem(FOLLOW_KEY, JSON.stringify([...next])); } catch { /* ignore */ }
      return next;
    });
  }, []);

  return { following, toggle };
}

📜 Git History

6631be1feat(leaders): equity sparkline (last-16 resolved BUYs) in card footer; routes.rs spark payload12 days ago
5cb10f3feat(leaders): 7d momentum metric (real pnl7d from backend chunk B)12 days ago
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...