Skip to content

Commit

Permalink
Merge pull request #11563 from vegaprotocol/11562
Browse files Browse the repository at this point in the history
feat: Update average notional metric with mark price at the end of th…
  • Loading branch information
ze97286 authored Aug 13, 2024
2 parents cbb9d89 + 3b98fda commit 8890989
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 112 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
- [11535](https://github.com/vegaprotocol/vega/issues/11535) - Added support for lottery rank distribution strategy.
- [11536](https://github.com/vegaprotocol/vega/issues/11536) - Make the batch market instructions errors programmatically usable.
- [11546](https://github.com/vegaprotocol/vega/issues/11546) - Add validation to market proposals metadata.

- [11562](https://github.com/vegaprotocol/vega/issues/11562) - Update average notional metric with mark price at the end of the epoch and when calculating live score.

### 🐛 Fixes

- [11521](https://github.com/vegaprotocol/vega/issues/11521) - Restore `AMM` position factor when loading from a snapshot.
Expand Down
75 changes: 66 additions & 9 deletions core/execution/common/market_activity_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ type twPosition struct {
}

type twNotional struct {
notional *num.Uint // last position's price
price *num.Uint // last position's price
notional *num.Uint // last position's notional value
t time.Time // time of last recorded notional position
currentEpochTWNotional *num.Uint // current epoch's running time-weighted notional position
}
Expand All @@ -73,6 +74,7 @@ type marketTracker struct {
lpPaidFees map[string]*num.Uint
buybackFeesPaid map[string]*num.Uint
treasuryFeesPaid map[string]*num.Uint
markPrice *num.Uint

totalMakerFeesReceived *num.Uint
totalMakerFeesPaid *num.Uint
Expand Down Expand Up @@ -228,6 +230,30 @@ func (mat *MarketActivityTracker) MarketProposed(asset, marketID, proposer strin
}
}

// UpdateMarkPrice is called for a futures market when the mark price is recalculated.
func (mat *MarketActivityTracker) UpdateMarkPrice(asset, market string, markPrice *num.Uint) {
if amt, ok := mat.assetToMarketTrackers[asset]; ok {
if mt, ok := amt[market]; ok {
mt.markPrice = markPrice.Clone()
}
}
}

// RestoreMarkPrice is called when a market is loaded from a snapshot and will set the price of the notional to
// the mark price is none is set (for migration).
func (mat *MarketActivityTracker) RestoreMarkPrice(asset, market string, markPrice *num.Uint) {
if amt, ok := mat.assetToMarketTrackers[asset]; ok {
if mt, ok := amt[market]; ok {
mt.markPrice = markPrice.Clone()
for _, twn := range mt.twNotional {
if twn.price == nil {
twn.price = markPrice.Clone()
}
}
}
}
}

func (mat *MarketActivityTracker) PublishGameMetric(ctx context.Context, dispatchStrategy []*vega.DispatchStrategy, now time.Time) {
m := map[string]map[string]map[string]*num.Uint{}

Expand Down Expand Up @@ -706,7 +732,7 @@ func (mat *MarketActivityTracker) RecordPosition(asset, party, market string, po
}
notional, _ := num.UintFromDecimal(num.UintZero().Mul(num.NewUint(absPos), price).ToDecimal().Div(positionFactor))
tracker.recordPosition(party, absPos, positionFactor, time, mat.epochStartTime)
tracker.recordNotional(party, notional, time, mat.epochStartTime)
tracker.recordNotional(party, notional, price, time, mat.epochStartTime)
}
}

Expand Down Expand Up @@ -1243,7 +1269,7 @@ func (mat *MarketActivityTracker) NotionalTakerVolumeForParty(party string) *num
return mat.partyTakerNotionalVolume[party].Clone()
}

func updateNotional(n *twNotional, notional *num.Uint, t, tn int64, time time.Time) {
func updateNotionalOnTrade(n *twNotional, notional, price *num.Uint, t, tn int64, time time.Time) {
tnOverT := num.UintZero()
tnOverTComp := uScalingFactor.Clone()
if t != 0 {
Expand All @@ -1254,45 +1280,76 @@ func updateNotional(n *twNotional, notional *num.Uint, t, tn int64, time time.Ti
p2 := num.UintZero().Mul(n.notional, tnOverT)
n.currentEpochTWNotional = num.UintZero().Div(p1.AddSum(p2), uScalingFactor)
n.notional = notional
n.price = price.Clone()
n.t = time
}

func calcNotionalAt(n *twNotional, t, tn int64) *num.Uint {
func updateNotionalOnEpochEnd(n *twNotional, notional, price *num.Uint, t, tn int64, time time.Time) {
tnOverT := num.UintZero()
tnOverTComp := uScalingFactor.Clone()
if t != 0 {
tnOverT = num.NewUint(uint64(tn / t))
tnOverTComp = tnOverTComp.Sub(tnOverTComp, tnOverT)
}
p1 := num.UintZero().Mul(n.currentEpochTWNotional, tnOverTComp)
p2 := num.UintZero().Mul(n.notional, tnOverT)
p2 := num.UintZero().Mul(notional, tnOverT)
n.currentEpochTWNotional = num.UintZero().Div(p1.AddSum(p2), uScalingFactor)
n.notional = notional
if price != nil && !price.IsZero() {
n.price = price.Clone()
}
n.t = time
}

func calcNotionalAt(n *twNotional, t, tn int64, markPrice *num.Uint) *num.Uint {
tnOverT := num.UintZero()
tnOverTComp := uScalingFactor.Clone()
if t != 0 {
tnOverT = num.NewUint(uint64(tn / t))
tnOverTComp = tnOverTComp.Sub(tnOverTComp, tnOverT)
}
p1 := num.UintZero().Mul(n.currentEpochTWNotional, tnOverTComp)
var notional *num.Uint
if markPrice != nil && !markPrice.IsZero() {
notional, _ = num.UintFromDecimal(n.notional.ToDecimal().Div(n.price.ToDecimal()).Mul(markPrice.ToDecimal()))
} else {
notional = n.notional
}
p2 := num.UintZero().Mul(notional, tnOverT)
return num.UintZero().Div(p1.AddSum(p2), uScalingFactor)
}

// recordNotional tracks the time weighted average notional for the party per market.
// notional = abs(position) x price / position_factor
// price in asset decimals.
func (mt *marketTracker) recordNotional(party string, notional *num.Uint, time time.Time, epochStartTime time.Time) {
func (mt *marketTracker) recordNotional(party string, notional *num.Uint, price *num.Uint, time time.Time, epochStartTime time.Time) {
if _, ok := mt.twNotional[party]; !ok {
mt.twNotional[party] = &twNotional{
t: time,
notional: notional,
currentEpochTWNotional: num.UintZero(),
price: price.Clone(),
}
return
}
t := int64(time.Sub(epochStartTime).Seconds())
n := mt.twNotional[party]
tn := int64(time.Sub(n.t).Seconds()) * scalingFactor
updateNotional(n, notional, t, tn, time)
updateNotionalOnTrade(n, notional, price, t, tn, time)
}

func (mt *marketTracker) processNotionalEndOfEpoch(epochStartTime time.Time, endEpochTime time.Time) {
t := int64(endEpochTime.Sub(epochStartTime).Seconds())
m := make(map[string]*num.Uint, len(mt.twNotional))
for party, twNotional := range mt.twNotional {
tn := int64(endEpochTime.Sub(twNotional.t).Seconds()) * scalingFactor
updateNotional(twNotional, twNotional.notional, t, tn, endEpochTime)
var notional *num.Uint
if mt.markPrice != nil && !mt.markPrice.IsZero() {
notional, _ = num.UintFromDecimal(twNotional.notional.ToDecimal().Div(twNotional.price.ToDecimal()).Mul(mt.markPrice.ToDecimal()))
} else {
notional = twNotional.notional
}
updateNotionalOnEpochEnd(twNotional, notional, mt.markPrice, t, tn, endEpochTime)
m[party] = twNotional.currentEpochTWNotional.Clone()
}
if len(mt.epochTimeWeightedNotional) == maxWindowSize {
Expand All @@ -1312,7 +1369,7 @@ func (mt *marketTracker) processNotionalAtMilestone(epochStartTime time.Time, mi
m := make(map[string]*num.Uint, len(mt.twNotional))
for party, twNotional := range mt.twNotional {
tn := int64(milestoneTime.Sub(twNotional.t).Seconds()) * scalingFactor
m[party] = calcNotionalAt(twNotional, t, tn)
m[party] = calcNotionalAt(twNotional, t, tn, mt.markPrice)
}
mt.epochTimeWeightedNotional = append(mt.epochTimeWeightedNotional, m)
}
Expand Down
10 changes: 5 additions & 5 deletions core/execution/common/market_activity_tracker_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,15 @@ func TestPositions(t *testing.T) {
func TestAverageNotional(t *testing.T) {
tracker := getDefaultTracker(t)
// epoch 1
tracker.recordNotional("p1", num.NewUint(50), time.Unix(5, 0), time.Unix(0, 0))
tracker.recordNotional("p1", num.NewUint(50), num.NewUint(1), time.Unix(5, 0), time.Unix(0, 0))
require.Equal(t, "0", tracker.twNotional["p1"].currentEpochTWNotional.String())

// (( 0 * 3333334 ) + ( 50 * 6666666 )) / 10000000 = 33
tracker.recordNotional("p1", num.NewUint(200), time.Unix(15, 0), time.Unix(0, 0))
tracker.recordNotional("p1", num.NewUint(200), num.NewUint(1), time.Unix(15, 0), time.Unix(0, 0))
require.Equal(t, "33", tracker.twNotional["p1"].currentEpochTWNotional.String())

// (( 33 * 5000000 ) + ( 200 * 5000000 )) / 10000000 = 116
tracker.recordNotional("p1", num.NewUint(600), time.Unix(30, 0), time.Unix(0, 0))
tracker.recordNotional("p1", num.NewUint(600), num.NewUint(1), time.Unix(30, 0), time.Unix(0, 0))
require.Equal(t, "116", tracker.twNotional["p1"].currentEpochTWNotional.String())

// (( 116 * 5000000 ) + ( 600 * 5000000 )) / 10000000 = 358
Expand All @@ -324,7 +324,7 @@ func TestAverageNotional(t *testing.T) {

// epoch 2
// (( 358 * 0 ) + ( 600 * 10000000 )) / 10000000 = 600
tracker.recordNotional("p1", num.NewUint(300), time.Unix(90, 0), time.Unix(60, 0))
tracker.recordNotional("p1", num.NewUint(300), num.NewUint(1), time.Unix(90, 0), time.Unix(60, 0))
require.Equal(t, "600", tracker.twNotional["p1"].currentEpochTWNotional.String())

// (( 600 * 5000000 ) + ( 300 * 5000000 )) / 10000000 = 450
Expand Down Expand Up @@ -1595,7 +1595,7 @@ func TestIntoProto(t *testing.T) {
totalLpFees: num.NewUint(11),
twPosition: map[string]*twPosition{"p1": {t: time.Now(), position: 200, currentEpochTWPosition: 300}},
partyM2M: map[string]num.Decimal{"p1": num.DecimalFromInt64(20)},
twNotional: map[string]*twNotional{"p2": {t: time.Now(), notional: num.NewUint(50), currentEpochTWNotional: num.NewUint(55)}},
twNotional: map[string]*twNotional{"p2": {t: time.Now(), notional: num.NewUint(50), currentEpochTWNotional: num.NewUint(55), price: num.NewUint(100)}},
epochTotalMakerFeesReceived: []*num.Uint{num.NewUint(3000), num.NewUint(7000)},
epochTotalMakerFeesPaid: []*num.Uint{num.NewUint(3300), num.NewUint(7700)},
epochTotalLpFees: []*num.Uint{num.NewUint(3600), num.NewUint(8400)},
Expand Down
6 changes: 6 additions & 0 deletions core/execution/common/market_activity_tracker_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ func timeWeightedNotionalToProto(twNotional map[string]*twNotional) []*checkpoin
pdProto.Notional = b[:]
twb := pd.currentEpochTWNotional.Bytes()
pdProto.TwNotional = twb[:]

pb := pd.price.Bytes()
pdProto.Price = pb[:]
data = append(data, pdProto)
}
return data
Expand Down Expand Up @@ -542,6 +545,9 @@ func marketTrackerFromProto(tracker *checkpoint.MarketActivityTracker) *marketTr
t: time.Unix(0, tn.Time),
currentEpochTWNotional: num.UintFromBytes(tn.TwNotional),
}
if len(tn.Price) > 0 {
mft.twNotional[tn.Party].price = num.UintFromBytes(tn.Price)
}
mft.allPartiesCache[tn.Party] = struct{}{}
}
}
Expand Down
4 changes: 4 additions & 0 deletions core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@ func (m *Market) PostRestore(ctx context.Context) error {
}
}
}
m.marketActivityTracker.RestoreMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())

// Disposal slippage was set as part of this upgrade, send event to ensure datanode is updated.
if vegacontext.InProgressUpgradeFromMultiple(ctx, "v0.75.8", "v0.75.7") {
Expand Down Expand Up @@ -1156,6 +1157,7 @@ func (m *Market) BlockEnd(ctx context.Context) {
m.risk.GetRiskFactors().Long,
true, false)
m.markPriceLock.Unlock()
m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())
if err != nil {
// start the monitoring auction if required
if m.as.AuctionStart() {
Expand Down Expand Up @@ -1786,6 +1788,7 @@ func (m *Market) leaveAuction(ctx context.Context, now time.Time) {
m.markPriceCalculator.OverridePrice(m.lastTradedPrice)
m.pMonitor.ResetPriceHistory(m.lastTradedPrice)
}
m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())
if m.perp {
if m.internalCompositePriceCalculator != nil {
m.internalCompositePriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t, m.matching)
Expand Down Expand Up @@ -4668,6 +4671,7 @@ func (m *Market) terminateMarket(ctx context.Context, finalState types.MarketSta
false,
false)
m.markPriceLock.Unlock()
m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())

if m.internalCompositePriceCalculator != nil {
m.internalCompositePriceCalculator.CalculateMarkPrice(
Expand Down
1 change: 0 additions & 1 deletion core/execution/future/market_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ func NewMarketFromSnapshot(
}
stateVarEngine.UnregisterStateVariable(asset, mkt.ID)
}

return market, nil
}

Expand Down
Loading

0 comments on commit 8890989

Please sign in to comment.