Розробка платформи соціального трейдингу
Соціальний трейдинг — це копіювання сделок успішних трейдерів у реальному часі. 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[]; // мініатюрний sparkline
}
Регуляторні аспекти
Соціальний трейдинг попадає під регулювання в багатьох юрисдикціях (провайдер де-факто управляє чужими коштами). Важливо:
- Юридична консультація по цільовій юрисдикції
- 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 місяців.







