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  }