Разработка платформы социального трейдинга
Социальный трейдинг — это копирование сделок успешных трейдеров в реальном времени. eToro сделал это mainstream в традиционных финансах, затем появились крипто-аналоги: Bybit Copy Trading, Bitget Copy Trade, OKX Copy Trading. Разработка собственной платформы — сложная задача с уникальными вызовами в синхронизации ордеров и распределении комиссий.
Архитектура платформы
Роли участников
Provider (мастер-трейдер) — опытный трейдер, чьи сделки копируются. Зарабатывает profit share (обычно 5–30% от прибыли копировщиков) или фиксированную подписку.
Follower (копировщик) — пользователь, выделяющий часть капитала на копирование. Выбирает провайдера по статистике, устанавливает лимиты.
Platform — получает комиссию с каждой скопированной сделки или с profit share.
Копирование ордеров
Ключевая техническая задача: когда провайдер размещает ордер, нужно синхронно разместить пропорциональные ордера для всех активных копировщиков.
type CopyEngine struct {
orderEngine *OrderEngine
db *DB
mu sync.RWMutex
}
func (ce *CopyEngine) OnProviderOrder(providerOrder Order) {
followers := ce.getActiveFollowers(providerOrder.UserID)
if len(followers) == 0 {
return
}
// Параллельное размещение ордеров для всех копировщиков
var wg sync.WaitGroup
errors := make(chan error, len(followers))
for _, follower := range followers {
wg.Add(1)
go func(f Follower) {
defer wg.Done()
copyOrder, err := ce.buildCopyOrder(providerOrder, f)
if err != nil {
errors <- fmt.Errorf("follower %d: %w", f.UserID, err)
return
}
_, err = ce.orderEngine.PlaceOrder(copyOrder)
if err != nil {
errors <- fmt.Errorf("follower %d order failed: %w", f.UserID, err)
}
}(follower)
}
wg.Wait()
close(errors)
// Логируем неудавшиеся копирования
for err := range errors {
log.Error("Copy order failed", "error", err)
}
}
Расчёт размера позиции копировщика
У провайдера может быть $100k, у копировщика — $500. Нужна пропорциональная адаптация:
type PositionSizer struct{}
func (ps *PositionSizer) Calculate(
providerOrder Order,
providerBalance Decimal,
follower Follower,
) (Decimal, error) {
// Какой % от баланса использует провайдер
providerUsagePct := providerOrder.Quantity.Mul(providerOrder.Price).
Div(providerBalance).Mul(Decimal("100"))
// Три режима копирования
switch follower.CopyMode {
case "fixed_amount":
// Всегда копируем фиксированную сумму
return follower.FixedAmount.Div(providerOrder.Price), nil
case "proportional":
// Такой же % от баланса копировщика
copyAmount := follower.AllocatedBalance.Mul(providerUsagePct).Div(Decimal("100"))
return copyAmount.Div(providerOrder.Price), nil
case "multiplier":
// X-кратное умножение относительного размера
copyAmount := follower.AllocatedBalance.Mul(providerUsagePct).
Mul(follower.Multiplier).Div(Decimal("100"))
return copyAmount.Div(providerOrder.Price), nil
}
return Decimal("0"), ErrUnknownCopyMode
}
Важные проверки перед копированием:
- Достаточно ли свободного баланса у копировщика
- Не превышает ли позиция max risk limit копировщика
- Не выходит ли позиция за пределы max open positions настройки
Синхронизация закрытия позиций
Когда провайдер закрывает позицию — нужно закрыть все копированные позиции:
func (ce *CopyEngine) OnProviderPositionClose(providerTradeID string) {
// Находим все открытые копированные ордера связанные с этой сделкой
copyOrders := ce.db.GetCopyOrdersByProviderTrade(providerTradeID)
for _, copyOrder := range copyOrders {
if copyOrder.Status == OrderStatusOpen {
// Закрываем рыночным ордером немедленно
closeOrder := Order{
UserID: copyOrder.FollowerID,
Pair: copyOrder.Pair,
Side: copyOrder.Side.Opposite(),
Type: Market,
Quantity: copyOrder.RemainingQty,
}
ce.orderEngine.PlaceOrder(closeOrder)
}
}
}
Статистика провайдеров
Статистика — главный инструмент выбора провайдера. Должна быть честной и верифицированной (рассчитывается сервером, не передаётся провайдером).
CREATE MATERIALIZED VIEW provider_stats AS
SELECT
p.user_id,
p.display_name,
-- Основные метрики
COUNT(DISTINCT t.id) AS total_trades,
COUNT(CASE WHEN t.pnl > 0 THEN 1 END)::float /
NULLIF(COUNT(DISTINCT t.id), 0) * 100 AS win_rate,
SUM(t.pnl) AS total_pnl_usd,
-- ROI за периоды
SUM(CASE WHEN t.closed_at >= NOW() - INTERVAL '7 days'
THEN t.pnl_pct ELSE 0 END) AS roi_7d,
SUM(CASE WHEN t.closed_at >= NOW() - INTERVAL '30 days'
THEN t.pnl_pct ELSE 0 END) AS roi_30d,
-- Риск-метрики
MIN(t.pnl_pct) AS max_loss_single_trade,
MAX(dd.drawdown) AS max_drawdown,
-- Sharpe Ratio (упрощённый)
AVG(t.pnl_pct) / NULLIF(STDDEV(t.pnl_pct), 0) AS sharpe,
-- Активность
COUNT(DISTINCT f.user_id) AS active_followers,
SUM(f.allocated_balance) AS total_aum
FROM providers p
JOIN trades t ON t.user_id = p.user_id AND t.is_copy_trade = false
LEFT JOIN drawdowns dd ON dd.provider_id = p.user_id
LEFT JOIN copy_relationships f ON f.provider_id = p.user_id AND f.status = 'active'
GROUP BY p.user_id, p.display_name;
REFRESH MATERIALIZED VIEW CONCURRENTLY provider_stats; -- каждые 5 минут
Equity curve
Для каждого провайдера рассчитывается equity curve — история роста $10,000 гипотетического депозита. Это лучший визуальный индикатор стабильности результатов.
def calculate_equity_curve(trades: list[Trade], initial_equity: float = 10000) -> list[dict]:
equity = initial_equity
curve = [{'date': trades[0].opened_at.date(), 'equity': equity}]
for trade in sorted(trades, key=lambda t: t.closed_at):
pnl_usd = equity * (trade.pnl_pct / 100)
equity += pnl_usd
curve.append({
'date': trade.closed_at.date(),
'equity': round(equity, 2),
'pnl_pct': round(trade.pnl_pct, 2),
})
return curve
Модель монетизации и распределение прибыли
Profit share расчёт (High Watermark)
High Watermark — стандартная модель для хедж-фондов и copy trading: провайдер получает profit share только с новых рекордных значений прибыли. Если капитал просел и восстановился до старого максимума — profit share не начисляется.
class ProfitShareCalculator:
def calculate(self, follower_id: int, provider_id: int) -> Decimal:
hwm = self.get_high_watermark(follower_id, provider_id)
current_equity = self.get_follower_equity_for_provider(follower_id, provider_id)
if current_equity <= hwm:
return Decimal('0') # нет новой прибыли — нет profit share
new_profit = current_equity - hwm
provider_share_pct = self.get_provider_share_pct(provider_id) # например 20%
platform_fee_pct = Decimal('2') # 2% платформа берёт с profit share
provider_earnings = new_profit * (provider_share_pct / 100)
platform_earnings = provider_earnings * (platform_fee_pct / 100)
# Обновляем high watermark
self.update_high_watermark(follower_id, provider_id, current_equity)
return provider_earnings - platform_earnings
Settlement расписание
Profit share рассчитывается еженедельно или ежемесячно. Settlement workflow:
- Snapshot всех equity позиций на дату расчёта
- Расчёт profit share для каждой пары (follower, provider)
- Перевод комиссий: у follower списывается, у provider начисляется
- Уведомления обеим сторонам
- Обновление high watermark
Risk controls для копировщиков
type CopyRiskControls struct {
MaxLoss Decimal // -20%: автоотключение при просадке
MaxOpenTrades int // максимум открытых сделок
MaxPerTrade Decimal // % от allocated balance на одну сделку
StopCopyOnClose bool // останавливать ли копирование при выходе?
}
func (ce *CopyEngine) CheckFollowerRisk(follower Follower, newOrder Order) error {
// Проверяем просадку
currentDrawdown := ce.calculateCurrentDrawdown(follower.ID)
if currentDrawdown.GreaterThan(follower.RiskControls.MaxLoss.Abs()) {
ce.stopCopying(follower.ID, "max_loss_reached")
return ErrMaxLossReached
}
// Проверяем количество открытых позиций
openTrades := ce.countOpenCopyTrades(follower.ID)
if openTrades >= follower.RiskControls.MaxOpenTrades {
return ErrMaxOpenTradesReached
}
return nil
}
Лидерборд и discovery
Страница выбора провайдеров — критична для конверсии:
// Фильтры для лидерборда
interface LeaderboardFilters {
period: '7d' | '30d' | '90d' | 'all';
minAUM: number;
minFollowers: number;
maxDrawdown: number;
riskLevel: 'low' | 'medium' | 'high';
strategy: 'spot' | 'futures' | 'mixed';
}
// Карточка провайдера
interface ProviderCard {
userId: string;
displayName: string;
avatar: string;
roi30d: number;
winRate: number;
maxDrawdown: number;
followers: number;
aum: number;
profitSharePct: number;
equityCurve: EquityPoint[]; // mini sparkline chart
}
Регуляторные аспекты
Социальный трейдинг попадает под регулирование во многих юрисдикциях (провайдер де-факто управляет чужими средствами). Важно:
- Юридическая консультация по целевой юрисдикции
- Disclaimers: прошлые результаты не гарантируют будущих
- Ограничения по суммам для нерегулируемых платформ
Сроки и стоимость
| Компонент | Срок |
|---|---|
| Copy engine (order routing) | 4–6 недель |
| Position sizing алгоритмы | 2–3 недели |
| Статистика провайдеров | 3–4 недели |
| Profit share расчёт | 2–3 недели |
| Лидерборд и discovery | 3–4 недели |
| Risk controls для копировщиков | 2–3 недели |
Полная платформа социального трейдинга: 4–6 месяцев.







