code.vegaprotocol.io/vega@v0.79.0/core/execution/stoporders/priced_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 ordersAtPrice struct { 30 price *num.Uint 31 orders []string 32 } 33 34 func (o *ordersAtPrice) String() string { 35 return fmt.Sprintf("%s:%v", o.price.String(), o.orders) 36 } 37 38 func lessFuncOrdersAtPrice(a, b *ordersAtPrice) bool { 39 return a.price.LT(b.price) 40 } 41 42 type orderAtPriceStat struct { 43 price *num.Uint 44 direction types.StopOrderTriggerDirection 45 } 46 47 type PricedStopOrders struct { 48 // mapping table for stop order ID 49 // help finding them back in the trees. 50 orders map[string]orderAtPriceStat 51 52 fallsBelow *btree.BTreeG[*ordersAtPrice] 53 risesAbove *btree.BTreeG[*ordersAtPrice] 54 } 55 56 func NewPricedStopOrders() *PricedStopOrders { 57 return &PricedStopOrders{ 58 orders: map[string]orderAtPriceStat{}, 59 fallsBelow: btree.NewG(2, lessFuncOrdersAtPrice), 60 risesAbove: btree.NewG(2, lessFuncOrdersAtPrice), 61 } 62 } 63 64 func NewPricedStopOrdersFromProto(p *v1.PricedStopOrders) *PricedStopOrders { 65 pso := NewPricedStopOrders() 66 67 for _, v := range p.FallsBellow { 68 for _, oid := range v.Orders { 69 price, overflow := num.UintFromString(v.Price, 10) 70 if overflow { 71 panic(fmt.Sprintf("invalid uint from snapshot, would overflow: %s", v.Price)) 72 } 73 pso.Insert(oid, price, types.StopOrderTriggerDirectionFallsBelow) 74 } 75 } 76 77 for _, v := range p.RisesAbove { 78 for _, oid := range v.Orders { 79 price, overflow := num.UintFromString(v.Price, 10) 80 if overflow { 81 panic(fmt.Sprintf("invalid uint from snapshot, would overflow: %s", v.Price)) 82 } 83 pso.Insert(oid, price, types.StopOrderTriggerDirectionRisesAbove) 84 } 85 } 86 87 return pso 88 } 89 90 func (p *PricedStopOrders) ToProto() *v1.PricedStopOrders { 91 return &v1.PricedStopOrders{ 92 FallsBellow: p.serialize(p.fallsBelow), 93 RisesAbove: p.serialize(p.risesAbove), 94 } 95 } 96 97 func (p *PricedStopOrders) serialize(tree *btree.BTreeG[*ordersAtPrice]) []*v1.OrdersAtPrice { 98 out := []*v1.OrdersAtPrice{} 99 100 tree.Ascend(func(item *ordersAtPrice) bool { 101 out = append(out, &v1.OrdersAtPrice{ 102 Price: item.price.String(), 103 Orders: slices.Clone(item.orders), 104 }) 105 return true 106 }) 107 108 return out 109 } 110 111 func (p *PricedStopOrders) PriceUpdated(newPrice *num.Uint) []string { 112 // first remove if price fallsBelow 113 orderIDs := p.trigger( 114 p.fallsBelow, 115 p.fallsBelow.DescendGreaterThan, 116 newPrice.Clone().Sub(newPrice.Clone(), num.NewUint(1)), 117 ) 118 119 // then if it rises above? 120 orderIDs = append(orderIDs, 121 p.trigger( 122 p.risesAbove, 123 p.risesAbove.AscendLessThan, 124 newPrice.Clone().Add(newPrice.Clone(), num.NewUint(1)), 125 )..., 126 ) 127 128 // here we can cleanup the mapping table as well 129 for _, v := range orderIDs { 130 delete(p.orders, v) 131 } 132 133 return orderIDs 134 } 135 136 func (p *PricedStopOrders) trigger( 137 tree *btree.BTreeG[*ordersAtPrice], 138 findFn func(pivot *ordersAtPrice, iterator btree.ItemIteratorG[*ordersAtPrice]), 139 newPrice *num.Uint, 140 ) []string { 141 orderIDs := []string{} 142 toDelete := []*num.Uint{} 143 findFn(&ordersAtPrice{price: newPrice}, func(item *ordersAtPrice) bool { 144 orderIDs = append(orderIDs, item.orders...) 145 toDelete = append(toDelete, item.price) 146 return true 147 }) 148 149 // now we delete all the unused tree item 150 for _, p := range toDelete { 151 tree.Delete(&ordersAtPrice{price: p}) 152 } 153 154 return orderIDs 155 } 156 157 func (p *PricedStopOrders) Insert( 158 id string, price *num.Uint, direction types.StopOrderTriggerDirection, 159 ) { 160 p.orders[id] = orderAtPriceStat{price.Clone(), direction} 161 162 switch direction { 163 case types.StopOrderTriggerDirectionFallsBelow: 164 p.insertOrUpdate(p.fallsBelow, id, price.Clone()) 165 case types.StopOrderTriggerDirectionRisesAbove: 166 p.insertOrUpdate(p.risesAbove, id, price.Clone()) 167 } 168 } 169 170 func (p *PricedStopOrders) insertOrUpdate( 171 tree *btree.BTreeG[*ordersAtPrice], id string, price *num.Uint, 172 ) { 173 oap, ok := tree.Get(&ordersAtPrice{price: price}) 174 if !ok { 175 oap = &ordersAtPrice{price: price} 176 } 177 178 // add to the list 179 oap.orders = append(oap.orders, id) 180 181 // finally insert or whatever 182 tree.ReplaceOrInsert(oap) 183 } 184 185 func (p *PricedStopOrders) Remove(id string) error { 186 oaps, ok := p.orders[id] 187 if !ok { 188 return ErrOrderNotFound 189 } 190 191 delete(p.orders, id) 192 193 var err error 194 switch oaps.direction { 195 case types.StopOrderTriggerDirectionFallsBelow: 196 err = p.removeAndMaybeDelete(p.fallsBelow, id, oaps.price) 197 case types.StopOrderTriggerDirectionRisesAbove: 198 err = p.removeAndMaybeDelete(p.risesAbove, id, oaps.price) 199 } 200 201 return err 202 } 203 204 func (p *PricedStopOrders) removeAndMaybeDelete( 205 tree *btree.BTreeG[*ordersAtPrice], id string, price *num.Uint, 206 ) error { 207 // just declare it first, we may reuse it by the end 208 item := &ordersAtPrice{price: price} 209 210 oap, ok := tree.Get(item) 211 if !ok { 212 return ErrPriceNotFound 213 } 214 215 before := len(oap.orders) 216 217 for n, v := range oap.orders { 218 // this is our ID 219 if v == id { 220 oap.orders = slices.Delete(oap.orders, n, n+1) 221 break 222 } 223 } 224 225 // didn't found the order, we can just panic it should never happen 226 if before == len(oap.orders) { 227 panic("order not in tree but in mapping table") 228 } 229 230 // now if the len is 0, we probably don't need that 231 // price level anymore 232 if len(oap.orders) <= 0 { 233 tree.Delete(item) 234 } 235 236 return nil 237 } 238 239 func (p *PricedStopOrders) DumpRisesAbove() string { 240 return dumpTree(p.risesAbove) 241 } 242 243 func (p *PricedStopOrders) DumpFallsBelow() string { 244 return dumpTree(p.fallsBelow) 245 }