Skip to content

Commit

Permalink
Mid price (#121)
Browse files Browse the repository at this point in the history
* calcMarginFraction -> hu.GetMarginFraction

* lastPrice -> midPrice in precompile

* LastPrices -> MidPrices

* fix e2e tests

* review/safer code

* Add margin_math tests

* review comments

---------

Co-authored-by: Shubham Goyal <[email protected]>
  • Loading branch information
atvanguard and lumos42 committed Sep 30, 2023
1 parent 68774e3 commit 9d06705
Show file tree
Hide file tree
Showing 15 changed files with 395 additions and 141 deletions.
5 changes: 5 additions & 0 deletions plugin/evm/orderbook/config_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type IConfigService interface {
getMinSizeRequirement(market Market) *big.Int
GetActiveMarketsCount() int64
GetUnderlyingPrices() []*big.Int
GetMidPrices() []*big.Int
GetCollaterals() []hu.Collateral
GetLastPremiumFraction(market Market, trader *common.Address) *big.Int
GetCumulativePremiumFraction(market Market) *big.Int
Expand Down Expand Up @@ -84,6 +85,10 @@ func (cs *ConfigService) GetUnderlyingPrices() []*big.Int {
return bibliophile.GetUnderlyingPrices(cs.getStateAtCurrentBlock())
}

func (cs *ConfigService) GetMidPrices() []*big.Int {
return bibliophile.GetMidPrices(cs.getStateAtCurrentBlock())
}

func (cs *ConfigService) GetCollaterals() []hu.Collateral {
return bibliophile.GetCollaterals(cs.getStateAtCurrentBlock())
}
Expand Down
3 changes: 0 additions & 3 deletions plugin/evm/orderbook/hubbleutils/data_structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ type Market = int
type Position struct {
OpenNotional *big.Int `json:"open_notional"`
Size *big.Int `json:"size"`
// UnrealisedFunding *big.Int `json:"unrealised_funding"`
// LastPremiumFraction *big.Int `json:"last_premium_fraction"`
// LiquidationThreshold *big.Int `json:"liquidation_threshold"`
}

type Trader struct {
Expand Down
24 changes: 17 additions & 7 deletions plugin/evm/orderbook/hubbleutils/margin_math.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package hubbleutils

import (
"math"
"math/big"
)

type HubbleState struct {
Assets []Collateral
OraclePrices map[Market]*big.Int
LastPrices map[Market]*big.Int
MidPrices map[Market]*big.Int
ActiveMarkets []Market
MinAllowableMargin *big.Int
MaintenanceMargin *big.Int
}

type UserState struct {
Expand All @@ -29,6 +31,14 @@ func GetAvailableMargin_(notionalPosition, margin, reservedMargin, minAllowableM
return Sub(Sub(margin, utilisedMargin), reservedMargin)
}

func GetMarginFraction(hState *HubbleState, userState *UserState) *big.Int {
notionalPosition, margin := GetNotionalPositionAndMargin(hState, userState, Maintenance_Margin)
if notionalPosition.Sign() == 0 {
return big.NewInt(math.MaxInt64)
}
return Div(Mul1e6(margin), notionalPosition)
}

func GetNotionalPositionAndMargin(hState *HubbleState, userState *UserState, marginMode MarginMode) (*big.Int, *big.Int) {
margin := Sub(GetNormalizedMargin(hState.Assets, userState.Margins), userState.PendingFunding)
notionalPosition, unrealizedPnl := GetTotalNotionalPositionAndUnrealizedPnl(hState, userState, margin, marginMode)
Expand All @@ -52,8 +62,8 @@ func GetOptimalPnl(hState *HubbleState, position *Position, margin *big.Int, mar
}

// based on last price
notionalPosition, unrealizedPnl, lastPriceBasedMF := GetPositionMetadata(
hState.LastPrices[market],
notionalPosition, unrealizedPnl, midPriceBasedMF := GetPositionMetadata(
hState.MidPrices[market],
position.OpenNotional,
position.Size,
margin,
Expand All @@ -67,8 +77,8 @@ func GetOptimalPnl(hState *HubbleState, position *Position, margin *big.Int, mar
margin,
)

if (marginMode == Maintenance_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == 1) || // for liquidations
(marginMode == Min_Allowable_Margin && oracleBasedMF.Cmp(lastPriceBasedMF) == -1) { // for increasing leverage
if (marginMode == Maintenance_Margin && oracleBasedMF.Cmp(midPriceBasedMF) == 1) || // for liquidations
(marginMode == Min_Allowable_Margin && oracleBasedMF.Cmp(midPriceBasedMF) == -1) { // for increasing leverage
return oracleBasedNotional, oracleBasedUnrealizedPnl
}
return notionalPosition, unrealizedPnl
Expand All @@ -80,7 +90,7 @@ func GetPositionMetadata(price *big.Int, openNotional *big.Int, size *big.Int, m
if notionalPosition.Sign() == 0 {
return big.NewInt(0), big.NewInt(0), big.NewInt(0)
}
if size.Cmp(big.NewInt(0)) > 0 {
if size.Sign() > 0 {
uPnL = Sub(notionalPosition, openNotional)
} else {
uPnL = Sub(openNotional, notionalPosition)
Expand All @@ -90,7 +100,7 @@ func GetPositionMetadata(price *big.Int, openNotional *big.Int, size *big.Int, m
}

func GetNotionalPosition(price *big.Int, size *big.Int) *big.Int {
return big.NewInt(0).Abs(Div1e18(Mul(size, price)))
return big.NewInt(0).Abs(Div1e18(Mul(price, size)))
}

func GetNormalizedMargin(assets []Collateral, margins []*big.Int) *big.Int {
Expand Down
134 changes: 134 additions & 0 deletions plugin/evm/orderbook/hubbleutils/margin_math_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package hubbleutils

import (
"fmt"
"math/big"
"testing"

"github.com/stretchr/testify/assert"
)

func TestWeightedAndSpotCollateral(t *testing.T) {
assets := []Collateral{
{
Price: big.NewInt(80500000), // 80.5
Weight: big.NewInt(800000), // 0.8
Decimals: 6,
},
{
Price: big.NewInt(410000), // 0.41
Weight: big.NewInt(900000), // 0.9
Decimals: 6,
},
}
margins := []*big.Int{
big.NewInt(3500000), // 3.5
big.NewInt(1040000000), // 1040
}
expectedWeighted := big.NewInt(609160000) // 609.16
expectedSpot := big.NewInt(708150000) // 708.15
resultWeighted, resultSpot := WeightedAndSpotCollateral(assets, margins)
fmt.Println(resultWeighted, resultSpot)
assert.Equal(t, expectedWeighted, resultWeighted)
assert.Equal(t, expectedSpot, resultSpot)

normalisedMargin := GetNormalizedMargin(assets, margins)
assert.Equal(t, expectedWeighted, normalisedMargin)

}

func TestGetNotionalPosition(t *testing.T) {
price := Scale(big.NewInt(1200), 6)
size := Scale(big.NewInt(5), 18)
expected := Scale(big.NewInt(6000), 6)

result := GetNotionalPosition(price, size)

assert.Equal(t, expected, result)
}

func TestGetPositionMetadata(t *testing.T) {
price := big.NewInt(20250000) // 20.25
openNotional := big.NewInt(75369000) // 75.369 (size * 18.5)
size := Scale(big.NewInt(40740), 14) // 4.074
margin := big.NewInt(20000000) // 20

notionalPosition, unrealisedPnl, marginFraction := GetPositionMetadata(price, openNotional, size, margin)

expectedNotionalPosition := big.NewInt(82498500) // 82.4985
expectedUnrealisedPnl := big.NewInt(7129500) // 7.1295
expectedMarginFraction := big.NewInt(328848) // 0.328848

assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedUnrealisedPnl, unrealisedPnl)
assert.Equal(t, expectedMarginFraction, marginFraction)

// ------ when size is negative ------
size = Scale(big.NewInt(-40740), 14) // -4.074
openNotional = big.NewInt(75369000) // 75.369 (size * 18.5)
notionalPosition, unrealisedPnl, marginFraction = GetPositionMetadata(price, openNotional, size, margin)
fmt.Println("notionalPosition", notionalPosition, "unrealisedPnl", unrealisedPnl, "marginFraction", marginFraction)

expectedNotionalPosition = big.NewInt(82498500) // 82.4985
expectedUnrealisedPnl = big.NewInt(-7129500) // -7.1295
expectedMarginFraction = big.NewInt(156008) // 0.156008

assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedUnrealisedPnl, unrealisedPnl)
assert.Equal(t, expectedMarginFraction, marginFraction)
}

func TestGetOptimalPnl(t *testing.T) {
hState := &HubbleState{
Assets: []Collateral{
{
Price: big.NewInt(101000000), // 101
Weight: big.NewInt(900000), // 0.9
Decimals: 6,
},
{
Price: big.NewInt(54360000), // 54.36
Weight: big.NewInt(700000), // 0.7
Decimals: 6,
},
},
MidPrices: map[Market]*big.Int{
0: big.NewInt(1545340000), // 1545.34
},
OraclePrices: map[Market]*big.Int{
0: big.NewInt(1545210000), // 1545.21
},
ActiveMarkets: []Market{
0,
},
MinAllowableMargin: big.NewInt(100000), // 0.1
MaintenanceMargin: big.NewInt(200000), // 0.2
}
position := &Position{
Size: Scale(big.NewInt(582), 14), // 0.0582
OpenNotional: big.NewInt(87500000), // 87.5
}
margin := big.NewInt(20000000) // 20
market := 0
marginMode := Maintenance_Margin

notionalPosition, uPnL := GetOptimalPnl(hState, position, margin, market, marginMode)

expectedNotionalPosition := big.NewInt(89938788)
expectedUPnL := big.NewInt(2438788)

assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedUPnL, uPnL)

// ------ when marginMode is Min_Allowable_Margin ------

marginMode = Min_Allowable_Margin

notionalPosition, uPnL = GetOptimalPnl(hState, position, margin, market, marginMode)

expectedNotionalPosition = big.NewInt(89931222)
expectedUPnL = big.NewInt(2431222)

assert.Equal(t, expectedNotionalPosition, notionalPosition)
assert.Equal(t, expectedUPnL, uPnL)
}
36 changes: 24 additions & 12 deletions plugin/evm/orderbook/liquidations.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ type LiquidablePosition struct {
}

func (liq LiquidablePosition) GetUnfilledSize() *big.Int {
return big.NewInt(0).Sub(liq.Size, liq.FilledSize)
return new(big.Int).Sub(liq.Size, liq.FilledSize)
}

func calcMarginFraction(trader *Trader, pendingFunding *big.Int, assets []hu.Collateral, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, markets []Market) *big.Int {
margin := new(big.Int).Sub(getNormalisedMargin(trader, assets), pendingFunding)
notionalPosition, unrealizePnL := getTotalNotionalPositionAndUnrealizedPnl(trader, margin, hu.Maintenance_Margin, oraclePrices, lastPrices, markets)
if notionalPosition.Sign() == 0 {
return big.NewInt(math.MaxInt64)
func calcMarginFraction(trader *Trader, hState *hu.HubbleState) *big.Int {
userState := &hu.UserState{
Positions: translatePositions(trader.Positions),
Margins: getMargins(trader, len(hState.Assets)),
PendingFunding: getTotalFunding(trader, hState.ActiveMarkets),
ReservedMargin: new(big.Int).Set(trader.Margin.Reserved),
}
margin.Add(margin, unrealizePnL)
return new(big.Int).Div(hu.Mul1e6(margin), notionalPosition)
return hu.GetMarginFraction(hState, userState)
}

func sortLiquidableSliceByMarginFraction(positions []LiquidablePosition) []LiquidablePosition {
Expand All @@ -45,6 +45,13 @@ func getNormalisedMargin(trader *Trader, assets []hu.Collateral) *big.Int {

func getMargins(trader *Trader, numAssets int) []*big.Int {
margin := make([]*big.Int, numAssets)
if trader.Margin.Deposited == nil {
return margin
}
numAssets_ := len(trader.Margin.Deposited)
if numAssets_ < numAssets {
numAssets = numAssets_
}
for i := 0; i < numAssets; i++ {
margin[i] = trader.Margin.Deposited[Collateral(i)]
}
Expand All @@ -54,7 +61,7 @@ func getMargins(trader *Trader, numAssets int) []*big.Int {
func getTotalFunding(trader *Trader, markets []Market) *big.Int {
totalPendingFunding := big.NewInt(0)
for _, market := range markets {
if trader.Positions[market] != nil {
if trader.Positions[market] != nil && trader.Positions[market].UnrealisedFunding != nil && trader.Positions[market].UnrealisedFunding.Sign() != 0 {
totalPendingFunding.Add(totalPendingFunding, trader.Positions[market].UnrealisedFunding)
}
}
Expand All @@ -63,11 +70,11 @@ func getTotalFunding(trader *Trader, markets []Market) *big.Int {

type MarginMode = hu.MarginMode

func getTotalNotionalPositionAndUnrealizedPnl(trader *Trader, margin *big.Int, marginMode MarginMode, oraclePrices map[Market]*big.Int, lastPrices map[Market]*big.Int, markets []Market) (*big.Int, *big.Int) {
func getTotalNotionalPositionAndUnrealizedPnl(trader *Trader, margin *big.Int, marginMode MarginMode, oraclePrices map[Market]*big.Int, midPrices map[Market]*big.Int, markets []Market) (*big.Int, *big.Int) {
return hu.GetTotalNotionalPositionAndUnrealizedPnl(
&hu.HubbleState{
OraclePrices: oraclePrices,
LastPrices: lastPrices,
MidPrices: midPrices,
ActiveMarkets: markets,
},
&hu.UserState{
Expand All @@ -89,7 +96,12 @@ func prettifyScaledBigInt(number *big.Int, precision int8) string {
func translatePositions(positions map[int]*Position) map[int]*hu.Position {
huPositions := make(map[int]*hu.Position)
for key, value := range positions {
huPositions[key] = &value.Position
if value != nil {
huPositions[key] = &hu.Position{
Size: new(big.Int).Set(value.Size),
OpenNotional: new(big.Int).Set(value.OpenNotional),
}
}
}
return huPositions
}
Loading

0 comments on commit 9d06705

Please sign in to comment.