Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mid price #121

Merged
merged 7 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)))
atvanguard marked this conversation as resolved.
Show resolved Hide resolved
}

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