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  }