code.vegaprotocol.io/vega@v0.79.0/datanode/sqlsubscribers/position.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package sqlsubscribers 17 18 import ( 19 "context" 20 "fmt" 21 "sync" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/types" 25 "code.vegaprotocol.io/vega/datanode/entities" 26 "code.vegaprotocol.io/vega/libs/num" 27 "code.vegaprotocol.io/vega/protos/vega" 28 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 29 30 "github.com/pkg/errors" 31 ) 32 33 type fundingPaymentsEvent interface { 34 MarketID() string 35 IsParty(id string) bool 36 FundingPayments() *eventspb.FundingPayments 37 } 38 39 type tradeEvent interface { 40 MarketID() string 41 IsParty(id string) bool // we don't use this one, but it's to make sure we identify the event correctly 42 Trade() vega.Trade 43 } 44 45 type positionEventBase interface { 46 events.Event 47 MarketID() string 48 PartyID() string 49 Timestamp() int64 50 } 51 52 type positionSettlement interface { 53 positionEventBase 54 Price() *num.Uint 55 PositionFactor() num.Decimal 56 Trades() []events.TradeSettlement 57 } 58 59 type lossSocialization interface { 60 positionEventBase 61 Amount() *num.Int 62 IsFunding() bool 63 } 64 65 type settleDistressed interface { 66 positionEventBase 67 Margin() *num.Uint 68 } 69 70 type ordersClosed interface { 71 MarketID() string 72 Parties() []string 73 } 74 75 type settleMarket interface { 76 positionEventBase 77 SettledPrice() *num.Uint 78 PositionFactor() num.Decimal 79 } 80 81 type distressedPositions interface { 82 MarketID() string 83 SafeParties() []string 84 DistressedParties() []string 85 } 86 87 type PositionStore interface { 88 Add(context.Context, entities.Position) error 89 GetByMarket(ctx context.Context, marketID string) ([]entities.Position, error) 90 GetByMarketAndParty(ctx context.Context, marketID string, partyID string) (entities.Position, error) 91 GetByMarketAndParties(ctx context.Context, marketID string, parties []string) ([]entities.Position, error) 92 Flush(ctx context.Context) error 93 } 94 95 type MarketSvc interface { 96 GetMarketScalingFactor(ctx context.Context, marketID string) (num.Decimal, bool) 97 IsSpotMarket(ctx context.Context, marketID string) bool 98 } 99 100 type Position struct { 101 subscriber 102 store PositionStore 103 mktSvc MarketSvc 104 mutex sync.Mutex 105 } 106 107 func NewPosition(store PositionStore, mktSvc MarketSvc) *Position { 108 t := &Position{ 109 store: store, 110 mktSvc: mktSvc, 111 } 112 return t 113 } 114 115 func (p *Position) Types() []events.Type { 116 return []events.Type{ 117 events.SettlePositionEvent, 118 events.SettleDistressedEvent, 119 events.LossSocializationEvent, 120 events.SettleMarketEvent, 121 events.TradeEvent, 122 events.DistressedOrdersClosedEvent, 123 events.DistressedPositionsEvent, 124 events.FundingPaymentsEvent, 125 } 126 } 127 128 func (p *Position) Flush(ctx context.Context) error { 129 err := p.store.Flush(ctx) 130 return errors.Wrap(err, "flushing positions") 131 } 132 133 func (p *Position) Push(ctx context.Context, evt events.Event) error { 134 switch event := evt.(type) { 135 case positionSettlement: 136 return p.handlePositionSettlement(ctx, event) 137 case lossSocialization: 138 return p.handleLossSocialization(ctx, event) 139 case settleDistressed: 140 return p.handleSettleDistressed(ctx, event) 141 case settleMarket: 142 return p.handleSettleMarket(ctx, event) 143 case tradeEvent: 144 return p.handleTradeEvent(ctx, event) 145 case ordersClosed: 146 return p.handleOrdersClosedEvent(ctx, event) 147 case distressedPositions: 148 return p.handleDistressedPositions(ctx, event) 149 case fundingPaymentsEvent: 150 return p.handleFundingPayments(ctx, event) 151 default: 152 return errors.Errorf("unknown event type %s", evt.Type().String()) 153 } 154 } 155 156 func (p *Position) handleFundingPayments(ctx context.Context, event fundingPaymentsEvent) error { 157 p.mutex.Lock() 158 defer p.mutex.Unlock() 159 mkt := event.MarketID() 160 evt := event.FundingPayments() 161 parties := make([]string, 0, len(evt.Payments)) 162 amounts := make(map[string]*num.Int, len(evt.Payments)) 163 for _, pay := range evt.Payments { 164 // amount is integer, but can be negative 165 amt, _ := num.IntFromString(pay.Amount, 10) 166 parties = append(parties, pay.PartyId) 167 amounts[pay.PartyId] = amt 168 } 169 positions, err := p.store.GetByMarketAndParties(ctx, mkt, parties) 170 if err != nil { 171 return err 172 } 173 for _, pos := range positions { 174 amt, ok := amounts[pos.PartyID.String()] 175 if !ok { 176 // should not be possible, but we may want to return an error here 177 continue 178 } 179 pos.ApplyFundingPayment(amt) 180 if err := p.updatePosition(ctx, pos); err != nil { 181 return fmt.Errorf("failed to apply funding payment: %w", err) 182 } 183 } 184 return nil 185 } 186 187 func (p *Position) handleDistressedPositions(ctx context.Context, event distressedPositions) error { 188 p.mutex.Lock() 189 defer p.mutex.Unlock() 190 parties := append(event.DistressedParties(), event.SafeParties()...) 191 positions, err := p.store.GetByMarketAndParties(ctx, event.MarketID(), parties) 192 if err != nil { 193 return fmt.Errorf("failed to get positions: %w", err) 194 } 195 for _, pos := range positions { 196 pos.ToggleDistressedStatus() 197 if err := p.updatePosition(ctx, pos); err != nil { 198 return fmt.Errorf("failed to update position: %w", err) 199 } 200 } 201 return nil 202 } 203 204 func (p *Position) handleOrdersClosedEvent(ctx context.Context, event ordersClosed) error { 205 p.mutex.Lock() 206 defer p.mutex.Unlock() 207 if sm := p.mktSvc.IsSpotMarket(ctx, event.MarketID()); sm { 208 return nil 209 } 210 211 positions, err := p.store.GetByMarketAndParties(ctx, event.MarketID(), event.Parties()) 212 if err != nil { 213 return fmt.Errorf("failed to get positions: %w", err) 214 } 215 for _, pos := range positions { 216 pos.UpdateOrdersClosed() 217 if err := p.updatePosition(ctx, pos); err != nil { 218 return fmt.Errorf("failed to update position: %w", err) 219 } 220 } 221 return nil 222 } 223 224 func (p *Position) handleTradeEvent(ctx context.Context, event tradeEvent) error { 225 trade := event.Trade() 226 p.mutex.Lock() 227 defer p.mutex.Unlock() 228 if sm := p.mktSvc.IsSpotMarket(ctx, trade.MarketId); sm { 229 return nil 230 } 231 sf, ok := p.mktSvc.GetMarketScalingFactor(ctx, trade.MarketId) 232 if !ok { 233 return fmt.Errorf("failed to get market scaling factor for market %s", trade.MarketId) 234 } 235 236 if trade.Type == types.TradeTypeNetworkCloseOutBad { 237 pos := p.getNetworkPosition(ctx, trade.MarketId) 238 seller := trade.Seller == types.NetworkParty 239 pos.UpdateWithTrade(trade, seller, sf) 240 return p.updatePosition(ctx, pos) 241 } 242 buyer, seller := p.getPositionsByTrade(ctx, trade) 243 buyer.UpdateWithTrade(trade, false, sf) 244 // this can't really result in an error... 245 _ = p.updatePosition(ctx, buyer) 246 seller.UpdateWithTrade(trade, true, sf) 247 return p.updatePosition(ctx, seller) 248 } 249 250 func (p *Position) handlePositionSettlement(ctx context.Context, event positionSettlement) error { 251 p.mutex.Lock() 252 defer p.mutex.Unlock() 253 pos := p.getPosition(ctx, event) 254 pos.UpdateWithPositionSettlement(event) 255 return p.updatePosition(ctx, pos) 256 } 257 258 func (p *Position) handleLossSocialization(ctx context.Context, event lossSocialization) error { 259 p.mutex.Lock() 260 defer p.mutex.Unlock() 261 pos := p.getPosition(ctx, event) 262 pos.UpdateWithLossSocialization(event) 263 return p.updatePosition(ctx, pos) 264 } 265 266 func (p *Position) handleSettleDistressed(ctx context.Context, event settleDistressed) error { 267 p.mutex.Lock() 268 defer p.mutex.Unlock() 269 pos := p.getPosition(ctx, event) 270 pos.UpdateWithSettleDistressed(event) 271 return p.updatePosition(ctx, pos) 272 } 273 274 func (p *Position) handleSettleMarket(ctx context.Context, event settleMarket) error { 275 p.mutex.Lock() 276 defer p.mutex.Unlock() 277 pos, err := p.store.GetByMarket(ctx, event.MarketID()) 278 if err != nil { 279 return errors.Wrap(err, "error getting positions") 280 } 281 if sm := p.mktSvc.IsSpotMarket(ctx, event.MarketID()); sm { 282 return nil 283 } 284 for i := range pos { 285 pos[i].UpdateWithSettleMarket(event) 286 err := p.updatePosition(ctx, pos[i]) 287 if err != nil { 288 return errors.Wrap(err, "error updating position") 289 } 290 } 291 292 return nil 293 } 294 295 func (p *Position) getPositionsByTrade(ctx context.Context, trade vega.Trade) (buyer entities.Position, seller entities.Position) { 296 mID := entities.MarketID(trade.MarketId) 297 bID := entities.PartyID(trade.Buyer) 298 sID := entities.PartyID(trade.Seller) 299 300 var err error 301 buyer, err = p.store.GetByMarketAndParty(ctx, mID.String(), bID.String()) 302 if errors.Is(err, entities.ErrNotFound) { 303 buyer = entities.NewEmptyPosition(mID, bID) 304 } else if err != nil { 305 // this is a really bad thing to happen :) 306 panic("unable to query for existing position") 307 } 308 seller, err = p.store.GetByMarketAndParty(ctx, mID.String(), sID.String()) 309 if errors.Is(err, entities.ErrNotFound) { 310 seller = entities.NewEmptyPosition(mID, sID) 311 } else if err != nil { 312 // this is a really bad thing to happen :) 313 panic("unable to query for existing position") 314 } 315 return buyer, seller 316 } 317 318 func (p *Position) getNetworkPosition(ctx context.Context, market string) entities.Position { 319 mID := entities.MarketID(market) 320 pID := entities.PartyID(types.NetworkParty) 321 pos, err := p.store.GetByMarketAndParty(ctx, mID.String(), pID.String()) 322 if errors.Is(err, entities.ErrNotFound) { 323 return entities.NewEmptyPosition(mID, pID) 324 } 325 if err != nil { 326 panic("unable to query existing positions") 327 } 328 return pos 329 } 330 331 func (p *Position) getPosition(ctx context.Context, e positionEventBase) entities.Position { 332 mID := entities.MarketID(e.MarketID()) 333 pID := entities.PartyID(e.PartyID()) 334 335 position, err := p.store.GetByMarketAndParty(ctx, mID.String(), pID.String()) 336 if errors.Is(err, entities.ErrNotFound) { 337 return entities.NewEmptyPosition(mID, pID) 338 } 339 340 if err != nil { 341 // TODO: Can we do something less drastic here? If we can't get existing positions 342 // things are a bit screwed as we'll start writing down wrong aggregates. 343 panic("unable to query for existing position") 344 } 345 346 return position 347 } 348 349 func (p *Position) updatePosition(ctx context.Context, pos entities.Position) error { 350 if pos.PartyID == entities.PartyID(types.NetworkParty) { 351 pos.PendingRealisedPnl = num.DecimalZero() 352 pos.RealisedPnl = num.DecimalZero() 353 pos.PendingUnrealisedPnl = num.DecimalZero() 354 pos.UnrealisedPnl = num.DecimalZero() 355 } 356 pos.VegaTime = p.vegaTime 357 358 err := p.store.Add(ctx, pos) 359 return errors.Wrap(err, "error updating position") 360 } 361 362 func (p *Position) Name() string { 363 return "Position" 364 }