From e7507732a1e637e508af271fab24385db80b9646 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 31 Mar 2015 17:28:51 +1100 Subject: [PATCH] Added OKCOIN Websocket authenticated account info/spot related trade functions. --- okcoinwebsocket.go | 269 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 228 insertions(+), 41 deletions(-) diff --git a/okcoinwebsocket.go b/okcoinwebsocket.go index df43138d..12f2f544 100644 --- a/okcoinwebsocket.go +++ b/okcoinwebsocket.go @@ -3,12 +3,29 @@ package main import ( "log" "net/http" + "net/url" "time" "fmt" "strings" + "strconv" "github.com/gorilla/websocket" ) +var OKConnWebsocket *websocket.Conn + +const ( + OKCOIN_WEBSOCKET_USD_REALTRADES = "ok_usd_realtrades" + OKCOIN_WEBSOCKET_CNY_REALTRADES = "ok_cny_realtrades" + OKCOIN_WEBSOCKET_SPOTUSD_TRADE = "ok_spotusd_trade" + OKCOIN_WEBSOCKET_SPOTCNY_TRADE = "ok_spotcny_trade" + OKCOIN_WEBSOCKET_SPOTUSD_CANCEL_ORDER = "ok_spotusd_cancel_order" + OKCOIN_WEBSOCKET_SPOTCNY_CANCEL_ORDER = "ok_spotcny_cancel_order" + OKCOIN_WEBSOCKET_SPOTUSD_USERINFO = "ok_spotusd_userinfo" + OKCOIN_WEBSOCKET_SPOTCNY_USERINFO = "ok_spotcny_userinfo" + OKCOIN_WEBSOCKET_SPOTUSD_ORDER_INFO = "ok_spotusd_order_info" + OKCOIN_WEBSOCKET_SPOTCNY_ORDER_INFO = "ok_spotcny_order_info" +) + type OKCoinWebsocketTicker struct { Timestamp int64 `json:"timestamp,string"` Vol string `json:"vol"` @@ -25,6 +42,43 @@ type OKCoinWebsocketOrderbook struct { Timestamp int64 `json:"timestamp,string"` } +type OKCoinWebsocketUserinfo struct { + Info struct { + Funds struct { + Asset struct { + Net float64 `json:"net,string"` + Total float64 `json:"total,string"` + } `json:"asset"` + Free struct { + BTC float64 `json:"btc,string"` + LTC float64 `json:"ltc,string"` + USD float64 `json:"usd,string"` + CNY float64 `json:"cny,string"` + } `json:"free"` + Frozen struct { + BTC float64 `json:"btc,string"` + LTC float64 `json:"ltc,string"` + USD float64 `json:"usd,string"` + CNY float64 `json:"cny,string"` + } `json:"freezed"` + } `json:"funds"` + } `json:"info"` + Result bool `json:"result"` +} + +type OKCoinWebsocketOrder struct { + Amount float64 `json:"amount"` + AvgPrice float64 `json:"avg_price"` + DateCreated float64 `json:"create_date"` + TradeAmount float64 `json:"deal_amount"` + OrderID float64 `json:"order_id"` + OrdersID float64 `json:"orders_id"` + Price float64 `json:"price"` + Status int64 `json:"status"` + Symbol string `json:"symbol"` + OrderType string `json:"type"` +} + type OKCoinWebsocketEvent struct { Event string `json:"event"` Channel string `json:"channel"` @@ -35,31 +89,20 @@ type OKCoinWebsocketResponse struct { Data interface{} `json:"data"` } -type OKCoinWebsocketParams struct { - Partner string `json:"partner"` -} - -type OKCoinWebsocketAuthParams struct { - Partner string `json:"partner"` - SecretKey string `json:"secretkey"` -} - type OKCoinWebsocketEventAuth struct { Event string `json:"event"` Channel string `json:"channel"` - Parameters OKCoinWebsocketAuthParams `json:"parameters"` + Parameters map[string]string `json:"parameters"` } type OKCoinWebsocketEventAuthRemove struct { Event string `json:"event"` Channel string `json:"channel"` - Parameters OKCoinWebsocketParams `json:"parameters"` + Parameters map[string]string `json:"parameters"` } -var okConn websocket.Conn - func (o *OKCoin) PingHandler(message string) (error) { - err := okConn.WriteControl(websocket.PingMessage, []byte("{'event':'ping'}"), time.Now().Add(time.Second)) + err := OKConnWebsocket.WriteControl(websocket.PingMessage, []byte("{'event':'ping'}"), time.Now().Add(time.Second)) if err != nil { log.Println(err) @@ -68,64 +111,143 @@ func (o *OKCoin) PingHandler(message string) (error) { return nil } -func (o *OKCoin) AddChannel(conn *websocket.Conn, channel string) { +func (o *OKCoin) AddChannel(channel string) { event := OKCoinWebsocketEvent{"addChannel", channel} json, err := JSONEncode(event) if err != nil { log.Println(err) return } - err = conn.WriteMessage(websocket.TextMessage, json) + err = OKConnWebsocket.WriteMessage(websocket.TextMessage, json) if err != nil { log.Println(err) return } + + if o.Verbose { + log.Printf("%s Adding channel: %s\n", o.GetName(), channel) + } } -func (o* OKCoin) RemoveChannel(conn *websocket.Conn, channel string) { +func (o* OKCoin) RemoveChannel(channel string) { event := OKCoinWebsocketEvent{"removeChannel", channel} json, err := JSONEncode(event) if err != nil { log.Println(err) return } - err = conn.WriteMessage(websocket.TextMessage, json) + err = OKConnWebsocket.WriteMessage(websocket.TextMessage, json) if err != nil { log.Println(err) return } + + if o.Verbose { + log.Printf("%s Removing channel: %s\n", o.GetName(), channel) + } } -func (o *OKCoin) AddChannelAuthenticated(conn *websocket.Conn, channel string) { - event := OKCoinWebsocketEventAuth{"addChannel", channel, OKCoinWebsocketAuthParams{o.PartnerID, o.SecretKey}} +func (o *OKCoin) WebsocketSpotTrade(symbol, orderType string, price, amount float64) { + values := make(map[string]string) + values["symbol"] = symbol + values["type"] = orderType + values["price"] = strconv.FormatFloat(price, 'f', 8, 64) + values["amount"] = strconv.FormatFloat(amount, 'f', 8, 64) + channel := "" + + if o.WebsocketURL == OKCOIN_WEBSOCKET_URL_CHINA { + channel = OKCOIN_WEBSOCKET_SPOTCNY_TRADE + } else { + channel = OKCOIN_WEBSOCKET_SPOTUSD_TRADE + } + + o.AddChannelAuthenticated(channel, values) +} + +func (o *OKCoin) WebsocketSpotCancel(symbol string, orderID int64) { + values := make(map[string]string) + values["symbol"] = symbol + values["order_id"] = strconv.FormatInt(orderID, 10) + channel := "" + + if o.WebsocketURL == OKCOIN_WEBSOCKET_URL_CHINA { + channel = OKCOIN_WEBSOCKET_SPOTCNY_CANCEL_ORDER + } else { + channel = OKCOIN_WEBSOCKET_SPOTUSD_CANCEL_ORDER + } + + o.AddChannelAuthenticated(channel, values) +} + +func (o *OKCoin) WebsocketSpotOrderInfo(symbol string, orderID int64) { + values := make(map[string]string) + values["symbol"] = symbol + values["order_id"] = strconv.FormatInt(orderID, 10) + channel := "" + + if o.WebsocketURL == OKCOIN_WEBSOCKET_URL_CHINA { + channel = OKCOIN_WEBSOCKET_SPOTCNY_ORDER_INFO + } else { + channel = OKCOIN_WEBSOCKET_SPOTUSD_ORDER_INFO + } + + o.AddChannelAuthenticated(channel, values) +} + +func (o *OKCoin) ConvertToURLValues(values map[string]string) (url.Values) { + urlVals := url.Values{} + for i, x := range values { + urlVals.Set(i, x) + } + return urlVals +} + +func (o *OKCoin) WebsocketSign(values map[string]string) (string) { + values["api_key"] = o.PartnerID + urlVals := o.ConvertToURLValues(values) + return strings.ToUpper(HexEncodeToString(GetMD5([]byte(urlVals.Encode() + "&secret_key=" + o.SecretKey)))) +} + +func (o *OKCoin) AddChannelAuthenticated(channel string, values map[string]string) { + values["sign"] = o.WebsocketSign(values) + event := OKCoinWebsocketEventAuth{"addChannel", channel, values} json, err := JSONEncode(event) if err != nil { log.Println(err) return } - err = conn.WriteMessage(websocket.TextMessage, json) + err = OKConnWebsocket.WriteMessage(websocket.TextMessage, json) if err != nil { log.Println(err) return } + + if o.Verbose { + log.Printf("%s Adding authenticated channel: %s\n", o.GetName(), channel) + } } -func (o *OKCoin) RemoveChannelAuthenticated(conn *websocket.Conn, channel string) { - event := OKCoinWebsocketEventAuthRemove{"removeChannel", channel, OKCoinWebsocketParams{o.PartnerID}} +func (o *OKCoin) RemoveChannelAuthenticated(conn *websocket.Conn, channel string, values map[string]string) { + values["sign"] = o.WebsocketSign(values) + event := OKCoinWebsocketEventAuthRemove{"removeChannel", channel, values} json, err := JSONEncode(event) if err != nil { log.Println(err) return } - err = conn.WriteMessage(websocket.TextMessage, json) + err = OKConnWebsocket.WriteMessage(websocket.TextMessage, json) if err != nil { log.Println(err) return } + + if o.Verbose { + log.Printf("%s Removing authenticated channel: %s\n", o.GetName(), channel) + } } func (o *OKCoin) WebsocketClient(currencies []string) { @@ -135,7 +257,9 @@ func (o *OKCoin) WebsocketClient(currencies []string) { } var Dialer websocket.Dialer - okConn, resp, err := Dialer.Dial(o.WebsocketURL, http.Header{}) + var err error + var resp *http.Response + OKConnWebsocket, resp, err = Dialer.Dial(o.WebsocketURL, http.Header{}) if err != nil { log.Println(err) @@ -147,29 +271,40 @@ func (o *OKCoin) WebsocketClient(currencies []string) { log.Println(resp) } - okConn.SetPingHandler(o.PingHandler) - - currencyChan := "" - if o.WebsocketURL == OKCOIN_WEBSOCKET_URL_CHINA { - currencyChan = "ok_cny_realtrades" - } else { - currencyChan = "ok_usd_realtrades" + OKConnWebsocket.SetPingHandler(o.PingHandler) + + if o.Verbose { + log.Printf("%s Collecting order and userinfo.\n") } - o.AddChannelAuthenticated(okConn, currencyChan) + currencyChan, userinfoChan := "", "" + if o.WebsocketURL == OKCOIN_WEBSOCKET_URL_CHINA { + currencyChan = OKCOIN_WEBSOCKET_CNY_REALTRADES + userinfoChan = OKCOIN_WEBSOCKET_SPOTCNY_USERINFO + o.WebsocketSpotOrderInfo("btc_cny", -1) + o.WebsocketSpotOrderInfo("ltc_cny", -1) + } else { + currencyChan = OKCOIN_WEBSOCKET_USD_REALTRADES + userinfoChan = OKCOIN_WEBSOCKET_SPOTUSD_USERINFO + o.WebsocketSpotOrderInfo("btc_usd", -1) + o.WebsocketSpotOrderInfo("ltc_usd", -1) + } + o.AddChannelAuthenticated(currencyChan, map[string]string{}) + o.AddChannelAuthenticated(userinfoChan, map[string]string{}) + klineValues := []string{"1min", "3min", "5min", "15min", "30min", "1hour", "2hour", "4hour", "6hour", "12hour", "day", "3day", "week"} for _, x := range currencies { - o.AddChannel(okConn, fmt.Sprintf("ok_%s_ticker", x)) - o.AddChannel(okConn, fmt.Sprintf("ok_%s_depth60", x)) - o.AddChannel(okConn, fmt.Sprintf("ok_%s_trades", x)) + o.AddChannel(fmt.Sprintf("ok_%s_ticker", x)) + o.AddChannel(fmt.Sprintf("ok_%s_depth60", x)) + o.AddChannel(fmt.Sprintf("ok_%s_trades_v1", x)) for _, y := range klineValues { - o.AddChannel(okConn, fmt.Sprintf("ok_%s_kline_%s", x, y)) + o.AddChannel(fmt.Sprintf("ok_%s_kline_%s", x, y)) } } for { - msgType, resp, err := okConn.ReadMessage() + msgType, resp, err := OKConnWebsocket.ReadMessage() if err != nil { log.Println(err) break @@ -188,13 +323,19 @@ func (o *OKCoin) WebsocketClient(currencies []string) { z := y.(map[string]interface{}) channel := z["channel"] data := z["data"] + success := z["success"] + errorcode := z["errorcode"] channelStr, ok := channel.(string) - + if !ok { log.Println("Unable to convert channel to string") continue } + if o.Verbose { + log.Printf("%s Websocket channel message: %s\n", o.GetName(), channelStr) + } + dataJSON, err := JSONEncode(data) if err != nil { @@ -219,7 +360,7 @@ func (o *OKCoin) WebsocketClient(currencies []string) { log.Println(err) continue } - case strings.Contains(channelStr, "trades"): + case strings.Contains(channelStr, "trades_v1"): type TradeResponse struct { Data [][]string } @@ -234,10 +375,56 @@ func (o *OKCoin) WebsocketClient(currencies []string) { // to-do: convert from string array to trade struct case strings.Contains(channelStr, "kline"): // to-do + case strings.Contains(channelStr, "realtrades"): + // to-do + case strings.Contains(channelStr, "spot") && strings.Contains(channelStr, "trade"): + if success == "false" { + log.Printf("Error placing trade, error code: %s", errorcode) + } else { + type TradeOrderResponse struct { + OrderID int64 `json:"order_id,string"` + Result bool `json:"result"` + } + tradeOrder := TradeOrderResponse{} + err := JSONDecode(dataJSON, &tradeOrder) + + if err != nil { + log.Println(err) + continue + } + } + case strings.Contains(channelStr, "userinfo"): + if success == "false" { + log.Printf("Error fetching user info, error code: %s", errorcode) + } else { + userinfo := OKCoinWebsocketUserinfo{} + err = JSONDecode(dataJSON, &userinfo) + + if err != nil { + log.Println(err) + continue + } + } + case strings.Contains(channelStr, "order_info"): + if success == "false" { + log.Printf("Error fetching order info, error code: %s", errorcode) + } else { + type OrderInfoResponse struct { + Result bool `json:"result"` + Orders []OKCoinWebsocketOrder `json:"orders"` + } + var orders OrderInfoResponse + err := JSONDecode(dataJSON, &orders) + + if err != nil { + log.Println(err) + continue + } + } } } } } - okConn.Close() + OKConnWebsocket.Close() log.Printf("%s Websocket client disconnected.", o.GetName()) } \ No newline at end of file