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 }