github.com/InjectiveLabs/sdk-go@v1.53.0/examples/exchange/derivatives/7_StreamOrderbookUpdate/example.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 8 "github.com/InjectiveLabs/sdk-go/client/common" 9 exchangeclient "github.com/InjectiveLabs/sdk-go/client/exchange" 10 derivativeExchangePB "github.com/InjectiveLabs/sdk-go/exchange/derivative_exchange_rpc/pb" 11 "github.com/shopspring/decimal" 12 ) 13 14 type MapOrderbook struct { 15 Sequence uint64 16 Levels map[bool]map[string]*derivativeExchangePB.PriceLevel 17 } 18 19 func main() { 20 network := common.LoadNetwork("devnet-1", "") 21 exchangeClient, err := exchangeclient.NewExchangeClient(network) 22 if err != nil { 23 fmt.Println(err) 24 panic(err) 25 } 26 27 ctx := context.Background() 28 marketIds := []string{"0x4ca0f92fc28be0c9761326016b5a1a2177dd6375558365116b5bdda9abc229ce"} 29 stream, err := exchangeClient.StreamDerivativeOrderbookUpdate(ctx, marketIds) 30 if err != nil { 31 fmt.Println(err) 32 panic(err) 33 } 34 35 updatesCh := make(chan *derivativeExchangePB.OrderbookLevelUpdates, 100000) 36 receiving := make(chan struct{}) 37 var receivingClosed bool 38 39 // stream orderbook price levels 40 go func() { 41 defer close(updatesCh) 42 for { 43 select { 44 case <-ctx.Done(): 45 return 46 default: 47 res, err := stream.Recv() 48 if err != nil { 49 fmt.Println(err) 50 return 51 } 52 u := res.OrderbookLevelUpdates 53 if !receivingClosed { 54 fmt.Println("receiving updates from stream") 55 close(receiving) 56 receivingClosed = true 57 } 58 updatesCh <- u 59 } 60 } 61 }() 62 63 // ensure we are receiving updates before getting orderbook 64 fmt.Println("waiting for streaming updates") 65 <-receiving 66 67 // prepare orderbooks map 68 orderbooks := map[string]*MapOrderbook{} 69 res, err := exchangeClient.GetDerivativeOrderbooksV2(ctx, marketIds) 70 if err != nil { 71 panic(err) 72 } 73 for _, ob := range res.Orderbooks { 74 // init inner maps not ready 75 _, ok := orderbooks[ob.MarketId] 76 if !ok { 77 orderbook := &MapOrderbook{ 78 Sequence: ob.Orderbook.Sequence, 79 Levels: make(map[bool]map[string]*derivativeExchangePB.PriceLevel), 80 } 81 orderbook.Levels[true] = make(map[string]*derivativeExchangePB.PriceLevel) 82 orderbook.Levels[false] = make(map[string]*derivativeExchangePB.PriceLevel) 83 orderbooks[ob.MarketId] = orderbook 84 } 85 86 for _, level := range ob.Orderbook.Buys { 87 orderbooks[ob.MarketId].Levels[true][level.Price] = level 88 } 89 for _, level := range ob.Orderbook.Sells { 90 orderbooks[ob.MarketId].Levels[false][level.Price] = level 91 } 92 } 93 94 // continuously consume level updates and maintain orderbook 95 skippedPastEvents := false 96 for { 97 updates, ok := <-updatesCh 98 if !ok { 99 fmt.Println("updates channel closed, must restart") 100 return // closed 101 } 102 103 // validate orderbook 104 orderbook, ok := orderbooks[updates.MarketId] 105 if !ok { 106 panic("level update doesn't belong to any orderbooks") 107 } 108 109 // skip if update sequence <= orderbook sequence until it's ready to consume 110 if !skippedPastEvents { 111 if orderbook.Sequence >= updates.Sequence { 112 continue 113 } 114 skippedPastEvents = true 115 } 116 117 // panic if update sequence > orderbook sequence + 1 118 if updates.Sequence > orderbook.Sequence+1 { 119 fmt.Printf("skipping levels: update sequence %d vs orderbook sequence %d\n", updates.Sequence, orderbook.Sequence) 120 panic("missing orderbook update events from stream, must restart") 121 } 122 123 // update orderbook map 124 orderbook.Sequence = updates.Sequence 125 for isBuy, update := range map[bool][]*derivativeExchangePB.PriceLevelUpdate{ 126 true: updates.Buys, 127 false: updates.Sells, 128 } { 129 for _, level := range update { 130 if level.IsActive { 131 // upsert 132 orderbook.Levels[isBuy][level.Price] = &derivativeExchangePB.PriceLevel{ 133 Price: level.Price, 134 Quantity: level.Quantity, 135 Timestamp: level.Timestamp, 136 } 137 } else { 138 // remove inactive level 139 delete(orderbook.Levels[isBuy], level.Price) 140 } 141 } 142 } 143 144 // construct orderbook arrays 145 sells, buys := maintainOrderbook(orderbook.Levels) 146 fmt.Println("after", orderbook.Sequence, len(sells), len(buys)) 147 148 if len(sells) > 0 && len(buys) > 0 { 149 // assert orderbook 150 topBuyPrice := decimal.RequireFromString(buys[0].Price) 151 lowestSellPrice := decimal.RequireFromString(sells[0].Price) 152 if topBuyPrice.GreaterThanOrEqual(lowestSellPrice) { 153 panic(fmt.Errorf("crossed orderbook, must restart: buy %s >= %s sell", topBuyPrice, lowestSellPrice)) 154 } 155 } 156 157 res, _ = exchangeClient.GetDerivativeOrderbooksV2(ctx, marketIds) 158 fmt.Println("query", res.Orderbooks[0].Orderbook.Sequence, len(res.Orderbooks[0].Orderbook.Sells), len(res.Orderbooks[0].Orderbook.Buys)) 159 160 // print orderbook 161 fmt.Println(" [SELLS] ========") 162 for _, s := range sells { 163 fmt.Println(s) 164 } 165 fmt.Println(" [BUYS] ========") 166 for _, b := range buys { 167 fmt.Println(b) 168 } 169 fmt.Println("=======================================================") 170 } 171 } 172 173 func maintainOrderbook(orderbook map[bool]map[string]*derivativeExchangePB.PriceLevel) (buys, sells []*derivativeExchangePB.PriceLevel) { 174 for _, v := range orderbook[false] { 175 sells = append(sells, v) 176 } 177 for _, v := range orderbook[true] { 178 buys = append(buys, v) 179 } 180 181 sort.Slice(sells, func(i, j int) bool { 182 return decimal.RequireFromString(sells[i].Price).LessThan(decimal.RequireFromString(sells[j].Price)) 183 }) 184 sort.Slice(buys, func(i, j int) bool { 185 return decimal.RequireFromString(buys[i].Price).GreaterThan(decimal.RequireFromString(buys[j].Price)) 186 }) 187 188 return sells, buys 189 }