Разработка системы маржинальной торговли

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы маржинальной торговли
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы маржинальной торговли

Маржинальная торговля — это торговля с использованием заёмных средств. Трейдер депонирует залог (margin), получает кредитное плечо (2×, 5×, 10×), и может торговать бо́льшим объёмом. Биржа зарабатывает на funding rate / interest. Задача разработки — построить систему, которая не допустит убытков больше, чем залог (иначе биржа несёт риск).

Типы маржинальной торговли

Isolated Margin

Каждая позиция имеет свой залог. Риск ограничен залогом конкретной позиции — другие позиции не затрагиваются при ликвидации.

type IsolatedPosition struct {
    UserID           int64
    Pair             string
    Side             Side          // Long | Short
    EntryPrice       Decimal
    Quantity         Decimal
    Leverage         int           // 1-100x
    InitialMargin    Decimal       // залог при открытии
    MaintenanceMargin Decimal      // минимальный залог для поддержания
    UnrealizedPnL    Decimal       // текущий P&L
    LiquidationPrice Decimal       // цена принудительного закрытия
}

Cross Margin

Все позиции пользователя используют общий пул залога. Прибыльные позиции поддерживают убыточные. Ликвидация наступает когда весь баланс уходит в минус.

type CrossMarginAccount struct {
    UserID           int64
    TotalEquity      Decimal  // залог + unrealized PnL
    TotalPositions   []CrossPosition
    MaintenanceMargin Decimal  // суммарный минимальный залог по всем позициям
    
    // Liquidation при: TotalEquity < MaintenanceMargin
}

Расчёт ликвидационной цены

Для Long позиции с isolated margin:

func CalculateLiquidationPrice(pos IsolatedPosition, config MarginConfig) Decimal {
    // Liquidation при: unrealized loss = initial margin - maintenance margin fee
    // (entry_price - liq_price) * quantity = initial_margin - maintenance_margin_amount
    
    maintenanceMarginAmount := pos.EntryPrice.Mul(pos.Quantity).Mul(config.MaintenanceMarginRate)
    lossAtLiquidation := pos.InitialMargin.Sub(maintenanceMarginAmount)
    priceDropAllowed := lossAtLiquidation.Div(pos.Quantity)
    
    if pos.Side == Long {
        return pos.EntryPrice.Sub(priceDropAllowed)
    } else {
        return pos.EntryPrice.Add(priceDropAllowed)
    }
}

// Пример: Long BTC, entry $42,000, leverage 10x
// Initial Margin: $42,000 / 10 = $4,200
// Maintenance Margin Rate: 0.5%
// MM Amount: $42,000 * 0.5% = $210
// Loss at liquidation: $4,200 - $210 = $3,990
// Price drop allowed: $3,990 / 1 BTC = $3,990
// Liquidation price: $42,000 - $3,990 = $38,010

Ликвидационный движок

Ликвидация должна происходить быстро: при резких движениях рынка позиции нескольких пользователей могут достичь liquidation price одновременно.

type LiquidationEngine struct {
    marketDataFeed *MarketDataFeed
    orderEngine    *OrderEngine
    db             *DB
    ticker         *time.Ticker
}

func (le *LiquidationEngine) Run(ctx context.Context) {
    for {
        select {
        case price := <-le.marketDataFeed.PriceUpdates:
            le.checkLiquidations(price)
        case <-ctx.Done():
            return
        }
    }
}

func (le *LiquidationEngine) checkLiquidations(update PriceUpdate) {
    // Находим все позиции, где current price пересёк liquidation price
    positions := le.db.GetPositionsForLiquidation(update.Pair, update.Price)
    
    for _, pos := range positions {
        go le.liquidatePosition(pos, update.Price)
    }
}

func (le *LiquidationEngine) liquidatePosition(pos IsolatedPosition, markPrice Decimal) {
    // 1. Помечаем позицию как "liquidating" (атомарно, один ликвидатор)
    if !le.db.TryMarkForLiquidation(pos.ID) {
        return // другой воркер уже берёт эту позицию
    }
    
    // 2. Размещаем market ордер на закрытие по текущей цене
    liquidationOrder := Order{
        UserID:   pos.UserID,
        Pair:     pos.Pair,
        Side:     pos.Side.Opposite(),
        Type:     Market,
        Quantity: pos.Quantity,
        IsLiquidation: true,
    }
    
    trades, err := le.orderEngine.PlaceOrder(liquidationOrder)
    if err != nil {
        log.Error("Liquidation order failed", "posID", pos.ID, "error", err)
        le.db.RevertLiquidationMark(pos.ID)
        return
    }
    
    // 3. Рассчитываем liquidation fee (обычно 0.5-1.5% от позиции)
    executedPrice := averageExecutionPrice(trades)
    positionValue := pos.Quantity.Mul(executedPrice)
    liquidationFee := positionValue.Mul(LIQUIDATION_FEE_RATE)
    
    // 4. Распределяем residual margin: покрываем убыток, остаток возвращаем пользователю
    loss := pos.EntryPrice.Sub(executedPrice).Mul(pos.Quantity)
    if pos.Side == Short {
        loss = executedPrice.Sub(pos.EntryPrice).Mul(pos.Quantity)
    }
    
    residual := pos.InitialMargin.Sub(loss).Sub(liquidationFee)
    
    if residual.IsPositive() {
        le.db.CreditUserBalance(pos.UserID, residual, "liquidation_refund")
    }
    // Если residual < 0 — Insurance Fund покрывает убыток
    
    le.db.MarkLiquidationComplete(pos.ID, executedPrice)
    le.notifyUser(pos.UserID, pos, executedPrice)
}

Insurance Fund

При резких движениях liquidation может исполниться по худшей цене, чем liquidation price. Remaining loss покрывает Insurance Fund — пул, формируемый из liquidation fees:

type InsuranceFund struct {
    db *DB
}

func (if_ *InsuranceFund) Cover(loss Decimal, currency string) error {
    balance := if_.db.GetBalance(currency)
    
    if balance.LessThan(loss) {
        // Insurance Fund иссяк — auto-deleveraging (ADL)
        if_.triggerADL(loss.Sub(balance), currency)
        loss = balance
    }
    
    if_.db.DeductBalance(currency, loss)
    log.Info("Insurance fund covered loss", "amount", loss, "currency", currency)
    return nil
}

ADL (Auto-Deleveraging): если Insurance Fund исчерпан, принудительно закрываются наиболее прибыльные позиции трейдеров для покрытия убытка. Это жёсткая мера — используется только как последний резорт.

Funding Rate для perpetual contracts

Perpetual futures (бессрочные контракты) не имеют срока экспирации. Цена привязывается к spot через механизм funding rate:

type FundingRate struct {
    Rate        Decimal  // может быть положительным или отрицательным
    NextFunding time.Time
    Interval    time.Duration  // обычно 8 часов
}

func (fe *FundingEngine) CalculateRate(pair string) Decimal {
    markPrice := fe.getMarkPrice(pair)
    indexPrice := fe.getIndexPrice(pair)  // цена spot с крупных бирж
    
    // Premium = (mark - index) / index
    premium := markPrice.Sub(indexPrice).Div(indexPrice)
    
    // Funding Rate = Premium + clamp(InterestRate - Premium, -0.05%, 0.05%)
    interestRate := Decimal("0.0001")  // 0.01% базовая ставка (Binance default)
    
    clampedDiff := Clamp(
        interestRate.Sub(premium),
        Decimal("-0.0005"),
        Decimal("0.0005"),
    )
    
    return premium.Add(clampedDiff)
}

func (fe *FundingEngine) ApplyFunding(pair string) {
    rate := fe.CalculateRate(pair)
    positions := fe.db.GetOpenPositions(pair)
    
    for _, pos := range positions {
        fundingPayment := pos.Quantity.Mul(pos.EntryPrice).Mul(rate)
        
        if pos.Side == Long {
            // Long платит если rate > 0 (mark > index), получает если rate < 0
            fe.db.DebitUserBalance(pos.UserID, fundingPayment)
        } else {
            fe.db.CreditUserBalance(pos.UserID, fundingPayment)
        }
    }
}

Mark Price vs Last Price

Liquidation должна основываться на mark price (агрегированная цена с нескольких бирж), а не на last trade price биржи. Иначе возможна манипуляция: крупный трейдер делает маленькую сделку по экстремальной цене и триггерит ликвидации.

func (fe *FundingEngine) GetMarkPrice(pair string) Decimal {
    // Median цена с Binance, OKX, Bybit
    prices := []Decimal{
        fe.binanceFeed.GetPrice(pair),
        fe.okxFeed.GetPrice(pair),
        fe.bybitFeed.GetPrice(pair),
    }
    sort.Slice(prices, func(i, j int) bool { return prices[i].LessThan(prices[j]) })
    return prices[1]  // медиана из 3
}

Margin Management UI

Пользователь должен видеть в реальном времени:

  • Текущее margin ratio (насколько далеко от ликвидации)
  • Liquidation price для каждой позиции
  • P&L (realized и unrealized)
  • Возможность добавить/вывести margin (изменить leverage)
function PositionCard({ position }: { position: Position }) {
  const marginRatio = position.equity / position.maintenanceMargin * 100;
  const urgency = marginRatio < 110 ? 'critical' : marginRatio < 150 ? 'warning' : 'safe';
  
  return (
    <Card>
      <PnLDisplay pnl={position.unrealizedPnl} />
      <div>Liquidation: ${position.liquidationPrice.toFixed(2)}</div>
      <MarginRatioBar ratio={marginRatio} urgency={urgency} />
      <div>Margin Ratio: {marginRatio.toFixed(1)}%</div>
      <AddMarginButton position={position} />
    </Card>
  );
}

Сроки разработки

Компонент Срок
Isolated margin engine 4–5 недель
Liquidation engine 3–4 недели
Cross margin 3–4 недели
Perpetual funding rate 2–3 недели
Insurance fund + ADL 2–3 недели
Mark price oracle 1–2 недели
UI для маржин торговли 4–6 недель

Полная система маржинальной торговли: 4–6 месяцев.