code.vegaprotocol.io/vega@v0.79.0/core/execution/stoporders/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 "log" 20 "sort" 21 22 "code.vegaprotocol.io/vega/core/positions" 23 "code.vegaprotocol.io/vega/core/types" 24 "code.vegaprotocol.io/vega/libs/num" 25 "code.vegaprotocol.io/vega/logging" 26 v1 "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 27 28 "golang.org/x/exp/maps" 29 ) 30 31 type Pool struct { 32 log *logging.Logger 33 // map partyId * map orderId * StopOrder 34 orders map[string]map[string]*types.StopOrder 35 // useful to find back a party from an order 36 orderToParty map[string]string 37 priced *PricedStopOrders 38 trailing *TrailingStopOrders 39 } 40 41 func New(log *logging.Logger) *Pool { 42 return &Pool{ 43 log: log, 44 orders: map[string]map[string]*types.StopOrder{}, 45 orderToParty: map[string]string{}, 46 priced: NewPricedStopOrders(), 47 trailing: NewTrailingStopOrders(), 48 } 49 } 50 51 func NewFromProto(log *logging.Logger, p *v1.StopOrders) *Pool { 52 pool := New(log) 53 54 for _, porder := range p.StopOrders { 55 order := types.NewStopOrderFromProto(porder) 56 57 if party, ok := pool.orders[order.Party]; ok { 58 if _, ok := party[order.ID]; ok { 59 pool.log.Panic("stop order already exists", logging.String("id", order.ID)) 60 } 61 } else { 62 pool.orders[order.Party] = map[string]*types.StopOrder{} 63 } 64 65 pool.orders[order.Party][order.ID] = order 66 pool.orderToParty[order.ID] = order.Party 67 } 68 69 pool.priced = NewPricedStopOrdersFromProto(p.PricedStopOrders) 70 pool.trailing = NewTrailingStopOrdersFromProto(p.TrailingStopOrders) 71 72 return pool 73 } 74 75 func (p *Pool) ToProto() *v1.StopOrders { 76 out := &v1.StopOrders{} 77 78 for _, v := range p.orders { 79 for _, order := range v { 80 out.StopOrders = append(out.StopOrders, order.ToProtoEvent()) 81 } 82 } 83 84 sort.Slice(out.StopOrders, func(i, j int) bool { 85 return out.StopOrders[i].StopOrder.Id < out.StopOrders[j].StopOrder.Id 86 }) 87 88 out.PricedStopOrders = p.priced.ToProto() 89 out.TrailingStopOrders = p.trailing.ToProto() 90 91 return out 92 } 93 94 func (p *Pool) GetStopOrderCount() uint64 { 95 return uint64(len(p.orderToParty)) 96 } 97 98 func (p *Pool) Settled() []*types.StopOrder { 99 out := []*types.StopOrder{} 100 for _, v := range p.orders { 101 for _, so := range v { 102 so.Status = types.StopOrderStatusStopped 103 out = append(out, so) 104 } 105 } 106 107 sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) 108 return out 109 } 110 111 func (p *Pool) CheckDirection(positions *positions.SnapshotEngine) []*types.StopOrder { 112 toCancel := []*types.StopOrder{} 113 for partyID, orders := range p.orders { 114 pos, ok := positions.GetPositionByPartyID(partyID) 115 if !ok { 116 continue 117 } 118 for _, order := range orders { 119 if order.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition { 120 if pos.Size() == 0 { 121 continue 122 } else if pos.Size() > 0 { 123 // Order needs to be a sell 124 if order.OrderSubmission.Side != types.SideSell { 125 order.Status = types.StopOrderStatusCancelled 126 toCancel = append(toCancel, order) 127 } 128 } else { 129 // Order needs to be a buy 130 if order.OrderSubmission.Side != types.SideBuy { 131 order.Status = types.StopOrderStatusCancelled 132 toCancel = append(toCancel, order) 133 } 134 } 135 } 136 } 137 } 138 139 // Remove the deleted items from the map 140 for _, stopOrder := range toCancel { 141 delete(p.orders[stopOrder.Party], stopOrder.OrderID) 142 delete(p.orderToParty, stopOrder.OrderID) 143 } 144 return toCancel 145 } 146 147 func (p *Pool) PriceUpdated(newPrice *num.Uint) (triggered, cancelled []*types.StopOrder) { 148 // first update prices and get triggered orders 149 ids := append( 150 p.priced.PriceUpdated(newPrice.Clone()), 151 p.trailing.PriceUpdated(newPrice.Clone())..., 152 ) 153 154 // first get all the orders which got triggered 155 for _, v := range ids { 156 pid, ok := p.orderToParty[v] 157 if !ok { 158 log.Panic("order in tree but not in pool", logging.String("order-id", v)) 159 } 160 161 // not needed anymore 162 delete(p.orderToParty, v) 163 164 orders, ok := p.orders[pid] 165 if !ok { 166 p.log.Panic("party was expected to have orders but have none", 167 logging.String("party-id", pid), logging.String("order-id", v)) 168 } 169 170 // now we are down to the actual order 171 sorder, ok := orders[v] 172 if !ok { 173 p.log.Panic("party was expected to have an order", 174 logging.String("party-id", pid), logging.String("order-id", v)) 175 } 176 177 sorder.Status = types.StopOrderStatusTriggered 178 triggered = append(triggered, sorder) 179 180 // now we can cleanup 181 delete(orders, v) 182 if len(orders) <= 0 { 183 // we can remove the trader altogether 184 delete(p.orders, pid) 185 } 186 } 187 188 // now we get all the OCO opposite to them as they shall 189 // be cancelled as well 190 for _, v := range triggered[:] { 191 if len(v.OCOLinkID) <= 0 { 192 continue 193 } 194 195 res, err := p.removeWithOCO(v.Party, v.OCOLinkID, false) 196 if err != nil || len(res) <= 0 { 197 // that should never happen, this mean for some 198 // reason that the other side of the OCO has been 199 // remove and left the pool in a bad state 200 p.log.Panic("other side of the oco missing from the pool", 201 logging.Error(err), 202 logging.PartyID(v.Party), 203 logging.OrderID(v.OCOLinkID)) 204 } 205 206 // only one order returned here 207 res[0].Status = types.StopOrderStatusStopped 208 cancelled = append(cancelled, res[0]) 209 } 210 211 return triggered, cancelled 212 } 213 214 func (p *Pool) Insert(order *types.StopOrder) { 215 if party, ok := p.orders[order.Party]; ok { 216 if _, ok := party[order.ID]; ok { 217 p.log.Panic("stop order already exists", logging.String("id", order.ID)) 218 } 219 } else { 220 p.orders[order.Party] = map[string]*types.StopOrder{} 221 } 222 223 p.orders[order.Party][order.ID] = order 224 p.orderToParty[order.ID] = order.Party 225 switch { 226 case order.Trigger.IsPrice(): 227 p.priced.Insert(order.ID, order.Trigger.Price().Clone(), order.Trigger.Direction) 228 case order.Trigger.IsTrailingPercentOffset(): 229 p.trailing.Insert(order.ID, order.Trigger.TrailingPercentOffset(), order.Trigger.Direction) 230 } 231 } 232 233 func (p *Pool) Cancel( 234 partyID string, 235 orderID string, // if empty remove all 236 ) ([]*types.StopOrder, error) { 237 orders, err := p.removeWithOCO(partyID, orderID, true) 238 if err == nil { 239 for _, v := range orders { 240 v.Status = types.StopOrderStatusCancelled 241 } 242 } 243 244 return orders, err 245 } 246 247 func (p *Pool) removeWithOCO( 248 partyID string, 249 orderID string, 250 withOCO bool, // not always necessary in case we are 251 ) ([]*types.StopOrder, error) { 252 partyOrders, ok := p.orders[partyID] 253 if !ok { 254 // return an error only when trying to find a specific stop order 255 if len(orderID) > 0 { 256 return nil, ErrStopOrderNotFound 257 } 258 259 // this party have no stop orders, move on 260 return nil, nil 261 } 262 263 // remove a single one and maybe OCO 264 if len(orderID) > 0 { 265 order, ok := partyOrders[orderID] 266 if !ok { 267 return nil, ErrStopOrderNotFound 268 } 269 270 orders := []*types.StopOrder{order} 271 if withOCO && len(order.OCOLinkID) > 0 { 272 orders = append(orders, partyOrders[order.OCOLinkID]) 273 } 274 275 p.remove(orders) 276 277 return orders, nil 278 } 279 280 orders := maps.Values(partyOrders) 281 sort.Slice(orders, func(i, j int) bool { return orders[i].ID < orders[j].ID }) 282 p.remove(orders) 283 284 return orders, nil 285 } 286 287 func (p *Pool) remove(orders []*types.StopOrder) { 288 for _, order := range orders { 289 delete(p.orderToParty, order.ID) 290 delete(p.orders[order.Party], order.ID) 291 292 if len(p.orders[order.Party]) <= 0 { 293 // no need of this entry anymore 294 delete(p.orders, order.Party) 295 } 296 297 switch { 298 case order.Trigger.IsPrice(): 299 p.priced.Remove(order.ID) 300 case order.Trigger.IsTrailingPercentOffset(): 301 p.trailing.Remove(order.ID) 302 } 303 } 304 } 305 306 func (p *Pool) RemoveExpired(orderIDs []string) []*types.StopOrder { 307 ordersM := map[string]*types.StopOrder{} 308 309 // first find all orders and add them to the map 310 for _, id := range orderIDs { 311 order := p.orders[p.orderToParty[id]][id] 312 order.Status = types.StopOrderStatusExpired 313 ordersM[id] = order 314 315 // once an order is removed, we also remove it's OCO link 316 if len(order.OCOLinkID) > 0 { 317 // first check if it's not been removed already 318 if _, ok := p.orderToParty[order.OCOLinkID]; ok { 319 // is the OCO link already mapped 320 if _, ok := ordersM[order.OCOLinkID]; !ok { 321 ordersM[order.OCOLinkID] = p.orders[p.orderToParty[id]][order.OCOLinkID] 322 ordersM[order.OCOLinkID].Status = types.StopOrderStatusStopped 323 } 324 } 325 } 326 } 327 328 orders := maps.Values(ordersM) 329 sort.Slice(orders, func(i, j int) bool { return orders[i].ID < orders[j].ID }) 330 p.remove(orders) 331 332 return orders 333 } 334 335 func (p *Pool) CountForParty(party string) uint64 { 336 orders, ok := p.orders[party] 337 if !ok { 338 return 0 339 } 340 341 return uint64(len(orders)) 342 }