Разработка системы лидербордов трейдеров
Лидерборды трейдеров — элемент геймификации и социального доказательства на торговых платформах. Правильно реализованный лидерборд стимулирует активность, помогает пользователям находить успешных трейдеров для копирования, создаёт комьюнити вокруг платформы.
Типы лидербордов
P&L Leaderboard — рейтинг по абсолютной или процентной прибыли за период. Самый популярный, но требует нормализации: трейдер с $100K депозитом и $5K прибыли имеет ту же доходность 5%, что и трейдер с $1K и $50 прибыли.
Risk-Adjusted Leaderboard — рейтинг по Sharpe ratio, Sortino ratio или Calmar ratio. Более справедливый: высокая доходность при огромном риске не должна быть на первом месте.
Competition Leaderboard — временные соревнования с призами. Фиксированный период (1 неделя, 1 месяц), одинаковые стартовые условия.
Asset-Specific — топ трейдеров по конкретному инструменту (BTC/USDT, ETH/USDT).
Схема данных
CREATE TABLE trader_performance (
user_id UUID NOT NULL,
period_type VARCHAR(16) NOT NULL, -- 'daily', 'weekly', 'monthly', 'all_time'
period_date DATE NOT NULL,
total_pnl NUMERIC(24, 8) NOT NULL,
pnl_pct NUMERIC(10, 4) NOT NULL,
sharpe_ratio NUMERIC(10, 4),
max_drawdown NUMERIC(10, 4),
win_rate NUMERIC(6, 4),
total_trades INTEGER,
volume NUMERIC(24, 8),
rank INTEGER,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, period_type, period_date)
);
CREATE INDEX ON trader_performance (period_type, period_date, rank);
CREATE INDEX ON trader_performance (period_type, period_date, pnl_pct DESC);
Расчёт рейтингов
class LeaderboardCalculator:
async def calculate_period_rankings(self, period_type: str, period_date: date):
# Загружаем торговые данные за период
trading_data = await self.trade_repo.get_period_summary(period_type, period_date)
# Рассчитываем метрики для каждого трейдера
performances = []
for user_id, trades in trading_data.items():
if len(trades) < 5: # минимальный порог активности
continue
daily_returns = self.compute_daily_returns(trades)
metrics = TraderMetrics(
user_id=user_id,
total_pnl=sum(t.pnl for t in trades),
pnl_pct=self.compute_pnl_pct(trades),
sharpe_ratio=self.sharpe(daily_returns),
max_drawdown=self.max_drawdown(daily_returns),
win_rate=len([t for t in trades if t.pnl > 0]) / len(trades),
total_trades=len(trades),
volume=sum(t.notional for t in trades),
)
performances.append(metrics)
# Сортируем по risk-adjusted метрике
performances.sort(key=lambda p: p.sharpe_ratio or 0, reverse=True)
# Назначаем ранги
for rank, perf in enumerate(performances, 1):
perf.rank = rank
# Сохраняем в БД
await self.performance_repo.bulk_upsert(
[p.to_db_row(period_type, period_date) for p in performances]
)
API эндпоинт
@app.get("/api/leaderboard")
async def get_leaderboard(
period: str = Query("weekly", regex="^(daily|weekly|monthly|all_time)$"),
metric: str = Query("pnl_pct", regex="^(pnl_pct|sharpe_ratio|win_rate)$"),
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
):
# Кэшируем лидерборд в Redis на 5 минут
cache_key = f"leaderboard:{period}:{metric}:{limit}:{offset}"
cached = await redis.get(cache_key)
if cached:
return json.loads(cached)
data = await db.get_leaderboard(period, metric, limit, offset)
result = {
"data": data,
"total": await db.get_leaderboard_count(period),
"period": period,
"metric": metric,
}
await redis.setex(cache_key, 300, json.dumps(result))
return result
Anti-Gaming меры
Лидерборды могут быть подвержены манипуляциям: трейдер открывает противоположные позиции с разных аккаунтов, один прибыльный — попадает в топ. Защита:
- Минимальный объём торгов за период (исключает случайные единичные сделки)
- Минимальное количество сделок (не менее 10–20 за период)
- Детектор wash trading — сделки с собственными ордерами через разные аккаунты
- KYC-привязка — один аккаунт на одного пользователя
- Cooldown период — результаты учитываются не раньше чем через неделю после регистрации
Анонимность vs верификация — компромисс: псевдоним для публичного лидерборда, реальные данные для верификации платформой.







