Development of Crypto Social Trading Platform
Social trading is copying deals of successful traders in real time. eToro made this mainstream in traditional finance, then crypto-analogs appeared: Bybit Copy Trading, Bitget Copy Trade, OKX Copy Trading. Developing own platform—complex task with unique challenges in order synchronization and commission distribution.
Platform Architecture
Participant Roles
Provider (master trader)—experienced trader whose deals are copied. Earns profit share (usually 5–30% of follower profit) or fixed subscription.
Follower (copier)—user allocating part of capital for copying. Chooses provider by stats, sets limits.
Platform—gets commission from each copied deal or from profit share.
Order Copying
Key technical challenge: when provider places order, need synchronously place proportional orders for all active followers.
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
}
// Parallel order placement for all copiers
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)
// Log failed copies
for err := range errors {
log.Error("Copy order failed", "error", err)
}
}
Follower Position Sizing
Provider may have $100k, follower—$500. Need proportional adaptation:
type PositionSizer struct{}
func (ps *PositionSizer) Calculate(
providerOrder Order,
providerBalance Decimal,
follower Follower,
) (Decimal, error) {
// What % of balance does provider use
providerUsagePct := providerOrder.Quantity.Mul(providerOrder.Price).
Div(providerBalance).Mul(Decimal("100"))
// Three copy modes
switch follower.CopyMode {
case "fixed_amount":
// Always copy fixed amount
return follower.FixedAmount.Div(providerOrder.Price), nil
case "proportional":
// Same % of follower's balance
copyAmount := follower.AllocatedBalance.Mul(providerUsagePct).Div(Decimal("100"))
return copyAmount.Div(providerOrder.Price), nil
case "multiplier":
// X-times the relative size
copyAmount := follower.AllocatedBalance.Mul(providerUsagePct).
Mul(follower.Multiplier).Div(Decimal("100"))
return copyAmount.Div(providerOrder.Price), nil
}
return Decimal("0"), ErrUnknownCopyMode
}
Important checks before copying:
- Enough free balance for follower
- Position doesn't exceed max risk limit
- Position doesn't exceed max open positions setting
Position Close Synchronization
When provider closes position—close all copied positions:
func (ce *CopyEngine) OnProviderPositionClose(providerTradeID string) {
// Find all open copy orders linked to this trade
copyOrders := ce.db.GetCopyOrdersByProviderTrade(providerTradeID)
for _, copyOrder := range copyOrders {
if copyOrder.Status == OrderStatusOpen {
// Close with market order immediately
closeOrder := Order{
UserID: copyOrder.FollowerID,
Pair: copyOrder.Pair,
Side: copyOrder.Side.Opposite(),
Type: Market,
Quantity: copyOrder.RemainingQty,
}
ce.orderEngine.PlaceOrder(closeOrder)
}
}
}
Provider Statistics
Statistics—main tool for provider selection. Must be honest and server-verified (not provider-submitted).
CREATE MATERIALIZED VIEW provider_stats AS
SELECT
p.user_id,
p.display_name,
-- Key metrics
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 by periods
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,
-- Risk metrics
MIN(t.pnl_pct) AS max_loss_single_trade,
MAX(dd.drawdown) AS max_drawdown,
-- Sharpe Ratio (simplified)
AVG(t.pnl_pct) / NULLIF(STDDEV(t.pnl_pct), 0) AS sharpe,
-- Activity
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; -- every 5 minutes
Equity Curve
For each provider—equity curve history—$10,000 hypothetical deposit growth. Best visual indicator of results stability.
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
Monetization Model and Profit Distribution
Profit Share Calculation (High Watermark)
High Watermark—standard model for hedge funds and copy trading: provider gets profit share only from new record profits. If capital drops and recovers to old max—no 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') # no new profit—no profit share
new_profit = current_equity - hwm
provider_share_pct = self.get_provider_share_pct(provider_id) # e.g., 20%
platform_fee_pct = Decimal('2') # 2% platform takes from profit share
provider_earnings = new_profit * (provider_share_pct / 100)
platform_earnings = provider_earnings * (platform_fee_pct / 100)
# Update high watermark
self.update_high_watermark(follower_id, provider_id, current_equity)
return provider_earnings - platform_earnings
Settlement Schedule
Profit share calculated weekly or monthly. Settlement workflow:
- Snapshot all equity positions on settlement date
- Calculate profit share for each (follower, provider) pair
- Transfer commissions: debit from follower, credit to provider
- Notify both sides
- Update high watermark
Risk Controls for Copiers
type CopyRiskControls struct {
MaxLoss Decimal // -20%: auto-stop on drawdown
MaxOpenTrades int // max open deals
MaxPerTrade Decimal // % of allocated balance per deal
StopCopyOnClose bool // stop copying on exit?
}
func (ce *CopyEngine) CheckFollowerRisk(follower Follower, newOrder Order) error {
// Check drawdown
currentDrawdown := ce.calculateCurrentDrawdown(follower.ID)
if currentDrawdown.GreaterThan(follower.RiskControls.MaxLoss.Abs()) {
ce.stopCopying(follower.ID, "max_loss_reached")
return ErrMaxLossReached
}
// Check open positions count
openTrades := ce.countOpenCopyTrades(follower.ID)
if openTrades >= follower.RiskControls.MaxOpenTrades {
return ErrMaxOpenTradesReached
}
return nil
}
Leaderboard and Discovery
Provider selection page—critical for conversion:
// Leaderboard filters
interface LeaderboardFilters {
period: '7d' | '30d' | '90d' | 'all';
minAUM: number;
minFollowers: number;
maxDrawdown: number;
riskLevel: 'low' | 'medium' | 'high';
strategy: 'spot' | 'futures' | 'mixed';
}
// Provider card
interface ProviderCard {
userId: string;
displayName: string;
avatar: string;
roi30d: number;
winRate: number;
maxDrawdown: number;
followers: number;
aum: number;
profitSharePct: number;
equityCurve: EquityPoint[]; // mini sparkline
}
Regulatory Aspects
Social trading falls under regulation in many jurisdictions (provider de-facto manages others' funds). Important:
- Legal consultation on target jurisdiction
- Disclaimers: past results don't guarantee future
- Amount limits for unregulated platforms
Timeline and Cost
| Component | Time |
|---|---|
| Copy engine (order routing) | 4–6 weeks |
| Position sizing algorithms | 2–3 weeks |
| Provider statistics | 3–4 weeks |
| Profit share calculation | 2–3 weeks |
| Leaderboard and discovery | 3–4 weeks |
| Risk controls for copiers | 2–3 weeks |
Full social trading platform: 4–6 months.







