github.com/frankrap/okex-api@v1.0.4/ws_base.go (about) 1 package okex 2 3 /* 4 OKEX websocket api wrapper 5 @author Lingting Fu 6 @date 2018-12-27 7 @version 1.0.0 8 */ 9 10 import ( 11 "bytes" 12 "errors" 13 "fmt" 14 "hash/crc32" 15 "strconv" 16 "strings" 17 ) 18 19 var ( 20 ERR_WS_SUBSCRIOTION_PARAMS = errors.New(`ws subscription parameter error`) 21 ERR_WS_CACHE_NOT_MATCH = errors.New(`ws hot cache not matched`) 22 ) 23 24 type BaseOp struct { 25 Op string `json:"op"` 26 Args []string `json:"args"` 27 } 28 29 func loginOp(apiKey string, passphrase string, timestamp string, sign string) (op *BaseOp, err error) { 30 b := BaseOp{ 31 Op: "login", 32 Args: []string{apiKey, passphrase, timestamp, sign}, 33 } 34 return &b, nil 35 } 36 37 type SubscriptionTopic struct { 38 channel string 39 filter string `default:""` 40 } 41 42 func (st *SubscriptionTopic) ToString() (topic string, err error) { 43 if len(st.channel) == 0 { 44 return "", ERR_WS_SUBSCRIOTION_PARAMS 45 } 46 47 if len(st.filter) > 0 { 48 return st.channel + ":" + st.filter, nil 49 } else { 50 return st.channel, nil 51 } 52 } 53 54 type WSEventResponse struct { 55 Event string `json:"event"` 56 Success string `json:success` 57 Channel string `json:"channel"` 58 } 59 60 func (r *WSEventResponse) Valid() bool { 61 return len(r.Event) > 0 && len(r.Channel) > 0 62 } 63 64 type WSTableResponse struct { 65 Table string `json:"table"` 66 Action string `json:"action",default:""` 67 Data []interface{} `json:"data"` 68 } 69 70 func (r *WSTableResponse) Valid() bool { 71 return (len(r.Table) > 0 || len(r.Action) > 0) && len(r.Data) > 0 72 } 73 74 type WSDepthItem struct { 75 InstrumentId string `json:"instrument_id"` 76 Asks [][4]interface{} `json:"asks"` 77 Bids [][4]interface{} `json:"bids"` 78 Timestamp string `json:"timestamp"` 79 Checksum int32 `json:"checksum"` 80 } 81 82 func mergeDepths(oldDepths [][4]interface{}, newDepths [][4]interface{}) (*[][4]interface{}, error) { 83 84 mergedDepths := [][4]interface{}{} 85 oldIdx, newIdx := 0, 0 86 87 for oldIdx < len(oldDepths) && newIdx < len(newDepths) { 88 89 oldItem := oldDepths[oldIdx] 90 newItem := newDepths[newIdx] 91 92 oldPrice, e1 := strconv.ParseFloat(oldItem[0].(string), 10) 93 newPrice, e2 := strconv.ParseFloat(newItem[0].(string), 10) 94 if e1 != nil || e2 != nil { 95 return nil, fmt.Errorf("Bad price, check why. e1: %+v, e2: %+v", e1, e2) 96 } 97 98 if oldPrice == newPrice { 99 newNum := StringToInt64(newItem[1].(string)) 100 101 if newNum > 0 { 102 mergedDepths = append(mergedDepths, newItem) 103 } 104 105 oldIdx++ 106 newIdx++ 107 } else if oldPrice > newPrice { 108 mergedDepths = append(mergedDepths, newItem) 109 newIdx++ 110 } else if oldPrice < newPrice { 111 mergedDepths = append(mergedDepths, oldItem) 112 oldIdx++ 113 } 114 } 115 116 for ; oldIdx < len(oldDepths); oldIdx++ { 117 mergedDepths = append(mergedDepths, oldDepths[oldIdx]) 118 } 119 120 for ; newIdx < len(newDepths); newIdx++ { 121 mergedDepths = append(mergedDepths, newDepths[newIdx]) 122 } 123 124 return &mergedDepths, nil 125 } 126 127 func (di *WSDepthItem) update(newDI *WSDepthItem) error { 128 newAskDepths, err1 := mergeDepths(di.Asks, newDI.Asks) 129 if err1 != nil { 130 return err1 131 } 132 133 newBidDepths, err2 := mergeDepths(di.Bids, newDI.Bids) 134 if err2 != nil { 135 return err2 136 } 137 138 crc32BaseBuffer, expectCrc32 := calCrc32(newAskDepths, newBidDepths) 139 140 if expectCrc32 != newDI.Checksum { 141 return fmt.Errorf("Checksum's not correct. LocalString: %s, LocalCrc32: %d, RemoteCrc32: %d", 142 crc32BaseBuffer.String(), expectCrc32, newDI.Checksum) 143 } else { 144 di.Checksum = newDI.Checksum 145 di.Bids = *newBidDepths 146 di.Asks = *newAskDepths 147 di.Timestamp = newDI.Timestamp 148 } 149 150 return nil 151 } 152 153 func calCrc32(askDepths *[][4]interface{}, bidDepths *[][4]interface{}) (bytes.Buffer, int32) { 154 crc32BaseBuffer := bytes.Buffer{} 155 crcAskDepth, crcBidDepth := 25, 25 156 if len(*askDepths) < 25 { 157 crcAskDepth = len(*askDepths) 158 } 159 if len(*bidDepths) < 25 { 160 crcBidDepth = len(*bidDepths) 161 } 162 if crcAskDepth == crcBidDepth { 163 for i := 0; i < crcAskDepth; i++ { 164 if crc32BaseBuffer.Len() > 0 { 165 crc32BaseBuffer.WriteString(":") 166 } 167 crc32BaseBuffer.WriteString( 168 fmt.Sprintf("%v:%v:%v:%v", 169 (*bidDepths)[i][0], (*bidDepths)[i][1], 170 (*askDepths)[i][0], (*askDepths)[i][1])) 171 } 172 } else { 173 for i := 0; i < crcBidDepth; i++ { 174 if crc32BaseBuffer.Len() > 0 { 175 crc32BaseBuffer.WriteString(":") 176 } 177 crc32BaseBuffer.WriteString( 178 fmt.Sprintf("%v:%v", (*bidDepths)[i][0], (*bidDepths)[i][1])) 179 } 180 181 for i := 0; i < crcAskDepth; i++ { 182 if crc32BaseBuffer.Len() > 0 { 183 crc32BaseBuffer.WriteString(":") 184 } 185 crc32BaseBuffer.WriteString( 186 fmt.Sprintf("%v:%v", (*askDepths)[i][0], (*askDepths)[i][1])) 187 } 188 } 189 expectCrc32 := int32(crc32.ChecksumIEEE(crc32BaseBuffer.Bytes())) 190 return crc32BaseBuffer, expectCrc32 191 } 192 193 type WSDepthTableResponse struct { 194 Table string `json:"table"` 195 Action string `json:"action",default:""` 196 Data []WSDepthItem `json:"data"` 197 } 198 199 func (r *WSDepthTableResponse) Valid() bool { 200 return (len(r.Table) > 0 || len(r.Action) > 0) && strings.Contains(r.Table, "depth") && len(r.Data) > 0 201 } 202 203 type WSHotDepths struct { 204 Table string 205 DepthMap map[string]*WSDepthItem 206 } 207 208 func NewWSHotDepths(tb string) *WSHotDepths { 209 hd := WSHotDepths{} 210 hd.Table = tb 211 hd.DepthMap = map[string]*WSDepthItem{} 212 return &hd 213 } 214 215 func (d *WSHotDepths) loadWSDepthTableResponse(r *WSDepthTableResponse) error { 216 if d.Table != r.Table { 217 return fmt.Errorf("Loading WSDepthTableResponse failed becoz of "+ 218 "WSTableResponse(%s) not matched with WSHotDepths(%s)", r.Table, d.Table) 219 } 220 221 if !r.Valid() { 222 return errors.New("WSDepthTableResponse's format error.") 223 } 224 225 switch r.Action { 226 case "partial": 227 d.Table = r.Table 228 for i := 0; i < len(r.Data); i++ { 229 crc32BaseBuffer, expectCrc32 := calCrc32(&r.Data[i].Asks, &r.Data[i].Bids) 230 if expectCrc32 == r.Data[i].Checksum { 231 d.DepthMap[r.Data[i].InstrumentId] = &r.Data[i] 232 } else { 233 return fmt.Errorf("Checksum's not correct. LocalString: %s, LocalCrc32: %d, RemoteCrc32: %d", 234 crc32BaseBuffer.String(), expectCrc32, r.Data[i].Checksum) 235 } 236 } 237 238 case "update": 239 for i := 0; i < len(r.Data); i++ { 240 newDI := r.Data[i] 241 oldDI := d.DepthMap[newDI.InstrumentId] 242 if oldDI != nil { 243 if err := oldDI.update(&newDI); err != nil { 244 return err 245 } 246 } else { 247 d.DepthMap[newDI.InstrumentId] = &newDI 248 } 249 } 250 251 default: 252 break 253 } 254 255 return nil 256 } 257 258 type WSErrorResponse struct { 259 Event string `json:"event"` 260 Message string `json:"message"` 261 ErrorCode int `json:"errorCode"` 262 } 263 264 func (r *WSErrorResponse) Valid() bool { 265 return len(r.Event) > 0 && len(r.Message) > 0 && r.ErrorCode >= 30000 266 } 267 268 func loadResponse(rspMsg []byte) (interface{}, error) { 269 270 //log.Printf("%s", rspMsg) 271 272 evtR := WSEventResponse{} 273 err := JsonBytes2Struct(rspMsg, &evtR) 274 if err == nil && evtR.Valid() { 275 return &evtR, nil 276 } 277 278 dtr := WSDepthTableResponse{} 279 err = JsonBytes2Struct(rspMsg, &dtr) 280 if err == nil && dtr.Valid() { 281 return &dtr, nil 282 } 283 284 tr := WSTableResponse{} 285 err = JsonBytes2Struct(rspMsg, &tr) 286 if err == nil && tr.Valid() { 287 return &tr, nil 288 } 289 290 er := WSErrorResponse{} 291 err = JsonBytes2Struct(rspMsg, &er) 292 if err == nil && er.Valid() { 293 return &er, nil 294 } 295 296 if string(rspMsg) == "pong" { 297 return string(rspMsg), nil 298 } 299 300 return nil, err 301 302 } 303 304 type ReceivedDataCallback func(interface{}) error 305 306 func defaultPrintData(obj interface{}) error { 307 switch obj.(type) { 308 case string: 309 fmt.Println(obj) 310 default: 311 msg, err := Struct2JsonString(obj) 312 if err != nil { 313 fmt.Println(err.Error()) 314 return err 315 } 316 fmt.Println(msg) 317 318 } 319 return nil 320 }