Files
dex/pkg/lx/margin_test.go
T
zeekay ccdd706ce0 refactor: simplify file naming - remove redundant suffixes
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.
2025-12-11 17:09:28 -08:00

869 lines
24 KiB
Go

package lx
import (
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestMarginEngine(t *testing.T) {
t.Run("CreateMarginAccount", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
// Create cross margin account
account, err := engine.CreateMarginAccount("user1", CrossMargin)
assert.NoError(t, err)
assert.NotNil(t, account)
assert.Equal(t, "user1", account.UserID)
assert.Equal(t, CrossMargin, account.AccountType)
assert.Equal(t, 10.0, account.MaxLeverage)
// Try to create duplicate account
_, err = engine.CreateMarginAccount("user1", CrossMargin)
assert.Error(t, err)
// Create isolated margin account
account2, err := engine.CreateMarginAccount("user2", IsolatedMargin)
assert.NoError(t, err)
assert.Equal(t, 20.0, account2.MaxLeverage)
// Create portfolio margin account
account3, err := engine.CreateMarginAccount("user3", PortfolioMargin)
assert.NoError(t, err)
assert.Equal(t, 100.0, account3.MaxLeverage)
assert.True(t, account3.PortfolioMarginMode)
})
t.Run("GetAccount", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
engine.CreateMarginAccount("user1", CrossMargin)
account := engine.GetAccount("user1")
assert.NotNil(t, account)
assert.Equal(t, "user1", account.UserID)
// Non-existent account
account2 := engine.GetAccount("nonexistent")
assert.Nil(t, account2)
})
t.Run("DepositCollateral", func(t *testing.T) {
oracle := NewPriceOracle()
// Set price through CurrentPrices map
oracle.CurrentPrices = make(map[string]*PriceData)
oracle.CurrentPrices["BTC"] = &PriceData{Price: 50000.0}
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
engine.CreateMarginAccount("user1", CrossMargin)
// Deposit BTC collateral
amount := big.NewInt(1000000) // 0.01 BTC in satoshis
err := engine.DepositCollateral("user1", "BTC", amount)
assert.NoError(t, err)
account := engine.GetAccount("user1")
collateral := account.CollateralAssets["BTC"]
assert.NotNil(t, collateral)
assert.Equal(t, amount, collateral.Amount)
assert.Equal(t, amount, collateral.Available)
// Test non-existent account
err = engine.DepositCollateral("nonexistent", "BTC", amount)
assert.Error(t, err)
})
t.Run("OpenPosition", func(t *testing.T) {
oracle := NewPriceOracle()
oracle.CurrentPrices = make(map[string]*PriceData)
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 50000.0}
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
// Deposit enough collateral to not need borrowing
collateral := big.NewInt(1000000) // $10,000 USDT
engine.DepositCollateral("user1", "USDT", collateral)
account.FreeMargin = collateral // Set free margin
// Open position with low leverage (no borrowing needed)
order := &Order{
Symbol: "BTC-USDT",
Side: Buy,
Price: 50000.0,
Size: 0.01,
User: "user1",
}
position, err := engine.OpenPosition("user1", order, 1.0) // Low leverage
assert.NoError(t, err)
assert.NotNil(t, position)
assert.Equal(t, "BTC-USDT", position.Symbol)
assert.Equal(t, Buy, position.Side)
assert.Equal(t, 0.01, position.Size)
assert.Equal(t, 1.0, position.Leverage)
// Test excessive leverage
order2 := &Order{
Symbol: "BTC-USDT",
Side: Buy,
Price: 50000.0,
Size: 0.01,
User: "user1",
}
_, err = engine.OpenPosition("user1", order2, 200.0)
assert.Error(t, err)
assert.Contains(t, err.Error(), "leverage exceeds maximum")
// Test insufficient margin - adjust test to use up free margin first
account.FreeMargin = big.NewInt(100) // Very small free margin
order3 := &Order{
Symbol: "BTC-USDT",
Side: Buy,
Price: 50000.0,
Size: 1.0,
User: "user1",
}
_, err = engine.OpenPosition("user1", order3, 1.0)
assert.Error(t, err)
// Error could be either insufficient margin or insufficient liquidity
assert.True(t, err != nil)
})
t.Run("ClosePosition", func(t *testing.T) {
oracle := NewPriceOracle()
oracle.CurrentPrices = make(map[string]*PriceData)
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 50000.0}
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
collateral := big.NewInt(1000000) // Large collateral
engine.DepositCollateral("user1", "USDT", collateral)
account.FreeMargin = collateral
account.Balance = collateral
// Open position with low leverage
order := &Order{
Symbol: "BTC-USDT",
Side: Buy,
Price: 50000.0,
Size: 0.01,
User: "user1",
}
position, err := engine.OpenPosition("user1", order, 1.0)
if err != nil {
t.Skipf("Skipping ClosePosition test due to OpenPosition error: %v", err)
return
}
// Update price for profit
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 51000.0}
// Close position
err = engine.ClosePosition("user1", position.ID, 0.01)
assert.NoError(t, err)
// Check position was removed
assert.NotContains(t, account.Positions, position.ID)
// Test closing non-existent position
err = engine.ClosePosition("user1", "nonexistent", 0.01)
assert.Error(t, err)
// Test closing for non-existent account
err = engine.ClosePosition("nonexistent", position.ID, 0.01)
assert.Error(t, err)
})
t.Run("ModifyLeverage", func(t *testing.T) {
oracle := NewPriceOracle()
oracle.CurrentPrices = make(map[string]*PriceData)
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 50000.0}
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
collateral := big.NewInt(1000000) // Large collateral
engine.DepositCollateral("user1", "USDT", collateral)
account.FreeMargin = collateral
// Open position with low leverage
order := &Order{
Symbol: "BTC-USDT",
Side: Buy,
Price: 50000.0,
Size: 0.01,
User: "user1",
}
position, err := engine.OpenPosition("user1", order, 2.0)
if err != nil {
t.Skipf("Skipping ModifyLeverage test due to OpenPosition error: %v", err)
return
}
// Increase leverage
err = engine.ModifyLeverage("user1", position.ID, 20.0)
assert.NoError(t, err)
assert.Equal(t, 20.0, position.Leverage)
// Decrease leverage
err = engine.ModifyLeverage("user1", position.ID, 5.0)
assert.NoError(t, err)
assert.Equal(t, 5.0, position.Leverage)
// Test excessive leverage
err = engine.ModifyLeverage("user1", position.ID, 200.0)
assert.Error(t, err)
// Test non-existent position
err = engine.ModifyLeverage("user1", "nonexistent", 10.0)
assert.Error(t, err)
})
t.Run("CalculateLiquidationPrice", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
// Long position
longPosition := &MarginPosition{
Symbol: "BTC-USDT",
Side: Buy,
EntryPrice: 50000.0,
Leverage: 10.0,
}
liquidationPrice := engine.calculateLiquidationPrice(longPosition, account)
assert.Greater(t, longPosition.EntryPrice, liquidationPrice)
// Short position
shortPosition := &MarginPosition{
Symbol: "BTC-USDT",
Side: Sell,
EntryPrice: 50000.0,
Leverage: 10.0,
}
liquidationPrice = engine.calculateLiquidationPrice(shortPosition, account)
assert.Less(t, shortPosition.EntryPrice, liquidationPrice)
})
t.Run("UpdateAccountMetrics", func(t *testing.T) {
oracle := NewPriceOracle()
oracle.CurrentPrices = make(map[string]*PriceData)
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 50000.0}
oracle.CurrentPrices["BTC"] = &PriceData{Price: 50000.0}
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
// Add collateral
btcAmount := big.NewInt(1000000) // 0.01 BTC
engine.DepositCollateral("user1", "BTC", btcAmount)
// Add position
position := &MarginPosition{
Symbol: "BTC-USDT",
Side: Buy,
Size: 0.01,
EntryPrice: 50000.0,
UnrealizedPnL: big.NewInt(0),
}
account.Positions["pos1"] = position
account.MarginUsed = big.NewInt(500)
// Update metrics
engine.updateAccountMetrics(account)
// Check metrics were updated
assert.NotNil(t, account.Equity)
assert.GreaterOrEqual(t, account.MarginLevel, 0.0)
assert.NotZero(t, account.LastUpdate)
})
t.Run("CheckLiquidations", func(t *testing.T) {
oracle := NewPriceOracle()
oracle.CurrentPrices = make(map[string]*PriceData)
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 50000.0}
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
account.MarginLevel = 90.0 // Below liquidation level
account.LiquidationLevel = 100.0
// Add position
position := &MarginPosition{
ID: "pos1",
Symbol: "BTC-USDT",
Side: Buy,
Size: 0.01,
EntryPrice: 50000.0,
LiquidationPrice: 45000.0,
UnrealizedPnL: big.NewInt(-1000),
Margin: big.NewInt(500),
}
account.Positions["pos1"] = position
// Run liquidation check
engine.CheckLiquidations()
// For isolated margin, check position-specific liquidation
account2, _ := engine.CreateMarginAccount("user2", IsolatedMargin)
position2 := &MarginPosition{
ID: "pos2",
Symbol: "BTC-USDT",
Side: Buy,
Size: 0.01,
EntryPrice: 50000.0,
LiquidationPrice: 51000.0, // Price below liquidation
UnrealizedPnL: big.NewInt(-1000),
Margin: big.NewInt(500),
}
account2.Positions["pos2"] = position2
oracle.CurrentPrices["BTC-USDT"] = &PriceData{Price: 45000.0} // Trigger liquidation
engine.CheckLiquidations()
})
t.Run("BorrowForPosition", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
// Create pool and add liquidity to lending pool
poolConfig := PoolConfig{
ReserveFactor: 0.1,
CollateralFactor: 0.75,
LiquidationThreshold: 0.85,
LiquidationPenalty: 0.05,
MaxBorrowRate: 0.5,
MinBorrowRate: 0.02,
OptimalUtilization: 0.8,
}
engine.LendingPool.CreatePool("BTC-USDT", poolConfig)
engine.LendingPool.Supply("lender1", "BTC-USDT", big.NewInt(1000000))
// Borrow for position
borrowAmount := big.NewInt(5000)
err := engine.borrowForPosition(account, "BTC-USDT", borrowAmount)
assert.NoError(t, err)
// Check borrowed amount
borrowed := account.BorrowedAmounts["BTC-USDT"]
assert.NotNil(t, borrowed)
assert.Equal(t, borrowAmount, borrowed.Amount)
// Test insufficient liquidity
hugeBorrow := big.NewInt(10000000)
err = engine.borrowForPosition(account, "BTC-USDT", hugeBorrow)
assert.Error(t, err)
assert.Contains(t, err.Error(), "insufficient liquidity")
})
t.Run("RepayBorrowed", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
// Create pool first
poolConfig := PoolConfig{
ReserveFactor: 0.1,
CollateralFactor: 0.75,
LiquidationThreshold: 0.85,
LiquidationPenalty: 0.05,
MaxBorrowRate: 0.5,
MinBorrowRate: 0.02,
OptimalUtilization: 0.8,
}
engine.LendingPool.CreatePool("BTC-USDT", poolConfig)
// Setup borrowed asset
borrowed := &BorrowedAsset{
Asset: "BTC-USDT",
Amount: big.NewInt(5000),
AccruedInterest: big.NewInt(0),
InterestRate: 0.1,
BorrowTime: time.Now(),
LastUpdate: time.Now(),
}
account.BorrowedAmounts["BTC-USDT"] = borrowed
account.BorrowingFees = big.NewInt(0)
// Initialize total borrowed
engine.TotalBorrowed["BTC-USDT"] = big.NewInt(5000)
engine.LendingPool.Supply("lender1", "BTC-USDT", big.NewInt(100000))
// Repay
repayAmount := big.NewInt(2500)
err := engine.repayBorrowed(account, "BTC-USDT", repayAmount)
assert.NoError(t, err)
// Check remaining borrowed
assert.Equal(t, big.NewInt(2500), borrowed.Amount)
// Repay non-existent borrow
err = engine.repayBorrowed(account, "ETH-USDT", repayAmount)
assert.Error(t, err)
})
t.Run("CalculateAccruedInterest", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
borrowed := &BorrowedAsset{
Asset: "BTC-USDT",
Amount: big.NewInt(10000),
InterestRate: 0.1, // 10% annual
LastUpdate: time.Now().Add(-24 * time.Hour), // 1 day ago
}
interest := engine.calculateAccruedInterest(borrowed)
assert.NotNil(t, interest)
assert.Greater(t, interest.Int64(), int64(0))
})
t.Run("GetMaxLeverage", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
// Test known symbol
leverage := engine.getMaxLeverage("BTC-USDT", CrossMargin)
assert.Equal(t, 100.0, leverage)
// Test portfolio margin (doubles leverage)
leverage = engine.getMaxLeverage("BTC-USDT", PortfolioMargin)
assert.Equal(t, 200.0, leverage)
// Test unknown symbol
leverage = engine.getMaxLeverage("UNKNOWN-PAIR", CrossMargin)
assert.Equal(t, 10.0, leverage) // Default
})
t.Run("GetAssetHaircut", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
// Test BTC haircut
haircut := engine.getAssetHaircut("BTC")
assert.Equal(t, 0.10, haircut)
// Test stablecoin (no haircut)
haircut = engine.getAssetHaircut("USDT")
assert.Equal(t, 0.00, haircut)
// Test unknown asset
haircut = engine.getAssetHaircut("UNKNOWN")
assert.Equal(t, 0.20, haircut) // Default
})
t.Run("GetAssetLTV", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
// Test BTC LTV
ltv := engine.getAssetLTV("BTC")
assert.Equal(t, 0.80, ltv)
// Test stablecoin LTV
ltv = engine.getAssetLTV("USDT")
assert.Equal(t, 0.95, ltv)
// Test unknown asset
ltv = engine.getAssetLTV("UNKNOWN")
assert.Equal(t, 0.50, ltv) // Default
})
t.Run("ShouldLiquidatePosition", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
// Long position
longPosition := &MarginPosition{
Side: Buy,
LiquidationPrice: 45000.0,
}
// Price above liquidation - should not liquidate
assert.False(t, engine.shouldLiquidatePosition(longPosition, 46000.0))
// Price at liquidation - should liquidate
assert.True(t, engine.shouldLiquidatePosition(longPosition, 45000.0))
// Price below liquidation - should liquidate
assert.True(t, engine.shouldLiquidatePosition(longPosition, 44000.0))
// Short position
shortPosition := &MarginPosition{
Side: Sell,
LiquidationPrice: 55000.0,
}
// Price below liquidation - should not liquidate
assert.False(t, engine.shouldLiquidatePosition(shortPosition, 54000.0))
// Price at liquidation - should liquidate
assert.True(t, engine.shouldLiquidatePosition(shortPosition, 55000.0))
// Price above liquidation - should liquidate
assert.True(t, engine.shouldLiquidatePosition(shortPosition, 56000.0))
})
t.Run("LiquidatePosition", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
// Create position with negative PnL
position := &MarginPosition{
ID: "pos1",
Symbol: "BTC-USDT",
Side: Buy,
Size: 0.01,
UnrealizedPnL: big.NewInt(-1000),
Margin: big.NewInt(500),
}
account.Positions["pos1"] = position
// Liquidate
engine.liquidatePosition("user1", "pos1", position)
// Check position was removed
assert.NotContains(t, account.Positions, "pos1")
})
t.Run("LiquidateAccount", func(t *testing.T) {
oracle := NewPriceOracle()
riskEngine := NewRiskEngine()
engine := NewMarginEngine(oracle, riskEngine)
account, _ := engine.CreateMarginAccount("user1", CrossMargin)
account.Equity = big.NewInt(1000)
account.MarginUsed = big.NewInt(500)
// Add positions
position1 := &MarginPosition{
ID: "pos1",
Symbol: "BTC-USDT",
Side: Buy,
Size: 0.01,
UnrealizedPnL: big.NewInt(-500),
Margin: big.NewInt(250),
}
position2 := &MarginPosition{
ID: "pos2",
Symbol: "ETH-USDT",
Side: Sell,
Size: 0.1,
UnrealizedPnL: big.NewInt(-300),
Margin: big.NewInt(250),
}
account.Positions["pos1"] = position1
account.Positions["pos2"] = position2
// Liquidate account
engine.liquidateAccount("user1", account)
// Check all positions removed
assert.Empty(t, account.Positions)
assert.Equal(t, big.NewInt(0), account.MarginUsed)
assert.Equal(t, 0.0, account.Leverage)
})
t.Run("GeneratePositionID", func(t *testing.T) {
id1 := generatePositionID()
id2 := generatePositionID()
assert.NotEmpty(t, id1)
assert.NotEmpty(t, id2)
assert.NotEqual(t, id1, id2)
assert.Contains(t, id1, "pos_")
})
t.Run("OppositeSide", func(t *testing.T) {
assert.Equal(t, Sell, oppositeSide(Buy))
assert.Equal(t, Buy, oppositeSide(Sell))
})
}
// TestLendingPool tests lending pool functionality
func TestLendingPool(t *testing.T) {
t.Run("NewLendingPool", func(t *testing.T) {
pool := NewLendingPool()
assert.NotNil(t, pool)
assert.NotNil(t, pool.Pools)
assert.NotNil(t, pool.Suppliers)
assert.NotNil(t, pool.Borrowers)
assert.NotNil(t, pool.TotalSupplied)
assert.NotNil(t, pool.TotalBorrowed)
})
t.Run("Supply", func(t *testing.T) {
pool := NewLendingPool()
// Create pool first
poolConfig := PoolConfig{
ReserveFactor: 0.1,
CollateralFactor: 0.75,
LiquidationThreshold: 0.85,
LiquidationPenalty: 0.05,
MaxBorrowRate: 0.5,
MinBorrowRate: 0.02,
OptimalUtilization: 0.8,
}
pool.CreatePool("BTC", poolConfig)
amount := big.NewInt(10000)
err := pool.Supply("user1", "BTC", amount)
assert.NoError(t, err)
available := pool.GetAvailable("BTC")
assert.Equal(t, amount, available)
})
t.Run("Borrow", func(t *testing.T) {
pool := NewLendingPool()
// Create pool first
poolConfig := PoolConfig{
ReserveFactor: 0.1,
CollateralFactor: 0.75,
LiquidationThreshold: 0.85,
LiquidationPenalty: 0.05,
MaxBorrowRate: 0.5,
MinBorrowRate: 0.02,
OptimalUtilization: 0.8,
}
pool.CreatePool("BTC", poolConfig)
// Supply first
supplied := big.NewInt(10000)
pool.Supply("user1", "BTC", supplied)
// Borrow
borrowAmount := big.NewInt(5000)
err := pool.Borrow("BTC", borrowAmount)
assert.NoError(t, err)
// Check available reduced
available := pool.GetAvailable("BTC")
expected := new(big.Int).Sub(supplied, borrowAmount)
assert.Equal(t, expected, available)
// Try to borrow more than available
err = pool.Borrow("BTC", big.NewInt(10000))
assert.Error(t, err)
})
t.Run("Repay", func(t *testing.T) {
pool := NewLendingPool()
// Create pool first
poolConfig := PoolConfig{
ReserveFactor: 0.1,
CollateralFactor: 0.75,
LiquidationThreshold: 0.85,
LiquidationPenalty: 0.05,
MaxBorrowRate: 0.5,
MinBorrowRate: 0.02,
OptimalUtilization: 0.8,
}
pool.CreatePool("BTC", poolConfig)
// Setup
pool.Supply("user1", "BTC", big.NewInt(10000))
pool.Borrow("BTC", big.NewInt(5000))
// Repay with interest
repayAmount := big.NewInt(5000)
interest := big.NewInt(50)
pool.Repay("BTC", repayAmount, interest)
// Check available increased
available := pool.GetAvailable("BTC")
assert.Greater(t, available.Int64(), int64(10000))
})
t.Run("GetBorrowRate", func(t *testing.T) {
pool := NewLendingPool()
// Test getting borrow rate
rate := pool.GetBorrowRate("BTC")
assert.GreaterOrEqual(t, rate, 0.0)
// Unknown asset should return default
rate = pool.GetBorrowRate("UNKNOWN")
assert.GreaterOrEqual(t, rate, 0.0)
})
t.Run("GetSupplyRate", func(t *testing.T) {
pool := NewLendingPool()
// Create pool first
poolConfig := PoolConfig{
ReserveFactor: 0.1,
CollateralFactor: 0.75,
LiquidationThreshold: 0.85,
LiquidationPenalty: 0.05,
MaxBorrowRate: 0.5,
MinBorrowRate: 0.02,
OptimalUtilization: 0.8,
}
pool.CreatePool("BTC", poolConfig)
// Supply and borrow to calculate utilization
pool.Supply("user1", "BTC", big.NewInt(10000))
pool.Borrow("BTC", big.NewInt(5000))
rate := pool.GetSupplyRate("BTC")
assert.Greater(t, rate, 0.0)
})
}
// TestLiquidationEngine tests liquidation engine
func TestLiquidationEngine(t *testing.T) {
t.Run("NewLiquidationEngine", func(t *testing.T) {
engine := NewLiquidationEngine()
assert.NotNil(t, engine)
assert.NotNil(t, engine.InsuranceFund)
assert.NotNil(t, engine.LiquidationQueue)
assert.NotNil(t, engine.Liquidators)
assert.NotNil(t, engine.MaintenanceMargin)
})
t.Run("ProcessLiquidation", func(t *testing.T) {
engine := NewLiquidationEngine()
position := &MarginPosition{
ID: "pos1",
Symbol: "BTC-USDT",
Side: Buy,
Size: 0.01,
Margin: big.NewInt(500),
UnrealizedPnL: big.NewInt(-100),
}
liquidationOrder := &Order{
Symbol: "BTC-USDT",
Side: Sell,
Type: Market,
Size: 0.01,
}
engine.ProcessLiquidation("user1", position, liquidationOrder)
// Check liquidation metrics updated
assert.Greater(t, engine.TotalLiquidations, uint64(0))
})
t.Run("InsuranceFund", func(t *testing.T) {
engine := NewLiquidationEngine()
// Test insurance fund operations
assert.NotNil(t, engine.InsuranceFund)
assert.NotNil(t, engine.InsuranceFund.Balance)
// Add to insurance fund for BTC
if engine.InsuranceFund.Balance["BTC"] == nil {
engine.InsuranceFund.Balance["BTC"] = big.NewInt(0)
}
initialBalance := new(big.Int).Set(engine.InsuranceFund.Balance["BTC"])
amount := big.NewInt(1000)
engine.InsuranceFund.Balance["BTC"].Add(engine.InsuranceFund.Balance["BTC"], amount)
// Check balance increased
assert.Greater(t, engine.InsuranceFund.Balance["BTC"].Int64(), initialBalance.Int64())
})
}
// TestRiskEngine tests risk engine functionality
func TestRiskEngine(t *testing.T) {
t.Run("NewRiskEngine", func(t *testing.T) {
engine := NewRiskEngine()
assert.NotNil(t, engine)
assert.NotNil(t, engine.MaxLeverage)
assert.NotNil(t, engine.MaintenanceMargin)
assert.NotNil(t, engine.InitialMargin)
assert.NotNil(t, engine.MaxPositionSize)
assert.Greater(t, engine.MaxTotalExposure, 0.0)
})
t.Run("CheckPositionRisk", func(t *testing.T) {
engine := NewRiskEngine()
// Test within limits
approved := engine.CheckPositionRisk("BTC-USDT", 1.0, 10.0)
assert.True(t, approved)
// Test excessive leverage
approved = engine.CheckPositionRisk("BTC-USDT", 1.0, 200.0)
assert.False(t, approved)
// Test excessive size
approved = engine.CheckPositionRisk("BTC-USDT", 1000.0, 10.0)
assert.False(t, approved)
})
t.Run("UpdateExposure", func(t *testing.T) {
engine := NewRiskEngine()
// Update exposure with delta
initialExposure := engine.TotalExposure
delta := 5000.0
engine.UpdateExposure(delta)
// Check exposure updated
assert.Equal(t, initialExposure+delta, engine.TotalExposure)
// Test negative delta
engine.UpdateExposure(-delta)
assert.Equal(t, initialExposure, engine.TotalExposure)
})
t.Run("CalculateVaR", func(t *testing.T) {
engine := NewRiskEngine()
// Test VaR calculation with positions
positions := []*MarginPosition{
{
Symbol: "BTC-USDT",
Size: 1.0,
EntryPrice: 50000.0,
MarkPrice: 49000.0,
UnrealizedPnL: big.NewInt(-1000),
},
{
Symbol: "ETH-USDT",
Size: 10.0,
EntryPrice: 3000.0,
MarkPrice: 2950.0,
UnrealizedPnL: big.NewInt(-500),
},
}
var_ := engine.CalculateVaR(positions, 0.95)
assert.GreaterOrEqual(t, var_, 0.0)
})
}