code.vegaprotocol.io/vega@v0.79.0/core/execution/stoporders/trailing_stop_orders.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 stoporders 17 18 import ( 19 "fmt" 20 21 "code.vegaprotocol.io/vega/core/types" 22 "code.vegaprotocol.io/vega/libs/num" 23 v1 "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 24 25 "github.com/google/btree" 26 "golang.org/x/exp/slices" 27 ) 28 29 type ordersAtOffset struct { 30 offset num.Decimal 31 orders []string 32 } 33 34 func (o *ordersAtOffset) String() string { 35 return fmt.Sprintf("%s:%v", o.offset.String(), o.orders) 36 } 37 38 type orderAtOffsetStat struct { 39 offset num.Decimal 40 direction types.StopOrderTriggerDirection 41 } 42 43 func lessFuncOrdersAtOffset(a, b *ordersAtOffset) bool { 44 return a.offset.LessThan(b.offset) 45 } 46 47 type offsetsAtPrice struct { 48 price *num.Uint 49 offsets *btree.BTreeG[*ordersAtOffset] 50 } 51 52 func (o *offsetsAtPrice) String() string { 53 return fmt.Sprintf("%s:%s", o.price.String(), dumpTree(o.offsets)) 54 } 55 56 func lessFuncOffsetsAtPrice(a, b *offsetsAtPrice) bool { 57 return a.price.LT(b.price) 58 } 59 60 type TrailingStopOrders struct { 61 lastSeenPrice *num.Uint 62 orders map[string]orderAtOffsetStat 63 risesAbove *btree.BTreeG[*offsetsAtPrice] 64 fallsBelow *btree.BTreeG[*offsetsAtPrice] 65 } 66 67 func NewTrailingStopOrders() *TrailingStopOrders { 68 return &TrailingStopOrders{ 69 orders: map[string]orderAtOffsetStat{}, 70 risesAbove: btree.NewG(2, lessFuncOffsetsAtPrice), 71 fallsBelow: btree.NewG(2, lessFuncOffsetsAtPrice), 72 } 73 } 74 75 func NewTrailingStopOrdersFromProto(p *v1.TrailingStopOrders) *TrailingStopOrders { 76 tso := NewTrailingStopOrders() 77 78 if len(p.LastSeenPrice) > 0 { 79 var overflow bool 80 tso.lastSeenPrice, overflow = num.UintFromString(p.LastSeenPrice, 10) 81 if overflow { 82 panic("lastSeenPrice should always be valid") 83 } 84 } 85 86 for _, v := range p.FallsBellow { 87 price, overflow := num.UintFromString(v.Price, 10) 88 if overflow { 89 panic(fmt.Sprintf("invalid uint from snapshot, would overflow: %s", v.Price)) 90 } 91 for _, offset := range v.Offsets { 92 off, err := num.DecimalFromString(offset.Offset) 93 if err != nil { 94 panic(fmt.Sprintf("invalid decimal from snapshot: %s", offset.Offset)) 95 } 96 for _, oid := range offset.Orders { 97 tso.insertAtPrice( 98 oid, off, price, types.StopOrderTriggerDirectionFallsBelow) 99 } 100 } 101 } 102 103 for _, v := range p.RisesAbove { 104 price, overflow := num.UintFromString(v.Price, 10) 105 if overflow { 106 panic(fmt.Sprintf("invalid uint from snapshot, would overflow: %s", v.Price)) 107 } 108 for _, offset := range v.Offsets { 109 off, err := num.DecimalFromString(offset.Offset) 110 if err != nil { 111 panic(fmt.Sprintf("invalid decimal from snapshot: %s", offset.Offset)) 112 } 113 for _, oid := range offset.Orders { 114 tso.insertAtPrice( 115 oid, off, price, types.StopOrderTriggerDirectionRisesAbove) 116 } 117 } 118 } 119 120 return tso 121 } 122 123 func (p *TrailingStopOrders) ToProto() *v1.TrailingStopOrders { 124 var lastSeenPrice string 125 if p.lastSeenPrice != nil { 126 lastSeenPrice = p.lastSeenPrice.String() 127 } 128 129 return &v1.TrailingStopOrders{ 130 LastSeenPrice: lastSeenPrice, 131 FallsBellow: p.serialize(p.fallsBelow), 132 RisesAbove: p.serialize(p.risesAbove), 133 } 134 } 135 136 func (p *TrailingStopOrders) serialize( 137 tree *btree.BTreeG[*offsetsAtPrice], 138 ) []*v1.OffsetsAtPrice { 139 out := []*v1.OffsetsAtPrice{} 140 141 tree.Ascend(func(item *offsetsAtPrice) bool { 142 offsets := []*v1.OrdersAtOffset{} 143 144 item.offsets.Ascend(func(item *ordersAtOffset) bool { 145 offsets = append(offsets, &v1.OrdersAtOffset{ 146 Offset: item.offset.String(), 147 Orders: slices.Clone(item.orders), 148 }) 149 return true 150 }) 151 152 out = append(out, &v1.OffsetsAtPrice{ 153 Price: item.price.String(), 154 Offsets: offsets, 155 }) 156 return true 157 }) 158 159 return out 160 } 161 162 func (p *TrailingStopOrders) PriceUpdated(newPrice *num.Uint) []string { 163 // short circuit for very first update, 164 // not much to do here, just set the newPrice 165 // and move on 166 if p.lastSeenPrice == nil { 167 p.lastSeenPrice = newPrice.Clone() 168 return nil 169 } 170 171 var out []string 172 // price increased, move trailing prices buckets 173 // for fallsBelow and executed triggers for risesAbove 174 if p.lastSeenPrice.LT(newPrice) { 175 p.adjustBuckets(p.fallsBelow, p.fallsBelow.AscendLessThan, newPrice) 176 out = p.trigger( 177 p.risesAbove, 178 p.risesAbove.Ascend, 179 func(a *num.Uint, b *num.Uint) bool { return a.GT(b) }, 180 func(a num.Decimal, b num.Decimal) bool { return a.GreaterThan(b) }, 181 func(a num.Decimal, offset num.Decimal) num.Decimal { return a.Add(a.Mul(offset)) }, 182 newPrice, 183 ) 184 } else if p.lastSeenPrice.GT(newPrice) { 185 p.adjustBuckets(p.risesAbove, p.risesAbove.DescendGreaterThan, newPrice) 186 out = p.trigger( 187 p.fallsBelow, 188 p.fallsBelow.Descend, 189 func(a *num.Uint, b *num.Uint) bool { return a.LT(b) }, 190 func(a num.Decimal, b num.Decimal) bool { return a.LessThan(b) }, 191 func(a num.Decimal, offset num.Decimal) num.Decimal { return a.Sub(a.Mul(offset)) }, 192 newPrice, 193 ) 194 } else { 195 // nothing happened 196 return nil 197 } 198 199 // remove orders from the mapping 200 for _, v := range out { 201 delete(p.orders, v) 202 } 203 204 p.lastSeenPrice = newPrice.Clone() 205 206 return out 207 } 208 209 func (p *TrailingStopOrders) adjustBuckets( 210 tree *btree.BTreeG[*offsetsAtPrice], 211 findFn func(*offsetsAtPrice, btree.ItemIteratorG[*offsetsAtPrice]), 212 newPrice *num.Uint, 213 ) { 214 // first we get all prices to adjust 215 item := &offsetsAtPrice{price: newPrice} 216 pricesToAdjust := []*num.Uint{} 217 findFn(item, func(oap *offsetsAtPrice) bool { 218 pricesToAdjust = append(pricesToAdjust, oap.price.Clone()) 219 return true 220 }) 221 222 // now for each of them, we pull the orders, and insert them in the new price. 223 for _, price := range pricesToAdjust { 224 current := &offsetsAtPrice{price: price} 225 // no error to check, we just iterated over, 226 // impossible we would not find it. 227 oap, _ := tree.Get(current) 228 229 // now for each orders of every leaf we can add at the new price 230 oap.offsets.Ascend(func(oao *ordersAtOffset) bool { 231 for _, order := range oao.orders { 232 // update the mapping 233 prevOrderAtOffsetStat := p.orders[order] 234 p.orders[order] = orderAtOffsetStat{oao.offset, prevOrderAtOffsetStat.direction} 235 // insert 236 p.insertOrUpdateOffsetAtPrice( 237 tree, order, newPrice, oao.offset, 238 ) 239 } 240 return true 241 }) 242 243 // now we delete this one, it's not needed anymore 244 _, _ = tree.Delete(current) 245 } 246 } 247 248 func (p *TrailingStopOrders) trigger( 249 tree *btree.BTreeG[*offsetsAtPrice], 250 iterateFn func(btree.ItemIteratorG[*offsetsAtPrice]), 251 cmpPriceFn func(*num.Uint, *num.Uint) bool, 252 cmpOffsetFn func(num.Decimal, num.Decimal) bool, 253 applyOffsetFn func(num.Decimal, num.Decimal) num.Decimal, 254 price *num.Uint, 255 ) (orders []string) { 256 priceDec := price.ToDecimal() 257 toRemovePrices := []*num.Uint{} 258 iterateFn(func(item *offsetsAtPrice) bool { 259 if cmpPriceFn(item.price, price) { 260 // continue but nothing to do here 261 return true 262 } 263 264 leafPriceDec := item.price.ToDecimal() 265 266 toRemoveOffsets := []num.Decimal{} 267 // now in here, we iterate all the 268 item.offsets.Ascend(func(item *ordersAtOffset) bool { 269 offsetedPrice := applyOffsetFn(leafPriceDec, item.offset) 270 if cmpOffsetFn(offsetedPrice, priceDec) { 271 // we have still margin, no need to process the others 272 return false 273 } 274 275 toRemoveOffsets = append(toRemoveOffsets, item.offset) 276 277 return true 278 }) 279 280 // now remove all 281 for _, o := range toRemoveOffsets { 282 oao, _ := item.offsets.Delete(&ordersAtOffset{offset: o}) 283 // add to the list of orders 284 orders = append(orders, oao.orders...) 285 } 286 287 // now we check if the offsets at the price have been depleted, 288 // and add them to the list to eventually remove 289 if item.offsets.Len() <= 0 { 290 toRemovePrices = append(toRemovePrices, item.price.Clone()) 291 } 292 293 return true 294 }) 295 296 // now we remove all depleted prices 297 for _, p := range toRemovePrices { 298 _, _ = tree.Delete(&offsetsAtPrice{price: p}) 299 } 300 301 return orders 302 } 303 304 func (p *TrailingStopOrders) Insert( 305 id string, offset num.Decimal, direction types.StopOrderTriggerDirection, 306 ) { 307 p.orders[id] = orderAtOffsetStat{offset, direction} 308 309 switch direction { 310 case types.StopOrderTriggerDirectionFallsBelow: 311 p.insertOrUpdateOffsetAtPrice(p.fallsBelow, id, p.lastSeenPrice.Clone(), offset) 312 case types.StopOrderTriggerDirectionRisesAbove: 313 p.insertOrUpdateOffsetAtPrice(p.risesAbove, id, p.lastSeenPrice.Clone(), offset) 314 } 315 } 316 317 func (p *TrailingStopOrders) insertAtPrice( 318 id string, offset num.Decimal, price *num.Uint, direction types.StopOrderTriggerDirection, 319 ) { 320 p.orders[id] = orderAtOffsetStat{offset, direction} 321 322 switch direction { 323 case types.StopOrderTriggerDirectionFallsBelow: 324 p.insertOrUpdateOffsetAtPrice(p.fallsBelow, id, price.Clone(), offset) 325 case types.StopOrderTriggerDirectionRisesAbove: 326 p.insertOrUpdateOffsetAtPrice(p.risesAbove, id, price.Clone(), offset) 327 } 328 } 329 330 func (p *TrailingStopOrders) insertOrUpdateOffsetAtPrice( 331 tree *btree.BTreeG[*offsetsAtPrice], 332 id string, 333 price *num.Uint, 334 offset num.Decimal, 335 ) { 336 oap, ok := tree.Get(&offsetsAtPrice{price: price}) 337 if !ok { 338 oap = &offsetsAtPrice{ 339 price: price, 340 offsets: btree.NewG(2, lessFuncOrdersAtOffset), 341 } 342 } 343 344 // add to the list 345 p.insertOrUpdateOrderAtOffset(oap.offsets, id, offset) 346 347 // finally insert or whatever 348 tree.ReplaceOrInsert(oap) 349 } 350 351 func (p *TrailingStopOrders) insertOrUpdateOrderAtOffset( 352 tree *btree.BTreeG[*ordersAtOffset], 353 id string, 354 offset num.Decimal, 355 ) { 356 oap, ok := tree.Get(&ordersAtOffset{offset: offset}) 357 if !ok { 358 oap = &ordersAtOffset{offset: offset} 359 } 360 361 // add to the list 362 oap.orders = append(oap.orders, id) 363 364 // finally insert or whatever 365 tree.ReplaceOrInsert(oap) 366 } 367 368 func (p *TrailingStopOrders) Remove(id string) error { 369 o, ok := p.orders[id] 370 if !ok { 371 return ErrOrderNotFound 372 } 373 374 // we can remove from the map now 375 delete(p.orders, id) 376 377 switch o.direction { 378 case types.StopOrderTriggerDirectionFallsBelow: 379 p.remove(p.fallsBelow, id, o.offset) 380 case types.StopOrderTriggerDirectionRisesAbove: 381 p.remove(p.risesAbove, id, o.offset) 382 } 383 384 return nil 385 } 386 387 func (p *TrailingStopOrders) remove( 388 tree *btree.BTreeG[*offsetsAtPrice], 389 id string, 390 offset num.Decimal, 391 ) { 392 var deletePrice *num.Uint 393 tree.Ascend(func(item *offsetsAtPrice) bool { 394 innerItem := &ordersAtOffset{offset: offset} 395 // does that offset exists at that price? 396 oao, ok := item.offsets.Get(innerItem) 397 if !ok { 398 return true // nope, keep moving 399 } 400 401 continu := true 402 for n, v := range oao.orders { 403 // we found our order! 404 if v == id { 405 oao.orders = slices.Delete(oao.orders, n, n+1) 406 continu = false 407 break 408 } 409 } 410 411 if len(oao.orders) <= 0 { 412 item.offsets.Delete(innerItem) 413 } 414 415 if item.offsets.Len() <= 0 { 416 deletePrice = item.price.Clone() 417 } 418 419 return continu 420 }) 421 422 if deletePrice != nil { 423 tree.Delete(&offsetsAtPrice{price: deletePrice}) 424 } 425 } 426 427 func (p *TrailingStopOrders) DumpRisesAbove() string { 428 return dumpTree(p.risesAbove) 429 } 430 431 func (p *TrailingStopOrders) DumpFallsBelow() string { 432 return dumpTree(p.fallsBelow) 433 }