Розробка системи лідербордів трейдерів
Лідербордy трейдерів — елемент геймифікації та соціального доказу на торговельних платформах. Правильно реалізований лідерборд стимулює активність, допомагає користувачам знаходити успішних трейдерів для копіювання, створює комьюніті навколо платформи.
Типи лідербордів
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 endpoint
@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 заходи
Лідербордy можуть бути схильні маніпуляціям: трейдер відкриває протилежні позиції з різних акаунтів, один прибутковий — потрапляє в топ. Захист:
- Мінімальний обсяг торгів за період (виключає випадкові одиничні угоди)
- Мінімальна кількість угод (не менше 10–20 за період)
- Детектор wash trading — угоди з власними ордерами через різні акаунти
- KYC-привязка — один акаунт на одного користувача
- Cooldown період — результати враховуються не раніше ніж через тиждень після реєстрації
Анонімність vs верифікація — компроміс: псевдонім для публічного лідерборду, реальні дані для верифікації платформою.







