From 8fdaceab2f4db369a41ec3a682f6401a0a3f708d Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 29 Feb 2020 16:56:13 +0800 Subject: [PATCH 01/29] [binance] BSV has renamed, so don't need adaptive --- binance/Binance.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/binance/Binance.go b/binance/Binance.go index 4c4e054b..17ad758e 100644 --- a/binance/Binance.go +++ b/binance/Binance.go @@ -683,14 +683,6 @@ func (bn *Binance) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize } func (bn *Binance) adaptCurrencyPair(pair CurrencyPair) CurrencyPair { - if pair.CurrencyA.Eq(BCH) || pair.CurrencyA.Eq(BCC) { - return NewCurrencyPair(NewCurrency("BCHABC", ""), pair.CurrencyB).AdaptUsdToUsdt() - } - - if pair.CurrencyA.Symbol == "BSV" { - return NewCurrencyPair(NewCurrency("BCHSV", ""), pair.CurrencyB).AdaptUsdToUsdt() - } - return pair.AdaptUsdToUsdt() } From da45e21fa8cd6e1626c4bc25fd00fb50467b1a47 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 2 Mar 2020 00:48:47 +0800 Subject: [PATCH 02/29] [bitstamp] remove obsolete log --- bitstamp/Bitstamp.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/bitstamp/Bitstamp.go b/bitstamp/Bitstamp.go index f7fb83bb..115013f6 100644 --- a/bitstamp/Bitstamp.go +++ b/bitstamp/Bitstamp.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" . "github.com/nntaoli-project/goex" - "log" "net/http" "net/url" "sort" @@ -37,7 +36,6 @@ func (bitstamp *Bitstamp) buildPostForm(params *url.Values) { params.Set("signature", strings.ToUpper(sign)) params.Set("nonce", fmt.Sprintf("%d", nonce)) params.Set("key", bitstamp.accessKey) - //log.Println(params.Encode()) } func (bitstamp *Bitstamp) GetAccount() (*Account, error) { @@ -46,15 +44,12 @@ func (bitstamp *Bitstamp) GetAccount() (*Account, error) { bitstamp.buildPostForm(¶ms) resp, err := HttpPostForm(bitstamp.client, urlStr, params) if err != nil { - log.Println(err) return nil, err } - //log.Println(string(resp)) var respmap map[string]interface{} err = json.Unmarshal(resp, &respmap) if err != nil { - log.Println(err) return nil, err } @@ -121,7 +116,6 @@ func (bitstamp *Bitstamp) placeOrder(side string, pair CurrencyPair, amount, pri respmap := make(map[string]interface{}) err = json.Unmarshal(resp, &respmap) if err != nil { - log.Println(string(resp)) return nil, err } @@ -220,7 +214,6 @@ func (bitstamp *Bitstamp) GetOneOrder(orderId string, currency CurrencyPair) (*O respmap := make(map[string]interface{}) err = json.Unmarshal(resp, &respmap) if err != nil { - log.Println(resp) return nil, err } @@ -290,10 +283,8 @@ func (bitstamp *Bitstamp) GetUnfinishOrders(currency CurrencyPair) ([]Order, err respmap := make([]interface{}, 1) err = json.Unmarshal(resp, &respmap) if err != nil { - log.Println(string(resp)) return nil, err } - //log.Println(respmap) orders := make([]Order, 0) for _, v := range respmap { ord := v.(map[string]interface{}) @@ -330,7 +321,6 @@ func (bitstamp *Bitstamp) GetTicker(currency CurrencyPair) (*Ticker, error) { if err != nil { return nil, err } - //log.Println(respmap) timestamp, _ := strconv.ParseUint(respmap["timestamp"].(string), 10, 64) return &Ticker{ Pair: currency, @@ -345,10 +335,8 @@ func (bitstamp *Bitstamp) GetTicker(currency CurrencyPair) (*Ticker, error) { func (bitstamp *Bitstamp) GetDepth(size int, currency CurrencyPair) (*Depth, error) { urlStr := BASE_URL + "v2/order_book/" + strings.ToLower(currency.ToSymbol("")) - //println(urlStr) respmap, err := HttpGet(bitstamp.client, urlStr) if err != nil { - log.Println("err") return nil, err } @@ -356,7 +344,6 @@ func (bitstamp *Bitstamp) GetDepth(size int, currency CurrencyPair) (*Depth, err bids, isok1 := respmap["bids"].([]interface{}) asks, isok2 := respmap["asks"].([]interface{}) if !isok1 || !isok2 { - log.Println(respmap) return nil, errors.New("Get Depth Error.") } From b166bcbcb76904c1b07892d305fb39c019b32b87 Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Thu, 5 Mar 2020 23:02:11 +0800 Subject: [PATCH 03/29] [Utils] GenerateOrderClientId Func --- Utils.go | 5 +++-- Utils_test.go | 4 ++++ bitmex/bitmex.go | 6 +++--- kucoin/kucoin.go | 12 ++++++------ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Utils.go b/Utils.go index 24af28d3..9d68b0c6 100644 --- a/Utils.go +++ b/Utils.go @@ -122,6 +122,7 @@ func FlateUnCompress(data []byte) ([]byte, error) { return ioutil.ReadAll(flate.NewReader(bytes.NewReader(data))) } -func UUID() string { - return strings.Replace(uuid.New().String(), "-", "", 32) +func GenerateOrderClientId(size int) string { + uuidStr := strings.Replace(uuid.New().String(), "-", "", 32) + return "goex-" + uuidStr[0:size-5] } diff --git a/Utils_test.go b/Utils_test.go index eb26164f..713b24d1 100644 --- a/Utils_test.go +++ b/Utils_test.go @@ -11,3 +11,7 @@ func TestFloatToString(t *testing.T) { assert.Equal(t, "1.10231", FloatToString(1.10231000, 8)) assert.NotEqual(t, "1.10231000", FloatToString(1.10231000, 8)) } + +func TestGenerateOrderClientId(t *testing.T) { + t.Log(len(GenerateOrderClientId(32)), GenerateOrderClientId(32)) +} diff --git a/bitmex/bitmex.go b/bitmex/bitmex.go index a94510eb..d2989f41 100644 --- a/bitmex/bitmex.go +++ b/bitmex/bitmex.go @@ -125,11 +125,11 @@ func (bm *bitmex) PlaceFutureOrder(currencyPair CurrencyPair, contractType, pric OrderId string `json:"orderID"` } - createOrderParameter.Text = "github.com/nntaoli-project/goex/bitmex" + createOrderParameter.Text = "github.com/nntaoli-project/goex/tree/master/bitmex" createOrderParameter.Symbol = bm.adaptCurrencyPairToSymbol(currencyPair, contractType) createOrderParameter.OrdType = "Limit" createOrderParameter.TimeInForce = "GoodTillCancel" - createOrderParameter.ClOrdID = "goex" + UUID() + createOrderParameter.ClOrdID = GenerateOrderClientId(32) createOrderParameter.OrderQty = ToInt(amount) if matchPrice == 0 { @@ -282,7 +282,7 @@ func (bm *bitmex) GetFee() (float64, error) { func (bm *bitmex) GetFutureDepth(currencyPair CurrencyPair, contractType string, size int) (*Depth, error) { sym := bm.adaptCurrencyPairToSymbol(currencyPair, contractType) - uri := fmt.Sprintf("/api/v1/orderBook/L2?symbol=%s&depth=%d", sym , size) + uri := fmt.Sprintf("/api/v1/orderBook/L2?symbol=%s&depth=%d", sym, size) resp, err := HttpGet3(bm.HttpClient, bm.Endpoint+uri, nil) if err != nil { return nil, HTTP_ERR_CODE.OriginErr(err.Error()) diff --git a/kucoin/kucoin.go b/kucoin/kucoin.go index 87369ff3..b5d95fd7 100644 --- a/kucoin/kucoin.go +++ b/kucoin/kucoin.go @@ -92,7 +92,7 @@ func (kc *KuCoin) GetTicker(currency CurrencyPair) (*Ticker, error) { } func (kc *KuCoin) LimitBuy(amount, price string, currency CurrencyPair) (*Order, error) { - clientID := UUID() + clientID := GenerateOrderClientId(32) params := map[string]string{ "clientOid": clientID, "side": "buy", @@ -122,7 +122,7 @@ func (kc *KuCoin) LimitBuy(amount, price string, currency CurrencyPair) (*Order, } func (kc *KuCoin) LimitSell(amount, price string, currency CurrencyPair) (*Order, error) { - clientID := UUID() + clientID := GenerateOrderClientId(32) params := map[string]string{ "clientOid": clientID, "side": "sell", @@ -152,7 +152,7 @@ func (kc *KuCoin) LimitSell(amount, price string, currency CurrencyPair) (*Order } func (kc *KuCoin) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { - clientID := UUID() + clientID := GenerateOrderClientId(32) params := map[string]string{ "clientOid": clientID, "side": "buy", @@ -182,7 +182,7 @@ func (kc *KuCoin) MarketBuy(amount, price string, currency CurrencyPair) (*Order } func (kc *KuCoin) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { - clientID := UUID() + clientID := GenerateOrderClientId(32) params := map[string]string{ "clientOid": clientID, "side": "sell", @@ -567,7 +567,7 @@ func (kc *KuCoin) CreateAccount(typo, currency string) (*kucoin.AccountModel, er // The inner transfer interface is used for transferring assets between the accounts of a user and is free of charges. // For example, a user could transfer assets from their main account to their trading account on the platform. func (kc *KuCoin) InnerTransfer(currency, from, to, amount string) (string, error) { - resp, err := kc.service.InnerTransferV2(UUID(), currency, from, to, amount) + resp, err := kc.service.InnerTransferV2(GenerateOrderClientId(32), currency, from, to, amount) if err != nil { log.Error("KuCoin InnerTransfer error:", err) return "", err @@ -586,7 +586,7 @@ func (kc *KuCoin) InnerTransfer(currency, from, to, amount string) (string, erro // SubTransfer transfers between master account and sub-account. func (kc *KuCoin) SubTransfer(currency, amount, direction, subUserId, accountType, subAccountType string) (string, error) { params := map[string]string{ - "clientOid": UUID(), + "clientOid": GenerateOrderClientId(32), "currency": currency, "amount": amount, "direction": direction, // IN or OUT From 3d3fa503d0e8b7f0ae9ba242c2ae801a5e0f3618 Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Thu, 5 Mar 2020 23:37:50 +0800 Subject: [PATCH 04/29] [api builder] build binance swap api --- builder/APIBuilder.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builder/APIBuilder.go b/builder/APIBuilder.go index bb242619..305509dd 100644 --- a/builder/APIBuilder.go +++ b/builder/APIBuilder.go @@ -1,7 +1,6 @@ package builder import ( - "context" "fmt" . "github.com/nntaoli-project/goex" @@ -16,7 +15,7 @@ import ( "github.com/nntaoli-project/goex/fmex" "github.com/nntaoli-project/goex/kucoin" - "github.com/nntaoli-project/goex/atop" + "github.com/nntaoli-project/goex/atop" //"github.com/nntaoli-project/goex/coin58" "github.com/nntaoli-project/goex/coinex" "github.com/nntaoli-project/goex/fcoin" @@ -321,8 +320,13 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { ApiSecretKey: builder.secretkey, }) - - + case BINANCE, BINANCE_SWAP: + return binance.NewBinanceSwap(&APIConfig{ + HttpClient: builder.client, + Endpoint: builder.futuresEndPoint, + ApiKey: builder.apiKey, + ApiSecretKey: builder.secretkey, + }) default: println(fmt.Sprintf("%s not support future", exName)) From 6260b54032edcb12b7fe70ea02935059fb821049 Mon Sep 17 00:00:00 2001 From: eric Date: Sun, 8 Mar 2020 11:03:46 +0800 Subject: [PATCH 05/29] [binance] remove adaptCurrencyPair to reuse for binance-us/binance-je --- binance/Binance.go | 30 ++++++++++-------------------- binance/Binance_test.go | 34 +++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/binance/Binance.go b/binance/Binance.go index 17ad758e..a8171dd3 100644 --- a/binance/Binance.go +++ b/binance/Binance.go @@ -12,7 +12,9 @@ import ( ) const ( - //API_BASE_URL = "https://api.binance.com/" + GLOBAL_API_BASE_URL = "https://api.binance.com" + US_API_BASE_URL = "https://api.binance.us" + JE_API_BASE_URL = "https://api.binance.je" //API_V1 = API_BASE_URL + "api/v1/" //API_V3 = API_BASE_URL + "api/v3/" @@ -176,14 +178,14 @@ func (bn *Binance) buildParamsSigned(postForm *url.Values) error { func New(client *http.Client, api_key, secret_key string) *Binance { return NewWithConfig(&APIConfig{ HttpClient: client, - Endpoint: "https://api.binance.com", + Endpoint: GLOBAL_API_BASE_URL, ApiKey: api_key, ApiSecretKey: secret_key}) } func NewWithConfig(config *APIConfig) *Binance { if config.Endpoint == "" { - config.Endpoint = "https://api.binance.com" + config.Endpoint = GLOBAL_API_BASE_URL } bn := &Binance{ @@ -224,8 +226,7 @@ func (bn *Binance) setTimeOffset() error { } func (bn *Binance) GetTicker(currency CurrencyPair) (*Ticker, error) { - currency2 := bn.adaptCurrencyPair(currency) - tickerUri := bn.apiV3 + fmt.Sprintf(TICKER_URI, currency2.ToSymbol("")) + tickerUri := bn.apiV3 + fmt.Sprintf(TICKER_URI, currency.ToSymbol("")) tickerMap, err := HttpGet(bn.httpClient, tickerUri) if err != nil { @@ -261,9 +262,8 @@ func (bn *Binance) GetDepth(size int, currencyPair CurrencyPair) (*Depth, error) } else { size = 1000 } - currencyPair2 := bn.adaptCurrencyPair(currencyPair) - apiUrl := fmt.Sprintf(bn.apiV3+DEPTH_URI, currencyPair2.ToSymbol(""), size) + apiUrl := fmt.Sprintf(bn.apiV3+DEPTH_URI, currencyPair.ToSymbol(""), size) resp, err := HttpGet(bn.httpClient, apiUrl) if err != nil { return nil, err @@ -309,7 +309,6 @@ func (bn *Binance) GetDepth(size int, currencyPair CurrencyPair) (*Depth, error) } func (bn *Binance) placeOrder(amount, price string, pair CurrencyPair, orderType, orderSide string) (*Order, error) { - pair = bn.adaptCurrencyPair(pair) path := bn.apiV3 + ORDER_URI params := url.Values{} params.Set("symbol", pair.ToSymbol("")) @@ -416,7 +415,6 @@ func (bn *Binance) MarketSell(amount, price string, currencyPair CurrencyPair) ( } func (bn *Binance) CancelOrder(orderId string, currencyPair CurrencyPair) (bool, error) { - currencyPair = bn.adaptCurrencyPair(currencyPair) path := bn.apiV3 + ORDER_URI params := url.Values{} params.Set("symbol", currencyPair.ToSymbol("")) @@ -446,7 +444,6 @@ func (bn *Binance) CancelOrder(orderId string, currencyPair CurrencyPair) (bool, func (bn *Binance) GetOneOrder(orderId string, currencyPair CurrencyPair) (*Order, error) { params := url.Values{} - currencyPair = bn.adaptCurrencyPair(currencyPair) params.Set("symbol", currencyPair.ToSymbol("")) if orderId != "" { params.Set("orderId", orderId) @@ -508,7 +505,6 @@ func (bn *Binance) GetOneOrder(orderId string, currencyPair CurrencyPair) (*Orde func (bn *Binance) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, error) { params := url.Values{} - currencyPair = bn.adaptCurrencyPair(currencyPair) params.Set("symbol", currencyPair.ToSymbol("")) bn.buildParamsSigned(¶ms) @@ -576,9 +572,8 @@ func (bn *Binance) GetAllUnfinishOrders() ([]Order, error) { } func (bn *Binance) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - currency2 := bn.adaptCurrencyPair(currency) params := url.Values{} - params.Set("symbol", currency2.ToSymbol("")) + params.Set("symbol", currency.ToSymbol("")) params.Set("interval", _INERNAL_KLINE_PERIOD_CONVERTER[period]) if since > 0 { params.Set("startTime", strconv.Itoa(since)) @@ -614,7 +609,7 @@ func (bn *Binance) GetKlineRecords(currency CurrencyPair, period, size, since in //注意:since is fromId func (bn *Binance) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { param := url.Values{} - param.Set("symbol", bn.adaptCurrencyPair(currencyPair).ToSymbol("")) + param.Set("symbol", currencyPair.ToSymbol("")) param.Set("limit", "500") if since > 0 { param.Set("fromId", strconv.Itoa(int(since))) @@ -648,8 +643,7 @@ func (bn *Binance) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, e func (bn *Binance) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { params := url.Values{} - currency1 := bn.adaptCurrencyPair(currency) - params.Set("symbol", currency1.ToSymbol("")) + params.Set("symbol", currency.ToSymbol("")) bn.buildParamsSigned(¶ms) path := bn.apiV3 + "allOrders?" + params.Encode() @@ -682,10 +676,6 @@ func (bn *Binance) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize } -func (bn *Binance) adaptCurrencyPair(pair CurrencyPair) CurrencyPair { - return pair.AdaptUsdToUsdt() -} - func (bn *Binance) toCurrencyPair(symbol string) CurrencyPair { if bn.ExchangeInfo == nil { var err error diff --git a/binance/Binance_test.go b/binance/Binance_test.go index 8a0bd813..75bea087 100644 --- a/binance/Binance_test.go +++ b/binance/Binance_test.go @@ -9,24 +9,28 @@ import ( "time" ) -var ba = New(&http.Client{ - Transport: &http.Transport{ - Proxy: func(req *http.Request) (*url.URL, error) { - return url.Parse("socks5://127.0.0.1:1080") - return nil, nil - }, - Dial: (&net.Dialer{ +var ba = NewWithConfig( + &goex.APIConfig{ + HttpClient: &http.Client{ + Transport: &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + return url.Parse("socks5://127.0.0.1:1080") + return nil, nil + }, + Dial: (&net.Dialer{ + Timeout: 10 * time.Second, + }).Dial, + }, Timeout: 10 * time.Second, - }).Dial, - }, - Timeout: 10 * time.Second, -}, "q6y6Gr7fF3jSJLncpfn2PmAA0xu4XRiRFHpFkyJy3d7K68WUxY0Gt8rrajCDUfbI", - "AP8C2kh4RyISN3fpRCFMZJddf233XbPcYWQ1S7gBan3pGjCQg2JnyQFSJrIaNzRh", -) + }, + Endpoint: US_API_BASE_URL, + ApiKey: "q6y6Gr7fF3jSJLncpfn2PmAA0xu4XRiRFHpFkyJy3d7K68WUxY0Gt8rrajCDUfbI", + ApiSecretKey: "AP8C2kh4RyISN3fpRCFMZJddf233XbPcYWQ1S7gBan3pGjCQg2JnyQFSJrIaNzRh", + }) func TestBinance_GetTicker(t *testing.T) { - ticker, _ := ba.GetTicker(goex.LTC_BTC) - t.Log(ticker) + ticker, err := ba.GetTicker(goex.NewCurrencyPair2("USDT_USD")) + t.Log(ticker, err) } func TestBinance_LimitBuy(t *testing.T) { From 92aa51f1066856b83b7876cf1905363dae2f6aa8 Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Mon, 9 Mar 2020 15:23:41 +0800 Subject: [PATCH 06/29] [HttpUtils] fasthttp --- HttpUtils.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/HttpUtils.go b/HttpUtils.go index 60639b83..5dd1e89f 100644 --- a/HttpUtils.go +++ b/HttpUtils.go @@ -17,48 +17,56 @@ import ( "time" ) -var fastHttpClient fasthttp.Client - -func init() { - fastHttpClient.MaxConnsPerHost = 2 - fastHttpClient.ReadTimeout = 10 * time.Second - fastHttpClient.WriteTimeout = 10 * time.Second -} +var ( + fastHttpClient = &fasthttp.Client{ + Name: "goex-http-utils", + MaxConnsPerHost: 16, + MaxIdleConnDuration: 20 * time.Second, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + socksDialer fasthttp.DialFunc +) func NewHttpRequestWithFasthttp(client *http.Client, reqMethod, reqUrl, postData string, headers map[string]string) ([]byte, error) { logger.Log.Debug("use fasthttp client") transport := client.Transport + if transport != nil { if proxy, err := transport.(*http.Transport).Proxy(nil); err == nil && proxy != nil { proxyUrl := proxy.String() logger.Log.Debug("proxy url: ", proxyUrl) if proxy.Scheme != "socks5" { logger.Log.Error("fasthttp only support the socks5 proxy") - } else { - fastHttpClient.Dial = fasthttpproxy.FasthttpSocksDialer(strings.TrimPrefix(proxyUrl, proxy.Scheme+"://")) + } else if socksDialer == nil { + socksDialer = fasthttpproxy.FasthttpSocksDialer(strings.TrimPrefix(proxyUrl, proxy.Scheme+"://")) + fastHttpClient.Dial = socksDialer } } } + req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() defer func() { fasthttp.ReleaseRequest(req) fasthttp.ReleaseResponse(resp) }() + for k, v := range headers { req.Header.Set(k, v) } req.Header.SetMethod(reqMethod) req.SetRequestURI(reqUrl) req.SetBodyString(postData) - err := fastHttpClient.DoTimeout(req, resp, 10*time.Second) + + err := fastHttpClient.Do(req, resp) if err != nil { return nil, err } + if resp.StatusCode() != 200 { return nil, errors.New(fmt.Sprintf("HttpStatusCode:%d ,Desc:%s", resp.StatusCode(), string(resp.Body()))) } - return resp.Body(), nil } From a7aca04349594ecbef498a9d118c73e43d4e4c87 Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Fri, 13 Mar 2020 18:26:40 +0800 Subject: [PATCH 07/29] [okex] fix json UnmarshalJSON exception --- okex/OKExSpot.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/okex/OKExSpot.go b/okex/OKExSpot.go index 354694aa..bc3ec25c 100644 --- a/okex/OKExSpot.go +++ b/okex/OKExSpot.go @@ -223,7 +223,7 @@ type OrderResponse struct { InstrumentId string `json:"instrument_id"` ClientOid string `json:"client_oid"` OrderId string `json:"order_id"` - Price float64 `json:"price,string"` + Price string `json:"price,omitempty"` Size float64 `json:"size,string"` Notional string `json:"notional"` Side string `json:"side"` @@ -240,7 +240,7 @@ func (ok *OKExSpot) adaptOrder(response OrderResponse) *Order { ordInfo := &Order{ Cid: response.ClientOid, OrderID2: response.OrderId, - Price: response.Price, + Price: ToFloat64(response.Price), Amount: response.Size, AvgPrice: ToFloat64(response.PriceAvg), DealAmount: ToFloat64(response.FilledSize), From a4b198029b871bd4bd4afe282320c5d7d4668bec Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Wed, 18 Mar 2020 18:28:04 +0800 Subject: [PATCH 08/29] [okex] delete okex api v1 --- okcoin/OKCoin_CN.go | 581 ----------- okcoin/OKCoin_CN_test.go | 24 - okcoin/OKCoin_COM.go | 90 -- okcoin/OKEx.go | 695 ------------- okcoin/OKExSpot.go | 72 -- okcoin/OKExSpot_test.go | 27 - okcoin/OKEx_Future_Ws.go | 187 ---- okcoin/OKEx_Future_Ws_test.go | 25 - okcoin/OKEx_Spot_Ws.go | 287 ------ okcoin/OKEx_Spot_Ws_test.go | 32 - okcoin/OKEx_V3.go | 1618 ------------------------------ okcoin/OKEx_V3_Future_Ws.go | 447 --------- okcoin/OKEx_V3_Future_Ws_test.go | 141 --- okcoin/OKEx_V3_test.go | 291 ------ okcoin/OKEx_V3_utils.go | 278 ----- okcoin/OKEx_test.go | 18 - okcoin/OKcoin_COM_test.go | 14 - 17 files changed, 4827 deletions(-) delete mode 100644 okcoin/OKCoin_CN.go delete mode 100644 okcoin/OKCoin_CN_test.go delete mode 100644 okcoin/OKCoin_COM.go delete mode 100644 okcoin/OKEx.go delete mode 100644 okcoin/OKExSpot.go delete mode 100644 okcoin/OKExSpot_test.go delete mode 100644 okcoin/OKEx_Future_Ws.go delete mode 100644 okcoin/OKEx_Future_Ws_test.go delete mode 100644 okcoin/OKEx_Spot_Ws.go delete mode 100644 okcoin/OKEx_Spot_Ws_test.go delete mode 100644 okcoin/OKEx_V3.go delete mode 100644 okcoin/OKEx_V3_Future_Ws.go delete mode 100644 okcoin/OKEx_V3_Future_Ws_test.go delete mode 100644 okcoin/OKEx_V3_test.go delete mode 100644 okcoin/OKEx_V3_utils.go delete mode 100644 okcoin/OKEx_test.go delete mode 100644 okcoin/OKcoin_COM_test.go diff --git a/okcoin/OKCoin_CN.go b/okcoin/OKCoin_CN.go deleted file mode 100644 index 83b7e2cf..00000000 --- a/okcoin/OKCoin_CN.go +++ /dev/null @@ -1,581 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - . "github.com/nntaoli-project/goex" - "net/http" - "net/url" - "strconv" - "strings" -) - -const ( - url_ticker = "ticker.do" - url_depth = "depth.do" - url_trades = "trades.do" - url_kline = "kline.do?symbol=%s&type=%s&size=%d" - - url_userinfo = "userinfo.do" - url_trade = "trade.do" - url_cancel_order = "cancel_order.do" - url_order_info = "order_info.do" - url_orders_info = "orders_info.do" - order_history_uri = "order_history.do" - trade_uri = "trade_history.do" -) - -type OKCoinCN_API struct { - client *http.Client - api_key string - secret_key string - api_base_url string -} - -var _INERNAL_KLINE_PERIOD_CONVERTER = map[int]string{ - KLINE_PERIOD_1MIN: "1min", - KLINE_PERIOD_5MIN: "5min", - KLINE_PERIOD_15MIN: "15min", - KLINE_PERIOD_30MIN: "30min", - KLINE_PERIOD_60MIN: "1hour", - KLINE_PERIOD_4H: "4hour", - KLINE_PERIOD_1DAY: "1day", - KLINE_PERIOD_1WEEK: "1week", -} - -//func currencyPair2String(currency CurrencyPair) string { -// switch currency { -// case BTC_CNY: -// return "btc_cny" -// case LTC_CNY: -// return "ltc_cny" -// case BTC_USD: -// return "btc_usd" -// case LTC_USD: -// return "ltc_usd" -// default: -// return "" -// } -//} - -func NewOKCoinCn(client *http.Client, api_key, secret_key string) *OKCoinCN_API { - return &OKCoinCN_API{client, api_key, secret_key, "https://www.okex.com/api/v1/"} -} - -func (ctx *OKCoinCN_API) buildPostForm(postForm *url.Values) error { - postForm.Set("api_key", ctx.api_key) - //postForm.Set("secret_key", ctx.secret_key); - - payload := postForm.Encode() - payload = payload + "&secret_key=" + ctx.secret_key - - sign, err := GetParamMD5Sign(ctx.secret_key, payload) - if err != nil { - return err - } - - postForm.Set("sign", strings.ToUpper(sign)) - //postForm.Del("secret_key") - return nil -} - -func (ctx *OKCoinCN_API) placeOrder(side, amount, price string, currency CurrencyPair) (*Order, error) { - postData := url.Values{} - postData.Set("type", side) - - if side != "buy_market" { - postData.Set("amount", amount) - } - if side != "sell_market" { - postData.Set("price", price) - } - postData.Set("symbol", strings.ToLower(currency.ToSymbol("_"))) - - err := ctx.buildPostForm(&postData) - if err != nil { - return nil, err - } - - body, err := HttpPostForm(ctx.client, ctx.api_base_url+url_trade, postData) - if err != nil { - return nil, err - } - - //println(string(body)); - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if err, isok := respMap["error_code"].(float64); isok { - return nil, errors.New(fmt.Sprint(err)) - } - - order := new(Order) - order.OrderID = int(respMap["order_id"].(float64)) - order.OrderID2 = fmt.Sprint(int(respMap["order_id"].(float64))) - order.Price, _ = strconv.ParseFloat(price, 64) - order.Amount, _ = strconv.ParseFloat(amount, 64) - order.Currency = currency - order.Status = ORDER_UNFINISH - - switch side { - case "buy": - order.Side = BUY - case "sell": - order.Side = SELL - } - - return order, nil -} - -func (ctx *OKCoinCN_API) LimitBuy(amount, price string, currency CurrencyPair) (*Order, error) { - return ctx.placeOrder("buy", amount, price, currency) -} - -func (ctx *OKCoinCN_API) LimitSell(amount, price string, currency CurrencyPair) (*Order, error) { - return ctx.placeOrder("sell", amount, price, currency) -} - -func (ctx *OKCoinCN_API) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { - return ctx.placeOrder("buy_market", amount, price, currency) -} - -func (ctx *OKCoinCN_API) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { - return ctx.placeOrder("sell_market", amount, price, currency) -} - -func (ctx *OKCoinCN_API) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { - postData := url.Values{} - postData.Set("order_id", orderId) - postData.Set("symbol", strings.ToLower(currency.ToSymbol("_"))) - - ctx.buildPostForm(&postData) - - body, err := HttpPostForm(ctx.client, ctx.api_base_url+url_cancel_order, postData) - - if err != nil { - return false, err - } - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return false, err - } - - if err, isok := respMap["error_code"].(float64); isok { - return false, errors.New(fmt.Sprint(err)) - } - - return true, nil -} - -func (ctx *OKCoinCN_API) getOrders(orderId string, currency CurrencyPair) ([]Order, error) { - postData := url.Values{} - postData.Set("order_id", orderId) - postData.Set("symbol", strings.ToLower(currency.ToSymbol("_"))) - - ctx.buildPostForm(&postData) - - body, err := HttpPostForm(ctx.client, ctx.api_base_url+url_order_info, postData) - if err != nil { - return nil, err - } - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if err, isok := respMap["error_code"].(float64); isok { - return nil, errors.New(fmt.Sprint(err)) - } - - orders := respMap["orders"].([]interface{}) - - var orderAr []Order - for _, v := range orders { - orderMap := v.(map[string]interface{}) - - var order Order - order.Currency = currency - order.OrderID = int(orderMap["order_id"].(float64)) - order.OrderID2 = fmt.Sprint(int(orderMap["order_id"].(float64))) - order.Amount = orderMap["amount"].(float64) - order.Price = orderMap["price"].(float64) - order.DealAmount = orderMap["deal_amount"].(float64) - order.AvgPrice = orderMap["avg_price"].(float64) - order.OrderTime = int(orderMap["create_date"].(float64)) - - //status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 - switch int(orderMap["status"].(float64)) { - case -1: - order.Status = ORDER_CANCEL - case 0: - order.Status = ORDER_UNFINISH - case 1: - order.Status = ORDER_PART_FINISH - case 2: - order.Status = ORDER_FINISH - case 4: - order.Status = ORDER_CANCEL_ING - } - - switch orderMap["type"].(string) { - case "buy": - order.Side = BUY - case "sell": - order.Side = SELL - case "buy_market": - order.Side = BUY_MARKET - case "sell_market": - order.Side = SELL_MARKET - } - - orderAr = append(orderAr, order) - } - - //fmt.Println(orders); - return orderAr, nil -} - -func (ctx *OKCoinCN_API) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) { - orderAr, err := ctx.getOrders(orderId, currency) - if err != nil { - return nil, err - } - - if len(orderAr) == 0 { - return nil, nil - } - - return &orderAr[0], nil -} - -func (ctx *OKCoinCN_API) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { - return ctx.getOrders("-1", currency) -} - -func (ctx *OKCoinCN_API) GetAccount() (*Account, error) { - postData := url.Values{} - err := ctx.buildPostForm(&postData) - if err != nil { - return nil, err - } - - body, err := HttpPostForm(ctx.client, ctx.api_base_url+url_userinfo, postData) - if err != nil { - return nil, err - } - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if err, isok := respMap["error_code"].(float64); isok { - return nil, errors.New(fmt.Sprint(err)) - } - - info, ok := respMap["info"].(map[string]interface{}) - if !ok { - return nil, errors.New(string(body)) - } - - funds := info["funds"].(map[string]interface{}) - asset := funds["asset"].(map[string]interface{}) - free := funds["free"].(map[string]interface{}) - freezed := funds["freezed"].(map[string]interface{}) - - account := new(Account) - account.Exchange = ctx.GetExchangeName() - account.Asset, _ = strconv.ParseFloat(asset["total"].(string), 64) - account.NetAsset, _ = strconv.ParseFloat(asset["net"].(string), 64) - - var btcSubAccount SubAccount - var ltcSubAccount SubAccount - var cnySubAccount SubAccount - var ethSubAccount SubAccount - var etcSubAccount SubAccount - var bccSubAccount SubAccount - - btcSubAccount.Currency = BTC - btcSubAccount.Amount, _ = strconv.ParseFloat(free["btc"].(string), 64) - btcSubAccount.LoanAmount = 0 - btcSubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["btc"].(string), 64) - - ltcSubAccount.Currency = LTC - ltcSubAccount.Amount, _ = strconv.ParseFloat(free["ltc"].(string), 64) - ltcSubAccount.LoanAmount = 0 - ltcSubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["ltc"].(string), 64) - - ethSubAccount.Currency = ETH - ethSubAccount.Amount, _ = strconv.ParseFloat(free["eth"].(string), 64) - ethSubAccount.LoanAmount = 0 - ethSubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["eth"].(string), 64) - - etcSubAccount.Currency = ETC - etcSubAccount.Amount = ToFloat64(free["etc"]) - etcSubAccount.LoanAmount = 0 - etcSubAccount.ForzenAmount = ToFloat64(freezed["etc"]) - - bccSubAccount.Currency = BCC - bccSubAccount.Amount = ToFloat64(free["bcc"]) - bccSubAccount.LoanAmount = 0 - bccSubAccount.ForzenAmount = ToFloat64(freezed["bcc"]) - - cnySubAccount.Currency = CNY - cnySubAccount.Amount, _ = strconv.ParseFloat(free["cny"].(string), 64) - cnySubAccount.LoanAmount = 0 - cnySubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["cny"].(string), 64) - - account.SubAccounts = make(map[Currency]SubAccount, 3) - account.SubAccounts[BTC] = btcSubAccount - account.SubAccounts[LTC] = ltcSubAccount - account.SubAccounts[CNY] = cnySubAccount - account.SubAccounts[ETH] = ethSubAccount - account.SubAccounts[ETC] = etcSubAccount - account.SubAccounts[BCC] = bccSubAccount - - return account, nil -} - -func (ctx *OKCoinCN_API) GetTicker(currency CurrencyPair) (*Ticker, error) { - var tickerMap map[string]interface{} - var ticker Ticker - - url := ctx.api_base_url + url_ticker + "?symbol=" + strings.ToLower(currency.ToSymbol("_")) - bodyDataMap, err := HttpGet(ctx.client, url) - if err != nil { - return nil, err - } - - if errCode, is := bodyDataMap["error_code"].(int); is { - return nil, errors.New(fmt.Sprint(errCode)) - } - - tickerMap, isok := bodyDataMap["ticker"].(map[string]interface{}) - if !isok { - return nil, errors.New(fmt.Sprintf("%+v", bodyDataMap)) - } - - ticker.Date, _ = strconv.ParseUint(bodyDataMap["date"].(string), 10, 64) - ticker.Last, _ = strconv.ParseFloat(tickerMap["last"].(string), 64) - ticker.Buy, _ = strconv.ParseFloat(tickerMap["buy"].(string), 64) - ticker.Sell, _ = strconv.ParseFloat(tickerMap["sell"].(string), 64) - ticker.Low, _ = strconv.ParseFloat(tickerMap["low"].(string), 64) - ticker.High, _ = strconv.ParseFloat(tickerMap["high"].(string), 64) - ticker.Vol, _ = strconv.ParseFloat(tickerMap["vol"].(string), 64) - ticker.Pair = currency - return &ticker, nil -} - -func (ctx *OKCoinCN_API) GetDepth(size int, currency CurrencyPair) (*Depth, error) { - var depth Depth - depth.Pair = currency - - url := ctx.api_base_url + url_depth + "?symbol=" + strings.ToLower(currency.ToSymbol("_")) + "&size=" + strconv.Itoa(size) - //fmt.Println(url) - bodyDataMap, err := HttpGet(ctx.client, url) - if err != nil { - return nil, err - } - - if err, isok := bodyDataMap["error_code"].(float64); isok { - return nil, errors.New(fmt.Sprint(err)) - } - - dep, isok := bodyDataMap["asks"].([]interface{}) - if !isok { - return nil, errors.New("parse data error") - } - - for _, v := range dep { - var dr DepthRecord - for i, vv := range v.([]interface{}) { - switch i { - case 0: - dr.Price = vv.(float64) - case 1: - dr.Amount = vv.(float64) - } - } - depth.AskList = append(depth.AskList, dr) - } - - for _, v := range bodyDataMap["bids"].([]interface{}) { - var dr DepthRecord - for i, vv := range v.([]interface{}) { - switch i { - case 0: - dr.Price = vv.(float64) - case 1: - dr.Amount = vv.(float64) - } - } - depth.BidList = append(depth.BidList, dr) - } - - return &depth, nil -} - -func (ctx *OKCoinCN_API) GetExchangeName() string { - return OKCOIN_CN -} - -func (ctx *OKCoinCN_API) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - - klineUrl := ctx.api_base_url + fmt.Sprintf(url_kline, - strings.ToLower(currency.ToSymbol("_")), - _INERNAL_KLINE_PERIOD_CONVERTER[period], size) - if since != -1 { - klineUrl += "&since=" + strconv.Itoa(since) - } - - body, err := HttpGet5(ctx.client, klineUrl, nil) - if err != nil { - return nil, err - } - - var klines [][]interface{} - - err = json.Unmarshal(body, &klines) - if err != nil { - return nil, err - } - var klineRecords []Kline - - for _, record := range klines { - r := Kline{} - r.Pair = currency - for i, e := range record { - switch i { - case 0: - r.Timestamp = int64(e.(float64)) / 1000 //to unix timestramp - case 1: - r.Open = ToFloat64(e) - case 2: - r.High = ToFloat64(e) - case 3: - r.Low = ToFloat64(e) - case 4: - r.Close = ToFloat64(e) - case 5: - r.Vol = ToFloat64(e) - } - } - klineRecords = append(klineRecords, r) - } - - return klineRecords, nil -} - -func (ctx *OKCoinCN_API) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - orderHistoryUrl := ctx.api_base_url + order_history_uri - - postData := url.Values{} - postData.Set("status", "1") - postData.Set("symbol", strings.ToLower(currency.ToSymbol("_"))) - postData.Set("current_page", fmt.Sprintf("%d", currentPage)) - postData.Set("page_length", fmt.Sprintf("%d", pageSize)) - - err := ctx.buildPostForm(&postData) - if err != nil { - return nil, err - } - - body, err := HttpPostForm(ctx.client, orderHistoryUrl, postData) - if err != nil { - return nil, err - } - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if err, isok := respMap["error_code"].(float64); isok { - return nil, errors.New(fmt.Sprint(err)) - } - - orders := respMap["orders"].([]interface{}) - - var orderAr []Order - for _, v := range orders { - orderMap := v.(map[string]interface{}) - - var order Order - order.Currency = currency - order.OrderID = int(orderMap["order_id"].(float64)) - order.OrderID2 = fmt.Sprint(int(orderMap["order_id"].(float64))) - order.Amount = orderMap["amount"].(float64) - order.Price = orderMap["price"].(float64) - order.DealAmount = orderMap["deal_amount"].(float64) - order.AvgPrice = orderMap["avg_price"].(float64) - order.OrderTime = int(orderMap["create_date"].(float64)) - order.Side = AdaptTradeSide(orderMap["type"].(string)) - - //status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 - switch int(orderMap["status"].(float64)) { - case -1: - order.Status = ORDER_CANCEL - case 0: - order.Status = ORDER_UNFINISH - case 1: - order.Status = ORDER_PART_FINISH - case 2: - order.Status = ORDER_FINISH - case 4: - order.Status = ORDER_CANCEL_ING - } - - orderAr = append(orderAr, order) - } - - return orderAr, nil -} - -func (ok *OKCoinCN_API) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - url := ok.api_base_url + url_trades + "?symbol=" + strings.ToLower(currencyPair.ToSymbol("_")) + "&since=" - if since > 0 { - url = url + fmt.Sprintf("%d", since) - } - - body, err := HttpGet5(ok.client, url, nil) - if err != nil { - return nil, err - } - fmt.Println(string(body)) - - var trades []Trade - var resp []interface{} - err = json.Unmarshal(body, &resp) - if err != nil { - return nil, errors.New(string(body)) - } - - for _, v := range resp { - item := v.(map[string]interface{}) - - tid := int64(item["tid"].(float64)) - direction := item["type"].(string) - amount := item["amount"].(float64) - price := item["price"].(float64) - time := int64(item["date_ms"].(float64)) - trades = append(trades, Trade{tid, AdaptTradeSide(direction), amount, price, time, currencyPair}) - } - - return trades, nil -} diff --git a/okcoin/OKCoin_CN_test.go b/okcoin/OKCoin_CN_test.go deleted file mode 100644 index 369996bd..00000000 --- a/okcoin/OKCoin_CN_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package okcoin - -import ( - "github.com/nntaoli-project/goex" - "net/http" - "testing" -) - -var okcn = NewOKCoinCn(http.DefaultClient, "", "") - -func TestOKCoinCN_API_GetTicker(t *testing.T) { - ticker, _ := okcn.GetTicker(goex.BTC_CNY) - t.Log(ticker) -} - -func TestOKCoinCN_API_GetDepth(t *testing.T) { - dep, _ := okcn.GetDepth(1, goex.ETH_CNY) - t.Log(dep) -} - -func TestOKCoinCN_API_GetKlineRecords(t *testing.T) { - klines, _ := okcn.GetKlineRecords(goex.BTC_USDT, goex.KLINE_PERIOD_1MIN, 1000, -1) - t.Log(klines) -} diff --git a/okcoin/OKCoin_COM.go b/okcoin/OKCoin_COM.go deleted file mode 100644 index 0a38d5b8..00000000 --- a/okcoin/OKCoin_COM.go +++ /dev/null @@ -1,90 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - . "github.com/nntaoli-project/goex" - "net/http" - "net/url" - "strconv" -) - -const ( - EXCHANGE_NAME_COM = "okcoin.com" -) - -type OKCoinCOM_API struct { - OKCoinCN_API -} - -func NewCOM(client *http.Client, api_key, secret_key string) *OKCoinCOM_API { - return &OKCoinCOM_API{OKCoinCN_API{client, api_key, secret_key, "https://www.okcoin.com/api/v1/"}} -} - -func (ctx *OKCoinCOM_API) GetAccount() (*Account, error) { - postData := url.Values{} - err := ctx.buildPostForm(&postData) - if err != nil { - return nil, err - } - - body, err := HttpPostForm(ctx.client, ctx.api_base_url+url_userinfo, postData) - if err != nil { - return nil, err - } - - // println(string(body)) - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if !respMap["result"].(bool) { - errcode := strconv.FormatFloat(respMap["error_code"].(float64), 'f', 0, 64) - return nil, errors.New(errcode) - } - - info := respMap["info"].(map[string]interface{}) - funds := info["funds"].(map[string]interface{}) - asset := funds["asset"].(map[string]interface{}) - free := funds["free"].(map[string]interface{}) - freezed := funds["freezed"].(map[string]interface{}) - - account := new(Account) - account.Exchange = ctx.GetExchangeName() - account.Asset, _ = strconv.ParseFloat(asset["total"].(string), 64) - account.NetAsset, _ = strconv.ParseFloat(asset["net"].(string), 64) - - var btcSubAccount SubAccount - var ltcSubAccount SubAccount - var cnySubAccount SubAccount - - btcSubAccount.Currency = BTC - btcSubAccount.Amount, _ = strconv.ParseFloat(free["btc"].(string), 64) - btcSubAccount.LoanAmount = 0 - btcSubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["btc"].(string), 64) - - ltcSubAccount.Currency = LTC - ltcSubAccount.Amount, _ = strconv.ParseFloat(free["ltc"].(string), 64) - ltcSubAccount.LoanAmount = 0 - ltcSubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["ltc"].(string), 64) - - cnySubAccount.Currency = CNY - cnySubAccount.Amount, _ = strconv.ParseFloat(free["usd"].(string), 64) - cnySubAccount.LoanAmount = 0 - cnySubAccount.ForzenAmount, _ = strconv.ParseFloat(freezed["usd"].(string), 64) - - account.SubAccounts = make(map[Currency]SubAccount, 3) - account.SubAccounts[BTC] = btcSubAccount - account.SubAccounts[LTC] = ltcSubAccount - account.SubAccounts[USD] = cnySubAccount - - return account, nil -} - -func (ctx *OKCoinCOM_API) GetExchangeName() string { - return OKCOIN_COM -} diff --git a/okcoin/OKEx.go b/okcoin/OKEx.go deleted file mode 100644 index 1533d3a6..00000000 --- a/okcoin/OKEx.go +++ /dev/null @@ -1,695 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - . "github.com/nntaoli-project/goex" - "io/ioutil" - "log" - "net/http" - "net/url" - "strconv" - "strings" -) - -const ( - FUTURE_API_BASE_URL = "https://www.okex.com/api/v1/" - FUTURE_TICKER_URI = "future_ticker.do?symbol=%s&contract_type=%s" - FUTURE_DEPTH_URI = "future_depth.do?symbol=%s&contract_type=%s" - FUTURE_INDEX_PRICE = "future_index.do?symbol=%s" - FUTURE_USERINFO_URI = "future_userinfo.do" - FUTURE_CANCEL_URI = "future_cancel.do" - FUTURE_ORDER_INFO_URI = "future_order_info.do" - FUTURE_ORDERS_INFO_URI = "future_orders_info.do" - FUTURE_POSITION_URI = "future_position.do" - FUTURE_TRADE_URI = "future_trade.do" - TRADES_URI = "future_trades.do" - FUTURE_ESTIMATED_PRICE = "future_estimated_price.do?symbol=%s" - _EXCHANGE_RATE_URI = "exchange_rate.do" - _GET_KLINE_URI = "future_kline.do" -) - -type OKEx struct { - apiKey, - apiSecretKey string - client *http.Client -} - -func NewOKEx(client *http.Client, api_key, secret_key string) *OKEx { - ok := new(OKEx) - ok.apiKey = api_key - ok.apiSecretKey = secret_key - ok.client = client - return ok -} - -func (ok *OKEx) buildPostForm(postForm *url.Values) error { - postForm.Set("api_key", ok.apiKey) - //postForm.Set("secret_key", ctx.secret_key); - - payload := postForm.Encode() - payload = payload + "&secret_key=" + ok.apiSecretKey - payload2, _ := url.QueryUnescape(payload) // can't escape for sign - // - sign, err := GetParamMD5Sign(ok.apiSecretKey, payload2) - if err != nil { - return err - } - - postForm.Set("sign", strings.ToUpper(sign)) - //postForm.Del("secret_key") - //fmt.Println(postForm) - return nil -} - -func (ok *OKEx) GetExchangeName() string { - return OKEX_FUTURE -} - -func (ok *OKEx) GetFutureEstimatedPrice(currencyPair CurrencyPair) (float64, error) { - resp, err := ok.client.Get(fmt.Sprintf(FUTURE_API_BASE_URL+FUTURE_ESTIMATED_PRICE, strings.ToLower(currencyPair.ToSymbol("_")))) - if err != nil { - return 0, err - } - - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return 0, err - } - - bodyMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &bodyMap) - if err != nil { - return 0, err - } - - //println(string(body)) - return bodyMap["forecast_price"].(float64), nil -} - -func (ok *OKEx) GetFutureTicker(currencyPair CurrencyPair, contractType string) (*Ticker, error) { - url := FUTURE_API_BASE_URL + FUTURE_TICKER_URI - //fmt.Println(fmt.Sprintf(url, strings.ToLower(currencyPair.ToSymbol("_")), contractType)); - resp, err := ok.client.Get(fmt.Sprintf(url, strings.ToLower(currencyPair.ToSymbol("_")), contractType)) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - //println(string(body)) - - bodyMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &bodyMap) - if err != nil { - return nil, err - } - - if bodyMap["result"] != nil && !bodyMap["result"].(bool) { - return nil, errors.New(string(body)) - } - - tickerMap := bodyMap["ticker"].(map[string]interface{}) - - ticker := new(Ticker) - ticker.Pair = currencyPair - ticker.Date, _ = strconv.ParseUint(bodyMap["date"].(string), 10, 64) - ticker.Buy = tickerMap["buy"].(float64) - ticker.Sell = tickerMap["sell"].(float64) - ticker.Last = tickerMap["last"].(float64) - ticker.High = tickerMap["high"].(float64) - ticker.Low = tickerMap["low"].(float64) - ticker.Vol = tickerMap["vol"].(float64) - - //fmt.Println(bodyMap) - return ticker, nil -} - -func (ok *OKEx) GetFutureDepth(currencyPair CurrencyPair, contractType string, size int) (*Depth, error) { - url := FUTURE_API_BASE_URL + FUTURE_DEPTH_URI - //fmt.Println(fmt.Sprintf(url, strings.ToLower(currencyPair.ToSymbol("_")), contractType)); - resp, err := ok.client.Get(fmt.Sprintf(url, strings.ToLower(strings.ToLower(currencyPair.ToSymbol("_"))), contractType)) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - //println(string(body)) - - bodyMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &bodyMap) - if err != nil { - return nil, err - } - - if bodyMap["error_code"] != nil { - log.Println(bodyMap) - return nil, errors.New(string(body)) - } - - depth := new(Depth) - depth.Pair = currencyPair - depth.ContractType = contractType - - size2 := len(bodyMap["asks"].([]interface{})) - skipSize := 0 - if size < size2 { - skipSize = size2 - size - } - - for _, v := range bodyMap["asks"].([]interface{}) { - if skipSize > 0 { - skipSize-- - continue - } - - var dr DepthRecord - for i, vv := range v.([]interface{}) { - switch i { - case 0: - dr.Price = vv.(float64) - case 1: - dr.Amount = vv.(float64) - } - } - depth.AskList = append(depth.AskList, dr) - } - - for _, v := range bodyMap["bids"].([]interface{}) { - var dr DepthRecord - for i, vv := range v.([]interface{}) { - switch i { - case 0: - dr.Price = vv.(float64) - case 1: - dr.Amount = vv.(float64) - } - } - depth.BidList = append(depth.BidList, dr) - - size-- - if size == 0 { - break - } - } - - //fmt.Println(bodyMap) - return depth, nil -} - -func (ok *OKEx) GetFutureIndex(currencyPair CurrencyPair) (float64, error) { - resp, err := ok.client.Get(fmt.Sprintf(FUTURE_API_BASE_URL+FUTURE_INDEX_PRICE, strings.ToLower(currencyPair.ToSymbol("_")))) - if err != nil { - return 0, err - } - - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return 0, err - } - - bodyMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &bodyMap) - if err != nil { - return 0, err - } - - //println(string(body)) - return bodyMap["future_index"].(float64), nil -} - -type futureUserInfoResponse struct { - Info struct { - Btc map[string]float64 `json:btc` - Ltc map[string]float64 `json:ltc` - Etc map[string]float64 `json:"etc"` - Eth map[string]float64 `json:"eth"` - Bch map[string]float64 `json:"bch"` - Xrp map[string]float64 `json:"xrp"` - Eos map[string]float64 `json:"eos"` - Btg map[string]float64 `json:"btg"` - Bsv map[string]float64 `json:"bsv"` - } `json:info` - Result bool `json:"result,bool"` - Error_code int `json:"error_code"` -} - -func (ok *OKEx) GetFutureUserinfo() (*FutureAccount, error) { - userInfoUrl := FUTURE_API_BASE_URL + FUTURE_USERINFO_URI - - postData := url.Values{} - ok.buildPostForm(&postData) - - body, err := HttpPostForm(ok.client, userInfoUrl, postData) - - if err != nil { - return nil, err - } - - //println(string(body)); - resp := futureUserInfoResponse{} - err = json.Unmarshal(body, &resp) - if err != nil { - return nil, err - } - - if !resp.Result && resp.Error_code > 0 { - return nil, ok.errorWrapper(resp.Error_code) - } - - account := new(FutureAccount) - account.FutureSubAccounts = make(map[Currency]FutureSubAccount, 2) - - btcMap := resp.Info.Btc - ltcMap := resp.Info.Ltc - bchMap := resp.Info.Bch - ethMap := resp.Info.Eth - etcMap := resp.Info.Etc - xrpMap := resp.Info.Xrp - eosMap := resp.Info.Eos - btgMap := resp.Info.Btg - bsvMap := resp.Info.Bsv - - account.FutureSubAccounts[BTC] = FutureSubAccount{BTC, btcMap["account_rights"], btcMap["keep_deposit"], btcMap["profit_real"], btcMap["profit_unreal"], btcMap["risk_rate"]} - account.FutureSubAccounts[LTC] = FutureSubAccount{LTC, ltcMap["account_rights"], ltcMap["keep_deposit"], ltcMap["profit_real"], ltcMap["profit_unreal"], ltcMap["risk_rate"]} - account.FutureSubAccounts[BCH] = FutureSubAccount{BCH, bchMap["account_rights"], bchMap["keep_deposit"], bchMap["profit_real"], bchMap["profit_unreal"], bchMap["risk_rate"]} - account.FutureSubAccounts[ETH] = FutureSubAccount{ETH, ethMap["account_rights"], ethMap["keep_deposit"], ethMap["profit_real"], ethMap["profit_unreal"], ethMap["risk_rate"]} - account.FutureSubAccounts[ETC] = FutureSubAccount{ETC, etcMap["account_rights"], etcMap["keep_deposit"], etcMap["profit_real"], etcMap["profit_unreal"], etcMap["risk_rate"]} - account.FutureSubAccounts[XRP] = FutureSubAccount{XRP, xrpMap["account_rights"], xrpMap["keep_deposit"], xrpMap["profit_real"], xrpMap["profit_unreal"], xrpMap["risk_rate"]} - account.FutureSubAccounts[EOS] = FutureSubAccount{EOS, eosMap["account_rights"], eosMap["keep_deposit"], eosMap["profit_real"], eosMap["profit_unreal"], eosMap["risk_rate"]} - account.FutureSubAccounts[BTG] = FutureSubAccount{BTG, btgMap["account_rights"], btgMap["keep_deposit"], btgMap["profit_real"], btgMap["profit_unreal"], btgMap["risk_rate"]} - account.FutureSubAccounts[BSV] = FutureSubAccount{BSV, bsvMap["account_rights"], bsvMap["keep_deposit"], bsvMap["profit_real"], bsvMap["profit_unreal"], bsvMap["risk_rate"]} - - return account, nil -} - -func (ok *OKEx) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice, leverRate int) (string, error) { - postData := url.Values{} - postData.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - postData.Set("price", price) - postData.Set("contract_type", contractType) - postData.Set("amount", amount) - postData.Set("type", strconv.Itoa(openType)) - postData.Set("lever_rate", strconv.Itoa(leverRate)) - postData.Set("match_price", strconv.Itoa(matchPrice)) - - ok.buildPostForm(&postData) - - placeOrderUrl := FUTURE_API_BASE_URL + FUTURE_TRADE_URI - body, err := HttpPostForm(ok.client, placeOrderUrl, postData) - - if err != nil { - return "", err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return "", err - } - - //println(string(body)); - - if !respMap["result"].(bool) { - return "", errors.New(string(body)) - } - - return fmt.Sprintf("%.0f", respMap["order_id"].(float64)), nil -} - -func (ok *OKEx) FutureCancelOrder(currencyPair CurrencyPair, contractType, orderId string) (bool, error) { - postData := url.Values{} - postData.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - postData.Set("order_id", orderId) - postData.Set("contract_type", contractType) - - ok.buildPostForm(&postData) - - cancelUrl := FUTURE_API_BASE_URL + FUTURE_CANCEL_URI - - body, err := HttpPostForm(ok.client, cancelUrl, postData) - if err != nil { - return false, err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return false, err - } - - if respMap["result"] != nil && !respMap["result"].(bool) { - return false, errors.New(string(body)) - } - - return true, nil -} - -func (ok *OKEx) GetFuturePosition(currencyPair CurrencyPair, contractType string) ([]FuturePosition, error) { - positionUrl := FUTURE_API_BASE_URL + FUTURE_POSITION_URI - - postData := url.Values{} - postData.Set("contract_type", contractType) - postData.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - - ok.buildPostForm(&postData) - - body, err := HttpPostForm(ok.client, positionUrl, postData) - - if err != nil { - return nil, err - } - - respMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if !respMap["result"].(bool) { - return nil, errors.New(string(body)) - } - - //println(string(body)) - - var posAr []FuturePosition - - forceLiquPriceStr := respMap["force_liqu_price"].(string) - forceLiquPriceStr = strings.Replace(forceLiquPriceStr, ",", "", 1) - forceLiquPrice, err := strconv.ParseFloat(forceLiquPriceStr, 64) - - holdings := respMap["holding"].([]interface{}) - for _, v := range holdings { - holdingMap := v.(map[string]interface{}) - - pos := FuturePosition{} - pos.ForceLiquPrice = forceLiquPrice - pos.LeverRate = int(holdingMap["lever_rate"].(float64)) - pos.ContractType = holdingMap["contract_type"].(string) - pos.ContractId = int64(holdingMap["contract_id"].(float64)) - pos.BuyAmount = holdingMap["buy_amount"].(float64) - pos.BuyAvailable = holdingMap["buy_available"].(float64) - pos.BuyPriceAvg = holdingMap["buy_price_avg"].(float64) - pos.BuyPriceCost = holdingMap["buy_price_cost"].(float64) - pos.BuyProfitReal = holdingMap["buy_profit_real"].(float64) - pos.SellAmount = holdingMap["sell_amount"].(float64) - pos.SellAvailable = holdingMap["sell_available"].(float64) - pos.SellPriceAvg = holdingMap["sell_price_avg"].(float64) - pos.SellPriceCost = holdingMap["sell_price_cost"].(float64) - pos.SellProfitReal = holdingMap["sell_profit_real"].(float64) - pos.CreateDate = int64(holdingMap["create_date"].(float64)) - pos.Symbol = currencyPair - posAr = append(posAr, pos) - - } - - return posAr, nil -} - -func (ok *OKEx) parseOrders(body []byte, currencyPair CurrencyPair) ([]FutureOrder, error) { - respMap := make(map[string]interface{}) - - err := json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if !respMap["result"].(bool) { - return nil, errors.New(string(body)) - } - - var orders []interface{} - orders = respMap["orders"].([]interface{}) - - var futureOrders []FutureOrder - - for _, v := range orders { - vv := v.(map[string]interface{}) - futureOrder := FutureOrder{} - futureOrder.OrderID = int64(vv["order_id"].(float64)) - futureOrder.OrderID2 = fmt.Sprint(int64(vv["order_id"].(float64))) - futureOrder.Amount = vv["amount"].(float64) - futureOrder.Price = vv["price"].(float64) - futureOrder.AvgPrice = vv["price_avg"].(float64) - futureOrder.DealAmount = vv["deal_amount"].(float64) - futureOrder.Fee = vv["fee"].(float64) - futureOrder.OType = int(vv["type"].(float64)) - futureOrder.OrderTime = int64(vv["create_date"].(float64)) - futureOrder.LeverRate = int(vv["lever_rate"].(float64)) - futureOrder.ContractName = vv["contract_name"].(string) - futureOrder.Currency = currencyPair - - switch s := int(vv["status"].(float64)); s { - case 0: - futureOrder.Status = ORDER_UNFINISH - case 1: - futureOrder.Status = ORDER_PART_FINISH - case 2: - futureOrder.Status = ORDER_FINISH - case 4: - futureOrder.Status = ORDER_CANCEL_ING - case -1: - futureOrder.Status = ORDER_CANCEL - - } - - futureOrders = append(futureOrders, futureOrder) - } - return futureOrders, nil -} - -func (ok *OKEx) GetFutureOrders(orderIds []string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - postData := url.Values{} - postData.Set("order_id", strings.Join(orderIds, ",")) - postData.Set("contract_type", contractType) - postData.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - ok.buildPostForm(&postData) - - body, err := HttpPostForm(ok.client, FUTURE_API_BASE_URL+FUTURE_ORDERS_INFO_URI, postData) - if err != nil { - return nil, err - } - - return ok.parseOrders(body, currencyPair) -} - -func (ok *OKEx) GetUnfinishFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - postData := url.Values{} - postData.Set("order_id", "-1") - postData.Set("contract_type", contractType) - postData.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - postData.Set("status", "1") - postData.Set("current_page", "1") - postData.Set("page_length", "50") - - ok.buildPostForm(&postData) - - body, err := HttpPostForm(ok.client, FUTURE_API_BASE_URL+FUTURE_ORDER_INFO_URI, postData) - if err != nil { - return nil, err - } - - //println(string(body)) - - return ok.parseOrders(body, currencyPair) -} - -func (ok *OKEx) GetFutureOrder(orderId string, currencyPair CurrencyPair, contractType string) (*FutureOrder, error) { - postData := url.Values{} - postData.Set("order_id", orderId) - postData.Set("contract_type", contractType) - postData.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - //postData.Set("status", "1") - postData.Set("current_page", "1") - postData.Set("page_length", "2") - - ok.buildPostForm(&postData) - - body, err := HttpPostForm(ok.client, FUTURE_API_BASE_URL+FUTURE_ORDER_INFO_URI, postData) - if err != nil { - return nil, err - } - - //println(string(body)) - orders, err := ok.parseOrders(body, currencyPair) - if err != nil { - return nil, err - } - if len(orders) == 0 { - return nil, errors.New("找不到订单") - } - - return &orders[0], nil -} - -func (ok *OKEx) GetFee() (float64, error) { - return 0.03, nil //期货固定0.03%手续费 -} - -func (ok *OKEx) GetExchangeRate() (float64, error) { - respMap, err := HttpGet(ok.client, FUTURE_API_BASE_URL+_EXCHANGE_RATE_URI) - - if err != nil { - log.Println(respMap) - return -1, err - } - - if respMap["rate"] == nil { - log.Println(respMap) - return -1, errors.New("error") - } - - return respMap["rate"].(float64), nil -} - -/** - * BTC: 100美元一张合约 - * LTC/ETH/ETC/BCH: 10美元一张合约 - */ -func (ok *OKEx) GetContractValue(currencyPair CurrencyPair) (float64, error) { - switch currencyPair { - case BTC_USD: - return 100, nil - case LTC_USD, ETH_USD, ETC_USD, BCH_USD: - return 10, nil - default: - return 10, nil - } - - return -1, errors.New("error") -} - -func (ok *OKEx) GetDeliveryTime() (int, int, int, int) { - return 4, 16, 0, 0 //星期五,下午4点交割 -} - -func (ok *OKEx) GetKlineRecords(contract_type string, currencyPair CurrencyPair, period, size, since int) ([]FutureKline, error) { - params := url.Values{} - params.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - params.Set("type", _INERNAL_KLINE_PERIOD_CONVERTER[period]) - params.Set("contract_type", contract_type) - params.Set("size", fmt.Sprintf("%d", size)) - params.Set("since", fmt.Sprintf("%d", since)) - //log.Println(params.Encode()) - resp, err := ok.client.Get(FUTURE_API_BASE_URL + _GET_KLINE_URI + "?" + params.Encode()) - if err != nil { - log.Println(err) - return nil, err - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Println(err) - return nil, err - } - //log.Println(string(body)) - - var klines [][]interface{} - err = json.Unmarshal(body, &klines) - if err != nil { - log.Println(string(body)) - return nil, err - } - - var klineRecords []FutureKline - for _, record := range klines { - r := FutureKline{} - r.Kline = new(Kline) - for i, e := range record { - switch i { - case 0: - r.Timestamp = int64(e.(float64)) / 1000 //to unix timestramp - case 1: - r.Open = e.(float64) - case 2: - r.High = e.(float64) - case 3: - r.Low = e.(float64) - case 4: - r.Close = e.(float64) - case 5: - r.Vol = e.(float64) - case 6: - r.Vol2 = e.(float64) - } - } - klineRecords = append(klineRecords, r) - } - - return klineRecords, nil -} - -func (okFuture *OKEx) GetTrades(contract_type string, currencyPair CurrencyPair, since int64) ([]Trade, error) { - params := url.Values{} - params.Set("symbol", strings.ToLower(currencyPair.ToSymbol("_"))) - params.Set("contract_type", contract_type) - //log.Println(params.Encode()) - - url := FUTURE_API_BASE_URL + TRADES_URI + "?" + params.Encode() - body, err := HttpGet5(okFuture.client, url, nil) - if err != nil { - return nil, err - } - fmt.Println(string(body)) - - var trades []Trade - var resp []interface{} - err = json.Unmarshal(body, &resp) - if err != nil { - return nil, errors.New(string(body)) - } - - for _, v := range resp { - item := v.(map[string]interface{}) - - tid := int64(item["tid"].(float64)) - direction := item["type"].(string) - amount := item["amount"].(float64) - price := item["price"].(float64) - time := int64(item["date_ms"].(float64)) - - trades = append(trades, Trade{tid, AdaptTradeSide(direction), amount, price, time, currencyPair}) - } - - return trades, nil -} - -func (okFuture *OKEx) errorWrapper(errorCode int) ApiError { - switch errorCode { - case 20024: - return EX_ERR_SIGN - case 20020: - return EX_ERR_NOT_FIND_SECRETKEY - case 20015: - return EX_ERR_NOT_FIND_ORDER - case 20049: - return EX_ERR_API_LIMIT - } - errmsg := fmt.Sprintf("%d", errorCode) - return ApiError{ErrCode: errmsg, OriginErrMsg: errmsg} -} diff --git a/okcoin/OKExSpot.go b/okcoin/OKExSpot.go deleted file mode 100644 index 0b2473ac..00000000 --- a/okcoin/OKExSpot.go +++ /dev/null @@ -1,72 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - . "github.com/nntaoli-project/goex" - "net/http" - "net/url" - "strconv" -) - -type OKExSpot struct { - OKCoinCN_API -} - -func NewOKExSpot(client *http.Client, accesskey, secretkey string) *OKExSpot { - return &OKExSpot{ - OKCoinCN_API: OKCoinCN_API{client, accesskey, secretkey, "https://www.okex.com/api/v1/"}} -} - -func (ctx *OKExSpot) GetExchangeName() string { - return OKEX -} - -func (ctx *OKExSpot) GetAccount() (*Account, error) { - postData := url.Values{} - err := ctx.buildPostForm(&postData) - if err != nil { - return nil, err - } - - body, err := HttpPostForm(ctx.client, ctx.api_base_url+url_userinfo, postData) - if err != nil { - return nil, err - } - - var respMap map[string]interface{} - - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - if errcode, isok := respMap["error_code"].(float64); isok { - errcodeStr := strconv.FormatFloat(errcode, 'f', 0, 64) - return nil, errors.New(errcodeStr) - } - //log.Println(respMap) - info, ok := respMap["info"].(map[string]interface{}) - if !ok { - return nil, errors.New(string(body)) - } - - funds := info["funds"].(map[string]interface{}) - free := funds["free"].(map[string]interface{}) - freezed := funds["freezed"].(map[string]interface{}) - - account := new(Account) - account.Exchange = ctx.GetExchangeName() - - account.SubAccounts = make(map[Currency]SubAccount, 6) - for k, v := range free { - currencyKey := NewCurrency(k, "") - subAcc := SubAccount{ - Currency: currencyKey, - Amount: ToFloat64(v), - ForzenAmount: ToFloat64(freezed[k])} - account.SubAccounts[currencyKey] = subAcc - } - - return account, nil -} diff --git a/okcoin/OKExSpot_test.go b/okcoin/OKExSpot_test.go deleted file mode 100644 index b1dbc092..00000000 --- a/okcoin/OKExSpot_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package okcoin - -import ( - "github.com/nntaoli-project/goex" - "github.com/stretchr/testify/assert" - "net/http" - "testing" -) - -var okexSpot = NewOKExSpot(http.DefaultClient, "", "") - -func TestOKExSpot_GetTicker(t *testing.T) { - ticker, err := okexSpot.GetTicker(goex.ETC_BTC) - assert.Nil(t, err) - t.Log(ticker) -} - -func TestOKExSpot_GetDepth(t *testing.T) { - dep, err := okexSpot.GetDepth(2, goex.ETC_BTC) - assert.Nil(t, err) - t.Log(dep) -} - -func TestOKExSpot_GetKlineRecords(t *testing.T) { - klines, err := okexSpot.GetKlineRecords(goex.LTC_BTC, goex.KLINE_PERIOD_1MIN, 1000, -1) - t.Log(err, klines) -} diff --git a/okcoin/OKEx_Future_Ws.go b/okcoin/OKEx_Future_Ws.go deleted file mode 100644 index 4a86d5a2..00000000 --- a/okcoin/OKEx_Future_Ws.go +++ /dev/null @@ -1,187 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - . "github.com/nntaoli-project/goex" - "log" - "strings" - "sync" - "time" -) - -type OKExFutureWs struct { - *WsBuilder - sync.Once - wsConn *WsConn - - tickerCallback func(*FutureTicker) - depthCallback func(*Depth) - tradeCallback func(*Trade, string) -} - -func NewOKExFutureWs() *OKExFutureWs { - okWs := &OKExFutureWs{WsBuilder: NewWsBuilder()} - okWs.WsBuilder = okWs.WsBuilder. - WsUrl("wss://real.okex.com:10440/ws/v1"). - AutoReconnect(). - Heartbeat(func() []byte { - return []byte("{\"event\": \"ping\"} ") - }, 30*time.Second). - UnCompressFunc(FlateUnCompress). - ProtoHandleFunc(okWs.handle) - return okWs -} - -func (okWs *OKExFutureWs) SetCallbacks(tickerCallback func(*FutureTicker), - depthCallback func(*Depth), - tradeCallback func(*Trade, string)) { - okWs.tickerCallback = tickerCallback - okWs.depthCallback = depthCallback - okWs.tradeCallback = tradeCallback -} - -func (okWs *OKExFutureWs) SubscribeTicker(pair CurrencyPair, contract string) error { - if okWs.tickerCallback == nil { - return errors.New("please set ticker callback func") - } - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_futureusd_%s_ticker_%s", strings.ToLower(pair.CurrencyA.Symbol), contract)}) -} - -func (okWs *OKExFutureWs) SubscribeDepth(pair CurrencyPair, contract string, size int) error { - if okWs.depthCallback == nil { - return errors.New("please set depth callback func") - } - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_futureusd_%s_depth_%s_%d", strings.ToLower(pair.CurrencyA.Symbol), contract, size)}) -} - -func (okWs *OKExFutureWs) SubscribeTrade(pair CurrencyPair, contract string) error { - if okWs.tradeCallback == nil { - return errors.New("please set trade callback func") - } - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_futureusd_%s_trade_%s", strings.ToLower(pair.CurrencyA.Symbol), contract)}) -} - -func (okWs *OKExFutureWs) subscribe(sub map[string]interface{}) error { - okWs.connectWs() - return okWs.wsConn.Subscribe(sub) -} - -func (okWs *OKExFutureWs) connectWs() { - okWs.Do(func() { - okWs.wsConn = okWs.WsBuilder.Build() - }) -} - -func (okWs *OKExFutureWs) handle(msg []byte) error { - //log.Println(string(msg)) - if string(msg) == "{\"event\":\"pong\"}" { - // log.Println(string(msg)) - return nil - } - - var resp []WsBaseResp - err := json.Unmarshal(msg, &resp) - if err != nil { - return err - } - - if len(resp) < 0 { - return nil - } - - if resp[0].Channel == "addChannel" { - log.Println("subscribe:", string(resp[0].Data)) - return nil - } - - pair, contract, ch := okWs.parseChannel(resp[0].Channel) - - if ch == "ticker" { - var t FutureTicker - err := json.Unmarshal(resp[0].Data, &t) - if err != nil { - return err - } - t.ContractType = contract - t.Pair = pair - okWs.tickerCallback(&t) - return nil - } - - if ch == "depth" { - var ( - d Depth - data struct { - Asks [][]float64 `json:"asks"` - Bids [][]float64 `json:"bids"` - Timestamp int64 `json:"timestamp"` - } - ) - - err := json.Unmarshal(resp[0].Data, &data) - if err != nil { - return err - } - - for _, a := range data.Asks { - d.AskList = append(d.AskList, DepthRecord{a[0], a[1]}) - } - - for _, b := range data.Bids { - d.BidList = append(d.BidList, DepthRecord{b[0], b[1]}) - } - - d.Pair = pair - d.ContractType = contract - d.UTime = time.Unix(data.Timestamp/1000, 0) - okWs.depthCallback(&d) - - return nil - } - - if ch == "trade" { - var data TradeData - err := json.Unmarshal(resp[0].Data, &data) - if err != nil { - return err - } - - for _, td := range data { - side := TradeSide(SELL) - if td[4] == "bid" { - side = BUY - } - okWs.tradeCallback(&Trade{Pair: pair, Tid: ToInt64(td[0]), Price: ToFloat64(td[1]), - Amount: ToFloat64(td[2]), Type: side, Date: okWs.adaptTime(td[3])}, contract) - } - - return nil - } - - return errors.New("unknown channel for " + resp[0].Channel) -} - -func (okWs *OKExFutureWs) parseChannel(channel string) (pair CurrencyPair, contract string, ch string) { - metas := strings.Split(channel, "_") - pair = NewCurrencyPair2(strings.ToUpper(metas[3] + "_USD")) - contract = metas[5] - ch = metas[4] - return pair, contract, ch -} - -func (okWs *OKExFutureWs) adaptTime(tm string) int64 { - format := "2006-01-02 15:04:05" - day := time.Now().Format("2006-01-02") - local, _ := time.LoadLocation("Asia/Chongqing") - t, _ := time.ParseInLocation(format, day+" "+tm, local) - return t.UnixNano() / 1e6 - -} diff --git a/okcoin/OKEx_Future_Ws_test.go b/okcoin/OKEx_Future_Ws_test.go deleted file mode 100644 index 1482f20e..00000000 --- a/okcoin/OKEx_Future_Ws_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package okcoin - -import ( - "github.com/nntaoli-project/goex" - "testing" - "time" -) - -func TestNewOKExFutureWs(t *testing.T) { - okWs := NewOKExFutureWs() - okWs.ErrorHandleFunc(func(err error) { - t.Log(err) - }) - okWs.SetCallbacks(func(ticker *goex.FutureTicker) { - t.Log(ticker, ticker.Ticker) - }, func(depth *goex.Depth) { - t.Log(depth.ContractType, depth.Pair, depth.AskList, depth.BidList) - }, func(trade *goex.Trade, contract string) { - t.Log(contract, trade) - }) - okWs.SubscribeTicker(goex.LTC_USD, goex.QUARTER_CONTRACT) - okWs.SubscribeDepth(goex.LTC_USD, goex.QUARTER_CONTRACT, 5) - okWs.SubscribeTrade(goex.LTC_USD, goex.QUARTER_CONTRACT) - time.Sleep(3 * time.Minute) -} diff --git a/okcoin/OKEx_Spot_Ws.go b/okcoin/OKEx_Spot_Ws.go deleted file mode 100644 index b3eb03e6..00000000 --- a/okcoin/OKEx_Spot_Ws.go +++ /dev/null @@ -1,287 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - . "github.com/nntaoli-project/goex" - "log" - "strings" - "sync" - "time" -) - -type WsBaseResp struct { - Channel string - Data json.RawMessage -} - -type AddChannelData struct { - Result bool - Channel string -} - -type TickerData struct { - Last float64 `json:"last,string"` - Sell float64 `json:"sell,string"` - Buy float64 `json:"buy,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Vol float64 `json:"vol,string"` - Timestamp int64 -} - -type DepthData struct { - Asks [][]string `json:"asks""` - Bids [][]string `json:"bids"` - Timestamp int64 -} - -type TradeData [][]string - -type KlineData [][]string - -type OKExSpotWs struct { - *WsBuilder - sync.Once - wsConn *WsConn - - tickerCallback func(*Ticker) - depthCallback func(*Depth) - tradeCallback func(*Trade) - klineCallback func(*Kline, int) -} - -func NewOKExSpotWs() *OKExSpotWs { - okWs := &OKExSpotWs{} - - okWs.WsBuilder = NewWsBuilder(). - WsUrl("wss://real.okex.com:10440/ws/v1"). - AutoReconnect(). - Heartbeat(func() []byte { return []byte("{\"event\": \"ping\"} ") }, 30*time.Second). - UnCompressFunc(FlateUnCompress). - ProtoHandleFunc(okWs.handle) - - return okWs -} - -func (okWs *OKExSpotWs) SetCallbacks(tickerCallback func(*Ticker), - depthCallback func(*Depth), - tradeCallback func(*Trade), - klineCallback func(*Kline, int)) { - okWs.tickerCallback = tickerCallback - okWs.depthCallback = depthCallback - okWs.tradeCallback = tradeCallback - okWs.klineCallback = klineCallback -} - -func (okWs *OKExSpotWs) subscribe(sub map[string]interface{}) error { - okWs.connectWs() - return okWs.wsConn.Subscribe(sub) -} - -func (okWs *OKExSpotWs) SubscribeDepth(pair CurrencyPair, size int) error { - if okWs.depthCallback == nil { - return errors.New("please set depth callback func") - } - - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_spot_%s_depth_%d", strings.ToLower(pair.ToSymbol("_")), size)}) -} - -func (okWs *OKExSpotWs) SubscribeTicker(pair CurrencyPair) error { - if okWs.tickerCallback == nil { - return errors.New("please set ticker callback func") - } - - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_spot_%s_ticker", strings.ToLower(pair.ToSymbol("_")))}) -} - -func (okWs *OKExSpotWs) SubscribeTrade(pair CurrencyPair) error { - if okWs.tradeCallback == nil { - return errors.New("please set trade callback func") - } - - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_spot_%s_deals", strings.ToLower(pair.ToSymbol("_")))}) -} - -func (okWs *OKExSpotWs) SubscribeKline(pair CurrencyPair, period int) error { - if okWs.klineCallback == nil { - return errors.New("place set kline callback func") - } - - return okWs.subscribe(map[string]interface{}{ - "event": "addChannel", - "channel": fmt.Sprintf("ok_sub_spot_%s_kline_%s", - strings.ToLower(pair.ToSymbol("_")), AdaptKlinePeriodForOKEx(period))}) -} - -func (okWs *OKExSpotWs) connectWs() { - okWs.Do(func() { - okWs.wsConn = okWs.WsBuilder.Build() - }) -} - -func (okWs *OKExSpotWs) handle(msg []byte) error { - if string(msg) == "{\"event\":\"pong\"}" { - return nil - } - - var resp []WsBaseResp - err := json.Unmarshal(msg, &resp) - if err != nil { - return err - } - - if len(resp) < 0 { - return nil - } - - if resp[0].Channel == "addChannel" { - log.Println("subscribe:", string(resp[0].Data)) - return nil - } - - pair, err := okWs.getPairFormChannel(resp[0].Channel) - if err != nil { - log.Println(err, string(msg)) - return nil - } - - if strings.Contains(resp[0].Channel, "depth") { - var ( - depthData DepthData - dep Depth - ) - - err := json.Unmarshal(resp[0].Data, &depthData) - if err != nil { - return err - } - - for _, ask := range depthData.Asks { - dep.AskList = append(dep.AskList, DepthRecord{ToFloat64(ask[0]), ToFloat64(ask[1])}) - } - - for _, bid := range depthData.Bids { - dep.BidList = append(dep.BidList, DepthRecord{ToFloat64(bid[0]), ToFloat64(bid[1])}) - } - - dep.Pair = pair - dep.UTime = time.Unix(depthData.Timestamp/1000, 0) - - okWs.depthCallback(&dep) - return nil - } - - if strings.Contains(resp[0].Channel, "ticker") { - var tickerData TickerData - err := json.Unmarshal(resp[0].Data, &tickerData) - if err != nil { - return err - } - okWs.tickerCallback(&Ticker{ - Pair: pair, - Last: tickerData.Last, - Low: tickerData.Low, - High: tickerData.High, - Sell: tickerData.Sell, - Buy: tickerData.Buy, - Vol: tickerData.Vol, - Date: uint64(tickerData.Timestamp)}) - return nil - } - - if strings.Contains(resp[0].Channel, "deals") { - var ( - tradeData TradeData - ) - - err := json.Unmarshal(resp[0].Data, &tradeData) - if err != nil { - return err - } - - for _, td := range tradeData { - side := TradeSide(SELL) - if td[4] == "bid" { - side = BUY - } - okWs.tradeCallback(&Trade{Pair: pair, Tid: ToInt64(td[0]), - Price: ToFloat64(td[1]), Amount: ToFloat64(td[2]), Type: side, Date: okWs.adaptTime(td[3])}) - } - - return nil - } - - if strings.Contains(resp[0].Channel, "kline") { - var k KlineData - period := okWs.getKlinePeriodFormChannel(resp[0].Channel) - err := json.Unmarshal(resp[0].Data, &k) - if err != nil { - return err - } - okWs.klineCallback(&Kline{ - Pair: pair, - Timestamp: ToInt64(k[0][0]), - Open: ToFloat64(k[0][1]), - Close: ToFloat64(k[0][4]), - High: ToFloat64(k[0][2]), - Low: ToFloat64(k[0][3]), - Vol: ToFloat64(k[0][5])}, period) - return nil - } - - return errors.New("unknown message " + resp[0].Channel) -} - -func (okWs *OKExSpotWs) getPairFormChannel(channel string) (CurrencyPair, error) { - metas := strings.Split(channel, "_") - if len(metas) < 5 { - return UNKNOWN_PAIR, errors.New("channel format error") - } - return NewCurrencyPair2(metas[3] + "_" + metas[4]), nil -} -func (okWs *OKExSpotWs) getKlinePeriodFormChannel(channel string) int { - metas := strings.Split(channel, "_") - if len(metas) < 7 { - return 0 - } - - switch metas[6] { - case "1hour": - return KLINE_PERIOD_1H - case "2hour": - return KLINE_PERIOD_2H - case "4hour": - return KLINE_PERIOD_4H - case "1min": - return KLINE_PERIOD_1MIN - case "5min": - return KLINE_PERIOD_5MIN - case "15min": - return KLINE_PERIOD_15MIN - case "30min": - return KLINE_PERIOD_30MIN - case "day": - return KLINE_PERIOD_1DAY - case "week": - return KLINE_PERIOD_1WEEK - default: - return 0 - } -} - -func (okWs *OKExSpotWs) adaptTime(tm string) int64 { - format := "2006-01-02 15:04:05" - day := time.Now().Format("2006-01-02") - local, _ := time.LoadLocation("Asia/Chongqing") - t, _ := time.ParseInLocation(format, day+" "+tm, local) - return t.UnixNano() / 1e6 - -} diff --git a/okcoin/OKEx_Spot_Ws_test.go b/okcoin/OKEx_Spot_Ws_test.go deleted file mode 100644 index bc7bfce4..00000000 --- a/okcoin/OKEx_Spot_Ws_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package okcoin - -import ( - "github.com/nntaoli-project/goex" - "testing" - "time" -) - -func TestNewOKExSpotWs(t *testing.T) { - okSpotWs := NewOKExSpotWs() -// okSpotWs.ProxyUrl("socks5://127.0.0.1:1080") - - okSpotWs.SetCallbacks(func(ticker *goex.Ticker) { - t.Log(ticker) - }, func(depth *goex.Depth) { - t.Log(depth) - }, func(trade *goex.Trade) { - t.Log(trade) - }, func(kline *goex.Kline, i int) { - t.Log(i, kline) - }) - - okSpotWs.ErrorHandleFunc(func(err error) { - t.Log(err) - }) - // t.Log(okSpotWs.SubscribeTicker(goex.BTC_USDT)) - // t.Log(okSpotWs.SubscribeTicker(goex.BCH_USDT)) - //okSpotWs.SubscribeDepth(goex.BTC_USDT, 5) - //okSpotWs.SubscribeTrade(goex.BTC_USDT) - t.Log(okSpotWs.SubscribeKline(goex.BTC_USDT, goex.KLINE_PERIOD_1H)) - time.Sleep(10 * time.Second) -} diff --git a/okcoin/OKEx_V3.go b/okcoin/OKEx_V3.go deleted file mode 100644 index e6219cea..00000000 --- a/okcoin/OKEx_V3.go +++ /dev/null @@ -1,1618 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "net/http" - "net/url" - "reflect" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/deckarep/golang-set" - . "github.com/nntaoli-project/goex" -) - -const ( - V3_FUTURE_HOST_URL = "https://www.okex.com/" - V3_FUTURE_API_BASE_URL = "api/futures/v3/" - V3_SWAP_API_BASE_URL = "api/swap/v3/" - V3_FUTRUR_INSTRUMENTS_URL = "instruments" - V3_FUTURE_TICKER_URI = "instruments/%s/ticker" - V3_SWAP_DEPTH_URI = "instruments/%s/depth" //wtf api - V3_FUTURE_DEPTH_URI = "instruments/%s/book" //wtf api - V3_FUTURE_ESTIMATED_PRICE = "instruments/%s/estimated_price" - V3_FUTURE_INDEX_PRICE = "instruments/%s/index" - V3_FUTURE_USERINFOS_URI = "accounts/%s" - V3_FUTURE_CANCEL_URI = "cancel_order/%s/%s" - V3_FUTURE_ORDER_INFO_URI = "orders/%s/%s" - V3_FUTURE_ORDERS_INFO_URI = "orders/%s" - V3_FUTURE_POSITION_URI = "%s/position" - V3_FUTURE_ORDER_URI = "order" - V3_FUTURE_TRADES_URI = "instruments/%s/trades" - V3_FUTURE_FILLS_URI = "fills" - V3_EXCHANGE_RATE_URI = "instruments/%s/rate" - V3_GET_KLINE_URI = "instruments/%s/candles" -) - -// common utils, maybe should be extracted in future -func timeStringToInt64(t string) (int64, error) { - timestamp, err := time.Parse(time.RFC3339, t) - if err != nil { - return 0, err - } - return timestamp.UnixNano() / int64(time.Millisecond), nil -} - -func int64ToTime(ti int64) time.Time { - return time.Unix(0, ti*int64(time.Millisecond)).UTC() -} - -func int64ToTimeString(ti int64) string { - t := int64ToTime(ti) - return t.Format(time.RFC3339) -} - -// contract information -type futureContract struct { - InstrumentID string `json:"instrument_id"` - UnderlyingIndex string `json:"underlying_index"` - QuoteCurrency string `json:"quote_currency"` - Coin string `json:"coin"` - TickSize string `json:"tick_size"` - ContractVal string `json:"contract_val"` - Listing string `json:"listing"` - Delivery string `json:"delivery"` - SizeIncrement string `json:"size_increment"` - TradeIncrement string `json:"trade_increment"` - Alias string `json:"alias"` -} - -var tail_zero_re = regexp.MustCompile("0+$") - -func normalizeByIncrement(num float64, increment string) (string, error) { - precision := 0 - i := strings.Index(increment, ".") - // increment is decimal - if i > -1 { - decimal := increment[i+1:] - trimTailZero := tail_zero_re.ReplaceAllString(decimal, "") - precision = len(trimTailZero) - return fmt.Sprintf("%."+fmt.Sprintf("%df", precision), num), nil - } - // increment is int - incrementInt, err := strconv.ParseInt(increment, 10, 64) - if err != nil { - return "", err - } - return strconv.Itoa(int(num) / int(incrementInt)), nil -} - -func (fc futureContract) normalizePrice(price float64) (string, error) { - tickSize := fc.TickSize - if len(tickSize) == 0 { - return "", fmt.Errorf("no tick size info in contract %v", fc) - } - return normalizeByIncrement(price, tickSize) -} - -func (fc futureContract) normalizePriceString(price string) (string, error) { - p, err := strconv.ParseFloat(price, 64) - if err != nil { - return "", err - } - return fc.normalizePrice(p) -} - -func (fc futureContract) getSizeIncrement() string { - if len(fc.SizeIncrement) > 0 { - return fc.SizeIncrement - } else if len(fc.TradeIncrement) > 0 { - return fc.TradeIncrement - } - return "" -} - -func (fc futureContract) normalizeAmount(amount float64) (string, error) { - increment := fc.getSizeIncrement() - if len(increment) == 0 { - return "", fmt.Errorf("no trade incrument info in contract %v", fc) - } - return normalizeByIncrement(amount, increment) -} - -func (fc futureContract) normalizeAmountString(amount string) (string, error) { - a, err := strconv.ParseFloat(amount, 64) - if err != nil { - return "", err - } - return fc.normalizeAmount(a) -} - -type futureContracts []futureContract - -type futureContractsMapKey struct { - UnderlyingIndex string - QuoteCurrency string - Alias string -} - -type futureContractsMap map[futureContractsMapKey]*futureContract -type futureContractsIDMap map[string]*futureContract - -func newFutureContractsMap(contracts futureContracts) futureContractsMap { - contractsMap := futureContractsMap{} - for _, v := range contracts { - func(v futureContract) { - key := futureContractsMapKey{ - UnderlyingIndex: v.UnderlyingIndex, - QuoteCurrency: v.QuoteCurrency, - Alias: v.Alias, - } - contractsMap[key] = &v - }(v) - } - return contractsMap -} - -func newFutureContractsIDMap(contracts futureContracts) futureContractsIDMap { - contractsIDMap := futureContractsIDMap{} - for _, v := range contracts { - func(v futureContract) { - contractsIDMap[v.InstrumentID] = &v - }(v) - } - return contractsIDMap -} - -// NOTE: -// contracts 默认五分钟更新一次。 -// 由于V3没有自动合约日期的映射,到了周五交割的时候,最好还是手动平仓,关闭策略,交割完后重启。 -type OKExV3 struct { - apiKey, - apiSecretKey, - passphrase, - endpoint string - dataParser *OKExV3DataParser - client *http.Client - contractsMap futureContractsMap - contractsIDMap map[string]*futureContract - contractsRW *sync.RWMutex -} - -func NewOKExV3(client *http.Client, api_key, secret_key, passphrase, endpoint string) *OKExV3 { - okv3 := new(OKExV3) - okv3.apiKey = api_key - okv3.apiSecretKey = secret_key - okv3.client = client - okv3.passphrase = passphrase - okv3.contractsRW = &sync.RWMutex{} - okv3.dataParser = NewOKExV3DataParser(okv3) - okv3.endpoint = endpoint - contracts, err := okv3.getAllContracts() - if err != nil { - panic(err) - } - okv3.setContracts(contracts) - return okv3 -} - -func (okv3 *OKExV3) GetUrlRoot() (url string) { - if okv3.endpoint != "" { - url = okv3.endpoint - } else { - url = V3_FUTURE_HOST_URL - } - if url[len(url)-1] != '/' { - url = url + "/" - } - return -} - -func (okv3 *OKExV3) setContracts(contracts futureContracts) { - contractsMap := newFutureContractsMap(contracts) - contractsIDMap := newFutureContractsIDMap(contracts) - okv3.contractsRW.Lock() - defer okv3.contractsRW.Unlock() - okv3.contractsMap = contractsMap - okv3.contractsIDMap = contractsIDMap -} - -func (okv3 *OKExV3) getAllContracts() (futureContracts, error) { - var err error - var futureContracts, swapContracts futureContracts - wg := &sync.WaitGroup{} - wg.Add(2) - go func() { - defer wg.Done() - var err2 error - futureContracts, err2 = okv3.getFutureContracts() - if err2 != nil { - err = err2 - } - }() - go func() { - defer wg.Done() - var err2 error - swapContracts, err2 = okv3.getSwapContracts() - if err2 != nil { - err = err2 - } - }() - wg.Wait() - if err != nil { - return nil, err - } - return append(futureContracts, swapContracts...), nil -} - -func (okv3 *OKExV3) getContractByKey(key futureContractsMapKey) (*futureContract, error) { - okv3.contractsRW.RLock() - defer okv3.contractsRW.RUnlock() - data, ok := okv3.contractsMap[key] - if !ok { - msg := fmt.Sprintf("no contract in okex contracts map for %v", key) - return nil, errors.New(msg) - } - return data, nil -} - -func (okv3 *OKExV3) GetContract(currencyPair CurrencyPair, contractType string) (*futureContract, error) { - key := futureContractsMapKey{ - UnderlyingIndex: currencyPair.CurrencyA.Symbol, - QuoteCurrency: currencyPair.CurrencyB.Symbol, - Alias: contractType, - } - return okv3.getContractByKey(key) -} - -func (okv3 *OKExV3) GetContractID(currencyPair CurrencyPair, contractType string) (string, error) { - fallback := "" - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return fallback, err - } - return contract.InstrumentID, nil -} - -func (okv3 *OKExV3) ParseContractID(contractID string) (CurrencyPair, string, error) { - contract, err := okv3.getContractByID(contractID) - if err != nil { - return UNKNOWN_PAIR, "", err - } - currencyA := NewCurrency(contract.UnderlyingIndex, "") - currencyB := NewCurrency(contract.QuoteCurrency, "") - return NewCurrencyPair(currencyA, currencyB), contract.Alias, nil -} - -func (okv3 *OKExV3) getContractByID(instrumentID string) (*futureContract, error) { - okv3.contractsRW.RLock() - defer okv3.contractsRW.RUnlock() - data, ok := okv3.contractsIDMap[instrumentID] - if !ok { - msg := fmt.Sprintf("no contract in okex contracts with id %s", instrumentID) - return nil, errors.New(msg) - } - return data, nil -} - -func (okv3 *OKExV3) startUpdateContractsLoop() { - interval := 5 * time.Minute - go func() { - for { - time.Sleep(interval) - contracts, err := okv3.getAllContracts() - if err == nil { - okv3.setContracts(contracts) - } - } - }() -} - -func (okv3 *OKExV3) GetExchangeName() string { - return OKEX_FUTURE -} - -func (okv3 *OKExV3) getTimestamp() string { - return time.Now().UTC().Format(time.RFC3339) -} - -func (okv3 *OKExV3) getSign(timestamp, method, url, body string) (string, error) { - relURL := "/" + strings.TrimPrefix(url, okv3.GetUrlRoot()) - data := timestamp + method + relURL + body - return GetParamHmacSHA256Base64Sign(okv3.apiSecretKey, data) -} - -func (okv3 *OKExV3) getSignedHTTPHeader(method, url string) (map[string]string, error) { - timestamp := okv3.getTimestamp() - sign, err := okv3.getSign(timestamp, method, url, "") - if err != nil { - return nil, err - } - - return map[string]string{ - "Content-Type": "application/json", - "OK-ACCESS-KEY": okv3.apiKey, - "OK-ACCESS-SIGN": sign, - "OK-ACCESS-TIMESTAMP": timestamp, - "OK-ACCESS-PASSPHRASE": okv3.passphrase, - }, nil -} - -func (okv3 *OKExV3) getSignedHTTPHeader2(method, url, body string) (map[string]string, error) { - timestamp := okv3.getTimestamp() - sign, err := okv3.getSign(timestamp, method, url, body) - if err != nil { - return nil, err - } - - return map[string]string{ - "Content-Type": "application/json", - "OK-ACCESS-KEY": okv3.apiKey, - "OK-ACCESS-SIGN": sign, - "OK-ACCESS-TIMESTAMP": timestamp, - "OK-ACCESS-PASSPHRASE": okv3.passphrase, - }, nil -} - -func (okv3 *OKExV3) getSignedHTTPHeader3(method, url string, postData map[string]string) (map[string]string, error) { - body, _ := json.Marshal(postData) - return okv3.getSignedHTTPHeader2(method, url, string(body)) -} - -func (okv3 *OKExV3) getSwapContracts() (futureContracts, error) { - url := okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTRUR_INSTRUMENTS_URL - headers, err := okv3.getSignedHTTPHeader("GET", url) - if err != nil { - return nil, err - } - - body, err := HttpGet5(okv3.client, url, headers) - if err != nil { - return nil, err - } - - contracts := futureContracts{} - err = json.Unmarshal(body, &contracts) - if err != nil { - return nil, err - } - for i := range contracts { - contracts[i].Alias = SWAP_CONTRACT - } - return contracts, nil -} - -func (okv3 *OKExV3) getFutureContracts() (futureContracts, error) { - url := okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTRUR_INSTRUMENTS_URL - headers, err := okv3.getSignedHTTPHeader("GET", url) - if err != nil { - return nil, err - } - - body, err := HttpGet5(okv3.client, url, headers) - if err != nil { - return nil, err - } - - contracts := futureContracts{} - err = json.Unmarshal(body, &contracts) - if err != nil { - return nil, err - } - return contracts, nil -} - -func (okv3 *OKExV3) GetFutureDepth(currencyPair CurrencyPair, contractType string, size int) (*Depth, error) { - var url string - if contractType == SWAP_CONTRACT { - url = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_SWAP_DEPTH_URI - } else { - url = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_DEPTH_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return nil, err - } - symbol := contract.InstrumentID - - url = fmt.Sprintf(url, symbol) - if size > 0 { - url = fmt.Sprintf(url+"?size=%d", size) - } - - headers, err := okv3.getSignedHTTPHeader("GET", url) - if err != nil { - return nil, err - } - - body, err := HttpGet5(okv3.client, url, headers) - if err != nil { - return nil, err - } - - // println(string(body)) - - bodyMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &bodyMap) - if err != nil { - return nil, err - } - - if bodyMap["code"] != nil { - log.Println(bodyMap) - return nil, errors.New(string(body)) - } else if bodyMap["error_code"] != nil { - log.Println(bodyMap) - return nil, errors.New(string(body)) - } - - depth := new(Depth) - depth.Pair = currencyPair - depth.ContractType = contractType - return okv3.dataParser.ParseDepth(depth, bodyMap, size) -} - -func (okv3 *OKExV3) GetFutureTicker(currencyPair CurrencyPair, contractType string) (*Ticker, error) { - var url string - if contractType == SWAP_CONTRACT { - url = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_TICKER_URI - } else { - url = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_TICKER_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return nil, err - } - symbol := contract.InstrumentID - - url = fmt.Sprintf(url, symbol) - - headers, err := okv3.getSignedHTTPHeader("GET", url) - if err != nil { - return nil, err - } - - body, err := HttpGet5(okv3.client, url, headers) - if err != nil { - return nil, err - } - - //println(string(body)) - bodyMap := make(map[string]interface{}) - - err = json.Unmarshal(body, &bodyMap) - if err != nil { - return nil, err - } - - if bodyMap["code"] != nil { - log.Println(bodyMap) - return nil, errors.New(string(body)) - } else if bodyMap["error_code"] != nil { - log.Println(bodyMap) - return nil, errors.New(string(body)) - } - - ticker := new(Ticker) - ticker.Pair = currencyPair - timestamp, _ := timeStringToInt64(bodyMap["timestamp"].(string)) - ticker.Date = uint64(timestamp) - ticker.Buy, _ = strconv.ParseFloat(bodyMap["best_ask"].(string), 64) - ticker.Sell, _ = strconv.ParseFloat(bodyMap["best_bid"].(string), 64) - ticker.Last, _ = strconv.ParseFloat(bodyMap["last"].(string), 64) - ticker.High, _ = strconv.ParseFloat(bodyMap["high_24h"].(string), 64) - ticker.Low, _ = strconv.ParseFloat(bodyMap["low_24h"].(string), 64) - ticker.Vol, _ = strconv.ParseFloat(bodyMap["volume_24h"].(string), 64) - - //fmt.Println(bodyMap) - return ticker, nil -} - -func (okv3 *OKExV3) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice, leverRate int) (string, error) { - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_ORDER_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_ORDER_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return "", err - } - symbol := contract.InstrumentID - - price, err = contract.normalizePriceString(price) - if err != nil { - return "", err - } - - amount, err = contract.normalizeAmountString(amount) - if err != nil { - return "", err - } - - postData := make(map[string]string) - postData["instrument_id"] = symbol - postData["price"] = price - postData["size"] = amount - postData["type"] = strconv.Itoa(openType) - // postData["order_type"] = strconv.Itoa(2) - if contractType != SWAP_CONTRACT { - postData["leverage"] = strconv.Itoa(leverRate) - } - postData["match_price"] = strconv.Itoa(matchPrice) - - headers, err := okv3.getSignedHTTPHeader3("POST", requestURL, postData) - if err != nil { - return "", err - } - - body, err := HttpPostForm4(okv3.client, requestURL, postData, headers) - - if err != nil { - return "", err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return "", err - } - - //println(string(body)); - if respMap["error_code"] != nil { - errorCode, err := strconv.Atoi(respMap["error_code"].(string)) - if err != nil || errorCode != 0 { - return "", errors.New(string(body)) - } - } - - return respMap["order_id"].(string), nil -} - -func (okv3 *OKExV3) FutureCancelOrder(currencyPair CurrencyPair, contractType, orderID string) (bool, error) { - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_CANCEL_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_CANCEL_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return false, err - } - symbol := contract.InstrumentID - - requestURL = fmt.Sprintf(requestURL, symbol, orderID) - - headers, err := okv3.getSignedHTTPHeader("POST", requestURL) - if err != nil { - return false, err - } - - body, err := HttpPostForm3(okv3.client, requestURL, "", headers) - - if err != nil { - return false, err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return false, err - } - - //println(string(body)); - if respMap["error_code"] != nil { - errorCode, err := strconv.Atoi(respMap["error_code"].(string)) - if err != nil || errorCode != 0 { - return false, errors.New(string(body)) - } - } - - // wtf api - switch v := respMap["result"].(type) { - case bool: - return v, nil - case string: - b, err := strconv.ParseBool(v) - return b, err - default: - return false, errors.New(string(body)) - } -} - -func (okv3 *OKExV3) parseOrder(respMap map[string]interface{}, currencyPair CurrencyPair, contractType string) (*FutureOrder, error) { - order, _, err := okv3.dataParser.ParseFutureOrder(respMap) - return order, err -} - -func (okv3 *OKExV3) parseOrders(body []byte, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - respMap := make(map[string]interface{}) - err := json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - var result bool - if respMap["result"] != nil { - switch v := respMap["result"].(type) { - case bool: - result = v - case string: - b, err := strconv.ParseBool(v) - if err != nil { - b = false - } - result = b - default: - result = false - } - } else { - result = true // no result field, should look at order_info field - } - - //{"error_message": "You have not uncompleted order at the moment", - // "result": false, "error_code": "32004", "order_id": "2924351754742784"} - if !result && respMap["error_code"] != nil { - log.Println(respMap["error_code"].(string)) - if respMap["error_code"].(string) == "32004" { - return []FutureOrder{}, nil - } - } - - if result && respMap["order_info"] != nil { - orderInfos := respMap["order_info"].([]interface{}) - orders := make([]FutureOrder, 0, len(orderInfos)) - for _, o := range orderInfos { - orderInfo := o.(map[string]interface{}) - order, err := okv3.parseOrder(orderInfo, currencyPair, contractType) - if err != nil { - return nil, err - } - orders = append(orders, *order) - } - return orders, nil - } - - return nil, errors.New(string(body)) -} - -func (okv3 *OKExV3) GetFutureOrder(orderID string, currencyPair CurrencyPair, contractType string) (*FutureOrder, error) { - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_ORDER_INFO_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_ORDER_INFO_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return nil, err - } - symbol := contract.InstrumentID - - requestURL = fmt.Sprintf(requestURL, symbol, orderID) - - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return nil, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - - if err != nil { - return nil, err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return nil, err - } - - //println(string(body)); - if respMap["error_code"] != nil { - errorCode, err := strconv.Atoi(respMap["error_code"].(string)) - if err != nil || errorCode != 0 { - return nil, errors.New(string(body)) - } - } - - return okv3.parseOrder(respMap, currencyPair, contractType) -} - -var orderStateOrder = map[TradeStatus]int{ - ORDER_UNFINISH: 0, - ORDER_PART_FINISH: 1, - ORDER_REJECT: 2, - ORDER_CANCEL_ING: 3, - ORDER_CANCEL: 4, - ORDER_FINISH: 5, -} - -func (okv3 *OKExV3) mergeOrders(ordersList ...[]FutureOrder) []FutureOrder { - orderMap := make(map[string]FutureOrder) - for _, orders := range ordersList { - if orders != nil { - for _, order := range orders { - if o, ok := orderMap[order.OrderID2]; ok { - if orderStateOrder[o.Status] < orderStateOrder[order.Status] { - orderMap[order.OrderID2] = order - } - } else { - orderMap[order.OrderID2] = order - } - } - } - } - orders := make([]FutureOrder, 0, len(orderMap)) - for _, value := range orderMap { - orders = append(orders, value) - } - return orders -} - -func (okv3 *OKExV3) GetFutureOrders(orderIds []string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - var err error - var orders1, orders2, orders3 []FutureOrder - - // TODO: when cancelling orders via api which query orders, order maybe not valiable in all the state, - // api which query single order is more stable, so when orderIDs's length is less than 3~5, - // just using query sigle order api. - wg := &sync.WaitGroup{} - wg.Add(3) - - go func() { - defer wg.Done() - var _err error - orders2, _err = okv3.GetUnfinishFutureOrdersByIDs(orderIds, currencyPair, contractType) - if _err != nil { - err = _err - } - }() - - // cancelling orders - go func() { - defer wg.Done() - var _err error - orders3, _err = okv3.GetFutureOrdersByIDsAndState(orderIds, "4", currencyPair, contractType) - if _err != nil { - err = _err - } - }() - - go func() { - defer wg.Done() - var _err error - orders1, _err = okv3.GetFinishedFutureOrdersByIDs(orderIds, currencyPair, contractType) - if _err != nil { - err = _err - } - }() - - wg.Wait() - if err != nil { - return nil, err - } - - return okv3.mergeOrders(orders1, orders2, orders3), nil -} - -func (okv3 *OKExV3) filterOrdersByIDs(orderIDs []string, orders []FutureOrder) []FutureOrder { - //if no orderIDs specific, return all the orders - if len(orderIDs) == 0 { - return orders - } - - orderSet := mapset.NewSet() - for _, o := range orderIDs { - orderSet.Add(o) - } - newOrders := make([]FutureOrder, 0, len(orderIDs)) - for _, order := range orders { - if orderSet.Contains(order.OrderID2) { - newOrders = append(newOrders, order) - } - } - return newOrders -} - -func (okv3 *OKExV3) GetFutureOrdersByIDsAndState(orderIDs []string, state string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_ORDERS_INFO_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_ORDERS_INFO_URI - } - - postData := url.Values{} - if len(orderIDs) > 0 { - sort.Strings(orderIDs) - if contractType == SWAP_CONTRACT { - // 设计api像cxk, from 参数对应的订单竟然不包含在查询结果中 - to, _ := strconv.ParseInt(orderIDs[0], 10, 64) - // to = to - 1 - postData.Set("to", fmt.Sprintf("%d", to)) - // from, _ := strconv.ParseInt(orderIDs[len(orderIDs) - 1], 10, 64) - // from = from + 1 - // postData.Set("from", fmt.Sprintf("%d", from)) - } else { - before, _ := strconv.ParseInt(orderIDs[0], 10, 64) - before = before - 1 - postData.Set("before", fmt.Sprintf("%d", before)) - after, _ := strconv.ParseInt(orderIDs[len(orderIDs)-1], 10, 64) - after = after + 1 - postData.Set("after", fmt.Sprintf("%d", after)) - } - } - postData.Set("state", state) - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return nil, err - } - symbol := contract.InstrumentID - - requestURL = fmt.Sprintf(requestURL, symbol) + "?" + postData.Encode() - - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return nil, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - if err != nil { - return nil, err - } - - orders, err := okv3.parseOrders(body, currencyPair, contractType) - if err != nil { - return nil, err - } - - return okv3.filterOrdersByIDs(orderIDs, orders), nil -} - -func (okv3 *OKExV3) GetUnfinishFutureOrdersByIDs(orderIDs []string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - return okv3.GetFutureOrdersByIDsAndState(orderIDs, "6", currencyPair, contractType) -} - -func (okv3 *OKExV3) GetUnfinishFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - return okv3.GetUnfinishFutureOrdersByIDs([]string{}, currencyPair, contractType) -} - -func (okv3 *OKExV3) GetFutureOrdersByState(state string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - return okv3.GetFutureOrdersByIDsAndState([]string{}, state, currencyPair, contractType) -} - -func (okv3 *OKExV3) GetFinishedFutureOrdersByIDs(orderIDs []string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - return okv3.GetFutureOrdersByIDsAndState(orderIDs, "7", currencyPair, contractType) -} - -func (okv3 *OKExV3) GetFinishedFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - return okv3.GetFinishedFutureOrdersByIDs([]string{}, currencyPair, contractType) -} - -var OKexOrderTypeMap = map[int]int{ - ORDER_FEATURE_LIMIT: 0, - ORDER_FEATURE_POST_ONLY: 1, - ORDER_FEATURE_FOK: 2, - ORDER_FEATURE_FAK: 3, -} - -func (okv3 *OKExV3) PlaceFutureOrder2(currencyPair CurrencyPair, contractType, price, amount string, orderType, openType, matchPrice, leverRate int) (string, error) { - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_ORDER_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_ORDER_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return "", err - } - symbol := contract.InstrumentID - - price, err = contract.normalizePriceString(price) - if err != nil { - return "", err - } - - amount, err = contract.normalizeAmountString(amount) - if err != nil { - return "", err - } - - postData := make(map[string]string) - postData["instrument_id"] = symbol - postData["price"] = price - postData["size"] = amount - postData["type"] = strconv.Itoa(openType) - if ot, ok := OKexOrderTypeMap[orderType]; ok { - postData["order_type"] = strconv.Itoa(ot) - } else { - return "", fmt.Errorf("unsupport order type %s in %s", OrderType(ot).String(), okv3.GetExchangeName()) - } - if contractType != SWAP_CONTRACT { - postData["leverage"] = strconv.Itoa(leverRate) - } - postData["match_price"] = strconv.Itoa(matchPrice) - - headers, err := okv3.getSignedHTTPHeader3("POST", requestURL, postData) - if err != nil { - return "", err - } - - body, err := HttpPostForm4(okv3.client, requestURL, postData, headers) - - if err != nil { - return "", err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return "", err - } - - //println(string(body)); - if respMap["error_code"] != nil { - errorCode, err := strconv.Atoi(respMap["error_code"].(string)) - if err != nil || errorCode != 0 { - return "", errors.New(string(body)) - } - } - - return respMap["order_id"].(string), nil -} - -func (okv3 *OKExV3) GetDeliveryTime() (int, int, int, int) { - return 4, 16, 0, 0 -} - -func (okv3 *OKExV3) GetContractValue(currencyPair CurrencyPair) (float64, error) { - // seems contractType should be one of the function parameters. - contractType := QUARTER_CONTRACT - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return -1, err - } - f, err := strconv.ParseFloat(contract.ContractVal, 64) - if err != nil { - return -1, err - } - return f, nil -} - -func (okv3 *OKExV3) GetFee() (float64, error) { - return 0.03, nil //期货固定0.03%手续费 -} - -func (okv3 *OKExV3) GetFutureEstimatedPrice(currencyPair CurrencyPair) (float64, error) { - fallback := -1.0 - - contractType := THIS_WEEK_CONTRACT - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return fallback, err - } - requestURL := okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_ESTIMATED_PRICE - requestURL = fmt.Sprintf(requestURL, contract.InstrumentID) - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return fallback, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - - if err != nil { - return fallback, err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return fallback, err - } - - //println(string(body)); - if respMap["error_code"] != nil { - errorCode, err := strconv.Atoi(respMap["error_code"].(string)) - if err != nil || errorCode != 0 { - return fallback, errors.New(string(body)) - } - } - - f, err := strconv.ParseFloat(respMap["settlement_price"].(string), 64) - if err != nil { - return fallback, err - } - return f, nil -} - -// long and short position should be separated, -// current FuturePosition struct should be redesigned. -var ( - OKEX_MARGIN_MODE_CROSSED = "crossed" - OKEX_MARGIN_MODE_FIXED = "fixed" -) - -func (okv3 *OKExV3) parsePositionsFuture(respMap map[string]interface{}, currencyPair CurrencyPair, contractType string) ([]FuturePosition, error) { - // wtf okex - var posAtr []FuturePosition - var pos FuturePosition - - holdings := respMap["holding"].([]interface{}) - - for _, v := range holdings { - holdingMap := v.(map[string]interface{}) - marginMode := holdingMap["margin_mode"].(string) - - pos = FuturePosition{} - pos.ContractType = contractType - pos.Symbol = currencyPair - if marginMode == OKEX_MARGIN_MODE_FIXED { - pos.ForceLiquPrice, _ = strconv.ParseFloat(holdingMap["long_liqui_price"].(string), 64) - i, _ := strconv.ParseInt(holdingMap["long_leverage"].(string), 10, 64) - pos.LeverRate = int(i) - } else { - pos.ForceLiquPrice, _ = strconv.ParseFloat(holdingMap["liquidation_price"].(string), 64) - i, _ := strconv.ParseInt(holdingMap["leverage"].(string), 10, 64) - pos.LeverRate = int(i) - } - pos.BuyAmount, _ = strconv.ParseFloat(holdingMap["long_qty"].(string), 64) - pos.BuyAvailable, _ = strconv.ParseFloat(holdingMap["long_avail_qty"].(string), 64) - pos.BuyPriceAvg, _ = strconv.ParseFloat(holdingMap["long_avg_cost"].(string), 64) - // TODO: what's mean of BuyPriceCost - pos.BuyPriceCost, _ = strconv.ParseFloat(holdingMap["long_avg_cost"].(string), 64) - pos.BuyProfitReal, _ = strconv.ParseFloat(holdingMap["long_pnl"].(string), 64) - pos.CreateDate, _ = timeStringToInt64(holdingMap["created_at"].(string)) - if pos.BuyAmount > 0 { - posAtr = append(posAtr, pos) - } - - pos = FuturePosition{} - pos.ContractType = contractType - pos.Symbol = currencyPair - if marginMode == OKEX_MARGIN_MODE_FIXED { - pos.ForceLiquPrice, _ = strconv.ParseFloat(holdingMap["short_liqui_price"].(string), 64) - i, _ := strconv.ParseInt(holdingMap["short_leverage"].(string), 10, 64) - pos.LeverRate = int(i) - } else { - pos.ForceLiquPrice, _ = strconv.ParseFloat(holdingMap["liquidation_price"].(string), 64) - i, _ := strconv.ParseInt(holdingMap["leverage"].(string), 10, 64) - pos.LeverRate = int(i) - } - pos.SellAmount, _ = strconv.ParseFloat(holdingMap["short_qty"].(string), 64) - pos.SellAvailable, _ = strconv.ParseFloat(holdingMap["short_avail_qty"].(string), 64) - pos.SellPriceAvg, _ = strconv.ParseFloat(holdingMap["short_avg_cost"].(string), 64) - pos.SellPriceCost, _ = strconv.ParseFloat(holdingMap["short_avg_cost"].(string), 64) - pos.SellProfitReal, _ = strconv.ParseFloat(holdingMap["short_pnl"].(string), 64) - pos.CreateDate, _ = timeStringToInt64(holdingMap["created_at"].(string)) - - if pos.SellAmount > 0 { - posAtr = append(posAtr, pos) - } - } - return posAtr, nil -} - -func (okv3 *OKExV3) parsePositionsSwap(respMap map[string]interface{}, currencyPair CurrencyPair, contractType string) ([]FuturePosition, error) { - // wtf okex - var pos FuturePosition - var posAr []FuturePosition - holdings := respMap["holding"].([]interface{}) - - for _, v := range holdings { - holdingMap := v.(map[string]interface{}) - - pos = FuturePosition{} - pos.ContractType = contractType - pos.Symbol = currencyPair - pos.ForceLiquPrice, _ = strconv.ParseFloat(holdingMap["liquidation_price"].(string), 64) - i, _ := strconv.ParseInt(holdingMap["leverage"].(string), 10, 64) - pos.LeverRate = int(i) - pos.CreateDate, _ = timeStringToInt64(holdingMap["timestamp"].(string)) - - side := holdingMap["side"].(string) - if side == "short" { - pos.SellAmount, _ = strconv.ParseFloat(holdingMap["position"].(string), 64) - pos.SellAvailable, _ = strconv.ParseFloat(holdingMap["avail_position"].(string), 64) - pos.SellPriceAvg, _ = strconv.ParseFloat(holdingMap["avg_cost"].(string), 64) - // TODO: what's mean of BuyPriceCost - pos.SellPriceCost, _ = strconv.ParseFloat(holdingMap["avg_cost"].(string), 64) - pos.SellProfitReal, _ = strconv.ParseFloat(holdingMap["realized_pnl"].(string), 64) - } else if side == "long" { - pos.BuyAmount, _ = strconv.ParseFloat(holdingMap["position"].(string), 64) - pos.BuyAvailable, _ = strconv.ParseFloat(holdingMap["avail_position"].(string), 64) - pos.BuyPriceAvg, _ = strconv.ParseFloat(holdingMap["avg_cost"].(string), 64) - pos.BuyPriceCost, _ = strconv.ParseFloat(holdingMap["avg_cost"].(string), 64) - pos.BuyProfitReal, _ = strconv.ParseFloat(holdingMap["realized_pnl"].(string), 64) - } else { - return nil, fmt.Errorf("unknown swap position side, respMap is: %v", respMap) - } - posAr = append(posAr, pos) - } - return posAr, nil -} - -func (okv3 *OKExV3) GetFuturePosition(currencyPair CurrencyPair, contractType string) ([]FuturePosition, error) { - var fallback []FuturePosition - - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_POSITION_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_POSITION_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return fallback, err - } - requestURL = fmt.Sprintf(requestURL, contract.InstrumentID) - - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return fallback, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - if err != nil { - return fallback, err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return fallback, err - } - - //println(string(body)); - if respMap["error_code"] != nil { - errorCode, err := strconv.Atoi(respMap["error_code"].(string)) - if err != nil || errorCode != 0 { - return fallback, errors.New(string(body)) - } - } - - if contractType == SWAP_CONTRACT { - return okv3.parsePositionsSwap(respMap, currencyPair, contractType) - } - return okv3.parsePositionsFuture(respMap, currencyPair, contractType) -} - -var KlineTypeSecondsMap = map[int]int{ - KLINE_PERIOD_1MIN: 60, - KLINE_PERIOD_3MIN: 180, - KLINE_PERIOD_5MIN: 300, - KLINE_PERIOD_15MIN: 900, - KLINE_PERIOD_30MIN: 1800, - KLINE_PERIOD_60MIN: 3600, - KLINE_PERIOD_1H: 3600, - KLINE_PERIOD_2H: 7200, - KLINE_PERIOD_4H: 14400, - KLINE_PERIOD_6H: 21600, - KLINE_PERIOD_12H: 43200, - KLINE_PERIOD_1DAY: 86400, - KLINE_PERIOD_1WEEK: 604800, -} - -func (okv3 *OKExV3) mergeKlineRecords(klines [][]*FutureKline) []FutureKline { - ret := make([]FutureKline, 0) - for _, kline := range klines { - for _, k := range kline { - ret = append(ret, *k) - } - } - return ret -} - -func (okv3 *OKExV3) getKlineRecords(contractType string, currencyPair CurrencyPair, seconds int, start, end *time.Time) ([]*FutureKline, error) { - var fallback []*FutureKline - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_GET_KLINE_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_GET_KLINE_URI - } - params := url.Values{} - params.Set("granularity", strconv.Itoa(seconds)) - - if start != nil { - params.Set("start", start.Format(time.RFC3339)) - } - if end != nil { - params.Set("end", end.Format(time.RFC3339)) - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return fallback, err - } - requestURL = fmt.Sprintf(requestURL, contract.InstrumentID) - requestURL = requestURL + "?" + params.Encode() - - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return fallback, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - - if err != nil { - return fallback, err - } - - var klines [][]interface{} - err = json.Unmarshal(body, &klines) - if err != nil { - return fallback, err - } - - var klineRecords []*FutureKline - for _, record := range klines { - r := FutureKline{} - r.Kline = new(Kline) - for i, e := range record { - switch i { - case 0: - r.Timestamp, _ = timeStringToInt64(e.(string)) //to unix timestramp - r.Timestamp = r.Timestamp / 1000 // Timestamp in kline is seconds not miliseconds - case 1: - r.Open, _ = strconv.ParseFloat(e.(string), 64) - case 2: - r.High, _ = strconv.ParseFloat(e.(string), 64) - case 3: - r.Low, _ = strconv.ParseFloat(e.(string), 64) - case 4: - r.Close, _ = strconv.ParseFloat(e.(string), 64) - case 5: - r.Vol, _ = strconv.ParseFloat(e.(string), 64) - case 6: - r.Vol2, _ = strconv.ParseFloat(e.(string), 64) - } - } - klineRecords = append(klineRecords, &r) - } - reverse(klineRecords) - return klineRecords, nil -} - -func reverse(s interface{}) { - n := reflect.ValueOf(s).Len() - swap := reflect.Swapper(s) - for i, j := 0, n-1; i < j; i, j = i+1, j-1 { - swap(i, j) - } -} - -func (okv3 *OKExV3) GetKlineRecords(contractType string, currencyPair CurrencyPair, period, size, since int) ([]FutureKline, error) { - var fallback []FutureKline - - var maxSize int - if contractType == SWAP_CONTRACT { - maxSize = 200 - } else { - maxSize = 300 - } - - seconds, ok := KlineTypeSecondsMap[period] - if !ok { - return nil, fmt.Errorf("invalid kline period for okex %d", period) - } - - starts := make([]*time.Time, 0) - ends := make([]*time.Time, 0) - - if since > 0 { - startTime := int64ToTime(int64(since)) - for start, left := startTime, size; true; { - if left > maxSize { - s := start - e := s.Add(time.Duration(seconds*maxSize) * time.Second) - starts = append(starts, &s) - ends = append(ends, &e) - start = e.Add(1) // a little trick - left -= maxSize - } else { - s := start - starts = append(starts, &s) - ends = append(ends, nil) - break - } - } - lock := &sync.Mutex{} - klinesSlice := make([][]*FutureKline, len(starts)) - var err error - wg := &sync.WaitGroup{} - wg.Add(len(starts)) - for i := 0; i < len(starts); i++ { - go func(i int) { - defer wg.Done() - klines, err2 := okv3.getKlineRecords(contractType, currencyPair, seconds, starts[i], ends[i]) - lock.Lock() - defer lock.Unlock() - if err2 != nil { - if err == nil { - err = err2 - } - log.Println("error when get kline: ", err2) - } - klinesSlice[i] = klines - }(i) - } - wg.Wait() - if err != nil { - return fallback, err - } - klines := okv3.mergeKlineRecords(klinesSlice) - l := len(klines) - if l > size { - klines = klines[0:size] - } - return klines, nil - } else { - endTime := time.Now().UTC() - for end, left := endTime, size; true; { - if left > maxSize { - e := end - s := e.Add(-time.Duration(seconds*maxSize) * time.Second) - starts = append(starts, &s) - ends = append(ends, &e) - end = s.Add(-1) // a little trick - left -= maxSize - } else { - e := end - starts = append(starts, nil) - ends = append(ends, &e) - break - } - } - reverse(starts) - reverse(ends) - - lock := &sync.Mutex{} - klinesSlice := make([][]*FutureKline, len(starts)) - var err error - wg := &sync.WaitGroup{} - wg.Add(len(starts)) - for i := 0; i < len(starts); i++ { - go func(i int) { - defer wg.Done() - klines, err2 := okv3.getKlineRecords(contractType, currencyPair, seconds, starts[i], ends[i]) - lock.Lock() - defer lock.Unlock() - if err2 != nil { - if err == nil { - err = err2 - } - log.Println("error when get kline: ", err2) - } - klinesSlice[i] = klines - }(i) - } - wg.Wait() - if err != nil { - return fallback, err - } - klines := okv3.mergeKlineRecords(klinesSlice) - l := len(klines) - if l > size { - klines = klines[l-size : l] - } - return klines, nil - } -} - -func (okv3 *OKExV3) GetTrades(contractType string, currencyPair CurrencyPair, since int64) ([]Trade, error) { - var fallback []Trade - - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_TRADES_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_TRADES_URI - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return fallback, err - } - requestURL = fmt.Sprintf(requestURL, contract.InstrumentID) - - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return fallback, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - if err != nil { - return fallback, err - } - - var trades []Trade - var resp []interface{} - err = json.Unmarshal(body, &resp) - if err != nil { - return nil, errors.New(string(body)) - } - - for _, v := range resp { - item := v.(map[string]interface{}) - trade := new(Trade) - trade.Pair = currencyPair - trade, _, err := okv3.dataParser.ParseTrade(trade, contractType, item) - if err != nil { - return nil, err - } - trades = append(trades, *trade) - } - - return trades, nil -} - -func (okv3 *OKExV3) GetFutureIndex(currencyPair CurrencyPair) (float64, error) { - fallback := -1.0 - - contractType := SWAP_CONTRACT - - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_INDEX_PRICE - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_INDEX_PRICE - } - - contract, err := okv3.GetContract(currencyPair, contractType) - if err != nil { - return fallback, err - } - requestURL = fmt.Sprintf(requestURL, contract.InstrumentID) - - headers, err := okv3.getSignedHTTPHeader("GET", requestURL) - if err != nil { - return fallback, err - } - - body, err := HttpGet5(okv3.client, requestURL, headers) - if err != nil { - return fallback, err - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err != nil { - return fallback, err - } - - f, err := strconv.ParseFloat(respMap["index"].(string), 64) - if err != nil { - return fallback, err - } - return f, nil -} - -func (okv3 *OKExV3) getAllCurrencies() []string { - okv3.contractsRW.RLock() - defer okv3.contractsRW.RUnlock() - - set := make(map[string]string) - for _, v := range okv3.contractsMap { - currency := strings.ToUpper(v.UnderlyingIndex) - set[currency] = currency - } - currencies := make([]string, 0, len(set)) - for _, v := range set { - currencies = append(currencies, v) - } - return currencies -} - -// GetFutureUserinfo 还只能查交割合约接口 -func (okv3 *OKExV3) GetFutureUserinfo() (*FutureAccount, error) { - var fallback *FutureAccount - - contractType := QUARTER_CONTRACT // 目前只查交割合约 - - var requestURL string - if contractType == SWAP_CONTRACT { - requestURL = okv3.GetUrlRoot() + V3_SWAP_API_BASE_URL + V3_FUTURE_USERINFOS_URI - } else { - requestURL = okv3.GetUrlRoot() + V3_FUTURE_API_BASE_URL + V3_FUTURE_USERINFOS_URI - } - - currencies := okv3.getAllCurrencies() - - wg := &sync.WaitGroup{} - wg.Add(len(currencies)) - - account := new(FutureAccount) - account.FutureSubAccounts = make(map[Currency]FutureSubAccount) - var err error - lock := &sync.Mutex{} - - for _, v := range currencies { - go func(currency string) { - defer wg.Done() - requestURL := fmt.Sprintf(requestURL, strings.ToLower(currency)) - - headers, err2 := okv3.getSignedHTTPHeader("GET", requestURL) - - lock.Lock() - defer lock.Unlock() - - if err2 != nil { - err = err2 - return - } - - body, err2 := HttpGet5(okv3.client, requestURL, headers) - if err2 != nil { - err = err2 - return - } - - respMap := make(map[string]interface{}) - err = json.Unmarshal(body, &respMap) - if err2 != nil { - err = err2 - return - } - marginMode := respMap["margin_mode"].(string) - symbol := NewCurrency(currency, "") - subAccount := FutureSubAccount{} - subAccount.Currency = symbol - subAccount.AccountRights, _ = strconv.ParseFloat(respMap["equity"].(string), 64) - if marginMode == OKEX_MARGIN_MODE_CROSSED { - subAccount.KeepDeposit, _ = strconv.ParseFloat(respMap["margin"].(string), 64) - subAccount.ProfitReal, _ = strconv.ParseFloat(respMap["realized_pnl"].(string), 64) - subAccount.ProfitUnreal, _ = strconv.ParseFloat(respMap["unrealized_pnl"].(string), 64) - subAccount.RiskRate, _ = strconv.ParseFloat(respMap["margin_ratio"].(string), 64) - } else { - totalMargin := 0.0 - totalRealizedPnl := 0.0 - totalUnrealizedPnl := 0.0 - if respMap["contracts"] != nil { - contracts := respMap["contracts"].([]interface{}) - for _, v := range contracts { - cMap := v.(map[string]interface{}) - marginFrozen, _ := strconv.ParseFloat(cMap["margin_frozen"].(string), 64) - marginUnfilled, _ := strconv.ParseFloat(cMap["margin_for_unfilled"].(string), 64) - realizedPnl, _ := strconv.ParseFloat(cMap["realized_pnl"].(string), 64) - unrealizedPnl, _ := strconv.ParseFloat(cMap["unrealized_pnl"].(string), 64) - totalMargin = totalMargin + marginFrozen + marginUnfilled - totalRealizedPnl = totalRealizedPnl + realizedPnl - totalUnrealizedPnl = totalUnrealizedPnl + unrealizedPnl - } - subAccount.KeepDeposit = totalMargin - subAccount.ProfitReal = totalRealizedPnl - subAccount.ProfitUnreal = totalUnrealizedPnl - } - } - account.FutureSubAccounts[symbol] = subAccount - }(v) - } - - wg.Wait() - - if err != nil { - return fallback, err - } - - return account, nil -} diff --git a/okcoin/OKEx_V3_Future_Ws.go b/okcoin/OKEx_V3_Future_Ws.go deleted file mode 100644 index 8df99d89..00000000 --- a/okcoin/OKEx_V3_Future_Ws.go +++ /dev/null @@ -1,447 +0,0 @@ -package okcoin - -import ( - "encoding/json" - "errors" - "fmt" - "log" - "strconv" - "strings" - "sync" - "time" - - . "github.com/nntaoli-project/goex" -) - -type OKExV3FutureWs struct { - *WsBuilder - sync.Once - wsConn *WsConn - loginCh chan interface{} - logined bool - loginLock *sync.Mutex - dataParser *OKExV3DataParser - contractIDProvider IContractIDProvider - apiKey string - apiSecretKey string - passphrase string - authoriedSubs []map[string]interface{} - - tickerCallback func(*FutureTicker) - depthCallback func(*Depth) - tradeCallback func(*Trade, string) - klineCallback func(*FutureKline, int) - orderCallback func(*FutureOrder, string) -} - -func getSign(apiSecretKey, timestamp, method, url, body string) (string, error) { - data := timestamp + method + url + body - return GetParamHmacSHA256Base64Sign(apiSecretKey, data) -} - -func NewOKExV3FutureWs(contractIDProvider IContractIDProvider) *OKExV3FutureWs { - okV3Ws := &OKExV3FutureWs{} - okV3Ws.contractIDProvider = contractIDProvider - okV3Ws.dataParser = NewOKExV3DataParser(contractIDProvider) - okV3Ws.loginCh = make(chan interface{}) - okV3Ws.logined = false - okV3Ws.loginLock = &sync.Mutex{} - okV3Ws.authoriedSubs = make([]map[string]interface{}, 0) - okV3Ws.WsBuilder = NewWsBuilder(). - WsUrl("wss://real.okex.com:10442/ws/v3"). - AutoReconnect(). - Heartbeat(func() []byte { - return []byte("ping") - }, 30*time.Second). - UnCompressFunc(FlateUnCompress). - ProtoHandleFunc(okV3Ws.handle) - - return okV3Ws -} - -func (okV3Ws *OKExV3FutureWs) TickerCallback(tickerCallback func(*FutureTicker)) *OKExV3FutureWs { - okV3Ws.tickerCallback = tickerCallback - return okV3Ws -} - -func (okV3Ws *OKExV3FutureWs) DepthCallback(depthCallback func(*Depth)) *OKExV3FutureWs { - okV3Ws.depthCallback = depthCallback - return okV3Ws -} - -func (okV3Ws *OKExV3FutureWs) TradeCallback(tradeCallback func(*Trade, string)) *OKExV3FutureWs { - okV3Ws.tradeCallback = tradeCallback - return okV3Ws -} - -func (okV3Ws *OKExV3FutureWs) OrderCallback(orderCallback func(*FutureOrder, string)) *OKExV3FutureWs { - okV3Ws.orderCallback = orderCallback - return okV3Ws -} - -func (okV3Ws *OKExV3FutureWs) SetCallbacks(tickerCallback func(*FutureTicker), - depthCallback func(*Depth), - tradeCallback func(*Trade, string), - klineCallback func(*FutureKline, int), - orderCallback func(*FutureOrder, string)) { - okV3Ws.tickerCallback = tickerCallback - okV3Ws.depthCallback = depthCallback - okV3Ws.tradeCallback = tradeCallback - okV3Ws.klineCallback = klineCallback - okV3Ws.orderCallback = orderCallback -} - -func (okV3Ws *OKExV3FutureWs) Login(apiKey string, apiSecretKey string, passphrase string) error { - // already logined - if okV3Ws.logined { - return nil - } - okV3Ws.connectWs() - okV3Ws.apiKey = apiKey - okV3Ws.apiSecretKey = apiSecretKey - okV3Ws.passphrase = passphrase - err := okV3Ws.login() - if err == nil { - okV3Ws.logined = true - } - return err -} - -func (okV3Ws *OKExV3FutureWs) getTimestamp() string { - seconds := float64(time.Now().UTC().UnixNano()) / float64(time.Second) - return fmt.Sprintf("%.3f", seconds) -} - -func (okV3Ws *OKExV3FutureWs) clearChan(c chan interface{}) { - for { - if len(c) > 0 { - <-c - } else { - break - } - } -} - -func (okV3Ws *OKExV3FutureWs) login() error { - okV3Ws.loginLock.Lock() - defer okV3Ws.loginLock.Unlock() - okV3Ws.clearChan(okV3Ws.loginCh) - apiKey := okV3Ws.apiKey - apiSecretKey := okV3Ws.apiSecretKey - passphrase := okV3Ws.passphrase - //clear last login result - timestamp := okV3Ws.getTimestamp() - method := "GET" - url := "/users/self/verify" - sign, _ := getSign(apiSecretKey, timestamp, method, url, "") - op := map[string]interface{}{ - "op": "login", - "args": []string{apiKey, passphrase, timestamp, sign}} - - err := okV3Ws.wsConn.SendJsonMessage(op) - if err != nil { - return err - } - event := <-okV3Ws.loginCh - if v, ok := event.(map[string]interface{}); ok { - var success = false - switch s := v["success"].(type) { - case bool: - success = s - case string: - success = s == "true" - } - if success { - log.Println("login success:", event) - return nil - } - } - log.Println("login failed:", event) - return fmt.Errorf("login failed: %v", event) -} - -func (okV3Ws *OKExV3FutureWs) subscribe(sub map[string]interface{}) error { - okV3Ws.connectWs() - return okV3Ws.wsConn.Subscribe(sub) -} - -func (okV3Ws *OKExV3FutureWs) getTablePrefix(currencyPair CurrencyPair, contractType string) string { - if contractType == SWAP_CONTRACT { - return "swap" - } - return "futures" -} - -func (okV3Ws *OKExV3FutureWs) SubscribeDepth(currencyPair CurrencyPair, contractType string, size int) error { - if (size > 0) && (size != 5) { - return errors.New("only support depth 5") - } - if okV3Ws.depthCallback == nil { - return errors.New("please set depth callback func") - } - - symbol, err := okV3Ws.contractIDProvider.GetContractID(currencyPair, contractType) - if err != nil { - return err - } - - chName := fmt.Sprintf("%s/depth5:%s", okV3Ws.getTablePrefix(currencyPair, contractType), symbol) - - return okV3Ws.subscribe(map[string]interface{}{ - "op": "subscribe", - "args": []string{chName}}) -} - -func (okV3Ws *OKExV3FutureWs) SubscribeTicker(currencyPair CurrencyPair, contractType string) error { - if okV3Ws.tickerCallback == nil { - return errors.New("please set ticker callback func") - } - - symbol, err := okV3Ws.contractIDProvider.GetContractID(currencyPair, contractType) - if err != nil { - return err - } - - chName := fmt.Sprintf("%s/ticker:%s", okV3Ws.getTablePrefix(currencyPair, contractType), symbol) - - return okV3Ws.subscribe(map[string]interface{}{ - "op": "subscribe", - "args": []string{chName}}) -} - -func (okV3Ws *OKExV3FutureWs) SubscribeTrade(currencyPair CurrencyPair, contractType string) error { - if okV3Ws.tradeCallback == nil { - return errors.New("please set trade callback func") - } - - symbol, err := okV3Ws.contractIDProvider.GetContractID(currencyPair, contractType) - if err != nil { - return err - } - - chName := fmt.Sprintf("%s/trade:%s", okV3Ws.getTablePrefix(currencyPair, contractType), symbol) - - return okV3Ws.subscribe(map[string]interface{}{ - "op": "subscribe", - "args": []string{chName}}) -} - -func (okV3Ws *OKExV3FutureWs) SubscribeKline(currencyPair CurrencyPair, contractType string, period int) error { - if okV3Ws.klineCallback == nil { - return errors.New("place set kline callback func") - } - - symbol, err := okV3Ws.contractIDProvider.GetContractID(currencyPair, contractType) - if err != nil { - return err - } - - seconds, ok := KlineTypeSecondsMap[period] - if !ok { - return fmt.Errorf("unsupported kline period %d in okex", period) - } - - chName := fmt.Sprintf("%s/candle%ds:%s", okV3Ws.getTablePrefix(currencyPair, contractType), seconds, symbol) - - return okV3Ws.subscribe(map[string]interface{}{ - "op": "subscribe", - "args": []string{chName}}) -} - -func (okV3Ws *OKExV3FutureWs) SubscribeOrder(currencyPair CurrencyPair, contractType string) error { - if okV3Ws.orderCallback == nil { - return errors.New("place set order callback func") - } - - symbol, err := okV3Ws.contractIDProvider.GetContractID(currencyPair, contractType) - if err != nil { - return err - } - - chName := fmt.Sprintf("%s/order:%s", okV3Ws.getTablePrefix(currencyPair, contractType), symbol) - - return okV3Ws.authoriedSubscribe(map[string]interface{}{ - "op": "subscribe", - "args": []string{chName}}) -} - -func (okV3Ws *OKExV3FutureWs) authoriedSubscribe(data map[string]interface{}) error { - okV3Ws.authoriedSubs = append(okV3Ws.authoriedSubs, data) - return okV3Ws.subscribe(data) -} - -func (okV3Ws *OKExV3FutureWs) reSubscribeAuthoriedChannel() { - for _, d := range okV3Ws.authoriedSubs { - okV3Ws.wsConn.SendJsonMessage(d) - } -} - -func (okV3Ws *OKExV3FutureWs) connectWs() { - okV3Ws.Do(func() { - okV3Ws.wsConn = okV3Ws.WsBuilder.Build() - }) -} - -func (okV3Ws *OKExV3FutureWs) handle(msg []byte) error { - if string(msg) == "pong" { - log.Println(string(msg)) - return nil - } - - var resp map[string]interface{} - - err := json.Unmarshal(msg, &resp) - if err != nil { - return err - } - - if resp["event"] != nil { - switch resp["event"].(string) { - case "subscribe": - log.Println("subscribed:", resp["channel"].(string)) - return nil - case "login": - select { - case okV3Ws.loginCh <- resp: - return nil - default: - return nil - } - case "error": - var errorCode int - switch v := resp["errorCode"].(type) { - case int: - errorCode = v - case float64: - errorCode = int(v) // float64 okex牛逼嗷 - case string: - i, _ := strconv.ParseInt(v, 10, 64) - errorCode = int(i) - } - - switch errorCode { - // event:error message:Already logged in errorCode:30042 - case 30041: - if okV3Ws.logined { // have logined successfully - go func() { - okV3Ws.login() - okV3Ws.reSubscribeAuthoriedChannel() - }() - } // else skip, or better hanle? - return nil - case 30042: - return nil - } - - // TODO: clearfy which errors should be treated as login result. - select { - case okV3Ws.loginCh <- resp: - return nil - default: - return fmt.Errorf("error in websocket: %v", resp) - } - } - return fmt.Errorf("unknown websocet message: %v", resp) - } - - if resp["table"] != nil { - ch, err := okV3Ws.parseChannel(resp["table"].(string)) - if err != nil { - return err - } - - switch ch { - case "ticker": - data, ok := resp["data"].([]interface{}) - if ok { - valid := true - for _, v := range data { - ticker, err := okV3Ws.dataParser.ParseFutureTicker(v) - if err != nil { - valid = false - break - } - okV3Ws.tickerCallback(ticker) - } - if valid { - return nil - } - } - case "depth5": - data, ok := resp["data"].([]interface{}) - if ok { - valid := true - for _, v := range data { - depth, err := okV3Ws.dataParser.ParseDepth(nil, v, 5) - if err != nil { - valid = false - break - } - okV3Ws.depthCallback(depth) - } - if valid { - return nil - } - } - case "trade": - data, ok := resp["data"].([]interface{}) - if ok { - valid := true - for _, v := range data { - trade, contractType, err := okV3Ws.dataParser.ParseTrade(nil, "", v) - if err != nil { - valid = false - break - } - okV3Ws.tradeCallback(trade, contractType) - } - if valid { - return nil - } - } - case "order": - data, ok := resp["data"].([]interface{}) - if ok { - valid := true - for _, v := range data { - order, contractType, err := okV3Ws.dataParser.ParseFutureOrder(v) - if err != nil { - valid = false - break - } - okV3Ws.orderCallback(order, contractType) - } - if valid { - return nil - } - } - } - } - - return fmt.Errorf("unknown websocet message: %v", resp) -} - -func (okV3Ws *OKExV3FutureWs) parseChannel(channel string) (string, error) { - metas := strings.Split(channel, "/") - if len(metas) != 2 { - return "", fmt.Errorf("unknown channel: %s", channel) - } - return metas[1], nil -} - -func (okV3Ws *OKExV3FutureWs) getKlinePeriodFormChannel(channel string) int { - metas := strings.Split(channel, ":") - if len(metas) != 2 { - return 0 - } - i, _ := strconv.ParseInt(metas[1], 10, 64) - return int(i) -} - -func (okV3Ws *OKExV3FutureWs) adaptTime(tm string) int64 { - format := "2006-01-02 15:04:05" - day := time.Now().Format("2006-01-02") - local, _ := time.LoadLocation("Asia/Chongqing") - t, _ := time.ParseInLocation(format, day+" "+tm, local) - return t.UnixNano() / 1e6 -} diff --git a/okcoin/OKEx_V3_Future_Ws_test.go b/okcoin/OKEx_V3_Future_Ws_test.go deleted file mode 100644 index cb788914..00000000 --- a/okcoin/OKEx_V3_Future_Ws_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package okcoin - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "log" - "testing" - "sync" - "github.com/nntaoli-project/goex" -) - - -func newOKExV3FutureWs()*OKExV3FutureWs{ - okV3Ws := NewOKExV3FutureWs(okexV3) - okV3Ws.WsUrl("wss://okexcomreal.bafang.com:10442/ws/v3") - okV3Ws.ErrorHandleFunc(func(err error) { - log.Println(err) - }) - return okV3Ws -} - -var ( - okV3Ws = newOKExV3FutureWs() -) - -func TestOKExV3FutureWsTickerCallback(t *testing.T) { - n := 10 - tickers := make([]goex.FutureTicker, 0) - wg := &sync.WaitGroup{} - wg.Add(1) - okV3Ws.TickerCallback(func(ticker *goex.FutureTicker) { - t.Log(ticker, ticker.Ticker) - if len(tickers) <= n { - tickers = append(tickers, *ticker) - } - if len(tickers) == n { - wg.Done() - } - }) - okV3Ws.SubscribeTicker(goex.EOS_USD, goex.QUARTER_CONTRACT) - okV3Ws.SubscribeTicker(goex.EOS_USD, goex.SWAP_CONTRACT) - wg.Wait() -} - -func TestOKExV3FutureWsDepthCallback(t *testing.T) { - n := 10 - depths := make([]goex.Depth, 0) - wg := &sync.WaitGroup{} - wg.Add(1) - okV3Ws.DepthCallback(func(depth *goex.Depth) { - if len(depths) <= n { - t.Log(depth) - depths = append(depths, *depth) - } - if len(depths) == n { - wg.Done() - } - }) - okV3Ws.SubscribeDepth(goex.EOS_USD, goex.QUARTER_CONTRACT, 5) - okV3Ws.SubscribeDepth(goex.EOS_USD, goex.SWAP_CONTRACT, 5) - wg.Wait() -} - -func TestOKExV3FutureWsTradeCallback(t *testing.T) { - n := 10 - trades := make([]goex.Trade, 0) - wg := &sync.WaitGroup{} - wg.Add(1) - okV3Ws.TradeCallback(func(trade *goex.Trade, contractType string) { - if len(trades) <= n { - t.Log(contractType, trade) - trades = append(trades, *trade) - } - if len(trades) == n { - wg.Done() - } - }) - okV3Ws.SubscribeTrade(goex.EOS_USD, goex.QUARTER_CONTRACT) - okV3Ws.SubscribeTrade(goex.EOS_USD, goex.SWAP_CONTRACT) - wg.Wait() -} - -func TestOKExV3FutureWsLogin(t *testing.T) { - if authed { - okV3Ws := newOKExV3FutureWs() - err := okV3Ws.Login("", apiSecretKey, passphrase) // fail - assert.True(t, err != nil) - okV3Ws = newOKExV3FutureWs() - err = okV3Ws.Login(apiKey, apiSecretKey, passphrase) //succes - assert.Nil(t, err) - err = okV3Ws.Login(apiKey, apiSecretKey, passphrase) //duplicate login - assert.Nil(t, err) - } else { - t.Log("not authed, skip test websocket login") - } -} - -func placeAndCancel(currencyPair goex.CurrencyPair, contractType string) { - leverage := 20 - depth := 100 - dep, _ := okexV3.GetFutureDepth(currencyPair, contractType, depth) - price := fmt.Sprintf("%f", dep.BidList[depth-1].Price) - symbol, _ := okexV3.GetContract(currencyPair, contractType) - amount := symbol.getSizeIncrement() - orderID, err := okexV3.PlaceFutureOrder( - currencyPair, contractType, price, amount, goex.OPEN_BUY, 0, leverage) - if err != nil { - log.Println(err) - } - _, err = okexV3.FutureCancelOrder(currencyPair, contractType, orderID) - if err != nil { - log.Println(err) - } -} - -func TestOKExV3FutureWsOrderCallback(t *testing.T) { - if authed { - err := okV3Ws.Login(apiKey, apiSecretKey, passphrase) - assert.Nil(t, err) - n := 4 - orders := make([]goex.FutureOrder, 0) - wg := &sync.WaitGroup{} - wg.Add(1) - okV3Ws.OrderCallback(func(order *goex.FutureOrder, contractType string) { - if len(orders) <= n { - t.Log(contractType, order) - orders = append(orders, *order) - } - if len(orders) == n { - wg.Done() - } - }) - err = okV3Ws.SubscribeOrder(goex.EOS_USD, goex.QUARTER_CONTRACT) - assert.Nil(t, err) - err = okV3Ws.SubscribeOrder(goex.EOS_USD, goex.SWAP_CONTRACT) - assert.Nil(t, err) - placeAndCancel(goex.EOS_USD, goex.QUARTER_CONTRACT) - placeAndCancel(goex.EOS_USD, goex.SWAP_CONTRACT) - wg.Wait() - } -} \ No newline at end of file diff --git a/okcoin/OKEx_V3_test.go b/okcoin/OKEx_V3_test.go deleted file mode 100644 index 638f0d40..00000000 --- a/okcoin/OKEx_V3_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package okcoin - -import ( - "fmt" - "net/http" - "os" - "sync" - "testing" - "time" - - . "github.com/nntaoli-project/goex" - "github.com/stretchr/testify/assert" -) - -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - return fallback -} - -var ( - apiKey = getEnv("GOEX_OKEX_API_KEY", "") - apiSecretKey = getEnv("GOEX_OKEX_API_SECRET_KEY", "") - passphrase = getEnv("GOEX_OKEX_PASSPHRASE", "") - endpoint = getEnv("GOEX_OKEX_RESTFUL_URL", "https://www.okex.me/") - authed = len(apiKey) > 0 && len(apiSecretKey) > 0 && len(passphrase) > 0 - okexV3 = NewOKExV3(http.DefaultClient, apiKey, apiSecretKey, passphrase, endpoint) -) - -func TestOKExV3_GetFutureDepth(t *testing.T) { - size := 10 - wg := &sync.WaitGroup{} - wg.Add(2) - go func() { - defer wg.Done() - dep, err := okexV3.GetFutureDepth(BTC_USD, QUARTER_CONTRACT, size) - assert.Nil(t, err) - t.Log(dep) - }() - go func() { - defer wg.Done() - dep, err := okexV3.GetFutureDepth(BTC_USD, SWAP_CONTRACT, size) - assert.Nil(t, err) - t.Log(dep) - }() - wg.Wait() -} - -func TestOKExV3_GetFutureTicker(t *testing.T) { - wg := &sync.WaitGroup{} - wg.Add(2) - go func() { - defer wg.Done() - ticker, err := okexV3.GetFutureTicker(BTC_USD, QUARTER_CONTRACT) - assert.Nil(t, err) - t.Log(ticker) - }() - go func() { - defer wg.Done() - ticker, err := okexV3.GetFutureTicker(BTC_USD, SWAP_CONTRACT) - assert.Nil(t, err) - t.Log(ticker) - }() - wg.Wait() -} - -func testPlaceAndCancel(t *testing.T, currencyPair CurrencyPair, contractType string) { - // 以100档对手价下买单然后马上撤掉 - leverage := 20 - depth := 100 - dep, err := okexV3.GetFutureDepth(currencyPair, contractType, depth) - assert.Nil(t, err) - price := fmt.Sprintf("%f", dep.BidList[depth-1].Price) - symbol, err := okexV3.GetContract(currencyPair, contractType) - assert.Nil(t, err) - amount := symbol.getSizeIncrement() - orderID, err := okexV3.PlaceFutureOrder( - currencyPair, contractType, price, amount, OPEN_BUY, 0, leverage) - assert.Nil(t, err) - t.Log(orderID) - order, err := okexV3.GetFutureOrder(orderID, currencyPair, contractType) - assert.Nil(t, err) - t.Log(order) - cancelled, err := okexV3.FutureCancelOrder(currencyPair, contractType, orderID) - assert.Nil(t, err) - assert.True(t, cancelled) - order, err = okexV3.GetFutureOrder(orderID, currencyPair, contractType) - assert.Nil(t, err) - t.Log(order) -} - -func TestOKExV3_PlaceAndCancelFutureOrder(t *testing.T) { - if authed { - testPlaceAndCancel(t, EOS_USD, QUARTER_CONTRACT) - testPlaceAndCancel(t, EOS_USD, SWAP_CONTRACT) - } else { - t.Log("not authed, skip test place and cancel future order") - } -} - -func testPlaceAndGetInfo(t *testing.T, currencyPair CurrencyPair, contractType string) { - leverage := 20 - depth := 100 - dep, err := okexV3.GetFutureDepth(currencyPair, contractType, depth) - assert.Nil(t, err) - price := fmt.Sprintf("%f", dep.BidList[depth-1].Price) - symbol, err := okexV3.GetContract(currencyPair, contractType) - assert.Nil(t, err) - amount := symbol.getSizeIncrement() - orderID1, err := okexV3.PlaceFutureOrder( - currencyPair, contractType, price, amount, OPEN_BUY, 0, leverage) - assert.Nil(t, err) - t.Log(orderID1) - orderID2, err := okexV3.PlaceFutureOrder( - currencyPair, contractType, price, amount, OPEN_BUY, 0, leverage) - assert.Nil(t, err) - t.Log(orderID2) - // get info of order1 - order, err := okexV3.GetFutureOrder(orderID1, currencyPair, contractType) - assert.Nil(t, err) - t.Log(order) - order, err = okexV3.GetFutureOrder(orderID2, currencyPair, contractType) - assert.Nil(t, err) - t.Log(order) - // sleep for a while when place order, - time.Sleep(1 * time.Second) - // get infos of order1 and order2 - orders, err := okexV3.GetFutureOrders([]string{orderID1, orderID2}, currencyPair, contractType) - assert.Nil(t, err) - assert.True(t, len(orders) == 2) - t.Log(orders) - //cancel order1 and order2 - cancelled, err := okexV3.FutureCancelOrder(currencyPair, contractType, orderID1) - assert.Nil(t, err) - assert.True(t, cancelled) - cancelled, err = okexV3.FutureCancelOrder(currencyPair, contractType, orderID2) - assert.Nil(t, err) - assert.True(t, cancelled) - // sleep for a while when cancel order, - // the order info will be missing in orders api for all states for a short time. - time.Sleep(2 * time.Second) - // get infos of order1 and order2 after cancelling - orders, err = okexV3.GetFutureOrders([]string{orderID1, orderID2}, currencyPair, contractType) - assert.Nil(t, err) - assert.True(t, len(orders) == 2) - t.Log(orders) -} - -func TestOKEV3_PlaceAndGetOrdersInfo(t *testing.T) { - if authed { - testPlaceAndGetInfo(t, EOS_USD, QUARTER_CONTRACT) - testPlaceAndGetInfo(t, EOS_USD, SWAP_CONTRACT) - } else { - t.Log("not authed, skip test place future order and get order info") - } -} - -func TestOKEV3_GetFutureEstimatedPrice(t *testing.T) { - f, err := okexV3.GetFutureEstimatedPrice(EOS_USD) - assert.Nil(t, err) - t.Log(f) -} - -func TestOKEV3_GetFee(t *testing.T) { - f, err := okexV3.GetFee() - assert.Nil(t, err) - assert.True(t, f > 0) - t.Log(f) -} - -func testGetContractValue(t *testing.T, currencyPair CurrencyPair) { - f, err := okexV3.GetContractValue(currencyPair) - assert.Nil(t, err) - assert.True(t, f > 0) - t.Log(f) -} - -func TestOKEV3_GetContractValue(t *testing.T) { - testGetContractValue(t, BTC_USD) - testGetContractValue(t, EOS_USD) -} - -func isEqualDiff(klines []FutureKline, seconds int64) bool { - for i := 0; i < len(klines) - 1; i ++ { - diff := klines[i + 1].Timestamp-klines[i].Timestamp - if diff != seconds { - return false - } - } - return true -} - -func testGetKlineRecords(t *testing.T, contractType string, currency CurrencyPair, maxSize int) { - now := time.Now().UTC() - timestamp := (now.UnixNano() - 20*int64(time.Hour)) / int64(time.Millisecond) - size := 10 - period := KLINE_PERIOD_1MIN - seconds := int64(60) - kline, err := okexV3.GetKlineRecords(contractType, currency, period, size, 0) - assert.Nil(t, err) - assert.True(t, len(kline) == size) - t.Log(len(kline)) - assert.True(t, isEqualDiff(kline, seconds)) - kline, err = okexV3.GetKlineRecords(contractType, currency, period, size, int(timestamp)) - assert.Nil(t, err) - assert.True(t, len(kline) == size) - t.Log(len(kline)) - assert.True(t, isEqualDiff(kline, seconds)) - size = maxSize - kline, err = okexV3.GetKlineRecords(contractType, currency, period, size, 0) - assert.Nil(t, err) - assert.True(t, len(kline) == size) - t.Log(len(kline)) - assert.True(t, isEqualDiff(kline, seconds)) - kline, err = okexV3.GetKlineRecords(contractType, currency, period, size, int(timestamp)) - assert.Nil(t, err) - assert.True(t, len(kline) == size) - t.Log(len(kline)) - assert.True(t, isEqualDiff(kline, seconds)) - size = 3 * maxSize - kline, err = okexV3.GetKlineRecords(contractType, currency, period, size, 0) - assert.Nil(t, err) - assert.True(t, len(kline) == size) - t.Log(len(kline)) - assert.True(t, isEqualDiff(kline, seconds)) - kline, err = okexV3.GetKlineRecords(contractType, currency, period, size, int(timestamp)) - assert.Nil(t, err) - assert.True(t, len(kline) == size) - t.Log(len(kline)) - assert.True(t, isEqualDiff(kline, seconds)) -} - -func TestOKEV3_GetKlineRecords(t *testing.T) { - testGetKlineRecords(t, QUARTER_CONTRACT, EOS_USD, 300) - testGetKlineRecords(t, SWAP_CONTRACT, EOS_USD, 200) -} - -func testGetFutureIndex(t *testing.T, currencyPair CurrencyPair) { - f, err := okexV3.GetFutureIndex(currencyPair) - assert.Nil(t, err) - assert.True(t, f > 0) - t.Log(f) -} - -func TestOKEV3_GetFutureIndex(t *testing.T) { - testGetFutureIndex(t, BTC_USD) - testGetFutureIndex(t, EOS_USD) -} - -func testGetFuturePosition(t *testing.T, currencyPair CurrencyPair, contractType string) { - ps, err := okexV3.GetFuturePosition(currencyPair, contractType) - assert.Nil(t, err) - t.Log(ps) -} - -func TestOKEV3_GetFuturePosition(t *testing.T) { - if authed { - testGetFuturePosition(t, EOS_USD, QUARTER_CONTRACT) - testGetFuturePosition(t, EOS_USD, SWAP_CONTRACT) - } else { - t.Log("not authed, skip test get future position") - } -} - -func testGetTrades(t *testing.T, contractType string, currencyPair CurrencyPair) { - trades, err := okexV3.GetTrades(contractType, currencyPair, 0) - assert.Nil(t, err) - t.Log(trades[0]) -} - -func TestOKEV3_GetTrades(t *testing.T) { - testGetTrades(t, QUARTER_CONTRACT, EOS_USD) - testGetTrades(t, SWAP_CONTRACT, EOS_USD) -} - -func testGetFutureUserinfo(t *testing.T) { - currencies := okexV3.getAllCurrencies() - account, err := okexV3.GetFutureUserinfo() - assert.Nil(t, err) - assert.True(t, len(currencies) == len(account.FutureSubAccounts)) - t.Log(account) -} - -func TestOKEV3_GetFutureUserinfo(t *testing.T) { - if authed { - testGetFutureUserinfo(t) - } else { - t.Log("not authed, skip test get future userinfo") - } -} \ No newline at end of file diff --git a/okcoin/OKEx_V3_utils.go b/okcoin/OKEx_V3_utils.go deleted file mode 100644 index 07c3f5dc..00000000 --- a/okcoin/OKEx_V3_utils.go +++ /dev/null @@ -1,278 +0,0 @@ -package okcoin - -import ( - "fmt" - "strconv" - "time" - - . "github.com/nntaoli-project/goex" -) - -type IContractIDProvider interface { - GetContractID(CurrencyPair, string) (string, error) - ParseContractID(string) (CurrencyPair, string, error) -} - -type OKExV3DataParser struct { - contractIDProvider IContractIDProvider -} - -func NewOKExV3DataParser(contractIDProvider IContractIDProvider) *OKExV3DataParser { - return &OKExV3DataParser{contractIDProvider: contractIDProvider} -} - -func (okV3dp *OKExV3DataParser) ParseFutureTicker(data interface{}) (*FutureTicker, error) { - var fallback *FutureTicker - switch v := data.(type) { - case map[string]interface{}: - contractID := v["instrument_id"].(string) - currencyPair, contractType, err := okV3dp.contractIDProvider.ParseContractID(contractID) - if err != nil { - return fallback, err - } - t := new(Ticker) - t.Pair = currencyPair - timestamp, _ := timeStringToInt64(v["timestamp"].(string)) - t.Date = uint64(timestamp) - t.Buy, _ = strconv.ParseFloat(v["best_ask"].(string), 64) - t.Sell, _ = strconv.ParseFloat(v["best_bid"].(string), 64) - t.Last, _ = strconv.ParseFloat(v["last"].(string), 64) - t.High, _ = strconv.ParseFloat(v["high_24h"].(string), 64) - t.Low, _ = strconv.ParseFloat(v["low_24h"].(string), 64) - t.Vol, _ = strconv.ParseFloat(v["volume_24h"].(string), 64) - ticker := new(FutureTicker) - ticker.ContractType = contractType - ticker.Ticker = t - return ticker, nil - } - - return fallback, fmt.Errorf("unknown FutureTicker data: %v", data) -} - -func (okV3dp *OKExV3DataParser) ParseDepth(depth *Depth, data interface{}, size int) (*Depth, error) { - var fallback *Depth - if depth == nil { - depth = new(Depth) - } - switch v := data.(type) { - case map[string]interface{}: - if !okV3dp.checkContractInfo(depth.Pair, depth.ContractType) { - if v["instrument_id"] != nil { - contractID := v["instrument_id"].(string) - currencyPair, contractType, err := okV3dp.contractIDProvider.ParseContractID(contractID) - if err != nil { - return fallback, err - } - depth.Pair = currencyPair - depth.ContractType = contractType - } - } - var err error - var timeStr string - //name of timestamp field is different between swap and future api - if v["time"] != nil { - timeStr = v["time"].(string) - } else if v["timestamp"] != nil { - timeStr = v["timestamp"].(string) - } else { - return fallback, fmt.Errorf("no time field in %v", v) - } - - depth.UTime, err = time.Parse(time.RFC3339, timeStr) - if err != nil { - return fallback, err - } - - size2 := len(v["asks"].([]interface{})) - skipSize := 0 - if size < size2 { - skipSize = size2 - size - } - - for _, v := range v["asks"].([]interface{}) { - if skipSize > 0 { - skipSize-- - continue - } - - var dr DepthRecord - for i, vv := range v.([]interface{}) { - switch i { - case 0: - dr.Price, err = strconv.ParseFloat(vv.(string), 64) - if err != nil { - return fallback, err - } - case 1: - dr.Amount, err = strconv.ParseFloat(vv.(string), 64) - if err != nil { - return fallback, err - } - } - } - depth.AskList = append(depth.AskList, dr) - } - - for _, v := range v["bids"].([]interface{}) { - var dr DepthRecord - for i, vv := range v.([]interface{}) { - switch i { - case 0: - dr.Price, err = strconv.ParseFloat(vv.(string), 64) - if err != nil { - return fallback, err - } - case 1: - dr.Amount, err = strconv.ParseFloat(vv.(string), 64) - if err != nil { - return fallback, err - } - } - } - depth.BidList = append(depth.BidList, dr) - - size-- - if size == 0 { - break - } - } - return depth, nil - } - - return fallback, fmt.Errorf("unknown Depth data: %v", data) -} - -var emptyPair = CurrencyPair{} - -func (okV3dp *OKExV3DataParser) checkContractInfo(currencyPair CurrencyPair, contractType string) bool { - if currencyPair.Eq(emptyPair) || currencyPair.Eq(UNKNOWN_PAIR) { - return false - } - if contractType == "" { - return false - } - return true -} - -func (okV3dp *OKExV3DataParser) ParseFutureOrder(data interface{}) (*FutureOrder, string, error) { - var fallback *FutureOrder - switch v := data.(type) { - case map[string]interface{}: - contractID := v["instrument_id"].(string) - currencyPair, contractType, err := okV3dp.contractIDProvider.ParseContractID(contractID) - if err != nil { - return fallback, "", err - } - - futureOrder := &FutureOrder{} - // swap orderID is not in int format, so just skip this error - futureOrder.OrderID, _ = strconv.ParseInt(v["order_id"].(string), 10, 64) - futureOrder.OrderID2 = v["order_id"].(string) - futureOrder.Amount, err = strconv.ParseFloat(v["size"].(string), 64) - if err != nil { - return fallback, "", err - } - futureOrder.Price, err = strconv.ParseFloat(v["price"].(string), 64) - if err != nil { - return fallback, "", err - } - futureOrder.AvgPrice, err = strconv.ParseFloat(v["price_avg"].(string), 64) - if err != nil { - return fallback, "", err - } - futureOrder.DealAmount, err = strconv.ParseFloat(v["filled_qty"].(string), 64) - if err != nil { - return fallback, "", err - } - futureOrder.Fee, err = strconv.ParseFloat(v["fee"].(string), 64) - if err != nil { - return fallback, "", err - } - if i, err := strconv.ParseInt(v["type"].(string), 10, 64); err == nil { - futureOrder.OType = int(i) - } else { - return fallback, "", err - } - futureOrder.OrderTime, err = timeStringToInt64(v["timestamp"].(string)) - if err != nil { - return fallback, "", err - } - // leverage not appear in swap - if v["leverage"] != nil { - i, err := strconv.ParseInt(v["leverage"].(string), 10, 64) - if err != nil { - return fallback, "", err - } - futureOrder.LeverRate = int(i) - } - futureOrder.ContractName = v["instrument_id"].(string) - futureOrder.Currency = currencyPair - - state, err := strconv.ParseInt(v["state"].(string), 10, 64) - if err != nil { - return fallback, "", err - } - switch state { - case 0: - futureOrder.Status = ORDER_UNFINISH - case 1: - futureOrder.Status = ORDER_PART_FINISH - case 2: - futureOrder.Status = ORDER_FINISH - case 4: - futureOrder.Status = ORDER_CANCEL_ING - case -1: - futureOrder.Status = ORDER_CANCEL - case 3: - futureOrder.Status = ORDER_UNFINISH - case -2: - futureOrder.Status = ORDER_REJECT - default: - return fallback, "", fmt.Errorf("unknown order status: %v", v) - } - return futureOrder, contractType, nil - } - - return fallback, "", fmt.Errorf("unknown FutureOrder data: %v", data) -} - -func (okV3dp *OKExV3DataParser) ParseTrade(trade *Trade, contractType string, data interface{}) (*Trade, string, error) { - var fallback *Trade - if trade == nil { - trade = new(Trade) - } - switch v := data.(type) { - case map[string]interface{}: - if !okV3dp.checkContractInfo(trade.Pair, contractType) { - if v["instrument_id"] != nil { - contractID := v["instrument_id"].(string) - currencyPair, _contractType, err := okV3dp.contractIDProvider.ParseContractID(contractID) - if err != nil { - return fallback, "", err - } - trade.Pair = currencyPair - contractType = _contractType - } - } - - tid, _ := strconv.ParseInt(v["trade_id"].(string), 10, 64) - direction := v["side"].(string) - var amountStr string - // wtf api - if v["qty"] != nil { - amountStr = v["qty"].(string) - } else if v["size"] != nil { - amountStr = v["size"].(string) - } - amount, _ := strconv.ParseFloat(amountStr, 64) - price, _ := strconv.ParseFloat(v["price"].(string), 64) - time, _ := timeStringToInt64(v["timestamp"].(string)) - trade.Tid = tid - trade.Type = AdaptTradeSide(direction) - trade.Amount = amount - trade.Price = price - trade.Date = time - return trade, contractType, nil - } - return fallback, "", fmt.Errorf("unknown Trade data: %v", data) -} diff --git a/okcoin/OKEx_test.go b/okcoin/OKEx_test.go deleted file mode 100644 index 634aad83..00000000 --- a/okcoin/OKEx_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package okcoin - -import ( - . "github.com/nntaoli-project/goex" - "github.com/stretchr/testify/assert" - "net/http" - "testing" -) - -var ( - okex = NewOKEx(http.DefaultClient, "", "") -) - -func TestOKEx_GetFutureDepth(t *testing.T) { - dep, err := okex.GetFutureDepth(BTC_USD, QUARTER_CONTRACT, 1) - assert.Nil(t, err) - t.Log(dep) -} diff --git a/okcoin/OKcoin_COM_test.go b/okcoin/OKcoin_COM_test.go deleted file mode 100644 index c11896f5..00000000 --- a/okcoin/OKcoin_COM_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package okcoin - -import ( - "github.com/nntaoli-project/goex" - "net/http" - "testing" -) - -var okcom = NewCOM(http.DefaultClient, "", "") - -func TestOKCoinCOM_API_GetTicker(t *testing.T) { - ticker, _ := okcom.GetTicker(goex.BTC_CNY) - t.Log(ticker) -} From 76a317f9264a39cf7a2472fa97d5ccdc714acf24 Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Wed, 18 Mar 2020 18:29:51 +0800 Subject: [PATCH 09/29] [okex] websocket v3 --- Const.go | 12 +- okex/OKEx.go | 15 +- okex/OKExFuture.go | 130 +++++----- okex/OKExFuturesWs.go | 481 +++++++++++++++++++++++++++++++++++++ okex/OKExFuturesWs_test.go | 57 +++++ okex/OKExSwap.go | 2 - okex/OKExV3Utils.go | 45 ++++ 7 files changed, 655 insertions(+), 87 deletions(-) create mode 100644 okex/OKExFuturesWs.go create mode 100644 okex/OKExFuturesWs_test.go create mode 100644 okex/OKExV3Utils.go diff --git a/Const.go b/Const.go index ffd7a4dc..9804d225 100644 --- a/Const.go +++ b/Const.go @@ -113,16 +113,16 @@ const ( ) var ( - THIS_WEEK_CONTRACT = "this_week" //周合约 - NEXT_WEEK_CONTRACT = "next_week" //次周合约 - QUARTER_CONTRACT = "quarter" //季度合约 - SWAP_CONTRACT = "swap" //永续合约 + THIS_WEEK_CONTRACT = "this_week" //周合约 + NEXT_WEEK_CONTRACT = "next_week" //次周合约 + QUARTER_CONTRACT = "quarter" //季度合约 + BI_QUARTER_CONTRACT = "bi_quarter" // NEXT QUARTER + SWAP_CONTRACT = "swap" //永续合约 ) //exchanges const const ( KUCOIN = "kucoin.com" - OKCOIN_CN = "okcoin.cn" OKCOIN_COM = "okcoin.com" OKEX = "okex.com" OKEX_V3 = "okex.com_v3" @@ -142,9 +142,7 @@ const ( GATEIO = "gate.io" BITTREX = "bittrex.com" GDAX = "gdax.com" - WEX_NZ = "wex.nz" BIGONE = "big.one" - COIN58 = "58coin.com" FCOIN = "fcoin.com" FCOIN_MARGIN = "fcoin.com_margin" FMEX = "fmex.com" diff --git a/okex/OKEx.go b/okex/OKEx.go index 4bd6a30a..45aaca5b 100644 --- a/okex/OKEx.go +++ b/okex/OKEx.go @@ -16,12 +16,13 @@ import ( const baseUrl = "https://www.okex.com" type OKEx struct { - config *APIConfig - OKExSpot *OKExSpot - OKExFuture *OKExFuture - OKExSwap *OKExSwap - OKExWallet *OKExWallet - OKExMargin *OKExMargin + config *APIConfig + OKExSpot *OKExSpot + OKExFuture *OKExFuture + OKExSwap *OKExSwap + OKExWallet *OKExWallet + OKExMargin *OKExMargin + OKExV3FutureWs *OKExV3FutureWs } func NewOKEx(config *APIConfig) *OKEx { @@ -34,6 +35,8 @@ func NewOKEx(config *APIConfig) *OKEx { okex.OKExWallet = &OKExWallet{okex} okex.OKExMargin = &OKExMargin{okex} okex.OKExSwap = &OKExSwap{okex, config} + okex.OKExV3FutureWs = NewOKExV3FuturesWs() + okex.OKExV3FutureWs.OKEx = okex return okex } diff --git a/okex/OKExFuture.go b/okex/OKExFuture.go index 3eca12db..ceb668dc 100644 --- a/okex/OKExFuture.go +++ b/okex/OKExFuture.go @@ -56,7 +56,7 @@ func (ok *OKExFuture) GetRate() (float64, error) { } func (ok *OKExFuture) GetFutureEstimatedPrice(currencyPair CurrencyPair) (float64, error) { - urlPath := fmt.Sprintf("/api/futures/v3/instruments/%s/estimated_price", ok.getFutureContractId(currencyPair, QUARTER_CONTRACT)) + urlPath := fmt.Sprintf("/api/futures/v3/instruments/%s/estimated_price", ok.GetFutureContractId(currencyPair, QUARTER_CONTRACT)) var response struct { InstrumentId string `json:"instrument_id"` SettlementPrice float64 `json:"settlement_price,string"` @@ -69,7 +69,7 @@ func (ok *OKExFuture) GetFutureEstimatedPrice(currencyPair CurrencyPair) (float6 return response.SettlementPrice, nil } -func (ok *OKExFuture) GetFutureContractInfo() ([]FutureContractInfo, error) { +func (ok *OKExFuture) GetAllFutureContractInfo() ([]FutureContractInfo, error) { urlPath := "/api/futures/v3/instruments" var response []FutureContractInfo err := ok.DoRequest("GET", urlPath, "", &response) @@ -79,8 +79,20 @@ func (ok *OKExFuture) GetFutureContractInfo() ([]FutureContractInfo, error) { return response, nil } -func (ok *OKExFuture) getFutureContractId(pair CurrencyPair, contractAlias string) string { - if contractAlias != QUARTER_CONTRACT && contractAlias != NEXT_WEEK_CONTRACT && contractAlias != THIS_WEEK_CONTRACT { //传Alias,需要转为具体ContractId +func (ok *OKExFuture) GetContractInfo(contractId string) (*FutureContractInfo, error) { + for _, itm := range ok.allContractInfo.contractInfos { + if itm.InstrumentID == contractId { + return &itm, nil + } + } + return nil, errors.New("unknown contract id " + contractId) +} + +func (ok *OKExFuture) GetFutureContractId(pair CurrencyPair, contractAlias string) string { + if contractAlias != QUARTER_CONTRACT && + contractAlias != NEXT_WEEK_CONTRACT && + contractAlias != THIS_WEEK_CONTRACT && + contractAlias != BI_QUARTER_CONTRACT { //传Alias,需要转为具体ContractId return contractAlias } @@ -92,13 +104,13 @@ func (ok *OKExFuture) getFutureContractId(pair CurrencyPair, contractAlias strin ok.Lock() defer ok.Unlock() - contractInfo, err := ok.GetFutureContractInfo() + contractInfo, err := ok.GetAllFutureContractInfo() if err == nil { ok.allContractInfo.uTime = time.Now() ok.allContractInfo.contractInfos = contractInfo } else { time.Sleep(120 * time.Millisecond) //retry - contractInfo, err = ok.GetFutureContractInfo() + contractInfo, err = ok.GetAllFutureContractInfo() if err != nil { logger.Warnf(fmt.Sprintf("Get Futures Contract Alias Error [%s] ???", err.Error())) } @@ -118,19 +130,22 @@ func (ok *OKExFuture) getFutureContractId(pair CurrencyPair, contractAlias strin return contractId } -func (ok *OKExFuture) GetFutureTicker(currencyPair CurrencyPair, contractType string) (*Ticker, error) { - var urlPath = fmt.Sprintf("/api/futures/v3/instruments/%s/ticker", ok.getFutureContractId(currencyPair, contractType)) +type tickerResponse struct { + InstrumentId string `json:"instrument_id"` + Last float64 `json:"last,string"` + High24h float64 `json:"high_24h,string"` + Low24h float64 `json:"low_24h,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + Volume24h float64 `json:"volume_24h,string"` + Timestamp string `json:"timestamp"` +} - var response struct { - InstrumentId string `json:"instrument_id"` - Last float64 `json:"last,string"` - High24h float64 `json:"high_24h,string"` - Low24h float64 `json:"low_24h,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - Volume24h float64 `json:"volume_24h,string"` - Timestamp string `json:"timestamp"` - } +func (ok *OKExFuture) GetFutureTicker(currencyPair CurrencyPair, contractType string) (*Ticker, error) { + var ( + urlPath = fmt.Sprintf("/api/futures/v3/instruments/%s/ticker", ok.GetFutureContractId(currencyPair, contractType)) + response tickerResponse + ) err := ok.DoRequest("GET", urlPath, "", &response) if err != nil { return nil, err @@ -152,16 +167,7 @@ func (ok *OKExFuture) GetFutureTicker(currencyPair CurrencyPair, contractType st func (ok *OKExFuture) GetFutureAllTicker() (*[]FutureTicker, error) { var urlPath = "/api/futures/v3/instruments/ticker" - var response []struct { - InstrumentId string `json:"instrument_id"` - Last float64 `json:"last,string"` - High24h float64 `json:"high_24h,string"` - Low24h float64 `json:"low_24h,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - Volume24h float64 `json:"volume_24h,string"` - Timestamp string `json:"timestamp"` - } + var response []tickerResponse err := ok.DoRequest("GET", urlPath, "", &response) if err != nil { @@ -187,19 +193,23 @@ func (ok *OKExFuture) GetFutureAllTicker() (*[]FutureTicker, error) { return &tickers, nil } +type depthResponse struct { + Bids [][4]interface{} `json:"bids"` + Asks [][4]interface{} `json:"asks"` + InstrumentId string `json:"instrument_id"` + Timestamp string `json:"timestamp"` +} + func (ok *OKExFuture) GetFutureDepth(currencyPair CurrencyPair, contractType string, size int) (*Depth, error) { - urlPath := fmt.Sprintf("/api/futures/v3/instruments/%s/book?size=%d", ok.getFutureContractId(currencyPair, contractType), size) - var response struct { - Bids [][4]interface{} `json:"bids"` - Asks [][4]interface{} `json:"asks"` - Timestamp string `json:"timestamp"` - } + var ( + response depthResponse + dep Depth + ) + urlPath := fmt.Sprintf("/api/futures/v3/instruments/%s/book?size=%d", ok.GetFutureContractId(currencyPair, contractType), size) err := ok.DoRequest("GET", urlPath, "", &response) if err != nil { return nil, err } - - var dep Depth dep.Pair = currencyPair dep.ContractType = contractType dep.UTime, _ = time.Parse(time.RFC3339, response.Timestamp) @@ -213,14 +223,13 @@ func (ok *OKExFuture) GetFutureDepth(currencyPair CurrencyPair, contractType str Price: ToFloat64(itm[0]), Amount: ToFloat64(itm[1])}) } - sort.Sort(sort.Reverse(dep.AskList)) return &dep, nil } func (ok *OKExFuture) GetFutureIndex(currencyPair CurrencyPair) (float64, error) { //统一交易对,当周,次周,季度指数一样的 - urlPath := fmt.Sprintf("/api/futures/v3/instruments/%s/index", ok.getFutureContractId(currencyPair, QUARTER_CONTRACT)) + urlPath := fmt.Sprintf("/api/futures/v3/instruments/%s/index", ok.GetFutureContractId(currencyPair, QUARTER_CONTRACT)) var response struct { InstrumentId string `json:"instrument_id"` Index float64 `json:"index,string"` @@ -347,7 +356,7 @@ func (ok *OKExFuture) PlaceFutureOrder2(matchPrice int, ord *FutureOrder) (*Futu if ord == nil { return nil, errors.New("ord param is nil") } - param.InstrumentId = ok.getFutureContractId(ord.Currency, ord.ContractName) + param.InstrumentId = ok.GetFutureContractId(ord.Currency, ord.ContractName) param.ClientOid = strings.Replace(uuid.New().String(), "-", "", 32) param.Type = ord.OType param.OrderType = ord.OrderType @@ -398,7 +407,7 @@ func (ok *OKExFuture) PlaceFutureOrder(currencyPair CurrencyPair, contractType, OrderId string `json:"order_id"` } - param.InstrumentId = ok.getFutureContractId(currencyPair, contractType) + param.InstrumentId = ok.GetFutureContractId(currencyPair, contractType) param.ClientOid = strings.Replace(uuid.New().String(), "-", "", 32) param.Type = fmt.Sprint(openType) param.OrderType = "0" @@ -417,7 +426,7 @@ func (ok *OKExFuture) PlaceFutureOrder(currencyPair CurrencyPair, contractType, } func (ok *OKExFuture) FutureCancelOrder(currencyPair CurrencyPair, contractType, orderId string) (bool, error) { - urlPath := fmt.Sprintf("/api/futures/v3/cancel_order/%s/%s", ok.getFutureContractId(currencyPair, contractType), orderId) + urlPath := fmt.Sprintf("/api/futures/v3/cancel_order/%s/%s", ok.GetFutureContractId(currencyPair, contractType), orderId) var response struct { Result bool `json:"result"` OrderId string `json:"order_id"` @@ -432,7 +441,7 @@ func (ok *OKExFuture) FutureCancelOrder(currencyPair CurrencyPair, contractType, } func (ok *OKExFuture) GetFuturePosition(currencyPair CurrencyPair, contractType string) ([]FuturePosition, error) { - urlPath := fmt.Sprintf("/api/futures/v3/%s/position", ok.getFutureContractId(currencyPair, contractType)) + urlPath := fmt.Sprintf("/api/futures/v3/%s/position", ok.GetFutureContractId(currencyPair, contractType)) var response struct { Result bool `json:"result"` MarginMode string `json:"margin_mode"` @@ -540,7 +549,7 @@ func (ok *OKExFuture) adaptOrder(response futureOrderResponse) FutureOrder { } func (ok *OKExFuture) GetFutureOrder(orderId string, currencyPair CurrencyPair, contractType string) (*FutureOrder, error) { - urlPath := fmt.Sprintf("/api/futures/v3/orders/%s/%s", ok.getFutureContractId(currencyPair, contractType), orderId) + urlPath := fmt.Sprintf("/api/futures/v3/orders/%s/%s", ok.GetFutureContractId(currencyPair, contractType), orderId) var response futureOrderResponse err := ok.DoRequest("GET", urlPath, "", &response) if err != nil { @@ -554,7 +563,7 @@ func (ok *OKExFuture) GetFutureOrder(orderId string, currencyPair CurrencyPair, } func (ok *OKExFuture) GetUnfinishFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { - urlPath := fmt.Sprintf("/api/futures/v3/orders/%s?state=6&limit=100", ok.getFutureContractId(currencyPair, contractType)) + urlPath := fmt.Sprintf("/api/futures/v3/orders/%s?state=6&limit=100", ok.GetFutureContractId(currencyPair, contractType)) var response struct { Result bool `json:"result"` OrderInfo []futureOrderResponse `json:"order_info"` @@ -601,39 +610,16 @@ func (ok *OKExFuture) GetDeliveryTime() (int, int, int, int) { */ func (ok *OKExFuture) GetKlineRecords(contract_type string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { urlPath := "/api/futures/v3/instruments/%s/candles?start=%s&granularity=%d" - contractId := ok.getFutureContractId(currency, contract_type) + contractId := ok.GetFutureContractId(currency, contract_type) sinceTime := time.Unix(int64(since), 0).UTC() if since/int(time.Second) != 1 { //如果不为秒,转为秒 sinceTime = time.Unix(int64(since)/int64(time.Second), 0).UTC() } - granularity := 60 - switch period { - case KLINE_PERIOD_1MIN: - granularity = 60 - case KLINE_PERIOD_3MIN: - granularity = 180 - case KLINE_PERIOD_5MIN: - granularity = 300 - case KLINE_PERIOD_15MIN: - granularity = 900 - case KLINE_PERIOD_30MIN: - granularity = 1800 - case KLINE_PERIOD_1H, KLINE_PERIOD_60MIN: - granularity = 3600 - case KLINE_PERIOD_2H: - granularity = 7200 - case KLINE_PERIOD_4H: - granularity = 14400 - case KLINE_PERIOD_6H: - granularity = 21600 - case KLINE_PERIOD_1DAY: - granularity = 86400 - case KLINE_PERIOD_1WEEK: - granularity = 604800 - default: - granularity = 1800 + granularity := adaptKLinePeriod(KlinePeriod(period)) + if granularity == -1 { + return nil, errors.New("kline period parameter is error") } var response [][]interface{} @@ -684,7 +670,7 @@ func (ok *OKExFuture) MarketCloseAllPosition(currency CurrencyPair, contract str Direction string `json:"direction"` } - param.InstrumentId = ok.getFutureContractId(currency, contract) + param.InstrumentId = ok.GetFutureContractId(currency, contract) if oType == CLOSE_BUY { param.Direction = "long" } else { diff --git a/okex/OKExFuturesWs.go b/okex/OKExFuturesWs.go new file mode 100644 index 00000000..12942935 --- /dev/null +++ b/okex/OKExFuturesWs.go @@ -0,0 +1,481 @@ +package okex + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/nntaoli-project/goex/internal/logger" + "sort" + "strconv" + "strings" + "sync" + "time" + + . "github.com/nntaoli-project/goex" +) + +type wsResp struct { + Event string `json:"event"` + Channel string `json:"channel"` + Table string `json:"table"` + Data json.RawMessage + Success bool `json:"success"` + ErrorCode interface{} `json:"errorCode"` +} + +type OKExV3FutureWs struct { + *OKEx + *WsBuilder + sync.Once + wsConn *WsConn + loginCh chan wsResp + logined bool + loginLock *sync.Mutex + authoriedSubs []map[string]interface{} + + tickerCallback func(*FutureTicker) + depthCallback func(*Depth) + tradeCallback func(*Trade, string) + klineCallback func(*FutureKline, int) + orderCallback func(*FutureOrder, string) +} + +func NewOKExV3FuturesWs() *OKExV3FutureWs { + okV3Ws := &OKExV3FutureWs{} + okV3Ws.loginCh = make(chan wsResp) + okV3Ws.logined = false + okV3Ws.loginLock = &sync.Mutex{} + okV3Ws.authoriedSubs = make([]map[string]interface{}, 0) + okV3Ws.WsBuilder = NewWsBuilder(). + WsUrl("wss://real.okex.com:8443/ws/v3"). + ReconnectInterval(2*time.Second). + AutoReconnect(). + Heartbeat(func() []byte { + return []byte("ping") + }, 28*time.Second). + UnCompressFunc(FlateUnCompress). + ProtoHandleFunc(okV3Ws.handle) + return okV3Ws +} + +func (okV3Ws *OKExV3FutureWs) getSign(timestamp, method, url, body string) (string, error) { + data := timestamp + method + url + body + return GetParamHmacSHA256Base64Sign(okV3Ws.config.ApiSecretKey, data) +} + +func (okV3Ws *OKExV3FutureWs) TickerCallback(tickerCallback func(*FutureTicker)) *OKExV3FutureWs { + okV3Ws.tickerCallback = tickerCallback + return okV3Ws +} + +func (okV3Ws *OKExV3FutureWs) DepthCallback(depthCallback func(*Depth)) *OKExV3FutureWs { + okV3Ws.depthCallback = depthCallback + return okV3Ws +} + +func (okV3Ws *OKExV3FutureWs) TradeCallback(tradeCallback func(*Trade, string)) *OKExV3FutureWs { + okV3Ws.tradeCallback = tradeCallback + return okV3Ws +} + +func (okV3Ws *OKExV3FutureWs) OrderCallback(orderCallback func(*FutureOrder, string)) *OKExV3FutureWs { + okV3Ws.orderCallback = orderCallback + return okV3Ws +} + +func (okV3Ws *OKExV3FutureWs) SetCallbacks(tickerCallback func(*FutureTicker), + depthCallback func(*Depth), + tradeCallback func(*Trade, string), + klineCallback func(*FutureKline, int), + orderCallback func(*FutureOrder, string)) { + okV3Ws.tickerCallback = tickerCallback + okV3Ws.depthCallback = depthCallback + okV3Ws.tradeCallback = tradeCallback + okV3Ws.klineCallback = klineCallback + okV3Ws.orderCallback = orderCallback +} + +func (okV3Ws *OKExV3FutureWs) Login() error { + // already logined + if okV3Ws.logined { + return nil + } + okV3Ws.connectWs() + err := okV3Ws.login() + if err == nil { + okV3Ws.logined = true + } + return err +} + +func (okV3Ws *OKExV3FutureWs) getTimestamp() string { + seconds := float64(time.Now().UTC().UnixNano()) / float64(time.Second) + return fmt.Sprintf("%.3f", seconds) +} + +func (okV3Ws *OKExV3FutureWs) clearChan(c chan wsResp) { + for { + if len(c) > 0 { + <-c + } else { + break + } + } +} + +func (okV3Ws *OKExV3FutureWs) login() error { + okV3Ws.loginLock.Lock() + defer okV3Ws.loginLock.Unlock() + + okV3Ws.clearChan(okV3Ws.loginCh) + timestamp := okV3Ws.getTimestamp() + method := "GET" + url := "/users/self/verify" + sign, _ := okV3Ws.getSign(timestamp, method, url, "") + op := map[string]interface{}{ + "op": "login", + "args": []string{okV3Ws.config.ApiKey, okV3Ws.config.ApiPassphrase, timestamp, sign}} + err := okV3Ws.wsConn.SendJsonMessage(op) + if err != nil { + logger.Error(err) + return err + } + + re := <-okV3Ws.loginCh + + if !re.Success { + return fmt.Errorf("login failed: %v", re) + } + + logger.Info("ws login success") + return nil +} + +func (okV3Ws *OKExV3FutureWs) subscribe(sub map[string]interface{}) error { + okV3Ws.connectWs() + return okV3Ws.wsConn.Subscribe(sub) +} + +func (okV3Ws *OKExV3FutureWs) getTablePrefix(currencyPair CurrencyPair, contractType string) string { + if contractType == SWAP_CONTRACT { + return "swap" + } + return "futures" +} + +func (okV3Ws *OKExV3FutureWs) SubscribeDepth(currencyPair CurrencyPair, contractType string, size int) error { + if (size > 0) && (size != 5) { + return errors.New("only support depth 5") + } + if okV3Ws.depthCallback == nil { + return errors.New("please set depth callback func") + } + + contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) + chName := fmt.Sprintf("%s/depth5:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) + + return okV3Ws.subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{chName}}) +} + +func (okV3Ws *OKExV3FutureWs) SubscribeTicker(currencyPair CurrencyPair, contractType string) error { + if okV3Ws.tickerCallback == nil { + return errors.New("please set ticker callback func") + } + + contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) + chName := fmt.Sprintf("%s/ticker:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) + + return okV3Ws.subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{chName}}) +} + +func (okV3Ws *OKExV3FutureWs) SubscribeTrade(currencyPair CurrencyPair, contractType string) error { + if okV3Ws.tradeCallback == nil { + return errors.New("please set trade callback func") + } + + contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) + chName := fmt.Sprintf("%s/trade:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) + + return okV3Ws.subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{chName}}) +} + +func (okV3Ws *OKExV3FutureWs) SubscribeKline(currencyPair CurrencyPair, contractType string, period int) error { + if okV3Ws.klineCallback == nil { + return errors.New("place set kline callback func") + } + + contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) + seconds := adaptKLinePeriod(KlinePeriod(period)) + if seconds == -1 { + return fmt.Errorf("unsupported kline period %d in okex", period) + } + + chName := fmt.Sprintf("%s/candle%ds:%s", okV3Ws.getTablePrefix(currencyPair, contractType), seconds, contractId) + + return okV3Ws.subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{chName}}) +} + +func (okV3Ws *OKExV3FutureWs) SubscribeOrder(currencyPair CurrencyPair, contractType string) error { + if okV3Ws.orderCallback == nil { + return errors.New("place set order callback func") + } + + contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) + chName := fmt.Sprintf("%s/order:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) + + return okV3Ws.authoriedSubscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{chName}}) +} + +func (okV3Ws *OKExV3FutureWs) authoriedSubscribe(data map[string]interface{}) error { + okV3Ws.authoriedSubs = append(okV3Ws.authoriedSubs, data) + return okV3Ws.subscribe(data) +} + +func (okV3Ws *OKExV3FutureWs) reSubscribeAuthoriedChannel() { + for _, d := range okV3Ws.authoriedSubs { + okV3Ws.wsConn.SendJsonMessage(d) + } +} + +func (okV3Ws *OKExV3FutureWs) connectWs() { + okV3Ws.Do(func() { + okV3Ws.wsConn = okV3Ws.WsBuilder.Build() + }) +} + +func (okV3Ws *OKExV3FutureWs) handle(msg []byte) error { + logger.Debug("[ws] [response] ", string(msg)) + if string(msg) == "pong" { + return nil + } + + var wsResp wsResp + err := json.Unmarshal(msg, &wsResp) + if err != nil { + return err + } + + if wsResp.ErrorCode != nil { + logger.Error(string(msg)) + return fmt.Errorf("%s", string(msg)) + } + + if wsResp.Event != "" { + switch wsResp.Event { + case "subscribe": + logger.Info("subscribed:", wsResp.Channel) + return nil + case "login": + select { + case okV3Ws.loginCh <- wsResp: + return nil + default: + return nil + } + case "error": + var errorCode int + switch v := wsResp.ErrorCode.(type) { + case int: + errorCode = v + case float64: + errorCode = int(v) // float64 okex牛逼嗷 + case string: + i, _ := strconv.ParseInt(v, 10, 64) + errorCode = int(i) + } + + switch errorCode { + // event:error message:Already logged in errorCode:30042 + case 30041: + if okV3Ws.logined { // have logined successfully + go func() { + okV3Ws.login() + okV3Ws.reSubscribeAuthoriedChannel() + }() + } // else skip, or better hanle? + return nil + case 30042: + return nil + } + + // TODO: clearfy which errors should be treated as login result. + select { + case okV3Ws.loginCh <- wsResp: + return nil + default: + return fmt.Errorf("error in websocket: %v", wsResp) + } + } + return fmt.Errorf("unknown websocet message: %v", wsResp) + } + + if wsResp.Table != "" { + ch, err := okV3Ws.parseChannel(wsResp.Table) + if err != nil { + return err + } + + switch ch { + case "ticker": + var tickers []tickerResponse + err = json.Unmarshal(wsResp.Data, &tickers) + if err != nil { + return err + } + + for _, t := range tickers { + contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(t.InstrumentId) + if err != nil { + logger.Warn(t.InstrumentId, " contract id error , ", err) + continue + } + date, _ := time.Parse(time.RFC3339, t.Timestamp) + okV3Ws.tickerCallback(&FutureTicker{ + Ticker: &Ticker{ + Pair: NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)), + Last: t.Last, + Buy: t.BestBid, + Sell: t.BestAsk, + High: t.High24h, + Low: t.Low24h, + Vol: t.Volume24h, + Date: uint64(date.UnixNano() / int64(time.Millisecond)), + }, + ContractType: contractInfo.Alias, + }) + } + case "depth5": + var ( + depthResp depthResponse + dep Depth + ) + err := json.Unmarshal(wsResp.Data, &depthResp) + if err != nil { + return err + } + contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(depthResp.InstrumentId) + if err != nil { + logger.Warn("") + return err + } + dep.Pair = NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)) + dep.ContractType = contractInfo.Alias + dep.UTime, _ = time.Parse(time.RFC3339, depthResp.Timestamp) + for _, itm := range depthResp.Asks { + dep.AskList = append(dep.AskList, DepthRecord{ + Price: ToFloat64(itm[0]), + Amount: ToFloat64(itm[1])}) + } + for _, itm := range depthResp.Bids { + dep.BidList = append(dep.BidList, DepthRecord{ + Price: ToFloat64(itm[0]), + Amount: ToFloat64(itm[1])}) + } + sort.Sort(sort.Reverse(dep.AskList)) + //call back func + okV3Ws.depthCallback(&dep) + case "trade": + var ( + tradeResponse []struct { + Side string `json:"side"` + TradeId int64 `json:"trade_id,string"` + Price float64 `json:"price,string"` + Qty float64 `json:"qty,string"` + InstrumentId string `json:"instrument_id"` + Timestamp string `json:"timestamp"` + } + ) + err := json.Unmarshal(wsResp.Data, &tradeResponse) + if err != nil { + logger.Error("unmarshal error :", err) + return err + } + + for _, resp := range tradeResponse { + contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(resp.InstrumentId) + if err != nil { + return err + } + + tradeSide := SELL + switch resp.Side { + case "buy": + tradeSide = BUY + } + + t, err := time.Parse(time.RFC3339, resp.Timestamp) + if err != nil { + logger.Warn("parse timestamp error:", err) + } + + okV3Ws.tradeCallback(&Trade{ + Tid: resp.TradeId, + Type: tradeSide, + Amount: resp.Qty, + Price: resp.Price, + Date: t.Unix(), + Pair: NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)), + }, contractInfo.Alias) + } + case "order": + //2020/03/18 18:05:00 OKExFuturesWs.go:257: [D] [ws] [response] {"table":"futures/order","data":[{"leverage":"20","last_fill_time":"2020-03-18T10:05:00.790Z","filled_qty":"4","fee":"-0.00010655","price_avg":"112.62","type":"1","client_oid":"ce1661e5cb614fd690d0463de7a2eeb0","last_fill_qty":"4","instrument_id":"BSV-USD-200327","last_fill_px":"112.62","pnl":"0","size":"4","price":"112.73","last_fill_id":"15229749","error_code":"0","state":"2","contract_val":"10","order_id":"4573750935784449","order_type":"0","timestamp":"2020-03-18T10:05:00.790Z","status":"2"}]} + var orderResp []futureOrderResponse + err := json.Unmarshal(wsResp.Data, &orderResp) + if err != nil { + return err + } + for _, o := range orderResp { + contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(o.InstrumentId) + if err != nil { + logger.Warn("get contract info error , instrument id:", o.InstrumentId) + continue + } + okV3Ws.orderCallback(&FutureOrder{ + ClientOid: o.ClientOid, + OrderID2: o.OrderId, + Price: o.Price, + Amount: o.Size, + AvgPrice: o.PriceAvg, + DealAmount: o.FilledQty, + Status: okV3Ws.adaptOrderState(o.State), + Currency: CurrencyPair{}, + OrderType: o.OrderType, + OType: o.Type, + LeverRate: o.Leverage, + Fee: o.Fee, + ContractName: o.InstrumentId, + OrderTime: o.Timestamp.UnixNano() / int64(time.Millisecond), + }, contractInfo.Alias) + } + } + } + + return fmt.Errorf("unknown websocet message: %v", wsResp) +} + +func (okV3Ws *OKExV3FutureWs) parseChannel(channel string) (string, error) { + metas := strings.Split(channel, "/") + if len(metas) != 2 { + return "", fmt.Errorf("unknown channel: %s", channel) + } + return metas[1], nil +} + +func (okV3Ws *OKExV3FutureWs) getKlinePeriodFormChannel(channel string) int { + metas := strings.Split(channel, ":") + if len(metas) != 2 { + return 0 + } + i, _ := strconv.ParseInt(metas[1], 10, 64) + return int(i) +} diff --git a/okex/OKExFuturesWs_test.go b/okex/OKExFuturesWs_test.go new file mode 100644 index 00000000..1f8ce4f0 --- /dev/null +++ b/okex/OKExFuturesWs_test.go @@ -0,0 +1,57 @@ +package okex + +import ( + "context" + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "net" + "net/http" + "net/url" + "os" + "testing" + "time" +) + +var ( + client *http.Client +) + +func init() { + logger.SetLevel(logger.DEBUG) + client = &http.Client{ + Transport: &http.Transport{Proxy: func(request *http.Request) (*url.URL, error) { + return url.Parse("socks5://127.0.0.1:1080") + }, + DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) { + conn, e = net.DialTimeout(network, addr, 5*time.Second) + return conn, e + }, + }, + Timeout: 10 * time.Second, + } +} + +func TestNewOKExV3FuturesWs(t *testing.T) { + os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:1080") + ok := NewOKEx(&goex.APIConfig{ + HttpClient: http.DefaultClient, + }) + ok.OKExV3FutureWs.TickerCallback(func(ticker *goex.FutureTicker) { + t.Log(ticker.Ticker, ticker.ContractType) + }) + ok.OKExV3FutureWs.DepthCallback(func(depth *goex.Depth) { + t.Log(depth) + }) + ok.OKExV3FutureWs.TradeCallback(func(trade *goex.Trade, s string) { + t.Log(s, trade) + }) + ok.OKExV3FutureWs.OrderCallback(func(order *goex.FutureOrder, s string) { + t.Log(s, order) + }) + //ok.OKExV3FutureWs.Login() + ok.OKExV3FutureWs.SubscribeTicker(goex.EOS_USD, goex.QUARTER_CONTRACT) + ok.OKExV3FutureWs.SubscribeDepth(goex.EOS_USD, goex.QUARTER_CONTRACT, 5) + ok.OKExV3FutureWs.SubscribeTrade(goex.EOS_USD, goex.QUARTER_CONTRACT) + //ok.OKExV3FutureWs.SubscribeOrder(goex.BSV_USD, goex.NEXT_WEEK_CONTRACT) + time.Sleep(5 * time.Minute) +} diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index d7ee6af9..2b7f989e 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -434,8 +434,6 @@ func (ok *OKExSwap) GetFuturePosition(currencyPair CurrencyPair, contractType st positions[0].SellProfitReal = sellPosition.RealizedPnl positions[0].SellPriceCost = sellPosition.SettlementPrice } - - //log.Println(resp) return positions, nil } diff --git a/okex/OKExV3Utils.go b/okex/OKExV3Utils.go new file mode 100644 index 00000000..90d385ee --- /dev/null +++ b/okex/OKExV3Utils.go @@ -0,0 +1,45 @@ +package okex + +import "time" + +// +import ( + . "github.com/nntaoli-project/goex" +) + +func adaptKLinePeriod(period KlinePeriod) int { + granularity := -1 + switch period { + case KLINE_PERIOD_1MIN: + granularity = 60 + case KLINE_PERIOD_3MIN: + granularity = 180 + case KLINE_PERIOD_5MIN: + granularity = 300 + case KLINE_PERIOD_15MIN: + granularity = 900 + case KLINE_PERIOD_30MIN: + granularity = 1800 + case KLINE_PERIOD_1H, KLINE_PERIOD_60MIN: + granularity = 3600 + case KLINE_PERIOD_2H: + granularity = 7200 + case KLINE_PERIOD_4H: + granularity = 14400 + case KLINE_PERIOD_6H: + granularity = 21600 + case KLINE_PERIOD_1DAY: + granularity = 86400 + case KLINE_PERIOD_1WEEK: + granularity = 604800 + } + return granularity +} + +func timeStringToInt64(t string) (int64, error) { + timestamp, err := time.Parse(time.RFC3339, t) + if err != nil { + return 0, err + } + return timestamp.UnixNano() / int64(time.Millisecond), nil +} From d33a9c6a2f400f69aba5e700fef7396a00700a4c Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Wed, 18 Mar 2020 18:34:35 +0800 Subject: [PATCH 10/29] [api builder] delete error code --- builder/APIBuilder.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/builder/APIBuilder.go b/builder/APIBuilder.go index 305509dd..fe582a45 100644 --- a/builder/APIBuilder.go +++ b/builder/APIBuilder.go @@ -24,7 +24,6 @@ import ( "github.com/nntaoli-project/goex/hitbtc" "github.com/nntaoli-project/goex/huobi" "github.com/nntaoli-project/goex/kraken" - "github.com/nntaoli-project/goex/okcoin" "github.com/nntaoli-project/goex/okex" "github.com/nntaoli-project/goex/poloniex" "github.com/nntaoli-project/goex/zb" @@ -197,8 +196,8 @@ func (builder *APIBuilder) Build(exName string) (api API) { // _api = okcoin.New(builder.client, builder.apiKey, builder.secretkey) case POLONIEX: _api = poloniex.New(builder.client, builder.apiKey, builder.secretkey) - case OKCOIN_COM: - _api = okcoin.NewCOM(builder.client, builder.apiKey, builder.secretkey) + //case OKCOIN_COM: + // _api = okcoin.NewCOM(builder.client, builder.apiKey, builder.secretkey) case BITSTAMP: _api = bitstamp.NewBitstamp(builder.client, builder.apiKey, builder.secretkey, builder.clientId) case HUOBI_PRO: @@ -208,9 +207,7 @@ func (builder *APIBuilder) Build(exName string) (api API) { Endpoint: builder.endPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey}) - case OKEX: - _api = okcoin.NewOKExSpot(builder.client, builder.apiKey, builder.secretkey) - case OKEX_V3: + case OKEX_V3, OKEX: _api = okex.NewOKEx(&APIConfig{ HttpClient: builder.client, ApiKey: builder.apiKey, From 12695914d262539720c05e8631f61e6a75ad9f73 Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Thu, 19 Mar 2020 16:02:37 +0800 Subject: [PATCH 11/29] [okex] ws v3 api ,support futures ,swap, usdt contract --- okex/OKEx.go | 5 +- okex/OKExFuturesWs.go | 533 ++++++++++++------------------------- okex/OKExFuturesWs_test.go | 9 +- okex/OKExWs.go | 222 +++++++++++++++ 4 files changed, 405 insertions(+), 364 deletions(-) create mode 100644 okex/OKExWs.go diff --git a/okex/OKEx.go b/okex/OKEx.go index 45aaca5b..96b6000f 100644 --- a/okex/OKEx.go +++ b/okex/OKEx.go @@ -22,7 +22,7 @@ type OKEx struct { OKExSwap *OKExSwap OKExWallet *OKExWallet OKExMargin *OKExMargin - OKExV3FutureWs *OKExV3FutureWs + OKExV3FutureWs *OKExV3FuturesWs } func NewOKEx(config *APIConfig) *OKEx { @@ -35,8 +35,7 @@ func NewOKEx(config *APIConfig) *OKEx { okex.OKExWallet = &OKExWallet{okex} okex.OKExMargin = &OKExMargin{okex} okex.OKExSwap = &OKExSwap{okex, config} - okex.OKExV3FutureWs = NewOKExV3FuturesWs() - okex.OKExV3FutureWs.OKEx = okex + okex.OKExV3FutureWs = NewOKExV3FuturesWs(okex) return okex } diff --git a/okex/OKExFuturesWs.go b/okex/OKExFuturesWs.go index 12942935..9064444d 100644 --- a/okex/OKExFuturesWs.go +++ b/okex/OKExFuturesWs.go @@ -4,35 +4,17 @@ import ( "encoding/json" "errors" "fmt" + . "github.com/nntaoli-project/goex" "github.com/nntaoli-project/goex/internal/logger" "sort" "strconv" "strings" - "sync" "time" - - . "github.com/nntaoli-project/goex" ) -type wsResp struct { - Event string `json:"event"` - Channel string `json:"channel"` - Table string `json:"table"` - Data json.RawMessage - Success bool `json:"success"` - ErrorCode interface{} `json:"errorCode"` -} - -type OKExV3FutureWs struct { - *OKEx - *WsBuilder - sync.Once - wsConn *WsConn - loginCh chan wsResp - logined bool - loginLock *sync.Mutex - authoriedSubs []map[string]interface{} - +type OKExV3FuturesWs struct { + base *OKEx + v3Ws *OKExV3Ws tickerCallback func(*FutureTicker) depthCallback func(*Depth) tradeCallback func(*Trade, string) @@ -40,50 +22,35 @@ type OKExV3FutureWs struct { orderCallback func(*FutureOrder, string) } -func NewOKExV3FuturesWs() *OKExV3FutureWs { - okV3Ws := &OKExV3FutureWs{} - okV3Ws.loginCh = make(chan wsResp) - okV3Ws.logined = false - okV3Ws.loginLock = &sync.Mutex{} - okV3Ws.authoriedSubs = make([]map[string]interface{}, 0) - okV3Ws.WsBuilder = NewWsBuilder(). - WsUrl("wss://real.okex.com:8443/ws/v3"). - ReconnectInterval(2*time.Second). - AutoReconnect(). - Heartbeat(func() []byte { - return []byte("ping") - }, 28*time.Second). - UnCompressFunc(FlateUnCompress). - ProtoHandleFunc(okV3Ws.handle) +func NewOKExV3FuturesWs(base *OKEx) *OKExV3FuturesWs { + okV3Ws := &OKExV3FuturesWs{ + base: base, + } + okV3Ws.v3Ws = NewOKExV3Ws(base, okV3Ws.handle) return okV3Ws } -func (okV3Ws *OKExV3FutureWs) getSign(timestamp, method, url, body string) (string, error) { - data := timestamp + method + url + body - return GetParamHmacSHA256Base64Sign(okV3Ws.config.ApiSecretKey, data) -} - -func (okV3Ws *OKExV3FutureWs) TickerCallback(tickerCallback func(*FutureTicker)) *OKExV3FutureWs { +func (okV3Ws *OKExV3FuturesWs) TickerCallback(tickerCallback func(*FutureTicker)) *OKExV3FuturesWs { okV3Ws.tickerCallback = tickerCallback return okV3Ws } -func (okV3Ws *OKExV3FutureWs) DepthCallback(depthCallback func(*Depth)) *OKExV3FutureWs { +func (okV3Ws *OKExV3FuturesWs) DepthCallback(depthCallback func(*Depth)) *OKExV3FuturesWs { okV3Ws.depthCallback = depthCallback return okV3Ws } -func (okV3Ws *OKExV3FutureWs) TradeCallback(tradeCallback func(*Trade, string)) *OKExV3FutureWs { +func (okV3Ws *OKExV3FuturesWs) TradeCallback(tradeCallback func(*Trade, string)) *OKExV3FuturesWs { okV3Ws.tradeCallback = tradeCallback return okV3Ws } -func (okV3Ws *OKExV3FutureWs) OrderCallback(orderCallback func(*FutureOrder, string)) *OKExV3FutureWs { +func (okV3Ws *OKExV3FuturesWs) OrderCallback(orderCallback func(*FutureOrder, string)) *OKExV3FuturesWs { okV3Ws.orderCallback = orderCallback return okV3Ws } -func (okV3Ws *OKExV3FutureWs) SetCallbacks(tickerCallback func(*FutureTicker), +func (okV3Ws *OKExV3FuturesWs) SetCallbacks(tickerCallback func(*FutureTicker), depthCallback func(*Depth), tradeCallback func(*Trade, string), klineCallback func(*FutureKline, int), @@ -95,383 +62,237 @@ func (okV3Ws *OKExV3FutureWs) SetCallbacks(tickerCallback func(*FutureTicker), okV3Ws.orderCallback = orderCallback } -func (okV3Ws *OKExV3FutureWs) Login() error { - // already logined - if okV3Ws.logined { - return nil - } - okV3Ws.connectWs() - err := okV3Ws.login() - if err == nil { - okV3Ws.logined = true - } - return err -} - -func (okV3Ws *OKExV3FutureWs) getTimestamp() string { - seconds := float64(time.Now().UTC().UnixNano()) / float64(time.Second) - return fmt.Sprintf("%.3f", seconds) -} +func (okV3Ws *OKExV3FuturesWs) getChannelName(currencyPair CurrencyPair, contractType string) string { + var ( + prefix string + contractId string + channelName string + ) -func (okV3Ws *OKExV3FutureWs) clearChan(c chan wsResp) { - for { - if len(c) > 0 { - <-c - } else { - break - } - } -} - -func (okV3Ws *OKExV3FutureWs) login() error { - okV3Ws.loginLock.Lock() - defer okV3Ws.loginLock.Unlock() - - okV3Ws.clearChan(okV3Ws.loginCh) - timestamp := okV3Ws.getTimestamp() - method := "GET" - url := "/users/self/verify" - sign, _ := okV3Ws.getSign(timestamp, method, url, "") - op := map[string]interface{}{ - "op": "login", - "args": []string{okV3Ws.config.ApiKey, okV3Ws.config.ApiPassphrase, timestamp, sign}} - err := okV3Ws.wsConn.SendJsonMessage(op) - if err != nil { - logger.Error(err) - return err + if contractType == SWAP_CONTRACT { + prefix = "swap" + contractId = fmt.Sprintf("%s-SWAP", currencyPair.ToSymbol("-")) + } else { + prefix = "futures" + contractId = okV3Ws.base.OKExFuture.GetFutureContractId(currencyPair, contractType) } - re := <-okV3Ws.loginCh + channelName = prefix + "/%s:" + contractId - if !re.Success { - return fmt.Errorf("login failed: %v", re) - } - - logger.Info("ws login success") - return nil + return channelName } -func (okV3Ws *OKExV3FutureWs) subscribe(sub map[string]interface{}) error { - okV3Ws.connectWs() - return okV3Ws.wsConn.Subscribe(sub) -} - -func (okV3Ws *OKExV3FutureWs) getTablePrefix(currencyPair CurrencyPair, contractType string) string { - if contractType == SWAP_CONTRACT { - return "swap" - } - return "futures" -} - -func (okV3Ws *OKExV3FutureWs) SubscribeDepth(currencyPair CurrencyPair, contractType string, size int) error { +func (okV3Ws *OKExV3FuturesWs) SubscribeDepth(currencyPair CurrencyPair, contractType string, size int) error { if (size > 0) && (size != 5) { return errors.New("only support depth 5") } + if okV3Ws.depthCallback == nil { return errors.New("please set depth callback func") } - contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) - chName := fmt.Sprintf("%s/depth5:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) + chName := okV3Ws.getChannelName(currencyPair, contractType) - return okV3Ws.subscribe(map[string]interface{}{ + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ "op": "subscribe", - "args": []string{chName}}) + "args": []string{fmt.Sprintf(chName, "depth5")}}) } -func (okV3Ws *OKExV3FutureWs) SubscribeTicker(currencyPair CurrencyPair, contractType string) error { +func (okV3Ws *OKExV3FuturesWs) SubscribeTicker(currencyPair CurrencyPair, contractType string) error { if okV3Ws.tickerCallback == nil { return errors.New("please set ticker callback func") } - - contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) - chName := fmt.Sprintf("%s/ticker:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) - - return okV3Ws.subscribe(map[string]interface{}{ + chName := okV3Ws.getChannelName(currencyPair, contractType) + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ "op": "subscribe", - "args": []string{chName}}) + "args": []string{fmt.Sprintf(chName, "ticker")}}) } -func (okV3Ws *OKExV3FutureWs) SubscribeTrade(currencyPair CurrencyPair, contractType string) error { +func (okV3Ws *OKExV3FuturesWs) SubscribeTrade(currencyPair CurrencyPair, contractType string) error { if okV3Ws.tradeCallback == nil { return errors.New("please set trade callback func") } - - contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) - chName := fmt.Sprintf("%s/trade:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) - - return okV3Ws.subscribe(map[string]interface{}{ + chName := okV3Ws.getChannelName(currencyPair, contractType) + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ "op": "subscribe", - "args": []string{chName}}) + "args": []string{fmt.Sprintf(chName, "trade")}}) } -func (okV3Ws *OKExV3FutureWs) SubscribeKline(currencyPair CurrencyPair, contractType string, period int) error { +func (okV3Ws *OKExV3FuturesWs) SubscribeKline(currencyPair CurrencyPair, contractType string, period int) error { if okV3Ws.klineCallback == nil { return errors.New("place set kline callback func") } - contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) seconds := adaptKLinePeriod(KlinePeriod(period)) if seconds == -1 { return fmt.Errorf("unsupported kline period %d in okex", period) } - chName := fmt.Sprintf("%s/candle%ds:%s", okV3Ws.getTablePrefix(currencyPair, contractType), seconds, contractId) - - return okV3Ws.subscribe(map[string]interface{}{ + chName := okV3Ws.getChannelName(currencyPair, contractType) + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ "op": "subscribe", - "args": []string{chName}}) + "args": []string{fmt.Sprintf(chName, fmt.Sprintf("candle%ds", seconds))}}) } -func (okV3Ws *OKExV3FutureWs) SubscribeOrder(currencyPair CurrencyPair, contractType string) error { +func (okV3Ws *OKExV3FuturesWs) SubscribeOrder(currencyPair CurrencyPair, contractType string) error { if okV3Ws.orderCallback == nil { return errors.New("place set order callback func") } - - contractId := okV3Ws.OKExFuture.GetFutureContractId(currencyPair, contractType) - chName := fmt.Sprintf("%s/order:%s", okV3Ws.getTablePrefix(currencyPair, contractType), contractId) - - return okV3Ws.authoriedSubscribe(map[string]interface{}{ + okV3Ws.v3Ws.Login() + chName := okV3Ws.getChannelName(currencyPair, contractType) + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ "op": "subscribe", - "args": []string{chName}}) -} - -func (okV3Ws *OKExV3FutureWs) authoriedSubscribe(data map[string]interface{}) error { - okV3Ws.authoriedSubs = append(okV3Ws.authoriedSubs, data) - return okV3Ws.subscribe(data) + "args": []string{fmt.Sprintf(chName, "order")}}) } -func (okV3Ws *OKExV3FutureWs) reSubscribeAuthoriedChannel() { - for _, d := range okV3Ws.authoriedSubs { - okV3Ws.wsConn.SendJsonMessage(d) +func (okV3Ws *OKExV3FuturesWs) getContractAliasAndCurrencyPairFromInstrumentId(instrumentId string) (alias string, pair CurrencyPair) { + if strings.HasSuffix(instrumentId, "SWAP") { + ar := strings.Split(instrumentId, "-") + return instrumentId, NewCurrencyPair2(fmt.Sprintf("%s_%s", ar[0], ar[1])) + } else { + contractInfo, err := okV3Ws.base.OKExFuture.GetContractInfo(instrumentId) + if err != nil { + logger.Error("instrument id invalid:", err) + return "", UNKNOWN_PAIR + } + alias = contractInfo.Alias + pair = NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)) + return alias, pair } } -func (okV3Ws *OKExV3FutureWs) connectWs() { - okV3Ws.Do(func() { - okV3Ws.wsConn = okV3Ws.WsBuilder.Build() - }) -} - -func (okV3Ws *OKExV3FutureWs) handle(msg []byte) error { - logger.Debug("[ws] [response] ", string(msg)) - if string(msg) == "pong" { - return nil - } - - var wsResp wsResp - err := json.Unmarshal(msg, &wsResp) - if err != nil { - return err - } +func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { + var ( + err error + tickers []tickerResponse + depthResp []depthResponse + dep Depth + tradeResponse []struct { + Side string `json:"side"` + TradeId int64 `json:"trade_id,string"` + Price float64 `json:"price,string"` + Qty float64 `json:"qty,string"` + InstrumentId string `json:"instrument_id"` + Timestamp string `json:"timestamp"` + } + orderResp []futureOrderResponse + ) - if wsResp.ErrorCode != nil { - logger.Error(string(msg)) - return fmt.Errorf("%s", string(msg)) - } + switch ch { + case "ticker": + err = json.Unmarshal(data, &tickers) + if err != nil { + return err + } - if wsResp.Event != "" { - switch wsResp.Event { - case "subscribe": - logger.Info("subscribed:", wsResp.Channel) + for _, t := range tickers { + alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(t.InstrumentId) + date, _ := time.Parse(time.RFC3339, t.Timestamp) + okV3Ws.tickerCallback(&FutureTicker{ + Ticker: &Ticker{ + Pair: pair, + Last: t.Last, + Buy: t.BestBid, + Sell: t.BestAsk, + High: t.High24h, + Low: t.Low24h, + Vol: t.Volume24h, + Date: uint64(date.UnixNano() / int64(time.Millisecond)), + }, + ContractType: alias, + }) + } + return nil + case "depth5": + err := json.Unmarshal(data, &depthResp) + if err != nil { + logger.Error(err) + return err + } + if len(depthResp) == 0 { return nil - case "login": - select { - case okV3Ws.loginCh <- wsResp: - return nil - default: - return nil - } - case "error": - var errorCode int - switch v := wsResp.ErrorCode.(type) { - case int: - errorCode = v - case float64: - errorCode = int(v) // float64 okex牛逼嗷 - case string: - i, _ := strconv.ParseInt(v, 10, 64) - errorCode = int(i) - } - - switch errorCode { - // event:error message:Already logged in errorCode:30042 - case 30041: - if okV3Ws.logined { // have logined successfully - go func() { - okV3Ws.login() - okV3Ws.reSubscribeAuthoriedChannel() - }() - } // else skip, or better hanle? - return nil - case 30042: - return nil - } - - // TODO: clearfy which errors should be treated as login result. - select { - case okV3Ws.loginCh <- wsResp: - return nil - default: - return fmt.Errorf("error in websocket: %v", wsResp) - } } - return fmt.Errorf("unknown websocet message: %v", wsResp) - } - - if wsResp.Table != "" { - ch, err := okV3Ws.parseChannel(wsResp.Table) + alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(depthResp[0].InstrumentId) + dep.Pair = pair + dep.ContractType = alias + dep.UTime, _ = time.Parse(time.RFC3339, depthResp[0].Timestamp) + for _, itm := range depthResp[0].Asks { + dep.AskList = append(dep.AskList, DepthRecord{ + Price: ToFloat64(itm[0]), + Amount: ToFloat64(itm[1])}) + } + for _, itm := range depthResp[0].Bids { + dep.BidList = append(dep.BidList, DepthRecord{ + Price: ToFloat64(itm[0]), + Amount: ToFloat64(itm[1])}) + } + sort.Sort(sort.Reverse(dep.AskList)) + //call back func + okV3Ws.depthCallback(&dep) + return nil + case "trade": + err := json.Unmarshal(data, &tradeResponse) if err != nil { + logger.Error("unmarshal error :", err) return err } - switch ch { - case "ticker": - var tickers []tickerResponse - err = json.Unmarshal(wsResp.Data, &tickers) - if err != nil { - return err - } + for _, resp := range tradeResponse { + alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(resp.InstrumentId) - for _, t := range tickers { - contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(t.InstrumentId) - if err != nil { - logger.Warn(t.InstrumentId, " contract id error , ", err) - continue - } - date, _ := time.Parse(time.RFC3339, t.Timestamp) - okV3Ws.tickerCallback(&FutureTicker{ - Ticker: &Ticker{ - Pair: NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)), - Last: t.Last, - Buy: t.BestBid, - Sell: t.BestAsk, - High: t.High24h, - Low: t.Low24h, - Vol: t.Volume24h, - Date: uint64(date.UnixNano() / int64(time.Millisecond)), - }, - ContractType: contractInfo.Alias, - }) - } - case "depth5": - var ( - depthResp depthResponse - dep Depth - ) - err := json.Unmarshal(wsResp.Data, &depthResp) - if err != nil { - return err - } - contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(depthResp.InstrumentId) - if err != nil { - logger.Warn("") - return err - } - dep.Pair = NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)) - dep.ContractType = contractInfo.Alias - dep.UTime, _ = time.Parse(time.RFC3339, depthResp.Timestamp) - for _, itm := range depthResp.Asks { - dep.AskList = append(dep.AskList, DepthRecord{ - Price: ToFloat64(itm[0]), - Amount: ToFloat64(itm[1])}) - } - for _, itm := range depthResp.Bids { - dep.BidList = append(dep.BidList, DepthRecord{ - Price: ToFloat64(itm[0]), - Amount: ToFloat64(itm[1])}) - } - sort.Sort(sort.Reverse(dep.AskList)) - //call back func - okV3Ws.depthCallback(&dep) - case "trade": - var ( - tradeResponse []struct { - Side string `json:"side"` - TradeId int64 `json:"trade_id,string"` - Price float64 `json:"price,string"` - Qty float64 `json:"qty,string"` - InstrumentId string `json:"instrument_id"` - Timestamp string `json:"timestamp"` - } - ) - err := json.Unmarshal(wsResp.Data, &tradeResponse) - if err != nil { - logger.Error("unmarshal error :", err) - return err + tradeSide := SELL + switch resp.Side { + case "buy": + tradeSide = BUY } - for _, resp := range tradeResponse { - contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(resp.InstrumentId) - if err != nil { - return err - } - - tradeSide := SELL - switch resp.Side { - case "buy": - tradeSide = BUY - } - - t, err := time.Parse(time.RFC3339, resp.Timestamp) - if err != nil { - logger.Warn("parse timestamp error:", err) - } - - okV3Ws.tradeCallback(&Trade{ - Tid: resp.TradeId, - Type: tradeSide, - Amount: resp.Qty, - Price: resp.Price, - Date: t.Unix(), - Pair: NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)), - }, contractInfo.Alias) - } - case "order": - //2020/03/18 18:05:00 OKExFuturesWs.go:257: [D] [ws] [response] {"table":"futures/order","data":[{"leverage":"20","last_fill_time":"2020-03-18T10:05:00.790Z","filled_qty":"4","fee":"-0.00010655","price_avg":"112.62","type":"1","client_oid":"ce1661e5cb614fd690d0463de7a2eeb0","last_fill_qty":"4","instrument_id":"BSV-USD-200327","last_fill_px":"112.62","pnl":"0","size":"4","price":"112.73","last_fill_id":"15229749","error_code":"0","state":"2","contract_val":"10","order_id":"4573750935784449","order_type":"0","timestamp":"2020-03-18T10:05:00.790Z","status":"2"}]} - var orderResp []futureOrderResponse - err := json.Unmarshal(wsResp.Data, &orderResp) + t, err := time.Parse(time.RFC3339, resp.Timestamp) if err != nil { - return err - } - for _, o := range orderResp { - contractInfo, err := okV3Ws.OKExFuture.GetContractInfo(o.InstrumentId) - if err != nil { - logger.Warn("get contract info error , instrument id:", o.InstrumentId) - continue - } - okV3Ws.orderCallback(&FutureOrder{ - ClientOid: o.ClientOid, - OrderID2: o.OrderId, - Price: o.Price, - Amount: o.Size, - AvgPrice: o.PriceAvg, - DealAmount: o.FilledQty, - Status: okV3Ws.adaptOrderState(o.State), - Currency: CurrencyPair{}, - OrderType: o.OrderType, - OType: o.Type, - LeverRate: o.Leverage, - Fee: o.Fee, - ContractName: o.InstrumentId, - OrderTime: o.Timestamp.UnixNano() / int64(time.Millisecond), - }, contractInfo.Alias) + logger.Warn("parse timestamp error:", err) } + + okV3Ws.tradeCallback(&Trade{ + Tid: resp.TradeId, + Type: tradeSide, + Amount: resp.Qty, + Price: resp.Price, + Date: t.Unix(), + Pair: pair, + }, alias) + } + return nil + case "order": + //2020/03/18 18:05:00 OKExFuturesWs.go:257: [D] [ws] [response] {"table":"futures/order","data":[{"leverage":"20","last_fill_time":"2020-03-18T10:05:00.790Z","filled_qty":"4","fee":"-0.00010655","price_avg":"112.62","type":"1","client_oid":"ce1661e5cb614fd690d0463de7a2eeb0","last_fill_qty":"4","instrument_id":"BSV-USD-200327","last_fill_px":"112.62","pnl":"0","size":"4","price":"112.73","last_fill_id":"15229749","error_code":"0","state":"2","contract_val":"10","order_id":"4573750935784449","order_type":"0","timestamp":"2020-03-18T10:05:00.790Z","status":"2"}]} + err := json.Unmarshal(data, &orderResp) + if err != nil { + return err + } + for _, o := range orderResp { + alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(o.InstrumentId) + okV3Ws.orderCallback(&FutureOrder{ + ClientOid: o.ClientOid, + OrderID2: o.OrderId, + Price: o.Price, + Amount: o.Size, + AvgPrice: o.PriceAvg, + DealAmount: o.FilledQty, + Status: okV3Ws.base.adaptOrderState(o.State), + Currency: pair, + OrderType: o.OrderType, + OType: o.Type, + LeverRate: o.Leverage, + Fee: o.Fee, + ContractName: o.InstrumentId, + OrderTime: o.Timestamp.UnixNano() / int64(time.Millisecond), + }, alias) } + return nil } - return fmt.Errorf("unknown websocet message: %v", wsResp) -} - -func (okV3Ws *OKExV3FutureWs) parseChannel(channel string) (string, error) { - metas := strings.Split(channel, "/") - if len(metas) != 2 { - return "", fmt.Errorf("unknown channel: %s", channel) - } - return metas[1], nil + return fmt.Errorf("unknown websocet message: %s", string(data)) } -func (okV3Ws *OKExV3FutureWs) getKlinePeriodFormChannel(channel string) int { +func (okV3Ws *OKExV3FuturesWs) getKlinePeriodFormChannel(channel string) int { metas := strings.Split(channel, ":") if len(metas) != 2 { return 0 diff --git a/okex/OKExFuturesWs_test.go b/okex/OKExFuturesWs_test.go index 1f8ce4f0..81ea6b50 100644 --- a/okex/OKExFuturesWs_test.go +++ b/okex/OKExFuturesWs_test.go @@ -48,10 +48,9 @@ func TestNewOKExV3FuturesWs(t *testing.T) { ok.OKExV3FutureWs.OrderCallback(func(order *goex.FutureOrder, s string) { t.Log(s, order) }) - //ok.OKExV3FutureWs.Login() - ok.OKExV3FutureWs.SubscribeTicker(goex.EOS_USD, goex.QUARTER_CONTRACT) - ok.OKExV3FutureWs.SubscribeDepth(goex.EOS_USD, goex.QUARTER_CONTRACT, 5) - ok.OKExV3FutureWs.SubscribeTrade(goex.EOS_USD, goex.QUARTER_CONTRACT) + //ok.OKExV3FutureWs.SubscribeTicker(goex.EOS_USD, goex.QUARTER_CONTRACT) + ok.OKExV3FutureWs.SubscribeDepth(goex.EOS_USDT, goex.QUARTER_CONTRACT, 5) + //ok.OKExV3FutureWs.SubscribeTrade(goex.EOS_USD, goex.QUARTER_CONTRACT) //ok.OKExV3FutureWs.SubscribeOrder(goex.BSV_USD, goex.NEXT_WEEK_CONTRACT) - time.Sleep(5 * time.Minute) + time.Sleep(1 * time.Minute) } diff --git a/okex/OKExWs.go b/okex/OKExWs.go new file mode 100644 index 00000000..48c7e975 --- /dev/null +++ b/okex/OKExWs.go @@ -0,0 +1,222 @@ +package okex + +import ( + "encoding/json" + "fmt" + "github.com/nntaoli-project/goex/internal/logger" + "strconv" + "strings" + "sync" + "time" + + . "github.com/nntaoli-project/goex" +) + +type wsResp struct { + Event string `json:"event"` + Channel string `json:"channel"` + Table string `json:"table"` + Data json.RawMessage + Success bool `json:"success"` + ErrorCode interface{} `json:"errorCode"` +} + +type OKExV3Ws struct { + base *OKEx + *WsBuilder + once *sync.Once + wsConn *WsConn + respHandle func(channel string, data json.RawMessage) error + loginCh chan wsResp + isLogin bool + loginLock *sync.Mutex + authoriedSubs []map[string]interface{} +} + +func NewOKExV3Ws(base *OKEx, handle func(channel string, data json.RawMessage) error) *OKExV3Ws { + okV3Ws := &OKExV3Ws{ + once: new(sync.Once), + base: base, + respHandle: handle, + } + okV3Ws.loginCh = make(chan wsResp) + okV3Ws.loginLock = &sync.Mutex{} + okV3Ws.authoriedSubs = make([]map[string]interface{}, 0) + okV3Ws.WsBuilder = NewWsBuilder(). + WsUrl("wss://real.okex.com:8443/ws/v3"). + ReconnectInterval(2*time.Second). + AutoReconnect(). + Heartbeat(func() []byte { return []byte("ping") }, 28*time.Second). + UnCompressFunc(FlateUnCompress).ProtoHandleFunc(okV3Ws.handle) + return okV3Ws +} + +func (okV3Ws *OKExV3Ws) clearChan(c chan wsResp) { + for { + if len(c) > 0 { + <-c + } else { + break + } + } +} + +func (okV3Ws *OKExV3Ws) getTablePrefix(currencyPair CurrencyPair, contractType string) string { + if contractType == SWAP_CONTRACT { + return "swap" + } + return "futures" +} + +func (okV3Ws *OKExV3Ws) authoriedSubscribe(data map[string]interface{}) error { + okV3Ws.authoriedSubs = append(okV3Ws.authoriedSubs, data) + return okV3Ws.Subscribe(data) +} + +func (okV3Ws *OKExV3Ws) reSubscribeAuthoriedChannel() { + for _, d := range okV3Ws.authoriedSubs { + okV3Ws.wsConn.SendJsonMessage(d) + } +} + +func (okV3Ws *OKExV3Ws) connectWs() { + okV3Ws.once.Do(func() { + okV3Ws.wsConn = okV3Ws.WsBuilder.Build() + }) +} + +func (okV3Ws *OKExV3Ws) parseChannel(channel string) (string, error) { + metas := strings.Split(channel, "/") + if len(metas) != 2 { + return "", fmt.Errorf("unknown channel: %s", channel) + } + return metas[1], nil +} + +func (okV3Ws *OKExV3Ws) getKlinePeriodFormChannel(channel string) int { + metas := strings.Split(channel, ":") + if len(metas) != 2 { + return 0 + } + i, _ := strconv.ParseInt(metas[1], 10, 64) + return int(i) +} + +func (okV3Ws *OKExV3Ws) handle(msg []byte) error { + logger.Debug("[ws] [response] ", string(msg)) + if string(msg) == "pong" { + return nil + } + + var wsResp wsResp + err := json.Unmarshal(msg, &wsResp) + if err != nil { + logger.Error(err) + return err + } + + if wsResp.ErrorCode != nil { + logger.Error(string(msg)) + return fmt.Errorf("%s", string(msg)) + } + + if wsResp.Event != "" { + switch wsResp.Event { + case "subscribe": + logger.Info("subscribed:", wsResp.Channel) + return nil + case "login": + select { + case okV3Ws.loginCh <- wsResp: + return nil + default: + return nil + } + case "error": + var errorCode int + switch v := wsResp.ErrorCode.(type) { + case int: + errorCode = v + case float64: + errorCode = int(v) // float64 okex牛逼嗷 + case string: + i, _ := strconv.ParseInt(v, 10, 64) + errorCode = int(i) + } + + switch errorCode { + // event:error message:Already logged in errorCode:30042 + case 30041: + //TODO: + return nil + case 30042: + return nil + } + + // TODO: clearfy which errors should be treated as login result. + select { + case okV3Ws.loginCh <- wsResp: + return nil + default: + return fmt.Errorf("error in websocket: %v", wsResp) + } + } + return fmt.Errorf("unknown websocet message: %v", wsResp) + } + + if wsResp.Table != "" { + channel, err := okV3Ws.parseChannel(wsResp.Table) + if err != nil { + logger.Error("parse ws channel error:", err) + return err + } + err = okV3Ws.respHandle(channel, wsResp.Data) + if err != nil { + logger.Error("handle ws data error:", err) + } + return err + } + + return fmt.Errorf("unknown websocet message: %v", wsResp) +} + +func (okV3Ws *OKExV3Ws) Login() error { + // already logined + if okV3Ws.isLogin { + return nil + } + + okV3Ws.connectWs() + + okV3Ws.loginLock.Lock() + defer okV3Ws.loginLock.Unlock() + + if okV3Ws.isLogin { //double check + return nil + } + + okV3Ws.clearChan(okV3Ws.loginCh) + + sign, tm := okV3Ws.base.doParamSign("GET", "/users/self/verify", "") + op := map[string]interface{}{ + "op": "login", "args": []string{okV3Ws.base.config.ApiKey, okV3Ws.base.config.ApiPassphrase, tm, sign}} + err := okV3Ws.wsConn.SendJsonMessage(op) + if err != nil { + logger.Error("ws login error:", err) + return err + } + + //wait login response + re := <-okV3Ws.loginCh + if !re.Success { + return fmt.Errorf("login failed: %v", re) + } + logger.Info("ws login success") + okV3Ws.isLogin = true + return nil +} + +func (okV3Ws *OKExV3Ws) Subscribe(sub map[string]interface{}) error { + okV3Ws.connectWs() + return okV3Ws.wsConn.Subscribe(sub) +} From ac489019afd25180b39b91b20da2e3e724b0ed7e Mon Sep 17 00:00:00 2001 From: nntaoli <2767415655@qq.com> Date: Thu, 19 Mar 2020 17:48:08 +0800 Subject: [PATCH 12/29] [okex] spot v3 ws , close #146 --- okex/OKEx.go | 2 + okex/OKExSpot.go | 21 ++-- okex/OKExSpotWs.go | 264 ++++++++++++++++++++++++++++++++++++++++ okex/OKExSpotWs_test.go | 35 ++++++ okex/OKExV3Utils.go | 29 +++++ 5 files changed, 342 insertions(+), 9 deletions(-) create mode 100644 okex/OKExSpotWs.go create mode 100644 okex/OKExSpotWs_test.go diff --git a/okex/OKEx.go b/okex/OKEx.go index 96b6000f..c55c9a3a 100644 --- a/okex/OKEx.go +++ b/okex/OKEx.go @@ -23,6 +23,7 @@ type OKEx struct { OKExWallet *OKExWallet OKExMargin *OKExMargin OKExV3FutureWs *OKExV3FuturesWs + OKExV3SpotWs *OKExV3SpotWs } func NewOKEx(config *APIConfig) *OKEx { @@ -36,6 +37,7 @@ func NewOKEx(config *APIConfig) *OKEx { okex.OKExMargin = &OKExMargin{okex} okex.OKExSwap = &OKExSwap{okex, config} okex.OKExV3FutureWs = NewOKExV3FuturesWs(okex) + okex.OKExV3SpotWs = NewOKExSpotV3Ws(okex) return okex } diff --git a/okex/OKExSpot.go b/okex/OKExSpot.go index bc3ec25c..cd582aa7 100644 --- a/okex/OKExSpot.go +++ b/okex/OKExSpot.go @@ -319,17 +319,20 @@ func (ok *OKExSpot) GetExchangeName() string { return OKEX } +type spotTickerResponse struct { + InstrumentId string `json:"instrument_id"` + Last float64 `json:"last,string"` + High24h float64 `json:"high_24h,string"` + Low24h float64 `json:"low_24h,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + BaseVolume24h float64 `json:"base_volume_24h,string"` + Timestamp string `json:"timestamp"` +} + func (ok *OKExSpot) GetTicker(currency CurrencyPair) (*Ticker, error) { urlPath := fmt.Sprintf("/api/spot/v3/instruments/%s/ticker", currency.AdaptUsdToUsdt().ToSymbol("-")) - var response struct { - Last float64 `json:"last,string"` - High24h float64 `json:"high_24h,string"` - Low24h float64 `json:"low_24h,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - BaseVolume24h float64 `json:"base_volume_24_h,string"` - Timestamp string `json:"timestamp"` - } + var response spotTickerResponse err := ok.OKEx.DoRequest("GET", urlPath, "", &response) if err != nil { return nil, err diff --git a/okex/OKExSpotWs.go b/okex/OKExSpotWs.go new file mode 100644 index 00000000..3c2328df --- /dev/null +++ b/okex/OKExSpotWs.go @@ -0,0 +1,264 @@ +package okex + +import ( + "encoding/json" + "errors" + "fmt" + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "sort" + "strconv" + "strings" + "time" +) + +type OKExV3SpotWs struct { + base *OKEx + v3Ws *OKExV3Ws + tickerCallback func(*Ticker) + depthCallback func(*Depth) + tradeCallback func(*Trade) + klineCallback func(*Kline, KlinePeriod) + orderCallback func(*Order) +} + +func NewOKExSpotV3Ws(base *OKEx) *OKExV3SpotWs { + okV3Ws := &OKExV3SpotWs{ + base: base, + } + okV3Ws.v3Ws = NewOKExV3Ws(base, okV3Ws.handle) + return okV3Ws +} + +func (okV3Ws *OKExV3SpotWs) TickerCallback(tickerCallback func(*Ticker)) *OKExV3SpotWs { + okV3Ws.tickerCallback = tickerCallback + return okV3Ws +} + +func (okV3Ws *OKExV3SpotWs) DepthCallback(depthCallback func(*Depth)) *OKExV3SpotWs { + okV3Ws.depthCallback = depthCallback + return okV3Ws +} + +func (okV3Ws *OKExV3SpotWs) TradeCallback(tradeCallback func(*Trade)) *OKExV3SpotWs { + okV3Ws.tradeCallback = tradeCallback + return okV3Ws +} +func (okV3Ws *OKExV3SpotWs) KLineCallback(klineCallback func(kline *Kline, period KlinePeriod)) *OKExV3SpotWs { + okV3Ws.klineCallback = klineCallback + return okV3Ws +} + +func (okV3Ws *OKExV3SpotWs) OrderCallback(orderCallback func(*Order)) *OKExV3SpotWs { + okV3Ws.orderCallback = orderCallback + return okV3Ws +} + +func (okV3Ws *OKExV3SpotWs) SetCallbacks(tickerCallback func(*Ticker), + depthCallback func(*Depth), + tradeCallback func(*Trade), + klineCallback func(*Kline, KlinePeriod), + orderCallback func(*Order)) { + okV3Ws.tickerCallback = tickerCallback + okV3Ws.depthCallback = depthCallback + okV3Ws.tradeCallback = tradeCallback + okV3Ws.klineCallback = klineCallback + okV3Ws.orderCallback = orderCallback +} + +func (okV3Ws *OKExV3SpotWs) SubscribeDepth(currencyPair CurrencyPair, size int) error { + if okV3Ws.depthCallback == nil { + return errors.New("please set depth callback func") + } + + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{fmt.Sprintf("spot/depth5:%s", currencyPair.ToSymbol("-"))}}) +} + +func (okV3Ws *OKExV3SpotWs) SubscribeTicker(currencyPair CurrencyPair) error { + if okV3Ws.tickerCallback == nil { + return errors.New("please set ticker callback func") + } + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{fmt.Sprintf("spot/ticker:%s", currencyPair.ToSymbol("-"))}}) +} + +func (okV3Ws *OKExV3SpotWs) SubscribeTrade(currencyPair CurrencyPair) error { + if okV3Ws.tradeCallback == nil { + return errors.New("please set trade callback func") + } + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{fmt.Sprintf("spot/trade:%s", currencyPair.ToSymbol("-"))}}) +} + +func (okV3Ws *OKExV3SpotWs) SubscribeKline(currencyPair CurrencyPair, period int) error { + if okV3Ws.klineCallback == nil { + return errors.New("place set kline callback func") + } + + seconds := adaptKLinePeriod(KlinePeriod(period)) + if seconds == -1 { + return fmt.Errorf("unsupported kline period %d in okex", period) + } + + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{fmt.Sprintf("spot/candle%ds:%s", seconds, currencyPair.ToSymbol("-"))}}) +} + +func (okV3Ws *OKExV3SpotWs) SubscribeOrder(currencyPair CurrencyPair, contractType string) error { + if okV3Ws.orderCallback == nil { + return errors.New("place set order callback func") + } + okV3Ws.v3Ws.Login() + return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ + "op": "subscribe", + "args": []string{fmt.Sprintf("spot/order:%s", currencyPair.ToSymbol("-"))}}) +} + +func (okV3Ws *OKExV3SpotWs) getCurrencyPair(instrumentId string) CurrencyPair { + return NewCurrencyPair3(instrumentId, "-") +} + +func (okV3Ws *OKExV3SpotWs) handle(ch string, data json.RawMessage) error { + var ( + err error + tickers []spotTickerResponse + depthResp []depthResponse + dep Depth + tradeResponse []struct { + Side string `json:"side"` + TradeId int64 `json:"trade_id,string"` + Price float64 `json:"price,string"` + Qty float64 `json:"qty,string"` + InstrumentId string `json:"instrument_id"` + Timestamp string `json:"timestamp"` + } + candleResponse []struct { + Candle []string `json:"candle"` + InstrumentId string `json:"instrument_id"` + } + orderResp []futureOrderResponse + ) + + switch ch { + case "ticker": + err = json.Unmarshal(data, &tickers) + if err != nil { + return err + } + + for _, t := range tickers { + date, _ := time.Parse(time.RFC3339, t.Timestamp) + okV3Ws.tickerCallback(&Ticker{ + Pair: okV3Ws.getCurrencyPair(t.InstrumentId), + Last: t.Last, + Buy: t.BestBid, + Sell: t.BestAsk, + High: t.High24h, + Low: t.Low24h, + Vol: t.BaseVolume24h, + Date: uint64(date.UnixNano() / int64(time.Millisecond)), + }) + } + return nil + case "depth5": + err := json.Unmarshal(data, &depthResp) + if err != nil { + logger.Error(err) + return err + } + if len(depthResp) == 0 { + return nil + } + + dep.Pair = okV3Ws.getCurrencyPair(depthResp[0].InstrumentId) + dep.UTime, _ = time.Parse(time.RFC3339, depthResp[0].Timestamp) + for _, itm := range depthResp[0].Asks { + dep.AskList = append(dep.AskList, DepthRecord{ + Price: ToFloat64(itm[0]), + Amount: ToFloat64(itm[1])}) + } + for _, itm := range depthResp[0].Bids { + dep.BidList = append(dep.BidList, DepthRecord{ + Price: ToFloat64(itm[0]), + Amount: ToFloat64(itm[1])}) + } + sort.Sort(sort.Reverse(dep.AskList)) + //call back func + okV3Ws.depthCallback(&dep) + return nil + case "trade": + err := json.Unmarshal(data, &tradeResponse) + if err != nil { + logger.Error("unmarshal error :", err) + return err + } + + for _, resp := range tradeResponse { + tradeSide := SELL + switch resp.Side { + case "buy": + tradeSide = BUY + } + + t, err := time.Parse(time.RFC3339, resp.Timestamp) + if err != nil { + logger.Warn("parse timestamp error:", err) + } + + okV3Ws.tradeCallback(&Trade{ + Tid: resp.TradeId, + Type: tradeSide, + Amount: resp.Qty, + Price: resp.Price, + Date: t.Unix(), + Pair: okV3Ws.getCurrencyPair(resp.InstrumentId), + }) + } + return nil + case "order": + err := json.Unmarshal(data, &orderResp) + if err != nil { + return err + } + return nil + default: + if strings.HasPrefix(ch, "candle") { + err := json.Unmarshal(data, &candleResponse) + if err != nil { + return err + } + periodMs := strings.TrimPrefix(ch, "candle") + periodMs = strings.TrimSuffix(periodMs, "s") + for _, k := range candleResponse { + pair := okV3Ws.getCurrencyPair(k.InstrumentId) + tm, _ := time.Parse(time.RFC3339, k.Candle[0]) + okV3Ws.klineCallback(&Kline{ + Pair: pair, + Timestamp: tm.Unix(), + Open: ToFloat64(k.Candle[1]), + Close: ToFloat64(k.Candle[4]), + High: ToFloat64(k.Candle[2]), + Low: ToFloat64(k.Candle[3]), + Vol: ToFloat64(k.Candle[5]), + }, adaptSecondsToKlinePeriod(ToInt(periodMs))) + } + return nil + } + } + + return fmt.Errorf("unknown websocet message: %s", string(data)) +} + +func (okV3Ws *OKExV3SpotWs) getKlinePeriodFormChannel(channel string) int { + metas := strings.Split(channel, ":") + if len(metas) != 2 { + return 0 + } + i, _ := strconv.ParseInt(metas[1], 10, 64) + return int(i) +} diff --git a/okex/OKExSpotWs_test.go b/okex/OKExSpotWs_test.go new file mode 100644 index 00000000..db07c8ba --- /dev/null +++ b/okex/OKExSpotWs_test.go @@ -0,0 +1,35 @@ +package okex + +import ( + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "os" + "testing" + "time" +) + +func init() { + logger.SetLevel(logger.DEBUG) +} + +func TestNewOKExSpotV3Ws(t *testing.T) { + os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:1080") + okexSpotV3Ws := okex.OKExV3SpotWs + okexSpotV3Ws.TickerCallback(func(ticker *goex.Ticker) { + t.Log(ticker) + }) + okexSpotV3Ws.DepthCallback(func(depth *goex.Depth) { + t.Log(depth) + }) + okexSpotV3Ws.TradeCallback(func(trade *goex.Trade) { + t.Log(trade) + }) + okexSpotV3Ws.KLineCallback(func(kline *goex.Kline, period goex.KlinePeriod) { + t.Log(period, kline) + }) + //okexSpotV3Ws.SubscribeDepth(goex.EOS_USDT, 5) + //okexSpotV3Ws.SubscribeTrade(goex.EOS_USDT) + //okexSpotV3Ws.SubscribeTicker(goex.EOS_USDT) + okexSpotV3Ws.SubscribeKline(goex.EOS_USDT, goex.KLINE_PERIOD_1H) + time.Sleep(time.Minute) +} diff --git a/okex/OKExV3Utils.go b/okex/OKExV3Utils.go index 90d385ee..4bd34f9c 100644 --- a/okex/OKExV3Utils.go +++ b/okex/OKExV3Utils.go @@ -36,6 +36,35 @@ func adaptKLinePeriod(period KlinePeriod) int { return granularity } +func adaptSecondsToKlinePeriod(seconds int) KlinePeriod { + var p KlinePeriod + switch seconds { + case 60: + p = KLINE_PERIOD_1MIN + case 180: + p = KLINE_PERIOD_3MIN + case 300: + p = KLINE_PERIOD_5MIN + case 900: + p = KLINE_PERIOD_15MIN + case 1800: + p = KLINE_PERIOD_30MIN + case 3600: + p = KLINE_PERIOD_1H + case 7200: + p = KLINE_PERIOD_2H + case 14400: + p = KLINE_PERIOD_4H + case 21600: + p = KLINE_PERIOD_6H + case 86400: + p = KLINE_PERIOD_1DAY + case 604800: + p = KLINE_PERIOD_1WEEK + } + return p +} + func timeStringToInt64(t string) (int64, error) { timestamp, err := time.Parse(time.RFC3339, t) if err != nil { From 0a2c2d5e2541cc41220aacc1854628080549ba81 Mon Sep 17 00:00:00 2001 From: eric Date: Sun, 22 Mar 2020 16:06:29 +0800 Subject: [PATCH 13/29] [okex] ws implement kline handler --- okex/OKExFuturesWs.go | 41 ++++++++++++++++++++++++++++++++++++++--- okex/OKExSpotWs.go | 2 +- okex/OKExWs.go | 4 ++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/okex/OKExFuturesWs.go b/okex/OKExFuturesWs.go index 9064444d..db3defa9 100644 --- a/okex/OKExFuturesWs.go +++ b/okex/OKExFuturesWs.go @@ -50,6 +50,11 @@ func (okV3Ws *OKExV3FuturesWs) OrderCallback(orderCallback func(*FutureOrder, st return okV3Ws } +func (okV3Ws *OKExV3FuturesWs) KlineCallback(klineCallback func(*FutureKline, int)) *OKExV3FuturesWs { + okV3Ws.klineCallback = klineCallback + return okV3Ws +} + func (okV3Ws *OKExV3FuturesWs) SetCallbacks(tickerCallback func(*FutureTicker), depthCallback func(*Depth), tradeCallback func(*Trade, string), @@ -175,9 +180,15 @@ func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { InstrumentId string `json:"instrument_id"` Timestamp string `json:"timestamp"` } - orderResp []futureOrderResponse + orderResp []futureOrderResponse + klineResponse []struct { + Candle []string `json:"candle"` + InstrumentId string `json:"instrument_id"` + } ) - + if strings.Contains(ch, "candle") { + ch = "candle" + } switch ch { case "ticker": err = json.Unmarshal(data, &tickers) @@ -203,6 +214,30 @@ func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { }) } return nil + case "candle": + err = json.Unmarshal(data, &klineResponse) + if err != nil { + return err + } + + for _, t := range klineResponse { + _, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(t.InstrumentId) + ts, _ := time.Parse(time.RFC3339, t.Candle[0]) + //granularity := adaptKLinePeriod(KlinePeriod(period)) + okV3Ws.klineCallback(&FutureKline{ + Kline: &Kline{ + Pair: pair, + High: ToFloat64(t.Candle[2]), + Low: ToFloat64(t.Candle[3]), + Timestamp: ts.Unix(), + Open: ToFloat64(t.Candle[1]), + Close: ToFloat64(t.Candle[4]), + Vol: ToFloat64(t.Candle[5]), + }, + Vol2: ToFloat64(t.Candle[6]), + }, 1) + } + return nil case "depth5": err := json.Unmarshal(data, &depthResp) if err != nil { @@ -289,7 +324,7 @@ func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { return nil } - return fmt.Errorf("unknown websocet message: %s", string(data)) + return fmt.Errorf("[%s] unknown websocket message: %s", ch, string(data)) } func (okV3Ws *OKExV3FuturesWs) getKlinePeriodFormChannel(channel string) int { diff --git a/okex/OKExSpotWs.go b/okex/OKExSpotWs.go index 3c2328df..f2e304fc 100644 --- a/okex/OKExSpotWs.go +++ b/okex/OKExSpotWs.go @@ -251,7 +251,7 @@ func (okV3Ws *OKExV3SpotWs) handle(ch string, data json.RawMessage) error { } } - return fmt.Errorf("unknown websocet message: %s", string(data)) + return fmt.Errorf("unknown websocket message: %s", string(data)) } func (okV3Ws *OKExV3SpotWs) getKlinePeriodFormChannel(channel string) int { diff --git a/okex/OKExWs.go b/okex/OKExWs.go index 48c7e975..595fc825 100644 --- a/okex/OKExWs.go +++ b/okex/OKExWs.go @@ -161,7 +161,7 @@ func (okV3Ws *OKExV3Ws) handle(msg []byte) error { return fmt.Errorf("error in websocket: %v", wsResp) } } - return fmt.Errorf("unknown websocet message: %v", wsResp) + return fmt.Errorf("unknown websocket message: %v", wsResp) } if wsResp.Table != "" { @@ -177,7 +177,7 @@ func (okV3Ws *OKExV3Ws) handle(msg []byte) error { return err } - return fmt.Errorf("unknown websocet message: %v", wsResp) + return fmt.Errorf("unknown websocket message: %v", wsResp) } func (okV3Ws *OKExV3Ws) Login() error { From 69e6b6ae385c657b345e0d69cfcce37fbcfc49fd Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 23 Mar 2020 13:16:28 +0800 Subject: [PATCH 14/29] [hbdm] fix panic: interface conversion: interface {} is nil, not []interface {} --- huobi/Hbdm.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/huobi/Hbdm.go b/huobi/Hbdm.go index b0f7fe8f..17e889bd 100644 --- a/huobi/Hbdm.go +++ b/huobi/Hbdm.go @@ -175,8 +175,8 @@ func (dm *Hbdm) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, params.Add("contract_code", "") if matchPrice == 1 { - params.Set("order_price_type" , "opponent") //对手价下单 - }else{ + params.Set("order_price_type", "opponent") //对手价下单 + } else { params.Set("order_price_type", "limit") params.Add("price", price) } @@ -371,9 +371,13 @@ func (dm *Hbdm) GetFutureDepth(currencyPair CurrencyPair, contractType string, s mills := ToUint64(ret["ts"]) dep.UTime = time.Unix(int64(mills/1000), int64(mills%1000)*int64(time.Millisecond)) - tick := ret["tick"].(map[string]interface{}) - asks := tick["asks"].([]interface{}) - bids := tick["bids"].([]interface{}) + tick, ok1 := ret["tick"].(map[string]interface{}) + asks, ok2 := tick["asks"].([]interface{}) + bids, ok3 := tick["bids"].([]interface{}) + + if !ok1 || !ok2 || !ok3 { + return nil, errors.New("data error") + } for _, item := range asks { askItem := item.([]interface{}) From 521bd0f0e7840ec786581ea28e739666fbb4abff Mon Sep 17 00:00:00 2001 From: xiaojun Date: Tue, 24 Mar 2020 16:34:00 +0800 Subject: [PATCH 15/29] =?UTF-8?q?=E5=AE=8C=E5=96=84OKExSpot=20=E5=AF=B9Get?= =?UTF-8?q?Trades=E6=96=B9=E6=B3=95=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完善OKExSpot 对GetTrades方法的支持 --- okex/OKExSpot.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/okex/OKExSpot.go b/okex/OKExSpot.go index cd582aa7..b43fc6cb 100644 --- a/okex/OKExSpot.go +++ b/okex/OKExSpot.go @@ -450,5 +450,32 @@ func (ok *OKExSpot) GetKlineRecords(currency CurrencyPair, period, size, since i //非个人,整个交易所的交易记录 func (ok *OKExSpot) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - panic("unsupported") + urlPath := fmt.Sprintf("/api/spot/v3/instruments/%s/trades?limit=%d", currency.AdaptUsdToUsdt().ToSymbol("-"), since) + + var response []struct { + Timestamp string `json:"timestamp"` + TradeId int64 `json:"trade_id,string"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Side string `json:"side"` + } + err := ok.DoRequest("GET", urlPath, "", &response) + if err != nil { + return nil, err + } + + var trades []Trade + for _, item := range response { + t, _ := time.Parse(time.RFC3339, item.Timestamp) + trades = append(trades, Trade{ + Tid: item.TradeId, + Type: AdaptTradeSide(item.Side), + Amount: item.Size, + Price: item.Price, + Date: t.Unix(), + Pair: currency, + }) + } + + return trades, nil } From 94bcaf358000c3b63f329cbc2af4dcad23d88df0 Mon Sep 17 00:00:00 2001 From: eric Date: Sun, 29 Mar 2020 07:37:26 +0800 Subject: [PATCH 16/29] [okex] fix build error for spot --- okex/OKExSpot.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/okex/OKExSpot.go b/okex/OKExSpot.go index b43fc6cb..8367fefc 100644 --- a/okex/OKExSpot.go +++ b/okex/OKExSpot.go @@ -450,7 +450,7 @@ func (ok *OKExSpot) GetKlineRecords(currency CurrencyPair, period, size, since i //非个人,整个交易所的交易记录 func (ok *OKExSpot) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - urlPath := fmt.Sprintf("/api/spot/v3/instruments/%s/trades?limit=%d", currency.AdaptUsdToUsdt().ToSymbol("-"), since) + urlPath := fmt.Sprintf("/api/spot/v3/instruments/%s/trades?limit=%d", currencyPair.AdaptUsdToUsdt().ToSymbol("-"), since) var response []struct { Timestamp string `json:"timestamp"` @@ -473,7 +473,7 @@ func (ok *OKExSpot) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, Amount: item.Size, Price: item.Price, Date: t.Unix(), - Pair: currency, + Pair: currencyPair, }) } From b3dc194d14ae016b1fa23145ded594801f913cb6 Mon Sep 17 00:00:00 2001 From: eric Date: Sun, 29 Mar 2020 07:44:01 +0800 Subject: [PATCH 17/29] [okex] add kline for okex swap --- okex/OKExSwap.go | 60 ++++++++++++++++++++++++++++++++++++++++--- okex/OKExSwap_test.go | 10 ++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index 2b7f989e..4248d7f9 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -5,6 +5,8 @@ import ( "github.com/google/uuid" . "github.com/nntaoli-project/goex" "github.com/pkg/errors" + "net/url" + "strconv" "strings" "time" ) @@ -464,11 +466,63 @@ func (ok *OKExSwap) GetDeliveryTime() (int, int, int, int) { panic("not support") } -func (ok *OKExSwap) GetKlineRecords(contract_type string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { - panic("not support") +func (ok *OKExSwap) GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { + + sinceTime := time.Unix(int64(since), 0).UTC() + + if since/int(time.Second) != 1 { //如果不为秒,转为秒 + sinceTime = time.Unix(int64(since)/int64(time.Second), 0).UTC() + } + + granularity := adaptKLinePeriod(KlinePeriod(period)) + if granularity == -1 { + return nil, errors.New("kline period parameter is error") + } + return ok.GetKlineRecords2(contractType, currency, sinceTime.Format(time.RFC3339), "", strconv.Itoa(granularity)) +} + +/** + since : 单位秒,开始时间 +*/ +func (ok *OKExSwap) GetKlineRecords2(contractType string, currency CurrencyPair, start, end, period string) ([]FutureKline, error) { + urlPath := "/api/swap/v3/instruments/%s/candles?%s" + params := url.Values{} + if start != "" { + params.Set("start", start) + } + if end != "" { + params.Set("end", end) + } + if period != "" { + params.Set("period", period) + } + contractId := ok.adaptContractType(currency) + + var response [][]interface{} + err := ok.DoRequest("GET", fmt.Sprintf(urlPath, contractId, params.Encode()), "", &response) + if err != nil { + return nil, err + } + + var klines []FutureKline + for _, itm := range response { + t, _ := time.Parse(time.RFC3339, fmt.Sprint(itm[0])) + klines = append(klines, FutureKline{ + Kline: &Kline{ + Timestamp: t.Unix(), + Pair: currency, + Open: ToFloat64(itm[1]), + High: ToFloat64(itm[2]), + Low: ToFloat64(itm[3]), + Close: ToFloat64(itm[4]), + Vol: ToFloat64(itm[5])}, + Vol2: ToFloat64(itm[6])}) + } + + return klines, nil } -func (ok *OKExSwap) GetTrades(contract_type string, currencyPair CurrencyPair, since int64) ([]Trade, error) { +func (ok *OKExSwap) GetTrades(contractType string, currencyPair CurrencyPair, since int64) ([]Trade, error) { panic("not support") } diff --git a/okex/OKExSwap_test.go b/okex/OKExSwap_test.go index 0444d505..4bb07711 100644 --- a/okex/OKExSwap_test.go +++ b/okex/OKExSwap_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "testing" + "time" ) var config = &goex.APIConfig{ @@ -67,3 +68,12 @@ func TestOKExSwap_GetHistoricalFunding(t *testing.T) { t.Log(err, len(funding)) } } + +func TestOKExSwap_GetKlineRecords(t *testing.T) { + since := time.Now().Add(-24 * time.Hour).Unix() + t.Log(okExSwap.GetKlineRecords(goex.SWAP_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 0, int(since))) +} + +func TestOKExSwap_GetKlineRecords2(t *testing.T) { + t.Log(okExSwap.GetKlineRecords2(goex.SWAP_CONTRACT, goex.BTC_USD, "", "", "")) +} From 9cb7220811c072537a9489ee8623e61acd4e10ee Mon Sep 17 00:00:00 2001 From: evzpav Date: Sat, 28 Mar 2020 16:39:24 -0300 Subject: [PATCH 18/29] [bitfinex] Add WS ticker --- bitfinex/bitfinex.go | 17 ++--- bitfinex/bitfinex_ws.go | 122 +++++++++++++++++++++++++++++++++++ bitfinex/bitfinex_ws_test.go | 23 +++++++ go.mod | 2 +- 4 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 bitfinex/bitfinex_ws.go create mode 100644 bitfinex/bitfinex_ws_test.go diff --git a/bitfinex/bitfinex.go b/bitfinex/bitfinex.go index ae967f16..6090b968 100644 --- a/bitfinex/bitfinex.go +++ b/bitfinex/bitfinex.go @@ -5,11 +5,12 @@ import ( "encoding/json" "errors" "fmt" - . "github.com/nntaoli-project/goex" "net/http" "strconv" "strings" "time" + + . "github.com/nntaoli-project/goex" ) type Bitfinex struct { @@ -223,7 +224,7 @@ func (bfx *Bitfinex) CancelOrder(orderId string, currencyPair CurrencyPair) (boo func (bfx *Bitfinex) toOrder(respmap map[string]interface{}) *Order { order := new(Order) - order.Currency = bfx.symbolToCurrencyPair(respmap["symbol"].(string)) + order.Currency = symbolToCurrencyPair(respmap["symbol"].(string)) order.OrderID = ToInt(respmap["id"]) order.OrderID2 = fmt.Sprint(ToInt(respmap["id"])) order.Amount = ToFloat64(respmap["original_amount"]) @@ -317,12 +318,6 @@ func (bfx *Bitfinex) currencyPairToSymbol(currencyPair CurrencyPair) string { return strings.ToUpper(currencyPair.ToSymbol("")) } -func (bfx *Bitfinex) symbolToCurrencyPair(symbol string) CurrencyPair { - currencyA := strings.ToUpper(symbol[0:3]) - currencyB := strings.ToUpper(symbol[3:]) - return NewCurrencyPair(NewCurrency(currencyA, ""), NewCurrency(currencyB, "")) -} - func (bfx *Bitfinex) adaptTimestamp(timestamp string) int { times := strings.Split(timestamp, ".") intTime, _ := strconv.Atoi(times[0]) @@ -357,3 +352,9 @@ func (bfx *Bitfinex) adaptCurrencyPair(pair CurrencyPair) CurrencyPair { return NewCurrencyPair(currencyA, currencyB) } + +func symbolToCurrencyPair(symbol string) CurrencyPair { + currencyA := strings.ToUpper(symbol[0:3]) + currencyB := strings.ToUpper(symbol[3:]) + return NewCurrencyPair(NewCurrency(currencyA, ""), NewCurrency(currencyB, "")) +} diff --git a/bitfinex/bitfinex_ws.go b/bitfinex/bitfinex_ws.go new file mode 100644 index 00000000..7ab6faa9 --- /dev/null +++ b/bitfinex/bitfinex_ws.go @@ -0,0 +1,122 @@ +package bitfinex + +import ( + "encoding/json" + "errors" + "sync" + "time" + + . "github.com/nntaoli-project/goex" +) + +const ticker = "ticker" +const subscribe = "subscribe" +const subscribed = "subscribed" + +type BitfinexWs struct { + *WsBuilder + sync.Once + wsConn *WsConn + eventMap map[int64]SubscribeEvent + + tickerCallback func(*Ticker) +} + +type SubscribeEvent struct { + Event string `json:"event"` + SubID string `json:"subId"` + Channel string `json:"channel"` + ChanID int64 `json:"chanId"` + Symbol string `json:"symbol"` + Precision string `json:"prec,omitempty"` + Frequency string `json:"freq,omitempty"` + Key string `json:"key,omitempty"` + Len string `json:"len,omitempty"` + Pair string `json:"pair"` +} + +type EventMap map[int64]SubscribeEvent + +func NewWs() *BitfinexWs { + bws := &BitfinexWs{WsBuilder: NewWsBuilder(), eventMap: make(map[int64]SubscribeEvent)} + bws.WsBuilder = bws.WsBuilder. + WsUrl("wss://api-pub.bitfinex.com/ws/2"). + AutoReconnect(). + ProtoHandleFunc(bws.handle) + return bws +} + +func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker)) { + bws.tickerCallback = tickerCallback +} + +func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { + if bws.tickerCallback == nil { + return errors.New("please set ticker callback func") + } + return bws.subscribe(map[string]interface{}{ + "event": subscribe, + "channel": ticker, + "symbol": convertPairToBitfinexSymbol(pair)}) +} + +func (bws *BitfinexWs) subscribe(sub map[string]interface{}) error { + bws.connectWs() + return bws.wsConn.Subscribe(sub) +} + +func (bws *BitfinexWs) connectWs() { + bws.Do(func() { + bws.wsConn = bws.WsBuilder.Build() + }) +} + +func (bws *BitfinexWs) handle(msg []byte) error { + var event SubscribeEvent + if err := json.Unmarshal(msg, &event); err == nil { + if event.Event == subscribed && event.Channel == ticker { + bws.eventMap[event.ChanID] = event + return nil + } + } + + var resp []interface{} + if err := json.Unmarshal(msg, &resp); err == nil { + if rawTicker, ok := resp[1].([]interface{}); ok { + channelID := ToInt64(resp[0]) + t := bws.tickerFromRaw(channelID, rawTicker) + bws.tickerCallback(t) + return nil + } + } + + return nil +} + +func (bws *BitfinexWs) resolveCurrencyPair(channelID int64) CurrencyPair { + ev, ok := bws.eventMap[channelID] + if ok { + return symbolToCurrencyPair(ev.Pair) + } + return UNKNOWN_PAIR +} + +func (bws *BitfinexWs) tickerFromRaw(channelID int64, rawTicker []interface{}) *Ticker { + pair := bws.resolveCurrencyPair(channelID) + return &Ticker{ + Pair: pair, + Buy: ToFloat64(rawTicker[0]), + Sell: ToFloat64(rawTicker[2]), + Last: ToFloat64(rawTicker[6]), + Vol: ToFloat64(rawTicker[7]), + High: ToFloat64(rawTicker[8]), + Low: ToFloat64(rawTicker[9]), + Date: uint64(time.Now().UnixNano() / int64(time.Millisecond)), + } + +} + +func convertPairToBitfinexSymbol(pair CurrencyPair) string { + symbol := pair.ToSymbol("") + return "t" + symbol +} diff --git a/bitfinex/bitfinex_ws_test.go b/bitfinex/bitfinex_ws_test.go new file mode 100644 index 00000000..87e6ba98 --- /dev/null +++ b/bitfinex/bitfinex_ws_test.go @@ -0,0 +1,23 @@ +package bitfinex + +import ( + "log" + "testing" + "time" + + "github.com/nntaoli-project/goex" +) + +func TestNewBitfinexWs(t *testing.T) { + bitfinexWs := NewWs() + + handleTicker := func(ticker *goex.Ticker) { + log.Printf("Ticker: %+v: ", ticker) + } + + bitfinexWs.SetCallbacks(handleTicker) + + t.Log(bitfinexWs.SubscribeTicker(goex.BTC_USD)) + t.Log(bitfinexWs.SubscribeTicker(goex.LTC_USD)) + time.Sleep(time.Minute) +} diff --git a/go.mod b/go.mod index c3646771..98993d9b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/Kucoin/kucoin-go-sdk v1.2.2 - github.com/deckarep/golang-set v1.7.1 + github.com/deckarep/golang-set v1.7.1 // indirect github.com/go-openapi/errors v0.19.2 github.com/google/uuid v1.1.1 github.com/gorilla/websocket v1.4.1 From 00943782fb1ba51014e7455074a8e31076ddab38 Mon Sep 17 00:00:00 2001 From: evzpav Date: Sun, 29 Mar 2020 17:38:57 -0300 Subject: [PATCH 19/29] [bitfinex] Add WS trades --- bitfinex/bitfinex_ws.go | 91 +++++++++++++++++++++++++++--------- bitfinex/bitfinex_ws_test.go | 13 +++++- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/bitfinex/bitfinex_ws.go b/bitfinex/bitfinex_ws.go index 7ab6faa9..591ebd16 100644 --- a/bitfinex/bitfinex_ws.go +++ b/bitfinex/bitfinex_ws.go @@ -3,15 +3,17 @@ package bitfinex import ( "encoding/json" "errors" + "math" "sync" "time" . "github.com/nntaoli-project/goex" ) -const ticker = "ticker" const subscribe = "subscribe" const subscribed = "subscribed" +const ticker = "ticker" +const trades = "trades" type BitfinexWs struct { *WsBuilder @@ -20,6 +22,7 @@ type BitfinexWs struct { eventMap map[int64]SubscribeEvent tickerCallback func(*Ticker) + tradeCallback func(*Trade) } type SubscribeEvent struct { @@ -46,8 +49,9 @@ func NewWs() *BitfinexWs { return bws } -func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker)) { +func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker), tradeCallback func(*Trade)) { bws.tickerCallback = tickerCallback + bws.tradeCallback = tradeCallback } func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { @@ -60,6 +64,16 @@ func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { "symbol": convertPairToBitfinexSymbol(pair)}) } +func (bws *BitfinexWs) SubscribeTrade(pair CurrencyPair) error { + if bws.tradeCallback == nil { + return errors.New("please set trade callback func") + } + return bws.subscribe(map[string]interface{}{ + "event": subscribe, + "channel": trades, + "symbol": convertPairToBitfinexSymbol(pair)}) +} + func (bws *BitfinexWs) subscribe(sub map[string]interface{}) error { bws.connectWs() return bws.wsConn.Subscribe(sub) @@ -74,7 +88,7 @@ func (bws *BitfinexWs) connectWs() { func (bws *BitfinexWs) handle(msg []byte) error { var event SubscribeEvent if err := json.Unmarshal(msg, &event); err == nil { - if event.Event == subscribed && event.Channel == ticker { + if event.Event == subscribed { bws.eventMap[event.ChanID] = event return nil } @@ -82,38 +96,69 @@ func (bws *BitfinexWs) handle(msg []byte) error { var resp []interface{} if err := json.Unmarshal(msg, &resp); err == nil { - if rawTicker, ok := resp[1].([]interface{}); ok { - channelID := ToInt64(resp[0]) - t := bws.tickerFromRaw(channelID, rawTicker) - bws.tickerCallback(t) + channelID := ToInt64(resp[0]) + event, ok := bws.eventMap[channelID] + if !ok { return nil } - } - return nil -} + switch event.Channel { + case ticker: + if rawTicker, ok := resp[1].([]interface{}); ok { + pair := symbolToCurrencyPair(event.Pair) + t := bws.tickerFromRaw(pair, rawTicker) + bws.tickerCallback(t) + return nil + } + case trades: + if len(resp) < 3 { + return nil + } + + if rawTrades, ok := resp[2].([]interface{}); ok { + pair := symbolToCurrencyPair(event.Pair) + trade := bws.tradeFromRaw(pair, rawTrades) + bws.tradeCallback(trade) + return nil + } + } -func (bws *BitfinexWs) resolveCurrencyPair(channelID int64) CurrencyPair { - ev, ok := bws.eventMap[channelID] - if ok { - return symbolToCurrencyPair(ev.Pair) } - return UNKNOWN_PAIR + + return nil } -func (bws *BitfinexWs) tickerFromRaw(channelID int64, rawTicker []interface{}) *Ticker { - pair := bws.resolveCurrencyPair(channelID) +func (bws *BitfinexWs) tickerFromRaw(pair CurrencyPair, raw []interface{}) *Ticker { return &Ticker{ Pair: pair, - Buy: ToFloat64(rawTicker[0]), - Sell: ToFloat64(rawTicker[2]), - Last: ToFloat64(rawTicker[6]), - Vol: ToFloat64(rawTicker[7]), - High: ToFloat64(rawTicker[8]), - Low: ToFloat64(rawTicker[9]), + Buy: ToFloat64(raw[0]), + Sell: ToFloat64(raw[2]), + Last: ToFloat64(raw[6]), + Vol: ToFloat64(raw[7]), + High: ToFloat64(raw[8]), + Low: ToFloat64(raw[9]), Date: uint64(time.Now().UnixNano() / int64(time.Millisecond)), } +} +func (bws *BitfinexWs) tradeFromRaw(pair CurrencyPair, raw []interface{}) *Trade { + + amount := ToFloat64(raw[2]) + var side TradeSide + if amount > 0 { + side = BUY + } else { + side = SELL + } + + return &Trade{ + Pair: pair, + Tid: ToInt64(raw[0]), + Date: ToInt64(raw[1]), + Amount: math.Abs(amount), + Price: ToFloat64(raw[3]), + Type: side, + } } func convertPairToBitfinexSymbol(pair CurrencyPair) string { diff --git a/bitfinex/bitfinex_ws_test.go b/bitfinex/bitfinex_ws_test.go index 87e6ba98..85b19b70 100644 --- a/bitfinex/bitfinex_ws_test.go +++ b/bitfinex/bitfinex_ws_test.go @@ -14,10 +14,19 @@ func TestNewBitfinexWs(t *testing.T) { handleTicker := func(ticker *goex.Ticker) { log.Printf("Ticker: %+v: ", ticker) } - - bitfinexWs.SetCallbacks(handleTicker) + handleTrade := func(trade *goex.Trade) { + log.Printf("Trade: %+v: ", trade) + } + + bitfinexWs.SetCallbacks(handleTicker, handleTrade) + + //Ticker t.Log(bitfinexWs.SubscribeTicker(goex.BTC_USD)) t.Log(bitfinexWs.SubscribeTicker(goex.LTC_USD)) + + //Trades + t.Log(bitfinexWs.SubscribeTrade(goex.BTC_USD)) + time.Sleep(time.Minute) } From 315dd96d352d5c284aa7949bc507e47e3cf379ec Mon Sep 17 00:00:00 2001 From: evzpav Date: Sun, 29 Mar 2020 22:06:18 -0300 Subject: [PATCH 20/29] [bitfinex] Add WS candles --- Const.go | 1 + bitfinex/bitfinex.go | 14 +++++++++ bitfinex/bitfinex_ws.go | 60 +++++++++++++++++++++++++++++++++--- bitfinex/bitfinex_ws_test.go | 8 ++++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/Const.go b/Const.go index 9804d225..f4032eff 100644 --- a/Const.go +++ b/Const.go @@ -65,6 +65,7 @@ const ( KLINE_PERIOD_60MIN KLINE_PERIOD_1H KLINE_PERIOD_2H + KLINE_PERIOD_3H KLINE_PERIOD_4H KLINE_PERIOD_6H KLINE_PERIOD_8H diff --git a/bitfinex/bitfinex.go b/bitfinex/bitfinex.go index 6090b968..4b2c7a47 100644 --- a/bitfinex/bitfinex.go +++ b/bitfinex/bitfinex.go @@ -358,3 +358,17 @@ func symbolToCurrencyPair(symbol string) CurrencyPair { currencyB := strings.ToUpper(symbol[3:]) return NewCurrencyPair(NewCurrency(currencyA, ""), NewCurrency(currencyB, "")) } + +var klinePeriods = map[KlinePeriod]string{ + KLINE_PERIOD_1MIN: "1m", + KLINE_PERIOD_5MIN: "5m", + KLINE_PERIOD_15MIN: "15m", + KLINE_PERIOD_30MIN: "30m", + KLINE_PERIOD_60MIN: "1h", + KLINE_PERIOD_3H: "3h", + KLINE_PERIOD_6H: "6h", + KLINE_PERIOD_12H: "12h", + KLINE_PERIOD_1DAY: "1D", + KLINE_PERIOD_1WEEK: "7D", + KLINE_PERIOD_1MONTH: "1M", +} diff --git a/bitfinex/bitfinex_ws.go b/bitfinex/bitfinex_ws.go index 591ebd16..7c891956 100644 --- a/bitfinex/bitfinex_ws.go +++ b/bitfinex/bitfinex_ws.go @@ -3,7 +3,9 @@ package bitfinex import ( "encoding/json" "errors" + "fmt" "math" + "strings" "sync" "time" @@ -14,6 +16,7 @@ const subscribe = "subscribe" const subscribed = "subscribed" const ticker = "ticker" const trades = "trades" +const candles = "candles" type BitfinexWs struct { *WsBuilder @@ -23,6 +26,7 @@ type BitfinexWs struct { tickerCallback func(*Ticker) tradeCallback func(*Trade) + candleCallback func(*Kline) } type SubscribeEvent struct { @@ -49,9 +53,10 @@ func NewWs() *BitfinexWs { return bws } -func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker), tradeCallback func(*Trade)) { +func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker), tradeCallback func(*Trade), candleCallback func(*Kline)) { bws.tickerCallback = tickerCallback bws.tradeCallback = tradeCallback + bws.candleCallback = candleCallback } func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { @@ -74,6 +79,24 @@ func (bws *BitfinexWs) SubscribeTrade(pair CurrencyPair) error { "symbol": convertPairToBitfinexSymbol(pair)}) } +func (bws *BitfinexWs) SubscribeCandle(pair CurrencyPair, klinePeriod KlinePeriod) error { + if bws.candleCallback == nil { + return errors.New("please set candle callback func") + } + symbol := convertPairToBitfinexSymbol(pair) + + period, ok := klinePeriods[klinePeriod] + if !ok { + return errors.New("invalid period") + } + + return bws.subscribe(map[string]interface{}{ + "event": subscribe, + "channel": candles, + "key": fmt.Sprintf("trade:%s:%s", period, symbol), + }) +} + func (bws *BitfinexWs) subscribe(sub map[string]interface{}) error { bws.connectWs() return bws.wsConn.Subscribe(sub) @@ -104,9 +127,9 @@ func (bws *BitfinexWs) handle(msg []byte) error { switch event.Channel { case ticker: - if rawTicker, ok := resp[1].([]interface{}); ok { + if raw, ok := resp[1].([]interface{}); ok { pair := symbolToCurrencyPair(event.Pair) - t := bws.tickerFromRaw(pair, rawTicker) + t := bws.tickerFromRaw(pair, raw) bws.tickerCallback(t) return nil } @@ -115,12 +138,22 @@ func (bws *BitfinexWs) handle(msg []byte) error { return nil } - if rawTrades, ok := resp[2].([]interface{}); ok { + if raw, ok := resp[2].([]interface{}); ok { pair := symbolToCurrencyPair(event.Pair) - trade := bws.tradeFromRaw(pair, rawTrades) + trade := bws.tradeFromRaw(pair, raw) bws.tradeCallback(trade) return nil } + case candles: + if raw, ok := resp[1].([]interface{}); ok { + if len(raw) > 6 { + return nil + } + + kline := bws.candleFromRaw(convertKeyToPair(event.Key), raw) + bws.candleCallback(kline) + return nil + } } } @@ -161,7 +194,24 @@ func (bws *BitfinexWs) tradeFromRaw(pair CurrencyPair, raw []interface{}) *Trade } } +func (bws *BitfinexWs) candleFromRaw(pair CurrencyPair, raw []interface{}) *Kline { + return &Kline{ + Pair: pair, + Timestamp: ToInt64(raw[0]), + Open: ToFloat64(raw[1]), + Close: ToFloat64(raw[2]), + High: ToFloat64(raw[3]), + Low: ToFloat64(raw[4]), + Vol: ToFloat64(raw[5]), + } +} + func convertPairToBitfinexSymbol(pair CurrencyPair) string { symbol := pair.ToSymbol("") return "t" + symbol } + +func convertKeyToPair(key string) CurrencyPair { + split := strings.Split(key, ":") + return symbolToCurrencyPair(split[2][1:]) +} diff --git a/bitfinex/bitfinex_ws_test.go b/bitfinex/bitfinex_ws_test.go index 85b19b70..d2967d82 100644 --- a/bitfinex/bitfinex_ws_test.go +++ b/bitfinex/bitfinex_ws_test.go @@ -19,7 +19,11 @@ func TestNewBitfinexWs(t *testing.T) { log.Printf("Trade: %+v: ", trade) } - bitfinexWs.SetCallbacks(handleTicker, handleTrade) + handleCandle := func(candle *goex.Kline) { + log.Printf("Candle: %+v: ", candle) + } + + bitfinexWs.SetCallbacks(handleTicker, handleTrade, handleCandle) //Ticker t.Log(bitfinexWs.SubscribeTicker(goex.BTC_USD)) @@ -28,5 +32,7 @@ func TestNewBitfinexWs(t *testing.T) { //Trades t.Log(bitfinexWs.SubscribeTrade(goex.BTC_USD)) + //Candles + t.Log(bitfinexWs.SubscribeCandle(goex.BTC_USD, goex.KLINE_PERIOD_1MIN)) time.Sleep(time.Minute) } From 6d982e86dbf5471db4aaf527d451405302952e3d Mon Sep 17 00:00:00 2001 From: evzpav Date: Sun, 29 Mar 2020 22:47:17 -0300 Subject: [PATCH 21/29] [bitfinex] Add exchange stop orders --- bitfinex/bitfinex.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bitfinex/bitfinex.go b/bitfinex/bitfinex.go index 4b2c7a47..e71da70c 100644 --- a/bitfinex/bitfinex.go +++ b/bitfinex/bitfinex.go @@ -64,7 +64,6 @@ func (bfx *Bitfinex) GetDepth(size int, currencyPair CurrencyPair) (*Depth, erro if err != nil { return nil, err } - println("resp:", resp) bids := resp["bids"].([]interface{}) asks := resp["asks"].([]interface{}) @@ -180,18 +179,15 @@ func (bfx *Bitfinex) placeOrder(orderType, side, amount, price string, pair Curr switch side { case "buy": - if orderType == "limit" || orderType == "exchange limit" { - order.Side = BUY - } else { + order.Side = BUY + if strings.Contains(orderType, "market") { order.Side = BUY_MARKET } case "sell": - if orderType == "limit" || orderType == "exchange limit" { - order.Side = SELL - } else { + order.Side = SELL + if strings.Contains(orderType, "market") { order.Side = SELL_MARKET } - } return order, nil } @@ -212,6 +208,14 @@ func (bfx *Bitfinex) MarketSell(amount, price string, currencyPair CurrencyPair) return bfx.placeOrder("exchange market", "sell", amount, price, currencyPair) } +func (bfx *Bitfinex) StopBuy(amount, price string, currencyPair CurrencyPair) (*Order, error) { + return bfx.placeOrder("exchange stop", "buy", amount, price, currencyPair) +} + +func (bfx *Bitfinex) StopSell(amount, price string, currencyPair CurrencyPair) (*Order, error) { + return bfx.placeOrder("exchange stop", "sell", amount, price, currencyPair) +} + func (bfx *Bitfinex) CancelOrder(orderId string, currencyPair CurrencyPair) (bool, error) { var respmap map[string]interface{} path := "order/cancel" From 3a606c91ee6f37d2e2b8c3ab5213ea302a4675f8 Mon Sep 17 00:00:00 2001 From: evzpav Date: Sun, 29 Mar 2020 23:45:08 -0300 Subject: [PATCH 22/29] [bitfinex] Implement GetKlineRecords --- bitfinex/BitfinexLending.go | 5 ++- bitfinex/bitfinex.go | 74 ++++++++++++++++++++++++++++-------- bitfinex/bitfinex_test.go | 10 ++++- bitfinex/bitfinex_ws.go | 41 ++++---------------- bitfinex/bitfinex_ws_test.go | 1 + 5 files changed, 80 insertions(+), 51 deletions(-) diff --git a/bitfinex/BitfinexLending.go b/bitfinex/BitfinexLending.go index ad13dbd0..508366f3 100644 --- a/bitfinex/BitfinexLending.go +++ b/bitfinex/BitfinexLending.go @@ -4,10 +4,11 @@ import ( "encoding/json" "errors" "fmt" - . "github.com/nntaoli-project/goex" "io/ioutil" "strconv" "strings" + + . "github.com/nntaoli-project/goex" ) type LendBookItem struct { @@ -84,7 +85,7 @@ func (bfx *Bitfinex) GetDepositWalletBalance() (*Account, error) { func (bfx *Bitfinex) GetLendBook(currency Currency) (error, *LendBook) { path := fmt.Sprintf("/lendbook/%s", currency.Symbol) - resp, err := bfx.httpClient.Get(BASE_URL + path) + resp, err := bfx.httpClient.Get(apiURLV1 + path) if err != nil { return err, nil } diff --git a/bitfinex/bitfinex.go b/bitfinex/bitfinex.go index e71da70c..74338b31 100644 --- a/bitfinex/bitfinex.go +++ b/bitfinex/bitfinex.go @@ -3,7 +3,6 @@ package bitfinex import ( "encoding/base64" "encoding/json" - "errors" "fmt" "net/http" "strconv" @@ -20,7 +19,9 @@ type Bitfinex struct { } const ( - BASE_URL = "https://api.bitfinex.com/v1" + baseURL string = "https://api.bitfinex.com" + apiURLV1 string = baseURL + "/v1" + apiURLV2 string = baseURL + "/v2" ) func New(client *http.Client, accessKey, secretKey string) *Bitfinex { @@ -35,14 +36,14 @@ func (bfx *Bitfinex) GetTicker(currencyPair CurrencyPair) (*Ticker, error) { //pubticker currencyPair = bfx.adaptCurrencyPair(currencyPair) - apiUrl := fmt.Sprintf("%s/pubticker/%s", BASE_URL, strings.ToLower(currencyPair.ToSymbol(""))) + apiUrl := fmt.Sprintf("%s/pubticker/%s", apiURLV1, strings.ToLower(currencyPair.ToSymbol(""))) resp, err := HttpGet(bfx.httpClient, apiUrl) if err != nil { return nil, err } if resp["error"] != nil { - return nil, errors.New(resp["error"].(string)) + return nil, fmt.Errorf("%s", resp["error"]) } //fmt.Println(resp) @@ -59,7 +60,7 @@ func (bfx *Bitfinex) GetTicker(currencyPair CurrencyPair) (*Ticker, error) { } func (bfx *Bitfinex) GetDepth(size int, currencyPair CurrencyPair) (*Depth, error) { - apiUrl := fmt.Sprintf("%s/book/%s?limit_bids=%d&limit_asks=%d", BASE_URL, bfx.currencyPairToSymbol(currencyPair), size, size) + apiUrl := fmt.Sprintf("%s/book/%s?limit_bids=%d&limit_asks=%d", apiURLV1, bfx.currencyPairToSymbol(currencyPair), size, size) resp, err := HttpGet(bfx.httpClient, apiUrl) if err != nil { return nil, err @@ -88,8 +89,36 @@ func (bfx *Bitfinex) GetDepth(size int, currencyPair CurrencyPair) (*Depth, erro return depth, nil } -func (bfx *Bitfinex) GetKlineRecords(currencyPair CurrencyPair, period, size, since int) ([]Kline, error) { - panic("not implement") +func (bfx *Bitfinex) GetKlineRecords(currencyPair CurrencyPair, klinePeriod, size, since int) ([]Kline, error) { + symbol := convertPairToBitfinexSymbol("t", currencyPair) + if size == 0 { + size = 100 + } + + period, ok := klinePeriods[KlinePeriod(klinePeriod)] + if !ok { + return nil, fmt.Errorf("invalid period") + } + apiURL := fmt.Sprintf("%s/candles/trade:%s:%s/hist?limit=%d", apiURLV2, period, symbol, size) + + respRaw, err := NewHttpRequest(bfx.httpClient, "GET", apiURL, "", nil) + if err != nil { + return nil, err + } + + var candles []Kline + + var resp [][]interface{} + if err := json.Unmarshal(respRaw, &resp); err != nil { + return nil, fmt.Errorf("Failed to unmarshal kline response: %v", err) + } + + for _, r := range resp { + k := klineFromRaw(currencyPair, r) + candles = append(candles, *k) + } + + return candles, nil } //非个人,整个交易所的交易记录 @@ -104,7 +133,6 @@ func (bfx *Bitfinex) GetWalletBalances() (map[string]*Account, error) { if err != nil { return nil, err } - //log.Println(respmap) walletmap := make(map[string]*Account, 1) @@ -290,20 +318,14 @@ func (bfx *Bitfinex) doAuthenticatedRequest(method, path string, payload map[str payload["request"] = "/v1/" + path payload["nonce"] = fmt.Sprintf("%d.2", nonce) - //for k, v := range params { - // payload[k] = v[0] - //} - p, err := json.Marshal(payload) if err != nil { return err } - //println(string(p)) encoded := base64.StdEncoding.EncodeToString(p) sign, _ := GetParamHmacSha384Sign(bfx.secretKey, encoded) - //log.Println(BASE_URL + "/" + path) - resp, err := NewHttpRequest(bfx.httpClient, method, BASE_URL+"/"+path, "", map[string]string{ + resp, err := NewHttpRequest(bfx.httpClient, method, apiURLV1+"/"+path, "", map[string]string{ "Content-Type": "application/json", "Accept": "application/json", "X-BFX-APIKEY": bfx.accessKey, @@ -376,3 +398,25 @@ var klinePeriods = map[KlinePeriod]string{ KLINE_PERIOD_1WEEK: "7D", KLINE_PERIOD_1MONTH: "1M", } + +func klineFromRaw(pair CurrencyPair, raw []interface{}) *Kline { + return &Kline{ + Pair: pair, + Timestamp: ToInt64(raw[0]), + Open: ToFloat64(raw[1]), + Close: ToFloat64(raw[2]), + High: ToFloat64(raw[3]), + Low: ToFloat64(raw[4]), + Vol: ToFloat64(raw[5]), + } +} + +func convertPairToBitfinexSymbol(prefix string, pair CurrencyPair) string { + symbol := pair.ToSymbol("") + return prefix + symbol +} + +func convertKeyToPair(key string) CurrencyPair { + split := strings.Split(key, ":") + return symbolToCurrencyPair(split[2][1:]) +} diff --git a/bitfinex/bitfinex_test.go b/bitfinex/bitfinex_test.go index f79ec0ee..14b9154b 100644 --- a/bitfinex/bitfinex_test.go +++ b/bitfinex/bitfinex_test.go @@ -1,9 +1,10 @@ package bitfinex import ( - "github.com/nntaoli-project/goex" "net/http" "testing" + + "github.com/nntaoli-project/goex" ) var bfx = New(http.DefaultClient, "", "") @@ -18,3 +19,10 @@ func TestBitfinex_GetDepth(t *testing.T) { t.Log(dep.AskList) t.Log(dep.BidList) } + +func TestBitfinex_GetKline(t *testing.T) { + kline, _ := bfx.GetKlineRecords(goex.BTC_USD, goex.KLINE_PERIOD_1MONTH, 10, 0) + for _, k := range kline { + t.Log(k) + } +} diff --git a/bitfinex/bitfinex_ws.go b/bitfinex/bitfinex_ws.go index 7c891956..6d6320d8 100644 --- a/bitfinex/bitfinex_ws.go +++ b/bitfinex/bitfinex_ws.go @@ -2,10 +2,8 @@ package bitfinex import ( "encoding/json" - "errors" "fmt" "math" - "strings" "sync" "time" @@ -61,33 +59,33 @@ func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker), tradeCallback func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { if bws.tickerCallback == nil { - return errors.New("please set ticker callback func") + return fmt.Errorf("please set ticker callback func") } return bws.subscribe(map[string]interface{}{ "event": subscribe, "channel": ticker, - "symbol": convertPairToBitfinexSymbol(pair)}) + "symbol": convertPairToBitfinexSymbol("t", pair)}) } func (bws *BitfinexWs) SubscribeTrade(pair CurrencyPair) error { if bws.tradeCallback == nil { - return errors.New("please set trade callback func") + return fmt.Errorf("please set trade callback func") } return bws.subscribe(map[string]interface{}{ "event": subscribe, "channel": trades, - "symbol": convertPairToBitfinexSymbol(pair)}) + "symbol": convertPairToBitfinexSymbol("t", pair)}) } func (bws *BitfinexWs) SubscribeCandle(pair CurrencyPair, klinePeriod KlinePeriod) error { if bws.candleCallback == nil { - return errors.New("please set candle callback func") + return fmt.Errorf("please set candle callback func") } - symbol := convertPairToBitfinexSymbol(pair) + symbol := convertPairToBitfinexSymbol("t", pair) period, ok := klinePeriods[klinePeriod] if !ok { - return errors.New("invalid period") + return fmt.Errorf("invalid period") } return bws.subscribe(map[string]interface{}{ @@ -150,7 +148,7 @@ func (bws *BitfinexWs) handle(msg []byte) error { return nil } - kline := bws.candleFromRaw(convertKeyToPair(event.Key), raw) + kline := klineFromRaw(convertKeyToPair(event.Key), raw) bws.candleCallback(kline) return nil } @@ -175,7 +173,6 @@ func (bws *BitfinexWs) tickerFromRaw(pair CurrencyPair, raw []interface{}) *Tick } func (bws *BitfinexWs) tradeFromRaw(pair CurrencyPair, raw []interface{}) *Trade { - amount := ToFloat64(raw[2]) var side TradeSide if amount > 0 { @@ -193,25 +190,3 @@ func (bws *BitfinexWs) tradeFromRaw(pair CurrencyPair, raw []interface{}) *Trade Type: side, } } - -func (bws *BitfinexWs) candleFromRaw(pair CurrencyPair, raw []interface{}) *Kline { - return &Kline{ - Pair: pair, - Timestamp: ToInt64(raw[0]), - Open: ToFloat64(raw[1]), - Close: ToFloat64(raw[2]), - High: ToFloat64(raw[3]), - Low: ToFloat64(raw[4]), - Vol: ToFloat64(raw[5]), - } -} - -func convertPairToBitfinexSymbol(pair CurrencyPair) string { - symbol := pair.ToSymbol("") - return "t" + symbol -} - -func convertKeyToPair(key string) CurrencyPair { - split := strings.Split(key, ":") - return symbolToCurrencyPair(split[2][1:]) -} diff --git a/bitfinex/bitfinex_ws_test.go b/bitfinex/bitfinex_ws_test.go index d2967d82..10558eb0 100644 --- a/bitfinex/bitfinex_ws_test.go +++ b/bitfinex/bitfinex_ws_test.go @@ -34,5 +34,6 @@ func TestNewBitfinexWs(t *testing.T) { //Candles t.Log(bitfinexWs.SubscribeCandle(goex.BTC_USD, goex.KLINE_PERIOD_1MIN)) + time.Sleep(time.Minute) } From a845b86c246903d7427a0a12e5968d6eb3164582 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 30 Mar 2020 18:17:24 +0800 Subject: [PATCH 23/29] [okex] add GetInstruments and SetMarginLevel --- okex/OKExSwap.go | 59 ++++++++++++++++++++++++++++++++++++++++++- okex/OKExSwap_test.go | 11 +++++--- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index 4248d7f9..772e7d87 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -1,6 +1,7 @@ package okex import ( + "encoding/json" "fmt" "github.com/google/uuid" . "github.com/nntaoli-project/goex" @@ -556,5 +557,61 @@ func (ok *OKExSwap) AdaptTradeStatus(status int) TradeStatus { } func (ok *OKExSwap) adaptContractType(currencyPair CurrencyPair) string { - return fmt.Sprintf("%s-SWAP", currencyPair.AdaptUsdtToUsd().ToSymbol("-")) + return fmt.Sprintf("%s-SWAP", currencyPair.ToSymbol("-")) +} + +type Instrument struct { + InstrumentID string `json:"instrument_id"` + UnderlyingIndex string `json:"underlying_index"` + QuoteCurrency string `json:"quote_currency"` + Coin string `json:"coin"` + ContractVal string `json:"contract_val"` + Listing time.Time `json:"listing"` + Delivery time.Time `json:"delivery"` + SizeIncrement string `json:"size_increment"` + TickSize string `json:"tick_size"` + BaseCurrency string `json:"base_currency"` + Underlying string `json:"underlying"` + SettlementCurrency string `json:"settlement_currency"` + IsInverse string `json:"is_inverse"` + ContractValCurrency string `json:"contract_val_currency"` +} + +func (ok *OKExSwap) GetInstruments() ([]Instrument, error) { + var resp []Instrument + err := ok.DoRequest("GET", "/api/swap/v3/instruments", "", &resp) + if err != nil { + return nil, err + } + return resp, nil +} + +type MarginLeverage struct { + LongLeverage string `json:"long_leverage"` + MarginMode string `json:"margin_mode"` + ShortLeverage string `json:"short_leverage"` + InstrumentId string `json:"instrument_id"` +} + +// marginmode +//1:逐仓-多仓 +//2:逐仓-空仓 +//3:全仓 +func (ok *OKExSwap) SetMarginLevel(currencyPair CurrencyPair, level, marginMode int) (*MarginLeverage, error) { + var resp MarginLeverage + uri := fmt.Sprintf("/api/swap/v3/accounts/%s/leverage", ok.adaptContractType(currencyPair)) + + reqBody := make(map[string]string) + reqBody["leverage"] = strconv.Itoa(level) + reqBody["side"] = strconv.Itoa(marginMode) + data, err := json.Marshal(reqBody) + if err != nil { + return nil, err + } + + err = ok.DoRequest("POST", uri, string(data), &resp) + if err != nil { + return nil, err + } + return &resp, nil } diff --git a/okex/OKExSwap_test.go b/okex/OKExSwap_test.go index 4bb07711..a45eebfd 100644 --- a/okex/OKExSwap_test.go +++ b/okex/OKExSwap_test.go @@ -71,9 +71,14 @@ func TestOKExSwap_GetHistoricalFunding(t *testing.T) { func TestOKExSwap_GetKlineRecords(t *testing.T) { since := time.Now().Add(-24 * time.Hour).Unix() - t.Log(okExSwap.GetKlineRecords(goex.SWAP_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 0, int(since))) + kline, err := okExSwap.GetKlineRecords(goex.SWAP_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 0, int(since)) + t.Log(err, kline[0].Kline) } -func TestOKExSwap_GetKlineRecords2(t *testing.T) { - t.Log(okExSwap.GetKlineRecords2(goex.SWAP_CONTRACT, goex.BTC_USD, "", "", "")) +func TestOKExSwap_GetInstruments(t *testing.T) { + t.Log(okExSwap.GetInstruments()) +} + +func TestOKExSwap_SetMarginLevel(t *testing.T) { + t.Log(okExSwap.SetMarginLevel(goex.EOS_USDT, 5, 3)) } From aec7cd2ff3069601f44252dece50dc5a69bdea3e Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 30 Mar 2020 20:49:15 +0800 Subject: [PATCH 24/29] [okex] add GetFutureAccountInfo --- okex/OKExSwap.go | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index 772e7d87..5f5ab51b 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -223,6 +223,36 @@ func (ok *OKExSwap) GetFutureUserinfo() (*FutureAccount, error) { return &acc, nil } +type AccountInfo struct { + Info struct { + Currency string `json:"currency"` + Equity string `json:"equity"` + FixedBalance string `json:"fixed_balance"` + InstrumentID string `json:"instrument_id"` + MaintMarginRatio string `json:"maint_margin_ratio"` + Margin string `json:"margin"` + MarginFrozen string `json:"margin_frozen"` + MarginMode string `json:"margin_mode"` + MarginRatio string `json:"margin_ratio"` + MaxWithdraw string `json:"max_withdraw"` + RealizedPnl string `json:"realized_pnl"` + Timestamp string `json:"timestamp"` + TotalAvailBalance string `json:"total_avail_balance"` + Underlying string `json:"underlying"` + UnrealizedPnl string `json:"unrealized_pnl"` + } `json:"info"` +} + +func (ok *OKExSwap) GetFutureAccountInfo(currency CurrencyPair) (*AccountInfo, error) { + var infos AccountInfo + + err := ok.OKEx.DoRequest("GET", fmt.Sprintf("/api/swap/v3/%s/accounts", ok.adaptContractType(currency)), "", &infos) + if err != nil { + return nil, err + } + return &infos, nil +} + /* OKEX swap api parameter's definition @author Lingting Fu @@ -505,10 +535,10 @@ func (ok *OKExSwap) GetKlineRecords2(contractType string, currency CurrencyPair, return nil, err } - var klines []FutureKline + var kline []FutureKline for _, itm := range response { t, _ := time.Parse(time.RFC3339, fmt.Sprint(itm[0])) - klines = append(klines, FutureKline{ + kline = append(kline, FutureKline{ Kline: &Kline{ Timestamp: t.Unix(), Pair: currency, @@ -520,7 +550,7 @@ func (ok *OKExSwap) GetKlineRecords2(contractType string, currency CurrencyPair, Vol2: ToFloat64(itm[6])}) } - return klines, nil + return kline, nil } func (ok *OKExSwap) GetTrades(contractType string, currencyPair CurrencyPair, since int64) ([]Trade, error) { From febaca8b80723667c5364dfcf60070dac47d7781 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 30 Mar 2020 21:42:21 +0800 Subject: [PATCH 25/29] [okex] update struct --- okex/OKExSwap.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index 5f5ab51b..cf2ee777 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -225,21 +225,21 @@ func (ok *OKExSwap) GetFutureUserinfo() (*FutureAccount, error) { type AccountInfo struct { Info struct { - Currency string `json:"currency"` - Equity string `json:"equity"` - FixedBalance string `json:"fixed_balance"` - InstrumentID string `json:"instrument_id"` - MaintMarginRatio string `json:"maint_margin_ratio"` - Margin string `json:"margin"` - MarginFrozen string `json:"margin_frozen"` - MarginMode string `json:"margin_mode"` - MarginRatio string `json:"margin_ratio"` - MaxWithdraw string `json:"max_withdraw"` - RealizedPnl string `json:"realized_pnl"` - Timestamp string `json:"timestamp"` - TotalAvailBalance string `json:"total_avail_balance"` - Underlying string `json:"underlying"` - UnrealizedPnl string `json:"unrealized_pnl"` + Currency string `json:"currency"` + Equity float64 `json:"equity,string"` + FixedBalance float64 `json:"fixed_balance,string"` + InstrumentID string `json:"instrument_id"` + MaintMarginRatio float64 `json:"maint_margin_ratio,string"` + Margin float64 `json:"margin,string"` + MarginFrozen float64 `json:"margin_frozen,string"` + MarginMode string `json:"margin_mode"` + MarginRatio float64 `json:"margin_ratio,string"` + MaxWithdraw float64 `json:"max_withdraw,string"` + RealizedPnl float64 `json:"realized_pnl,string"` + Timestamp string `json:"timestamp"` + TotalAvailBalance float64 `json:"total_avail_balance,string"` + Underlying string `json:"underlying"` + UnrealizedPnl float64 `json:"unrealized_pnl,string"` } `json:"info"` } From 93076d194da087b3d4952be6c0348521da42d427 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 30 Mar 2020 22:54:40 +0800 Subject: [PATCH 26/29] [okex] update struct --- okex/OKExSwap.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index cf2ee777..b1010be0 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -595,15 +595,15 @@ type Instrument struct { UnderlyingIndex string `json:"underlying_index"` QuoteCurrency string `json:"quote_currency"` Coin string `json:"coin"` - ContractVal string `json:"contract_val"` + ContractVal float64 `json:"contract_val,string"` Listing time.Time `json:"listing"` Delivery time.Time `json:"delivery"` - SizeIncrement string `json:"size_increment"` - TickSize string `json:"tick_size"` + SizeIncrement int `json:"size_increment,string"` + TickSize float64 `json:"tick_size,string"` BaseCurrency string `json:"base_currency"` Underlying string `json:"underlying"` SettlementCurrency string `json:"settlement_currency"` - IsInverse string `json:"is_inverse"` + IsInverse bool `json:"is_inverse,string"` ContractValCurrency string `json:"contract_val_currency"` } @@ -617,10 +617,10 @@ func (ok *OKExSwap) GetInstruments() ([]Instrument, error) { } type MarginLeverage struct { - LongLeverage string `json:"long_leverage"` - MarginMode string `json:"margin_mode"` - ShortLeverage string `json:"short_leverage"` - InstrumentId string `json:"instrument_id"` + LongLeverage float64 `json:"long_leverage,string"` + MarginMode string `json:"margin_mode"` + ShortLeverage float64 `json:"short_leverage,string"` + InstrumentId string `json:"instrument_id"` } // marginmode From f55219bcfc56387916a2d99fdf13aae93c2182fa Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 31 Mar 2020 15:22:34 +0800 Subject: [PATCH 27/29] [okex] add get margin level for swap --- okex/OKExSwap.go | 14 +++++++++++++- okex/OKExSwap_test.go | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index b1010be0..a10b5669 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -525,7 +525,7 @@ func (ok *OKExSwap) GetKlineRecords2(contractType string, currency CurrencyPair, params.Set("end", end) } if period != "" { - params.Set("period", period) + params.Set("granularity", period) } contractId := ok.adaptContractType(currency) @@ -623,6 +623,18 @@ type MarginLeverage struct { InstrumentId string `json:"instrument_id"` } +func (ok *OKExSwap) GetMarginLevel(currencyPair CurrencyPair) (*MarginLeverage, error) { + var resp MarginLeverage + uri := fmt.Sprintf("/api/swap/v3/accounts/%s/settings", ok.adaptContractType(currencyPair)) + + err := ok.DoRequest("GET", uri, "", &resp) + if err != nil { + return nil, err + } + return &resp, nil + +} + // marginmode //1:逐仓-多仓 //2:逐仓-空仓 diff --git a/okex/OKExSwap_test.go b/okex/OKExSwap_test.go index a45eebfd..d49e3c89 100644 --- a/okex/OKExSwap_test.go +++ b/okex/OKExSwap_test.go @@ -75,6 +75,13 @@ func TestOKExSwap_GetKlineRecords(t *testing.T) { t.Log(err, kline[0].Kline) } +func TestOKExSwap_GetKlineRecords2(t *testing.T) { + start := time.Now().Add(time.Minute * -30).UTC().Format(time.RFC3339) + t.Log(start) + kline, err := okExSwap.GetKlineRecords2(goex.SWAP_CONTRACT, goex.BTC_USDT, start, "", "900") + t.Log(err, kline[0].Kline) +} + func TestOKExSwap_GetInstruments(t *testing.T) { t.Log(okExSwap.GetInstruments()) } @@ -82,3 +89,11 @@ func TestOKExSwap_GetInstruments(t *testing.T) { func TestOKExSwap_SetMarginLevel(t *testing.T) { t.Log(okExSwap.SetMarginLevel(goex.EOS_USDT, 5, 3)) } + +func TestOKExSwap_GetMarginLevel(t *testing.T) { + t.Log(okExSwap.GetMarginLevel(goex.EOS_USDT)) +} + +func TestOKExSwap_GetFutureAccountInfo(t *testing.T) { + t.Log(okExSwap.GetFutureAccountInfo(goex.BTC_USDT)) +} From 2ee44b4504b704982116baa51dad63586473d638 Mon Sep 17 00:00:00 2001 From: inkb Date: Tue, 7 Apr 2020 16:03:00 +0800 Subject: [PATCH 28/29] support kucoin sdk v1.2.7 --- README.md | 3 +- go.mod | 2 +- go.sum | 2 ++ kucoin/kucoin.go | 67 ++++++++++++++++++++++--------------------- kucoin/kucoin_test.go | 1 + 5 files changed, 40 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 3ca21ad3..ec9dc7ba 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ goex项目是为了统一并标准化各个数字资产交易平台的接口而 [English](https://github.com/nntaoli-project/goex/blob/dev/README_en.md) -### goex已支持交易所 `22+` +### goex已支持交易所 `23+` | 交易所 | 行情接口 | 交易接口 | 版本号 | | --- | --- | --- | --- | +| kucoin.com | Y | Y | 1 | | hbg.com | Y | Y | 1 | | hbdm.com | Y| Y | 1 | | okex.com | Y | Y | 3 | diff --git a/go.mod b/go.mod index 98993d9b..6c4d577f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/nntaoli-project/goex go 1.12 require ( - github.com/Kucoin/kucoin-go-sdk v1.2.2 + github.com/Kucoin/kucoin-go-sdk v1.2.7 github.com/deckarep/golang-set v1.7.1 // indirect github.com/go-openapi/errors v0.19.2 github.com/google/uuid v1.1.1 diff --git a/go.sum b/go.sum index fa7f588d..2aa4cfdb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Kucoin/kucoin-go-sdk v1.2.2 h1:q3w6gpQ9fGxZ77aWscdXYLwWeCBLJEUi3wi/1F0Qhz4= github.com/Kucoin/kucoin-go-sdk v1.2.2/go.mod h1:Wz3fTuM5gIct9chN6H6OBCXbku10XEcAjH5g/FL3wIY= +github.com/Kucoin/kucoin-go-sdk v1.2.7 h1:lh74YnCmcswmnvkk0nMeodw+y17UEjMhyEzrIS14SDs= +github.com/Kucoin/kucoin-go-sdk v1.2.7/go.mod h1:Wz3fTuM5gIct9chN6H6OBCXbku10XEcAjH5g/FL3wIY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/kucoin/kucoin.go b/kucoin/kucoin.go index b5d95fd7..25e8fd70 100644 --- a/kucoin/kucoin.go +++ b/kucoin/kucoin.go @@ -93,15 +93,15 @@ func (kc *KuCoin) GetTicker(currency CurrencyPair) (*Ticker, error) { func (kc *KuCoin) LimitBuy(amount, price string, currency CurrencyPair) (*Order, error) { clientID := GenerateOrderClientId(32) - params := map[string]string{ - "clientOid": clientID, - "side": "buy", - "symbol": currency.ToSymbol("-"), - "type": "limit", - "price": price, - "size": amount, + in := kucoin.CreateOrderModel{ + ClientOid: clientID, + Side: "buy", + Symbol: currency.ToSymbol("-"), + Type: "limit", + Price: price, + Size: amount, } - resp, err := kc.service.CreateOrder(params) + resp, err := kc.service.CreateOrder(&in) if err != nil { log.Error("KuCoin LimitBuy error:", err) return nil, err @@ -123,15 +123,15 @@ func (kc *KuCoin) LimitBuy(amount, price string, currency CurrencyPair) (*Order, func (kc *KuCoin) LimitSell(amount, price string, currency CurrencyPair) (*Order, error) { clientID := GenerateOrderClientId(32) - params := map[string]string{ - "clientOid": clientID, - "side": "sell", - "symbol": currency.ToSymbol("-"), - "type": "limit", - "price": price, - "size": amount, + in := kucoin.CreateOrderModel{ + ClientOid: clientID, + Side: "sell", + Symbol: currency.ToSymbol("-"), + Type: "limit", + Price: price, + Size: amount, } - resp, err := kc.service.CreateOrder(params) + resp, err := kc.service.CreateOrder(&in) if err != nil { log.Error("KuCoin LimitSell error:", err) return nil, err @@ -153,15 +153,16 @@ func (kc *KuCoin) LimitSell(amount, price string, currency CurrencyPair) (*Order func (kc *KuCoin) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { clientID := GenerateOrderClientId(32) - params := map[string]string{ - "clientOid": clientID, - "side": "buy", - "symbol": currency.ToSymbol("-"), - "type": "market", - "price": price, - "size": amount, + in := kucoin.CreateOrderModel{ + ClientOid: clientID, + Side: "buy", + Symbol: currency.ToSymbol("-"), + Type: "market", + Price: price, + Size: amount, } - resp, err := kc.service.CreateOrder(params) + + resp, err := kc.service.CreateOrder(&in) if err != nil { log.Error("KuCoin MarketBuy error:", err) return nil, err @@ -183,15 +184,15 @@ func (kc *KuCoin) MarketBuy(amount, price string, currency CurrencyPair) (*Order func (kc *KuCoin) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { clientID := GenerateOrderClientId(32) - params := map[string]string{ - "clientOid": clientID, - "side": "sell", - "symbol": currency.ToSymbol("-"), - "type": "market", - "price": price, - "size": amount, - } - resp, err := kc.service.CreateOrder(params) + in := kucoin.CreateOrderModel{ + ClientOid: clientID, + Side: "sell", + Symbol: currency.ToSymbol("-"), + Type: "market", + Price: price, + Size: amount, + } + resp, err := kc.service.CreateOrder(&in) if err != nil { log.Error("KuCoin MarketSell error:", err) return nil, err diff --git a/kucoin/kucoin_test.go b/kucoin/kucoin_test.go index 2917df80..c538a706 100644 --- a/kucoin/kucoin_test.go +++ b/kucoin/kucoin_test.go @@ -27,3 +27,4 @@ func TestKuCoin_GetTrades(t *testing.T) { trades, _ := kc.GetTrades(goex.BTC_USDT, 0) t.Log(trades) } + From 5c83fb2900b448306208bf43efdbfb40ebf56561 Mon Sep 17 00:00:00 2001 From: inkb Date: Tue, 7 Apr 2020 16:21:10 +0800 Subject: [PATCH 29/29] update README.md --- README.md | 2 +- README_en.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec9dc7ba..cc47c2b2 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ goex项目是为了统一并标准化各个数字资产交易平台的接口而 | 交易所 | 行情接口 | 交易接口 | 版本号 | | --- | --- | --- | --- | -| kucoin.com | Y | Y | 1 | | hbg.com | Y | Y | 1 | | hbdm.com | Y| Y | 1 | | okex.com | Y | Y | 3 | | binance.com | Y | Y | 1 | +| kucoin.com | Y | Y | 1 | | bitstamp.net | Y | Y | 1 | | bitfinex.com | Y | Y | 1 | | zb.com | Y | Y | 1 | diff --git a/README_en.md b/README_en.md index 5eb62a30..921944d8 100644 --- a/README_en.md +++ b/README_en.md @@ -8,7 +8,7 @@ goex project is designed to unify and standardize the interfaces of each digital [中文](https://github.com/nntaoli-project/goex/blob/dev/README.md) -### Exchanges are supported by goex `22+` +### Exchanges are supported by goex `23+` | Exchange | Market API | Order API | Version | | --- | --- | --- | --- | | hbg.com | Y | Y | 1 | @@ -16,6 +16,7 @@ goex project is designed to unify and standardize the interfaces of each digital | okex.com (spot/future)| Y (REST / WS) | Y | 1 | | okex.com (swap future) | Y | Y | 2 | | binance.com | Y | Y | 1 | +| kucoin.com | Y | Y | 1 | | bitstamp.net | Y | Y | 1 | | bitfinex.com | Y | Y | 1 | | zb.com | Y | Y | 1 |