mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
Renamed files: - lending_pool.go → lending.go - lending_pool_test.go → lending_test.go - margin_trading.go → margin.go - margin_trading_test.go → margin_test.go - liquidation_engine.go → liquidation.go - liquidation_engine_test.go → liquidation_test.go - liquidation_engine_comprehensive_test.go → liquidation_comprehensive_test.go No API changes - all public types/functions remain the same.
956 lines
25 KiB
Go
956 lines
25 KiB
Go
package lx
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// LiquidationEngine manages position liquidations and insurance fund
|
|
type LiquidationEngine struct {
|
|
// Core components
|
|
InsuranceFund *InsuranceFund
|
|
LiquidationQueue *LiquidationQueue
|
|
AutoDeleveraging *AutoDeleveragingEngine
|
|
SocializedLoss *SocializedLossEngine
|
|
|
|
// Liquidation parameters
|
|
MaintenanceMargin map[string]float64 // Per-asset maintenance margin
|
|
LiquidationFee float64 // Fee charged on liquidation
|
|
InsuranceFundFee float64 // Portion of fees going to insurance fund
|
|
|
|
// Liquidators
|
|
Liquidators map[string]*Liquidator
|
|
LiquidatorRewards map[string]*big.Int
|
|
|
|
// Performance tracking
|
|
TotalLiquidations uint64
|
|
TotalLiquidated *big.Int
|
|
TotalLosses *big.Int
|
|
InsuranceFundUsed *big.Int
|
|
LiquidationMetrics *LiquidationMetrics
|
|
LiquidationHistory []*LiquidationOrder
|
|
|
|
// Risk management
|
|
RiskEngine *RiskEngine
|
|
CircuitBreaker *CircuitBreaker
|
|
EmergencyMode bool
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// InsuranceFund manages the insurance fund for covering losses
|
|
type InsuranceFund struct {
|
|
// Fund balances
|
|
Balance map[string]*big.Int // Per-asset balances
|
|
TotalValueUSD *big.Int
|
|
|
|
// Fund parameters
|
|
TargetSize *big.Int
|
|
MinimumSize *big.Int
|
|
MaxDrawdown float64
|
|
|
|
// Fund operations
|
|
Contributions []*FundContribution
|
|
Withdrawals []*FundWithdrawal
|
|
LossCoverage []*LossCoverageEvent
|
|
|
|
// Performance
|
|
HighWaterMark *big.Int
|
|
CurrentDrawdown float64
|
|
APY float64
|
|
|
|
// Governance
|
|
FundManagers []string
|
|
WithdrawalRules *WithdrawalRules
|
|
|
|
LastUpdate time.Time
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// LiquidationQueue manages pending liquidations
|
|
type LiquidationQueue struct {
|
|
// Queue structure
|
|
HighPriority []*LiquidationOrder
|
|
MediumPriority []*LiquidationOrder
|
|
LowPriority []*LiquidationOrder
|
|
Orders []*LiquidationOrder // For testing compatibility
|
|
|
|
// Processing
|
|
ProcessingOrders map[string]*LiquidationOrder
|
|
CompletedOrders map[string]*LiquidationOrder
|
|
FailedOrders map[string]*LiquidationOrder
|
|
|
|
// Configuration
|
|
MaxQueueSize int
|
|
ProcessingInterval time.Duration
|
|
RetryAttempts int
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// LiquidationOrder represents a liquidation order
|
|
type LiquidationOrder struct {
|
|
OrderID string
|
|
PositionID string
|
|
UserID string
|
|
Symbol string
|
|
Side Side
|
|
Size float64
|
|
MarkPrice float64
|
|
LiquidationPrice float64
|
|
CollateralValue *big.Int
|
|
Loss *big.Int
|
|
Priority LiquidationPriority
|
|
Status LiquidationOrderStatus
|
|
CreatedAt time.Time
|
|
ProcessedAt *time.Time
|
|
LiquidatorID string
|
|
ExecutionPrice float64
|
|
LiquidationFee *big.Int
|
|
InsuranceFundClaim *big.Int
|
|
}
|
|
|
|
// LiquidationPriority defines liquidation priority levels
|
|
type LiquidationPriority int
|
|
|
|
const (
|
|
HighPriority LiquidationPriority = iota
|
|
MediumPriority
|
|
LowPriority
|
|
)
|
|
|
|
// LiquidationOrderStatus represents the status of a liquidation order
|
|
type LiquidationOrderStatus int
|
|
|
|
const (
|
|
LiquidationPending LiquidationOrderStatus = iota
|
|
LiquidationProcessing
|
|
LiquidationPartial
|
|
LiquidationComplete
|
|
LiquidationFailed
|
|
LiquidationCancelled
|
|
)
|
|
|
|
// Liquidator represents a liquidation participant
|
|
type Liquidator struct {
|
|
LiquidatorID string
|
|
Balance *big.Int
|
|
ActiveLiquidations []*LiquidationOrder
|
|
CompletedCount uint64
|
|
TotalProfit *big.Int
|
|
SuccessRate float64
|
|
AverageFillTime time.Duration
|
|
Reputation float64
|
|
Tier LiquidatorTier
|
|
LastActive time.Time
|
|
}
|
|
|
|
// LiquidatorTier represents liquidator tiers
|
|
type LiquidatorTier int
|
|
|
|
const (
|
|
BronzeLiquidator LiquidatorTier = iota
|
|
SilverLiquidator
|
|
GoldLiquidator
|
|
PlatinumLiquidator
|
|
DiamondLiquidator
|
|
)
|
|
|
|
// AutoDeleveragingEngine handles auto-deleveraging
|
|
type AutoDeleveragingEngine struct {
|
|
// ADL queue
|
|
ADLQueue map[string][]*ADLCandidate
|
|
|
|
// ADL parameters
|
|
ADLThreshold float64 // Insurance fund depletion threshold
|
|
MaxADLPercentage float64 // Maximum position reduction
|
|
|
|
// ADL history
|
|
ADLEvents []*ADLEvent
|
|
TotalADLVolume *big.Int
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// ADLCandidate represents a position eligible for auto-deleveraging
|
|
type ADLCandidate struct {
|
|
PositionID string
|
|
UserID string
|
|
PnLRanking float64 // Profit ranking for ADL priority
|
|
PositionSize float64
|
|
UnrealizedPnL *big.Int
|
|
Leverage float64
|
|
ADLPriority int
|
|
}
|
|
|
|
// ADLEvent represents an auto-deleveraging event
|
|
type ADLEvent struct {
|
|
EventID string
|
|
Timestamp time.Time
|
|
TriggerReason string
|
|
AffectedPositions []*ADLAffectedPosition
|
|
TotalReduced float64
|
|
InsuranceFundBefore *big.Int
|
|
InsuranceFundAfter *big.Int
|
|
}
|
|
|
|
// ADLAffectedPosition represents a position affected by ADL
|
|
type ADLAffectedPosition struct {
|
|
PositionID string
|
|
UserID string
|
|
OriginalSize float64
|
|
ReducedSize float64
|
|
ReductionPercentage float64
|
|
CompensationPaid *big.Int
|
|
}
|
|
|
|
// SocializedLossEngine handles socialized losses
|
|
type SocializedLossEngine struct {
|
|
// Loss distribution
|
|
PendingLosses map[string]*SocializedLoss
|
|
DistributedLosses map[string]*SocializedLoss
|
|
|
|
// Parameters
|
|
LossThreshold *big.Int // Minimum loss to trigger socialization
|
|
MaxLossPerUser float64 // Maximum loss percentage per user
|
|
|
|
// History
|
|
LossHistory []*SocializedLossEvent
|
|
TotalSocialized *big.Int
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// SocializedLoss represents a socialized loss
|
|
type SocializedLoss struct {
|
|
LossID string
|
|
TotalLoss *big.Int
|
|
AffectedAsset string
|
|
DistributionMethod string
|
|
AffectedUsers map[string]*UserLossShare
|
|
Timestamp time.Time
|
|
Status SocializedLossStatus
|
|
}
|
|
|
|
// UserLossShare represents a user's share of socialized loss
|
|
type UserLossShare struct {
|
|
UserID string
|
|
PositionSize float64
|
|
LossAmount *big.Int
|
|
Percentage float64
|
|
Compensated bool
|
|
CompensationAmount *big.Int
|
|
}
|
|
|
|
// SocializedLossStatus represents the status of socialized loss
|
|
type SocializedLossStatus int
|
|
|
|
const (
|
|
LossPending SocializedLossStatus = iota
|
|
LossDistributing
|
|
LossDistributed
|
|
LossCancelled
|
|
)
|
|
|
|
// LiquidationMetrics tracks liquidation performance
|
|
type LiquidationMetrics struct {
|
|
TotalLiquidations uint64
|
|
SuccessfulLiquidations uint64
|
|
FailedLiquidations uint64
|
|
PartialLiquidations uint64
|
|
TotalVolume *big.Int
|
|
AverageLoss *big.Int
|
|
MaxLoss *big.Int
|
|
InsuranceFundUsage *big.Int
|
|
ADLTriggers uint64
|
|
SocializedLosses uint64
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// CircuitBreaker provides emergency controls
|
|
type CircuitBreaker struct {
|
|
Enabled bool
|
|
TriggerConditions map[string]*TriggerCondition
|
|
CooldownPeriod time.Duration
|
|
LastTriggered time.Time
|
|
TriggeredCount uint64
|
|
}
|
|
|
|
// TriggerCondition defines circuit breaker trigger conditions
|
|
type TriggerCondition struct {
|
|
ConditionID string
|
|
Name string
|
|
Check func() bool
|
|
Action func() error
|
|
Severity TriggerSeverity
|
|
Enabled bool
|
|
}
|
|
|
|
// TriggerSeverity represents trigger severity levels
|
|
type TriggerSeverity int
|
|
|
|
const (
|
|
LowSeverity TriggerSeverity = iota
|
|
MediumSeverity
|
|
HighSeverity
|
|
CriticalSeverity
|
|
)
|
|
|
|
// NewLiquidationEngine creates a new liquidation engine
|
|
func NewLiquidationEngine() *LiquidationEngine {
|
|
return &LiquidationEngine{
|
|
InsuranceFund: NewInsuranceFund(),
|
|
LiquidationQueue: NewLiquidationQueue(),
|
|
AutoDeleveraging: NewAutoDeleveragingEngine(),
|
|
SocializedLoss: NewSocializedLossEngine(),
|
|
MaintenanceMargin: initLiquidationMaintenanceMargins(),
|
|
LiquidationFee: 0.005, // 0.5% liquidation fee
|
|
InsuranceFundFee: 0.5, // 50% of liquidation fees go to insurance fund
|
|
Liquidators: make(map[string]*Liquidator),
|
|
LiquidatorRewards: make(map[string]*big.Int),
|
|
TotalLiquidated: big.NewInt(0),
|
|
TotalLosses: big.NewInt(0),
|
|
InsuranceFundUsed: big.NewInt(0),
|
|
LiquidationMetrics: NewLiquidationMetrics(),
|
|
LiquidationHistory: make([]*LiquidationOrder, 0),
|
|
RiskEngine: NewRiskEngine(),
|
|
CircuitBreaker: NewCircuitBreaker(),
|
|
EmergencyMode: false,
|
|
}
|
|
}
|
|
|
|
// NewInsuranceFund creates a new insurance fund
|
|
func NewInsuranceFund() *InsuranceFund {
|
|
return &InsuranceFund{
|
|
Balance: make(map[string]*big.Int),
|
|
TotalValueUSD: big.NewInt(0),
|
|
TargetSize: big.NewInt(10000000), // $10M target
|
|
MinimumSize: big.NewInt(1000000), // $1M minimum
|
|
MaxDrawdown: 0.5, // 50% max drawdown
|
|
Contributions: make([]*FundContribution, 0),
|
|
Withdrawals: make([]*FundWithdrawal, 0),
|
|
LossCoverage: make([]*LossCoverageEvent, 0),
|
|
HighWaterMark: big.NewInt(0),
|
|
FundManagers: make([]string, 0),
|
|
WithdrawalRules: NewWithdrawalRules(),
|
|
LastUpdate: time.Now(),
|
|
}
|
|
}
|
|
|
|
// ProcessLiquidation processes a liquidation
|
|
func (le *LiquidationEngine) ProcessLiquidation(userID string, position *MarginPosition, liquidationOrder *Order) error {
|
|
le.mu.Lock()
|
|
defer le.mu.Unlock()
|
|
|
|
// Check circuit breaker
|
|
if le.CircuitBreaker.Enabled && le.CircuitBreaker.ShouldTrigger() {
|
|
return errors.New("circuit breaker triggered")
|
|
}
|
|
|
|
// Create liquidation order
|
|
liqOrder := &LiquidationOrder{
|
|
OrderID: generateLiquidationOrderID(),
|
|
PositionID: position.ID,
|
|
UserID: userID,
|
|
Symbol: position.Symbol,
|
|
Side: position.Side,
|
|
Size: position.Size,
|
|
MarkPrice: position.MarkPrice,
|
|
LiquidationPrice: position.LiquidationPrice,
|
|
CollateralValue: position.Margin,
|
|
Priority: le.calculatePriority(position),
|
|
Status: LiquidationPending,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Calculate potential loss
|
|
loss := le.calculateLoss(position)
|
|
liqOrder.Loss = loss
|
|
|
|
// Add to queue based on priority
|
|
le.LiquidationQueue.Add(liqOrder)
|
|
|
|
// Process immediately if high priority
|
|
if liqOrder.Priority == HighPriority {
|
|
go le.executeLiquidation(liqOrder)
|
|
}
|
|
|
|
// Update metrics
|
|
le.TotalLiquidations++
|
|
le.LiquidationMetrics.TotalLiquidations++
|
|
|
|
return nil
|
|
}
|
|
|
|
// executeLiquidation executes a liquidation order
|
|
func (le *LiquidationEngine) executeLiquidation(order *LiquidationOrder) error {
|
|
// Update status
|
|
order.Status = LiquidationProcessing
|
|
le.LiquidationQueue.mu.Lock()
|
|
le.LiquidationQueue.ProcessingOrders[order.OrderID] = order
|
|
le.LiquidationQueue.mu.Unlock()
|
|
|
|
// Find liquidator
|
|
liquidator := le.findBestLiquidator(order)
|
|
if liquidator == nil {
|
|
// No liquidator available, try ADL
|
|
return le.triggerADL(order)
|
|
}
|
|
|
|
// Execute liquidation
|
|
executionPrice, err := le.executeTrade(order, liquidator)
|
|
if err != nil {
|
|
order.Status = LiquidationFailed
|
|
return err
|
|
}
|
|
|
|
order.ExecutionPrice = executionPrice
|
|
order.LiquidatorID = liquidator.LiquidatorID
|
|
|
|
// Calculate fees
|
|
orderValue := big.NewInt(int64(order.Size * executionPrice))
|
|
liquidationFee := new(big.Int).Mul(orderValue, big.NewInt(int64(le.LiquidationFee*1000)))
|
|
liquidationFee.Div(liquidationFee, big.NewInt(1000))
|
|
order.LiquidationFee = liquidationFee
|
|
|
|
// Distribute fees
|
|
insuranceFundPortion := new(big.Int).Mul(liquidationFee, big.NewInt(int64(le.InsuranceFundFee*1000)))
|
|
insuranceFundPortion.Div(insuranceFundPortion, big.NewInt(1000))
|
|
le.InsuranceFund.AddContribution(order.Symbol, insuranceFundPortion)
|
|
|
|
// Handle loss if any
|
|
if order.Loss.Cmp(big.NewInt(0)) > 0 {
|
|
if err := le.handleLoss(order); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Update liquidator stats
|
|
liquidator.CompletedCount++
|
|
liquidator.TotalProfit.Add(liquidator.TotalProfit, liquidationFee)
|
|
liquidator.LastActive = time.Now()
|
|
|
|
// Complete order
|
|
order.Status = LiquidationComplete
|
|
order.ProcessedAt = &[]time.Time{time.Now()}[0]
|
|
|
|
// Move to completed
|
|
le.LiquidationQueue.mu.Lock()
|
|
delete(le.LiquidationQueue.ProcessingOrders, order.OrderID)
|
|
le.LiquidationQueue.CompletedOrders[order.OrderID] = order
|
|
le.LiquidationQueue.mu.Unlock()
|
|
|
|
// Update metrics
|
|
le.TotalLiquidated.Add(le.TotalLiquidated, orderValue)
|
|
le.LiquidationMetrics.SuccessfulLiquidations++
|
|
le.LiquidationMetrics.TotalVolume.Add(le.LiquidationMetrics.TotalVolume, orderValue)
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleLoss handles losses from liquidation
|
|
func (le *LiquidationEngine) handleLoss(order *LiquidationOrder) error {
|
|
// First try to cover from insurance fund
|
|
if le.InsuranceFund.CanCoverLoss(order.Symbol, order.Loss) {
|
|
return le.InsuranceFund.CoverLoss(order.Symbol, order.Loss, order.OrderID)
|
|
}
|
|
|
|
// If insurance fund insufficient, check for ADL
|
|
insuranceRatio := le.InsuranceFund.GetCoverageRatio()
|
|
if insuranceRatio < le.AutoDeleveraging.ADLThreshold {
|
|
return le.triggerADL(order)
|
|
}
|
|
|
|
// Last resort: socialized loss
|
|
return le.SocializedLoss.DistributeLoss(order)
|
|
}
|
|
|
|
// triggerADL triggers auto-deleveraging
|
|
func (le *LiquidationEngine) triggerADL(order *LiquidationOrder) error {
|
|
return le.AutoDeleveraging.Execute(order)
|
|
}
|
|
|
|
// findBestLiquidator finds the best liquidator for an order
|
|
func (le *LiquidationEngine) findBestLiquidator(order *LiquidationOrder) *Liquidator {
|
|
var bestLiquidator *Liquidator
|
|
bestScore := 0.0
|
|
|
|
for _, liquidator := range le.Liquidators {
|
|
// Check if liquidator has sufficient balance
|
|
requiredBalance := big.NewInt(int64(order.Size * order.MarkPrice))
|
|
if liquidator.Balance.Cmp(requiredBalance) < 0 {
|
|
continue
|
|
}
|
|
|
|
// Calculate liquidator score
|
|
score := le.calculateLiquidatorScore(liquidator, order)
|
|
if score > bestScore {
|
|
bestScore = score
|
|
bestLiquidator = liquidator
|
|
}
|
|
}
|
|
|
|
return bestLiquidator
|
|
}
|
|
|
|
// calculateLiquidatorScore calculates a liquidator's score
|
|
func (le *LiquidationEngine) calculateLiquidatorScore(liquidator *Liquidator, order *LiquidationOrder) float64 {
|
|
score := 0.0
|
|
|
|
// Factor in reputation
|
|
score += liquidator.Reputation * 0.3
|
|
|
|
// Factor in success rate
|
|
score += liquidator.SuccessRate * 0.3
|
|
|
|
// Factor in tier
|
|
tierBonus := float64(liquidator.Tier) * 0.1
|
|
score += tierBonus
|
|
|
|
// Factor in recent activity
|
|
timeSinceActive := time.Since(liquidator.LastActive).Hours()
|
|
if timeSinceActive < 1 {
|
|
score += 0.2
|
|
} else if timeSinceActive < 24 {
|
|
score += 0.1
|
|
}
|
|
|
|
// Factor in balance (more balance = higher score)
|
|
balanceRatio := new(big.Float).SetInt(liquidator.Balance)
|
|
balanceRatio.Quo(balanceRatio, big.NewFloat(1000000)) // Normalize to millions
|
|
balanceScore, _ := balanceRatio.Float64()
|
|
score += math.Min(balanceScore*0.1, 0.2) // Cap at 0.2
|
|
|
|
return score
|
|
}
|
|
|
|
// calculatePriority calculates liquidation priority
|
|
func (le *LiquidationEngine) calculatePriority(position *MarginPosition) LiquidationPriority {
|
|
// Calculate how far below liquidation price
|
|
currentPrice := position.MarkPrice
|
|
liquidationPrice := position.LiquidationPrice
|
|
|
|
var priceRatio float64
|
|
if position.Side == Buy {
|
|
priceRatio = currentPrice / liquidationPrice
|
|
} else {
|
|
priceRatio = liquidationPrice / currentPrice
|
|
}
|
|
|
|
// High priority if well below liquidation price
|
|
if priceRatio < 0.95 {
|
|
return HighPriority
|
|
} else if priceRatio < 0.98 {
|
|
return MediumPriority
|
|
}
|
|
return LowPriority
|
|
}
|
|
|
|
// calculateLoss calculates the loss from a position
|
|
func (le *LiquidationEngine) calculateLoss(position *MarginPosition) *big.Int {
|
|
// If UnrealizedPnL is positive or zero, no loss
|
|
if position.UnrealizedPnL.Sign() >= 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
|
|
// UnrealizedPnL is negative, calculate loss
|
|
// Loss = abs(UnrealizedPnL) - Margin
|
|
absLoss := new(big.Int).Abs(position.UnrealizedPnL)
|
|
|
|
// If loss is less than margin, no uncovered loss
|
|
if absLoss.Cmp(position.Margin) <= 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
|
|
// Return the uncovered loss (amount exceeding margin)
|
|
return new(big.Int).Sub(absLoss, position.Margin)
|
|
}
|
|
|
|
// executeTrade executes the liquidation trade
|
|
func (le *LiquidationEngine) executeTrade(order *LiquidationOrder, liquidator *Liquidator) (float64, error) {
|
|
// Simulate trade execution
|
|
// In production, this would interact with the order book
|
|
slippage := 0.002 // 0.2% slippage
|
|
var executionPrice float64
|
|
|
|
if order.Side == Buy {
|
|
// Liquidating a long position, need to sell
|
|
executionPrice = order.MarkPrice * (1 - slippage)
|
|
} else {
|
|
// Liquidating a short position, need to buy
|
|
executionPrice = order.MarkPrice * (1 + slippage)
|
|
}
|
|
|
|
return executionPrice, nil
|
|
}
|
|
|
|
// InsuranceFund methods
|
|
|
|
func (fund *InsuranceFund) AddContribution(asset string, amount *big.Int) {
|
|
fund.mu.Lock()
|
|
defer fund.mu.Unlock()
|
|
|
|
if fund.Balance[asset] == nil {
|
|
fund.Balance[asset] = big.NewInt(0)
|
|
}
|
|
fund.Balance[asset].Add(fund.Balance[asset], amount)
|
|
fund.TotalValueUSD.Add(fund.TotalValueUSD, amount) // Simplified, should convert to USD
|
|
|
|
// Update high water mark
|
|
if fund.TotalValueUSD.Cmp(fund.HighWaterMark) > 0 {
|
|
fund.HighWaterMark.Set(fund.TotalValueUSD)
|
|
}
|
|
|
|
fund.LastUpdate = time.Now()
|
|
}
|
|
|
|
func (fund *InsuranceFund) CanCoverLoss(asset string, loss *big.Int) bool {
|
|
fund.mu.RLock()
|
|
defer fund.mu.RUnlock()
|
|
|
|
return fund.canCoverLossInternal(asset, loss)
|
|
}
|
|
|
|
func (fund *InsuranceFund) canCoverLossInternal(asset string, loss *big.Int) bool {
|
|
balance := fund.Balance[asset]
|
|
if balance == nil {
|
|
return false
|
|
}
|
|
|
|
return balance.Cmp(loss) >= 0
|
|
}
|
|
|
|
func (fund *InsuranceFund) CoverLoss(asset string, loss *big.Int, referenceID string) error {
|
|
fund.mu.Lock()
|
|
defer fund.mu.Unlock()
|
|
|
|
if !fund.canCoverLossInternal(asset, loss) {
|
|
return errors.New("insufficient insurance fund balance")
|
|
}
|
|
|
|
fund.Balance[asset].Sub(fund.Balance[asset], loss)
|
|
fund.TotalValueUSD.Sub(fund.TotalValueUSD, loss) // Simplified
|
|
|
|
// Record loss coverage event
|
|
event := &LossCoverageEvent{
|
|
EventID: generateEventID(),
|
|
Asset: asset,
|
|
Amount: loss,
|
|
ReferenceID: referenceID,
|
|
Timestamp: time.Now(),
|
|
}
|
|
fund.LossCoverage = append(fund.LossCoverage, event)
|
|
|
|
// Update drawdown
|
|
if fund.HighWaterMark.Cmp(big.NewInt(0)) > 0 {
|
|
drawdown := new(big.Float).SetInt(fund.HighWaterMark)
|
|
drawdown.Sub(drawdown, new(big.Float).SetInt(fund.TotalValueUSD))
|
|
drawdown.Quo(drawdown, new(big.Float).SetInt(fund.HighWaterMark))
|
|
fund.CurrentDrawdown, _ = drawdown.Float64()
|
|
}
|
|
|
|
fund.LastUpdate = time.Now()
|
|
return nil
|
|
}
|
|
|
|
func (fund *InsuranceFund) GetCoverageRatio() float64 {
|
|
fund.mu.RLock()
|
|
defer fund.mu.RUnlock()
|
|
|
|
if fund.TargetSize.Cmp(big.NewInt(0)) == 0 {
|
|
return 0
|
|
}
|
|
|
|
ratio := new(big.Float).SetInt(fund.TotalValueUSD)
|
|
ratio.Quo(ratio, new(big.Float).SetInt(fund.TargetSize))
|
|
result, _ := ratio.Float64()
|
|
return result
|
|
}
|
|
|
|
// LiquidationQueue methods
|
|
|
|
func NewLiquidationQueue() *LiquidationQueue {
|
|
return &LiquidationQueue{
|
|
HighPriority: make([]*LiquidationOrder, 0),
|
|
MediumPriority: make([]*LiquidationOrder, 0),
|
|
LowPriority: make([]*LiquidationOrder, 0),
|
|
Orders: make([]*LiquidationOrder, 0), // For testing compatibility
|
|
ProcessingOrders: make(map[string]*LiquidationOrder),
|
|
CompletedOrders: make(map[string]*LiquidationOrder),
|
|
FailedOrders: make(map[string]*LiquidationOrder),
|
|
MaxQueueSize: 10000,
|
|
ProcessingInterval: 100 * time.Millisecond,
|
|
RetryAttempts: 3,
|
|
}
|
|
}
|
|
|
|
func (queue *LiquidationQueue) Add(order *LiquidationOrder) {
|
|
queue.mu.Lock()
|
|
defer queue.mu.Unlock()
|
|
|
|
switch order.Priority {
|
|
case HighPriority:
|
|
queue.HighPriority = append(queue.HighPriority, order)
|
|
case MediumPriority:
|
|
queue.MediumPriority = append(queue.MediumPriority, order)
|
|
case LowPriority:
|
|
queue.LowPriority = append(queue.LowPriority, order)
|
|
}
|
|
}
|
|
|
|
// AutoDeleveraging methods
|
|
|
|
func NewAutoDeleveragingEngine() *AutoDeleveragingEngine {
|
|
return &AutoDeleveragingEngine{
|
|
ADLQueue: make(map[string][]*ADLCandidate),
|
|
ADLThreshold: 0.2, // Trigger ADL when insurance fund < 20% of target
|
|
MaxADLPercentage: 0.5, // Maximum 50% position reduction
|
|
ADLEvents: make([]*ADLEvent, 0),
|
|
TotalADLVolume: big.NewInt(0),
|
|
}
|
|
}
|
|
|
|
func (adl *AutoDeleveragingEngine) Execute(order *LiquidationOrder) error {
|
|
adl.mu.Lock()
|
|
defer adl.mu.Unlock()
|
|
|
|
// Get ADL candidates for the symbol
|
|
candidates := adl.ADLQueue[order.Symbol]
|
|
if len(candidates) == 0 {
|
|
return errors.New("no ADL candidates available")
|
|
}
|
|
|
|
// Sort candidates by PnL ranking (highest profit first)
|
|
// In production, implement proper sorting
|
|
|
|
event := &ADLEvent{
|
|
EventID: generateEventID(),
|
|
Timestamp: time.Now(),
|
|
TriggerReason: fmt.Sprintf("Insurance fund below threshold for %s", order.OrderID),
|
|
AffectedPositions: make([]*ADLAffectedPosition, 0),
|
|
}
|
|
|
|
remainingSize := order.Size
|
|
for _, candidate := range candidates {
|
|
if remainingSize <= 0 {
|
|
break
|
|
}
|
|
|
|
// Calculate reduction
|
|
reductionSize := math.Min(candidate.PositionSize*adl.MaxADLPercentage, remainingSize)
|
|
reductionPercentage := reductionSize / candidate.PositionSize
|
|
|
|
// Create affected position record
|
|
affected := &ADLAffectedPosition{
|
|
PositionID: candidate.PositionID,
|
|
UserID: candidate.UserID,
|
|
OriginalSize: candidate.PositionSize,
|
|
ReducedSize: reductionSize,
|
|
ReductionPercentage: reductionPercentage,
|
|
CompensationPaid: big.NewInt(0), // Calculate compensation
|
|
}
|
|
|
|
event.AffectedPositions = append(event.AffectedPositions, affected)
|
|
remainingSize -= reductionSize
|
|
}
|
|
|
|
adl.ADLEvents = append(adl.ADLEvents, event)
|
|
return nil
|
|
}
|
|
|
|
// SocializedLoss methods
|
|
|
|
func NewSocializedLossEngine() *SocializedLossEngine {
|
|
return &SocializedLossEngine{
|
|
PendingLosses: make(map[string]*SocializedLoss),
|
|
DistributedLosses: make(map[string]*SocializedLoss),
|
|
LossThreshold: big.NewInt(100000), // $100k minimum for socialized loss
|
|
MaxLossPerUser: 0.1, // Maximum 10% loss per user
|
|
LossHistory: make([]*SocializedLossEvent, 0),
|
|
TotalSocialized: big.NewInt(0),
|
|
}
|
|
}
|
|
|
|
func (sl *SocializedLossEngine) DistributeLoss(order *LiquidationOrder) error {
|
|
sl.mu.Lock()
|
|
defer sl.mu.Unlock()
|
|
|
|
if order.Loss.Cmp(sl.LossThreshold) < 0 {
|
|
return errors.New("loss below socialization threshold")
|
|
}
|
|
|
|
loss := &SocializedLoss{
|
|
LossID: generateLossID(),
|
|
TotalLoss: order.Loss,
|
|
AffectedAsset: order.Symbol,
|
|
DistributionMethod: "proportional",
|
|
AffectedUsers: make(map[string]*UserLossShare),
|
|
Timestamp: time.Now(),
|
|
Status: LossPending,
|
|
}
|
|
|
|
sl.PendingLosses[loss.LossID] = loss
|
|
|
|
// In production, would calculate and distribute loss shares
|
|
// based on user positions and risk profile
|
|
|
|
return nil
|
|
}
|
|
|
|
// CircuitBreaker methods
|
|
|
|
func NewCircuitBreaker() *CircuitBreaker {
|
|
return &CircuitBreaker{
|
|
Enabled: true,
|
|
TriggerConditions: initTriggerConditions(),
|
|
CooldownPeriod: 5 * time.Minute,
|
|
TriggeredCount: 0,
|
|
}
|
|
}
|
|
|
|
func (cb *CircuitBreaker) ShouldTrigger() bool {
|
|
if !cb.Enabled {
|
|
return false
|
|
}
|
|
|
|
// Check cooldown
|
|
if time.Since(cb.LastTriggered) < cb.CooldownPeriod {
|
|
return false
|
|
}
|
|
|
|
// Check trigger conditions
|
|
for _, condition := range cb.TriggerConditions {
|
|
if condition.Enabled && condition.Check() {
|
|
cb.LastTriggered = time.Now()
|
|
cb.TriggeredCount++
|
|
go condition.Action() // Execute action asynchronously
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func initLiquidationMaintenanceMargins() map[string]float64 {
|
|
return map[string]float64{
|
|
"BTC-USDT": 0.005,
|
|
"ETH-USDT": 0.01,
|
|
"BNB-USDT": 0.02,
|
|
"SOL-USDT": 0.025,
|
|
"AVAX-USDT": 0.025,
|
|
}
|
|
}
|
|
|
|
func initTriggerConditions() map[string]*TriggerCondition {
|
|
return map[string]*TriggerCondition{
|
|
"high_liquidation_rate": {
|
|
ConditionID: "high_liq_rate",
|
|
Name: "High Liquidation Rate",
|
|
Check: func() bool {
|
|
// Check if liquidation rate is too high
|
|
return false // Placeholder
|
|
},
|
|
Action: func() error {
|
|
// Pause new positions
|
|
return nil
|
|
},
|
|
Severity: HighSeverity,
|
|
Enabled: true,
|
|
},
|
|
"insurance_fund_depleted": {
|
|
ConditionID: "ins_fund_depl",
|
|
Name: "Insurance Fund Depleted",
|
|
Check: func() bool {
|
|
// Check if insurance fund is critically low
|
|
return false // Placeholder
|
|
},
|
|
Action: func() error {
|
|
// Enter emergency mode
|
|
return nil
|
|
},
|
|
Severity: CriticalSeverity,
|
|
Enabled: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func generateLiquidationOrderID() string {
|
|
return fmt.Sprintf("liq_%d", time.Now().UnixNano())
|
|
}
|
|
|
|
func generateEventID() string {
|
|
return fmt.Sprintf("event_%d", time.Now().UnixNano())
|
|
}
|
|
|
|
func generateLossID() string {
|
|
return fmt.Sprintf("loss_%d", time.Now().UnixNano())
|
|
}
|
|
|
|
func NewLiquidationMetrics() *LiquidationMetrics {
|
|
return &LiquidationMetrics{
|
|
TotalVolume: big.NewInt(0),
|
|
AverageLoss: big.NewInt(0),
|
|
MaxLoss: big.NewInt(0),
|
|
InsuranceFundUsage: big.NewInt(0),
|
|
LastUpdate: time.Now(),
|
|
}
|
|
}
|
|
|
|
func NewWithdrawalRules() *WithdrawalRules {
|
|
return &WithdrawalRules{
|
|
MinBalance: big.NewInt(1000000),
|
|
MaxWithdrawal: big.NewInt(100000),
|
|
CooldownPeriod: 24 * time.Hour,
|
|
RequiredVotes: 3,
|
|
}
|
|
}
|
|
|
|
// Additional types
|
|
|
|
type FundContribution struct {
|
|
ContributionID string
|
|
Source string
|
|
Asset string
|
|
Amount *big.Int
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type FundWithdrawal struct {
|
|
WithdrawalID string
|
|
Recipient string
|
|
Asset string
|
|
Amount *big.Int
|
|
Reason string
|
|
ApprovedBy []string
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type LossCoverageEvent struct {
|
|
EventID string
|
|
Asset string
|
|
Amount *big.Int
|
|
ReferenceID string
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type WithdrawalRules struct {
|
|
MinBalance *big.Int
|
|
MaxWithdrawal *big.Int
|
|
CooldownPeriod time.Duration
|
|
RequiredVotes int
|
|
}
|
|
|
|
type SocializedLossEvent struct {
|
|
EventID string
|
|
LossID string
|
|
TotalLoss *big.Int
|
|
UsersAffected int
|
|
Timestamp time.Time
|
|
}
|