github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/pkg/models/book/book.go (about)

     1  package book
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  
     9  	"github.com/bitfinexcom/bitfinex-api-go/pkg/convert"
    10  	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/common"
    11  )
    12  
    13  // BookAction represents a new/update or removal for a book entry.
    14  type BookAction byte
    15  
    16  const (
    17  	//BookEntry represents a new or updated book entry.
    18  	BookEntry BookAction = 0
    19  	//BookRemoveEntry represents a removal of a book entry.
    20  	BookRemoveEntry BookAction = 1
    21  )
    22  
    23  // Book represents an order book price update.
    24  type Book struct {
    25  	Symbol      string // book symbol
    26  	ID          int64  // the book update ID, optional
    27  	Count       int64  // updated count, optional
    28  	Period      int64
    29  	Price       float64 // updated price
    30  	Amount      float64 // updated amount
    31  	Rate        float64
    32  	PriceJsNum  json.Number      // update price as json.Number
    33  	AmountJsNum json.Number      // update amount as json.Number
    34  	Side        common.OrderSide // side
    35  	Action      BookAction       // action (add/remove)
    36  }
    37  
    38  type Snapshot struct {
    39  	Snapshot []*Book
    40  }
    41  
    42  func SnapshotFromRaw(symbol, precision string, raw [][]interface{}, rawNumbers interface{}) (*Snapshot, error) {
    43  	if len(raw) <= 0 {
    44  		return nil, fmt.Errorf("data slice too short for book snapshot: %#v", raw)
    45  	}
    46  
    47  	snap := make([]*Book, len(raw))
    48  	for i, v := range raw {
    49  		b, err := FromRaw(symbol, precision, v, rawNumbers.([]interface{})[i])
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  		snap[i] = b
    54  	}
    55  
    56  	return &Snapshot{Snapshot: snap}, nil
    57  }
    58  
    59  func IsRawBook(precision string) bool {
    60  	return precision == "R0"
    61  }
    62  
    63  // FromRaw creates a new book object from raw data. Precision determines how
    64  // to interpret the side (baked into Count versus Amount)
    65  // raw book updates [ID, price, qty], aggregated book updates [price, amount, count]
    66  func FromRaw(symbol, precision string, raw []interface{}, rawNumbers interface{}) (b *Book, err error) {
    67  	if len(raw) < 3 {
    68  		return b, fmt.Errorf("raw slice too short for book, expected %d got %d: %#v", 3, len(raw), raw)
    69  	}
    70  
    71  	rawBook := IsRawBook(precision)
    72  
    73  	if len(raw) == 3 && rawBook {
    74  		b = rawTradingPairsBook(raw, rawNumbers)
    75  	}
    76  
    77  	if len(raw) == 3 && !rawBook {
    78  		b = tradingPairsBook(raw, rawNumbers)
    79  	}
    80  
    81  	if len(raw) >= 4 && rawBook {
    82  		b = rawFundingPairsBook(raw, rawNumbers)
    83  	}
    84  
    85  	if len(raw) >= 4 && !rawBook {
    86  		b = fundingPairsBook(raw, rawNumbers)
    87  	}
    88  
    89  	b.Symbol = symbol
    90  
    91  	return
    92  }
    93  
    94  // FromWSRaw - based on condition will return snapshot of books or single book
    95  func FromWSRaw(symbol, precision string, data []interface{}) (interface{}, error) {
    96  	if len(data) == 0 {
    97  		return nil, errors.New("empty data slice")
    98  	}
    99  
   100  	_, isSnapshot := data[0].([]interface{})
   101  	if isSnapshot {
   102  		return SnapshotFromRaw(symbol, precision, convert.ToInterfaceArray(data), data)
   103  	}
   104  
   105  	return FromRaw(symbol, precision, data, data)
   106  }
   107  
   108  func rawTradingPairsBook(raw []interface{}, rawNumbers interface{}) *Book {
   109  	// [ ORDER_ID, PRICE, AMOUNT ] - raw trading pairs signature
   110  	var (
   111  		side   common.OrderSide
   112  		action BookAction
   113  	)
   114  
   115  	rawNumSlice := rawNumbers.([]interface{})
   116  	price := convert.F64ValOrZero(raw[1])
   117  	amount := convert.F64ValOrZero(raw[2])
   118  
   119  	if amount > 0 {
   120  		side = common.Bid
   121  	} else {
   122  		side = common.Ask
   123  	}
   124  
   125  	if price <= 0 {
   126  		action = BookRemoveEntry
   127  	} else {
   128  		action = BookEntry
   129  	}
   130  
   131  	return &Book{
   132  		Price:       math.Abs(price),
   133  		PriceJsNum:  convert.FloatToJsonNumber(rawNumSlice[1]),
   134  		Amount:      math.Abs(amount),
   135  		AmountJsNum: convert.FloatToJsonNumber(rawNumSlice[2]),
   136  		Side:        side,
   137  		Action:      action,
   138  		ID:          convert.I64ValOrZero(raw[0]),
   139  	}
   140  }
   141  
   142  func tradingPairsBook(raw []interface{}, rawNumbers interface{}) *Book {
   143  	// [ PRICE, COUNT, AMOUNT ] - trading pairs signature
   144  	var (
   145  		price    float64
   146  		count    int64
   147  		priceNum json.Number
   148  		side     common.OrderSide
   149  		action   BookAction
   150  	)
   151  
   152  	rawNumSlice := rawNumbers.([]interface{})
   153  	amount := convert.F64ValOrZero(raw[2])
   154  	amountNum := convert.FloatToJsonNumber(rawNumSlice[2])
   155  
   156  	price = convert.F64ValOrZero(raw[0])
   157  	priceNum = convert.FloatToJsonNumber(rawNumSlice[0])
   158  	count = convert.I64ValOrZero(raw[1])
   159  
   160  	if amount > 0 {
   161  		side = common.Bid
   162  	} else {
   163  		side = common.Ask
   164  	}
   165  
   166  	if count <= 0 {
   167  		action = BookRemoveEntry
   168  	} else {
   169  		action = BookEntry
   170  	}
   171  
   172  	return &Book{
   173  		Price:       math.Abs(price),
   174  		PriceJsNum:  priceNum,
   175  		Count:       count,
   176  		Amount:      math.Abs(amount),
   177  		AmountJsNum: amountNum,
   178  		Side:        side,
   179  		Action:      action,
   180  	}
   181  }
   182  
   183  func rawFundingPairsBook(raw []interface{}, rawNumbers interface{}) *Book {
   184  	// [ ORDER_ID, PERIOD, RATE, AMOUNT ] - raw funding pairs signature
   185  	rawNumSlice := rawNumbers.([]interface{})
   186  
   187  	return &Book{
   188  		ID:          convert.I64ValOrZero(raw[0]),
   189  		Period:      convert.I64ValOrZero(raw[1]),
   190  		Rate:        convert.F64ValOrZero(raw[2]),
   191  		Amount:      convert.F64ValOrZero(raw[3]),
   192  		AmountJsNum: convert.FloatToJsonNumber(rawNumSlice[3]),
   193  	}
   194  }
   195  
   196  func fundingPairsBook(raw []interface{}, rawNumbers interface{}) *Book {
   197  	// [ RATE, PERIOD, COUNT, AMOUNT ], - funding pairs signature
   198  	rawNumSlice := rawNumbers.([]interface{})
   199  
   200  	return &Book{
   201  		Rate:        convert.F64ValOrZero(raw[0]),
   202  		Period:      convert.I64ValOrZero(raw[1]),
   203  		Count:       convert.I64ValOrZero(raw[2]),
   204  		Amount:      convert.F64ValOrZero(raw[3]),
   205  		AmountJsNum: convert.FloatToJsonNumber(rawNumSlice[3]),
   206  	}
   207  }