diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..c23774cd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + "env": {}, + "args": [] + } + ] +} \ No newline at end of file diff --git a/Models.go b/Models.go index 916a4fc4..ae341fa1 100644 --- a/Models.go +++ b/Models.go @@ -191,6 +191,21 @@ type HistoricalFunding struct { FundingTime time.Time `json:"funding_time"` } +type TickSize struct { + InstrumentID string + UnderlyingIndex string + QuoteCurrency string + PriceTickSize float64 //下单价格精度 + AmountTickSize float64 //数量精度 +} + +type FuturesContractInfo struct { + *TickSize + ContractVal float64 //合约面值(美元) + Delivery string //交割日期 + ContractType string // 本周 this_week 次周 next_week 季度 quarter +} + //api parameter struct type BorrowParameter struct { diff --git a/Utils.go b/Utils.go index 9d68b0c6..0222988e 100644 --- a/Utils.go +++ b/Utils.go @@ -110,7 +110,7 @@ func ValuesToJson(v url.Values) ([]byte, error) { return json.Marshal(parammap) } -func GzipUnCompress(data []byte) ([]byte, error) { +func GzipDecompress(data []byte) ([]byte, error) { r, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { return nil, err @@ -118,7 +118,7 @@ func GzipUnCompress(data []byte) ([]byte, error) { return ioutil.ReadAll(r) } -func FlateUnCompress(data []byte) ([]byte, error) { +func FlateDecompress(data []byte) ([]byte, error) { return ioutil.ReadAll(flate.NewReader(bytes.NewReader(data))) } diff --git a/WsApi.go b/WsApi.go new file mode 100644 index 00000000..425d96e2 --- /dev/null +++ b/WsApi.go @@ -0,0 +1,35 @@ +package goex + +type FuturesWsApi interface { + DepthCallback(func(depth *Depth)) + TickerCallback(func(ticker *FutureTicker)) + TradeCallback(func(trade *Trade, contract string)) + //OrderCallback(func(order *FutureOrder)) + //PositionCallback(func(position *FuturePosition)) + //AccountCallback(func(account *FutureAccount)) + + SubscribeDepth(pair CurrencyPair, contractType string) error + SubscribeTicker(pair CurrencyPair, contractType string) error + SubscribeTrade(pair CurrencyPair, contractType string) error + + //Login() error + //SubscribeOrder(pair CurrencyPair, contractType string) error + //SubscribePosition(pair CurrencyPair, contractType string) error + //SubscribeAccount(pair CurrencyPair) error +} + +type SpotWsApi interface { + DepthCallback(func(depth *Depth)) + TickerCallback(func(ticker *Ticker)) + TradeCallback(func(trade *Trade)) + //OrderCallback(func(order *Order)) + //AccountCallback(func(account *Account)) + + SubscribeDepth(pair CurrencyPair) error + SubscribeTicker(pair CurrencyPair) error + SubscribeTrade(pair CurrencyPair) error + + //Login() error + //SubscribeOrder(pair CurrencyPair) error + //SubscribeAccount(pair CurrencyPair) error +} diff --git a/aacoin/aacoin.go b/aacoin/aacoin.go deleted file mode 100755 index e4f30ca4..00000000 --- a/aacoin/aacoin.go +++ /dev/null @@ -1,445 +0,0 @@ -package aacoin - -import ( - . "github.com/nntaoli-project/goex" - "net/http" - "net/url" - - "encoding/json" - "errors" - "log" - "sort" - "strconv" - "strings" - "time" -) - -const ( - host = "https://api.aacoin.com/v1" -) - -type Aacoin struct { - accessKey, - secretKey string - httpClient *http.Client -} - -func New(client *http.Client, api_key, secret_key string) *Aacoin { - return &Aacoin{accessKey: api_key, secretKey: secret_key, httpClient: client} -} - -func (aa *Aacoin) GetExchangeName() string { - return "aacoin.com" -} - -func (aa *Aacoin) buildSigned(params *url.Values) string { - - //log.Println("params", params.Encode()) - params.Set("accessKey", aa.accessKey) - s, _ := GetParamHmacSHA256Sign(aa.secretKey, params.Encode()) - params.Set("sign", s) - - return s -} - -func (aa *Aacoin) GetAccount() (*Account, error) { - api_url := host + "/account/accounts" - - params := url.Values{} - //params.Set("accessKey", aa.accessKey) - //params.Set("sign", aa.buildSigned(¶ms)) - aa.buildSigned(¶ms) - body, err := HttpPostForm(aa.httpClient, api_url, params) - if err != nil { - return nil, err - } - //log.Println("body", string(body)) - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(body, &bodyDataMap) - if err != nil { - // log.Println("respData", string(body)) - return nil, err - } - - if bodyDataMap["status"].(string) != "1000" { - return nil, errors.New(bodyDataMap["msg"].(string)) - } - - balances, isok := bodyDataMap["data"].([]interface{}) - if isok != true { - return nil, errors.New("No account data!") - } - - acc := Account{} - acc.Exchange = aa.GetExchangeName() - acc.SubAccounts = make(map[Currency]SubAccount) - - for _, v := range balances { - vv := v.(map[string]interface{}) - currency := NewCurrency(vv["currencyCode"].(string), "") - trade := 0.0 - frozen := 0.0 - vvv := vv["accounts"].([]interface{}) - for _, vvvv := range vvv { - vvvvv := vvvv.(map[string]interface{}) - if vvvvv["type"].(string) == "trade" { - trade = vvvvv["balance"].(float64) - } else if vvvvv["type"].(string) == "frozen" { - frozen = vvvvv["balance"].(float64) - } - } - - acc.SubAccounts[currency] = SubAccount{ - Currency: currency, - Amount: trade + frozen, - ForzenAmount: frozen, - } - } - return &acc, nil -} - -func (aa *Aacoin) placeOrder(amount, price string, pair CurrencyPair, orderType, orderSide string) (*Order, error) { - path := host + "/order/place" - params := url.Values{} - params.Set("symbol", pair.String()) - params.Set("type", orderSide+"-"+orderType) - params.Set("quantity", amount) - params.Set("price", price) - aa.buildSigned(¶ms) - - resp, err := HttpPostForm(aa.httpClient, path, params) - log.Println("resp:", string(resp), "err:", err) - if err != nil { - return nil, err - } - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(resp, &bodyDataMap) - if err != nil { - // log.Println("respData", string(resp)) - return nil, err - } - - if bodyDataMap["status"].(string) != "1000" { - return nil, errors.New(bodyDataMap["msg"].(string)) - } - - side := BUY - if orderSide == "ASK" { - side = SELL - } - - return &Order{ - Currency: pair, - OrderID2: strconv.FormatFloat(ToFloat64(bodyDataMap["data"]), 'f', 30, 64), - Price: ToFloat64(price), - Amount: ToFloat64(amount), - DealAmount: 0, - AvgPrice: 0, - Side: TradeSide(side), - Status: ORDER_UNFINISH, - OrderTime: int(time.Now().Unix())}, nil -} - -func (aa *Aacoin) LimitBuy(amount, price string, currencyPair CurrencyPair) (*Order, error) { - return aa.placeOrder(amount, price, currencyPair, "limit", "buy") -} - -func (aa *Aacoin) LimitSell(amount, price string, currencyPair CurrencyPair) (*Order, error) { - return aa.placeOrder(amount, price, currencyPair, "limit", "sell") -} - -func (aa *Aacoin) MarketBuy(amount, price string, currencyPair CurrencyPair) (*Order, error) { - return aa.placeOrder(amount, price, currencyPair, "market", "sell") -} - -func (aa *Aacoin) MarketSell(amount, price string, currencyPair CurrencyPair) (*Order, error) { - return aa.placeOrder(amount, price, currencyPair, "market", "sell") -} - -func (aa *Aacoin) CancelOrder(orderId string, currencyPair CurrencyPair) (bool, error) { - path := host + "/order/cancel" - params := url.Values{} - params.Set("orderId", orderId) - aa.buildSigned(¶ms) - - resp, err := HttpPostForm(aa.httpClient, path, params) - - // log.Println("resp:", string(resp), "err:", err) - if err != nil { - return false, err - } - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(resp, &bodyDataMap) - if err != nil { - // log.Println("respData", string(resp)) - return false, err - } - - if bodyDataMap["status"].(string) != "1000" { - return false, errors.New(bodyDataMap["msg"].(string)) - } - - return true, nil -} - -func (aa *Aacoin) CancelOrders(orderId []string) (bool, error) { - path := host + "/order/batchCancel" - params := url.Values{} - - orders := strings.Join(orderId, ",") - //log.Println("orders", orders) - params.Set("orderIds", orders) - aa.buildSigned(¶ms) - - resp, err := HttpPostForm(aa.httpClient, path, params) - - // log.Println("resp:", string(resp), "err:", err) - if err != nil { - return false, err - } - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(resp, &bodyDataMap) - if err != nil { - // log.Println("respData", string(resp)) - return false, err - } - - if bodyDataMap["status"].(string) != "1000" { - return false, errors.New(bodyDataMap["msg"].(string)) - } - - return true, nil -} - -func (aa *Aacoin) GetOneOrder(orderId string, currencyPair CurrencyPair) (*Order, error) { - //path := API_BASE_URL + TRADE_URI + "/" + orderId - // - //respmap, err := HttpGet2(aa.httpClient, path, aa.privateHeader()) - ////log.Println(respmap) - //if err != nil { - // return nil, err - //} - //_order, isok := respmap["data"].(map[string]interface{}) - //if !isok { - // return nil, errors.New("no order data") - //} - //status, _ := _order["order_state"].(string) - //side, _ := _order["order_side"].(string) - // - //ord := Order{} - //ord.Currency = currencyPair - //ord.OrderID2 = orderId - // - //if side == "ASK" { - // ord.Side = SELL - //} else { - // ord.Side = BUY - //} - // - //switch status { - //case "open": - // ord.Status = ORDER_UNFINISH - //case "filled": - // ord.Status = ORDER_FINISH - // //case "PARTIALLY_FILLED": - // // ord.Status = ORDER_PART_FINISH - //case "canceled": - // ord.Status = ORDER_CANCEL - // //case "PENDING_CANCEL": - // // ord.Status = ORDER_CANCEL_ING - // //case "REJECTED": - // // ord.Status = ORDER_REJECT - //} - // - //ord.Amount = ToFloat64(_order["amount"].(string)) - //ord.Price = ToFloat64(_order["price"].(string)) - //ord.DealAmount = ToFloat64(_order["filled_amount"]) - //ord.AvgPrice = ord.Price // response no avg price , fill price - // - //return &ord, nil - return nil, nil -} -func (aa *Aacoin) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, error) { - path := host + "/order/currentOrders" - params := url.Values{} - - params.Set("symbol", currencyPair.String()) - aa.buildSigned(¶ms) - - resp, err := HttpPostForm(aa.httpClient, path, params) - - log.Println("resp:", string(resp), "err:", err) - if err != nil { - return nil, err - } - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(resp, &bodyDataMap) - if err != nil { - // log.Println("respData", string(resp)) - return nil, err - } - - if bodyDataMap["status"].(string) != "1000" { - return nil, errors.New(bodyDataMap["msg"].(string)) - } - data := bodyDataMap["data"].(map[string]interface{}) - list := data["list"].([]interface{}) - var orders []Order - for _, v := range list { - vv := v.(map[string]interface{}) - //yyyy-MM-dd HH:mm:ss - ordertime, _ := time.Parse("2016-02-02 11:11:11", vv["orderTime"].(string)) - orders = append(orders, Order{ - Currency: NewCurrencyPair2(vv["symbol"].(string)), - OrderID2: vv["orderId"].(string), - Price: ToFloat64(vv["price"]), - Amount: ToFloat64(vv["quantity"]), - DealAmount: ToFloat64(vv["filledQuantity"]), - Side: orderTypeAdapter(vv["type"].(string)), - Status: orderStatusAdapter(vv["status"].(string)), - OrderTime: int(ordertime.Unix()), - }) - - } - - return nil, nil -} -func (aa *Aacoin) GetOrderHistorys(currencyPair CurrencyPair, currentPage, pageSize int) ([]Order, error) { - return nil, nil -} -func (aa *Aacoin) GetDepth(size int, currencyPair CurrencyPair) (*Depth, error) { - path := host + "/market/depth" - params := url.Values{} - - params.Set("symbol", currencyPair.String()) - //aa.buildSigned(¶ms) - - resp, err := HttpPostForm(aa.httpClient, path, params) - - //log.Println("resp:", string(resp), "err:", err) - if err != nil { - return nil, err - } - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(resp, &bodyDataMap) - if err != nil { - // log.Println("respData", string(resp)) - return nil, err - } - - if bodyDataMap["status"].(string) != "1000" { - return nil, errors.New(bodyDataMap["msg"].(string)) - } - - dep := bodyDataMap["data"].(map[string]interface{}) - - bids, _ := dep["bids"].([]interface{}) - asks, _ := dep["asks"].([]interface{}) - - depth := new(Depth) - //i := 0 - for _, bid := range bids { - _bid := bid.([]interface{}) - amount := ToFloat64(_bid[1]) - price := ToFloat64(_bid[0]) - dr := DepthRecord{Amount: amount, Price: price} - depth.BidList = append(depth.BidList, dr) - //if i < size { - // i++ - //} else { - // break - //} - } - - //i = 0 - for _, ask := range asks { - _ask := ask.([]interface{}) - amount := ToFloat64(_ask[1]) - price := ToFloat64(_ask[0]) - dr := DepthRecord{Amount: amount, Price: price} - depth.AskList = append(depth.AskList, dr) - //if i < size { - // i++ - //} else { - // break - //} - } - sort.Sort(depth.AskList) - sort.Sort(sort.Reverse(depth.BidList)) - depth.AskList = depth.AskList[0:size] - depth.BidList = depth.BidList[0:size] - //log.Println(depth) - return depth, nil -} - -func (aa *Aacoin) GetTicker(currencyPair CurrencyPair) (*Ticker, error) { - path := host + "/market/detail" - params := url.Values{} - - params.Set("symbol", currencyPair.String()) - - resp, err := HttpPostForm(aa.httpClient, path, params) - - // log.Println("resp:", string(resp), "err:", err) - if err != nil { - return nil, err - } - - var bodyDataMap map[string]interface{} - err = json.Unmarshal(resp, &bodyDataMap) - if err != nil { - // log.Println("respData", string(resp)) - return nil, err - } - - if bodyDataMap["status"].(string) != "1000" { - return nil, errors.New(bodyDataMap["msg"].(string)) - } - tickerMap := bodyDataMap["data"].(map[string]interface{}) - var ticker Ticker - - ticker.Date = uint64(time.Now().Unix()) - ticker.Last = ToFloat64(tickerMap["current"]) - ticker.Buy = ToFloat64(tickerMap["buy"]) - ticker.Sell = ToFloat64(tickerMap["sell"]) - ticker.Low = ToFloat64(tickerMap["lowest"]) - ticker.High = ToFloat64(tickerMap["highest"]) - ticker.Vol = ToFloat64(tickerMap["totalTradeAmount"]) - return &ticker, nil - -} - -func (aa *Aacoin) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - panic("not implement") -} - -//非个人,整个交易所的交易记录 -func (aa *Aacoin) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - panic("not implement") -} - -func orderTypeAdapter(t string) TradeSide { - switch t { - case "sell": - return SELL - case "buy": - return BUY - } - return BUY -} - -func orderStatusAdapter(s string) TradeStatus { - switch s { - case "open": - return ORDER_UNFINISH - case "partial_filled": - return ORDER_PART_FINISH - } - return ORDER_FINISH -} diff --git a/aacoin/aacoin_test.go b/aacoin/aacoin_test.go deleted file mode 100644 index 75fee2ed..00000000 --- a/aacoin/aacoin_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package aacoin - -import ( - "github.com/nntaoli-project/goex" - "net/http" - "net/url" - "testing" -) - -var aa = New(http.DefaultClient, "xxx", "b0xxxxxx-c6xxxxxx-94xxxxxx-dxxxx") - -func TestAacoin_GetExchangeName(t *testing.T) { - return - params := url.Values{} - params.Set("accessKey", aa.accessKey) - params.Set("price", aa.accessKey) - params.Set("quantity", aa.accessKey) - params.Set("symbol", aa.accessKey) - params.Set("type", aa.accessKey) - t.Log(aa.buildSigned(¶ms)) -} -func TestAacoin_GetAccount(t *testing.T) { - return - t.Log(aa.GetAccount()) -} -func TestAacoin_GetTicker(t *testing.T) { - return - t.Log(aa.GetTicker(goex.BTC_USDT)) -} -func TestAacoin_GetDepth(t *testing.T) { - return - t.Log(aa.GetDepth(1, goex.BTC_USDT)) -} -func TestAacoin_LimitSell(t *testing.T) { - //return - t.Log(aa.LimitSell("1", "1000000", goex.BTC_USDT)) -} - -func TestAacoin_LimitBuy(t *testing.T) { - t.Log(aa.LimitBuy("1", "1", goex.BTC_USDT)) -} - -func TestAacoin_GetUnfinishOrders(t *testing.T) { - t.Log(aa.GetUnfinishOrders(goex.BTC_USDT)) -} diff --git a/binance/BinanceWs.go b/binance/BinanceWs.go index 8359a239..0bb2eb5d 100644 --- a/binance/BinanceWs.go +++ b/binance/BinanceWs.go @@ -92,7 +92,7 @@ func (bnWs *BinanceWs) SetCallbacks( bnWs.klineCallback = klineCallback } -func (bnWs *BinanceWs) subscribe(endpoint string, handle func(msg []byte) error) { +func (bnWs *BinanceWs) Subscribe(endpoint string, handle func(msg []byte) error) { wsConn := NewWsBuilder(). WsUrl(endpoint). AutoReconnect(). @@ -121,9 +121,9 @@ func (bnWs *BinanceWs) SubscribeDepth(pair CurrencyPair, size int) error { handle := func(msg []byte) error { rawDepth := struct { - LastUpdateID int64 `json:"lastUpdateId"` - Bids [][]interface{} `json:"bids"` - Asks [][]interface{} `json:"asks"` + LastUpdateID int64 `json:"T"` + Bids [][]interface{} `json:"b"` + Asks [][]interface{} `json:"a"` }{} err := json.Unmarshal(msg, &rawDepth) @@ -137,7 +137,7 @@ func (bnWs *BinanceWs) SubscribeDepth(pair CurrencyPair, size int) error { bnWs.depthCallback(depth) return nil } - bnWs.subscribe(endpoint, handle) + bnWs.Subscribe(endpoint, handle) return nil } @@ -170,7 +170,7 @@ func (bnWs *BinanceWs) SubscribeTicker(pair CurrencyPair) error { return errors.New("unknown message " + msgType) } } - bnWs.subscribe(endpoint, handle) + bnWs.Subscribe(endpoint, handle) return nil } @@ -217,7 +217,7 @@ func (bnWs *BinanceWs) SubscribeTrade(pair CurrencyPair) error { return errors.New("unknown message " + msgType) } } - bnWs.subscribe(endpoint, handle) + bnWs.Subscribe(endpoint, handle) return nil } @@ -256,7 +256,7 @@ func (bnWs *BinanceWs) SubscribeKline(pair CurrencyPair, period int) error { return errors.New("unknown message " + msgType) } } - bnWs.subscribe(endpoint, handle) + bnWs.Subscribe(endpoint, handle) return nil } @@ -341,7 +341,7 @@ func (bnWs *BinanceWs) SubscribeAggTrade(pair CurrencyPair, tradeCallback func(* return errors.New("unknown message " + msgType) } } - bnWs.subscribe(endpoint, handle) + bnWs.Subscribe(endpoint, handle) return nil } @@ -380,7 +380,7 @@ func (bnWs *BinanceWs) SubscribeDiffDepth(pair CurrencyPair, depthCallback func( depthCallback((*Depth)(unsafe.Pointer(diffDepth))) return nil } - bnWs.subscribe(endpoint, handle) + bnWs.Subscribe(endpoint, handle) return nil } diff --git a/binance/BinanceWs_test.go b/binance/BinanceWs_test.go index 1950d2af..b31d127b 100644 --- a/binance/BinanceWs_test.go +++ b/binance/BinanceWs_test.go @@ -12,6 +12,8 @@ var bnWs = NewBinanceWs() func init() { bnWs.proxyUrl = "socks5://127.0.0.1:1080" + //bnWs.SetBaseUrl("wss://fstream.binancezh.com/ws") + //bnWs.SetCombinedBaseURL("wss://fstream.binancezh.com/stream?streams=") bnWs.SetCallbacks(printfTicker, printfDepth, printfTrade, printfKline) } diff --git a/bitstamp/Bitstamp_test.go b/bitstamp/Bitstamp_test.go index 08bd3116..35e3f775 100644 --- a/bitstamp/Bitstamp_test.go +++ b/bitstamp/Bitstamp_test.go @@ -47,16 +47,14 @@ func TestBitstamp_LimitSell(t *testing.T) { t.Log(ord) } - func TestBitstamp_MarketBuy(t *testing.T) { - ord, err := btmp.MarketBuy("1", goex.XRP_USD) + ord, err := btmp.MarketBuy("1", "", goex.XRP_USD) assert.Nil(t, err) t.Log(ord) } - func TestBitstamp_MarketSell(t *testing.T) { - ord, err := btmp.MarketSell("2", goex.XRP_USD) + ord, err := btmp.MarketSell("2", "", goex.XRP_USD) assert.Nil(t, err) t.Log(ord) } diff --git a/builder/APIBuilder.go b/builder/APIBuilder.go index 122fe6e7..e2835fea 100644 --- a/builder/APIBuilder.go +++ b/builder/APIBuilder.go @@ -2,6 +2,7 @@ package builder import ( "context" + "errors" "fmt" . "github.com/nntaoli-project/goex" @@ -311,3 +312,26 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { return nil } } + +func (builder *APIBuilder) BuildFuturesWs(exName string) (FuturesWsApi, error) { + switch exName { + case OKEX_V3, OKEX, OKEX_FUTURE: + return okex.NewOKExV3FuturesWs(okex.NewOKEx(&APIConfig{ + HttpClient: builder.client, + Endpoint: builder.futuresEndPoint, + })), nil + case HBDM: + return huobi.NewHbdmWs(), nil + } + return nil, errors.New("not support the exchange " + exName) +} + +func (builder *APIBuilder) BuildSpotWs(exName string) (SpotWsApi, error) { + switch exName { + case OKEX_V3, OKEX: + return okex.NewOKExSpotV3Ws(nil), nil + case HUOBI_PRO, HUOBI: + return huobi.NewSpotWs(), nil + } + return nil, errors.New("not support the exchange " + exName) +} diff --git a/builder/APIBuilder_test.go b/builder/APIBuilder_test.go index d8463ffe..0c6d987f 100644 --- a/builder/APIBuilder_test.go +++ b/builder/APIBuilder_test.go @@ -2,12 +2,19 @@ package builder import ( "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" "github.com/stretchr/testify/assert" + "log" "testing" + "time" ) var builder = NewAPIBuilder() +func init() { + logger.SetLevel(logger.INFO) +} + func TestAPIBuilder_Build(t *testing.T) { assert.Equal(t, builder.APIKey("").APISecretkey("").Build(goex.OKCOIN_COM).GetExchangeName(), goex.OKCOIN_COM) assert.Equal(t, builder.APIKey("").APISecretkey("").Build(goex.HUOBI_PRO).GetExchangeName(), goex.HUOBI_PRO) @@ -19,3 +26,23 @@ func TestAPIBuilder_Build(t *testing.T) { assert.Equal(t, builder.APIKey("").APISecretkey("").Build(goex.FCOIN_MARGIN).GetExchangeName(), goex.FCOIN_MARGIN) assert.Equal(t, builder.APIKey("").APISecretkey("").BuildFuture(goex.HBDM).GetExchangeName(), goex.HBDM) } + +func TestAPIBuilder_BuildSpotWs(t *testing.T) { + //os.Setenv("HTTPS_PROXY" , "socks5://127.0.0.1:1080") + wsApi , _ := builder.BuildSpotWs(goex.OKEX_V3) + wsApi.DepthCallback(func(depth *goex.Depth) { + log.Println(depth) + }) + wsApi.SubscribeDepth(goex.BTC_USDT) + time.Sleep(time.Minute) +} + +func TestAPIBuilder_BuildFuturesWs(t *testing.T) { + //os.Setenv("HTTPS_PROXY" , "socks5://127.0.0.1:1080") + wsApi , _ := builder.BuildFuturesWs(goex.OKEX_V3) + wsApi.DepthCallback(func(depth *goex.Depth) { + log.Println(depth) + }) + wsApi.SubscribeDepth(goex.BTC_USD , goex.QUARTER_CONTRACT) + time.Sleep(time.Minute) +} diff --git a/coinpark/coinpark.go b/coinpark/coinpark.go deleted file mode 100644 index 173a6a3a..00000000 --- a/coinpark/coinpark.go +++ /dev/null @@ -1,433 +0,0 @@ -package coinpark - -import ( - "net/http" - //"log" - "fmt" - . "github.com/nntaoli-project/goex" - //"net/url" - "encoding/json" - "errors" - "log" - "math/rand" - "strconv" - "strings" - "time" -) - -const ( - API_BASE_URL = "https://api.coinpark.cc/" - V1 = "v1/" - API_URL = API_BASE_URL + V1 - TICKER_API = "mdata?cmd=ticker&pair=%s" - ALL_TICKERs = "mdata?cmd=marketAll" - Pair_List = "mdata?cmd=pairList" - DEPTH_API = "mdata?cmd=depth&pair=%s&size=%d" - - TRADE_URL = "orderpending" - GET_ACCOUNT_API = "transfer" - GET_ORDER_API = "orders/%s" - //GET_ORDERS_LIST_API = "" - GET_UNFINISHED_ORDERS_API = "getUnfinishedOrdersIgnoreTradeType" - CANCEL_ORDER_API = TRADE_URL + "/%s/submit-cancel" - PLACE_ORDER_API = "order" - WITHDRAW_API = "withdraw" - CANCELWITHDRAW_API = "cancelWithdraw" -) - -type Cpk struct { - httpClient *http.Client - accessKey, - secretKey string -} - -func New(client *http.Client, apikey, secretkey string) *Cpk { - return &Cpk{accessKey: apikey, secretKey: secretkey, httpClient: client} -} - -func (c *Cpk) buildSigned(cmd string) string { - signed, _ := GetParamHmacMD5Sign(c.secretKey, cmd) - return signed -} - -func (c *Cpk) GetExchangeName() string { - return "coinpark.cc" -} - -func (c *Cpk) GetServerTime() error { - return nil -} - -func (c *Cpk) GetTicker(currencyPair CurrencyPair) (*Ticker, error) { - url := API_URL + fmt.Sprintf(TICKER_API, currencyPair.String()) - respmap, err := HttpGet(c.httpClient, url) - if err != nil { - return nil, err - } - //log.Println("ticker respmap:", respmap) - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return nil, errors.New(errcode["msg"].(string)) - } - - tickmap, ok := respmap["result"].(map[string]interface{}) - if !ok { - return nil, errors.New("tick assert error") - } - ticker := new(Ticker) - ticker.Pair = currencyPair - ticker.Date = uint64(time.Now().Nanosecond() / 1000) - ticker.Last = ToFloat64(tickmap["last"]) - ticker.Vol = ToFloat64(tickmap["vol"]) - ticker.Low = ToFloat64(tickmap["low"]) - ticker.High = ToFloat64(tickmap["high"]) - ticker.Buy = ToFloat64(tickmap["buy"]) - ticker.Sell = ToFloat64(tickmap["sell"]) - return ticker, nil - -} - -func (c *Cpk) GetDepth(size int, currency CurrencyPair) (*Depth, error) { - url := API_URL + fmt.Sprintf(DEPTH_API, currency.String(), size) - respmap, err := HttpGet(c.httpClient, url) - if err != nil { - return nil, err - } - - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return nil, errors.New(errcode["msg"].(string)) - } - - bids, ok1 := respmap["bids"].([]interface{}) - asks, ok2 := respmap["asks"].([]interface{}) - - if !ok1 || !ok2 { - return nil, errors.New("tick assert error") - } - - depth := new(Depth) - - for _, r := range asks { - var dr DepthRecord - rr := r.([]interface{}) - dr.Price = ToFloat64(rr[0]) - dr.Amount = ToFloat64(rr[1]) - depth.AskList = append(depth.AskList, dr) - } - - for _, r := range bids { - var dr DepthRecord - rr := r.([]interface{}) - dr.Price = ToFloat64(rr[0]) - dr.Amount = ToFloat64(rr[1]) - depth.BidList = append(depth.BidList, dr) - } - - return depth, nil -} - -func (c *Cpk) placeOrder(orderType, orderSide, amount, price string, pair CurrencyPair) (*Order, error) { - path := API_URL + TRADE_URL - params := make(map[string]interface{}) - params["cmd"] = "orderpending/trade" - params["index"] = strconv.Itoa(rand.Intn(1000)) - - body := make(map[string]interface{}) - body["pair"] = pair.String() - body["account_type"] = "0" - if orderType == "limit" { - body["order_type"] = "2" - } else if orderType == "market" { - body["order_type"] = "1" - body["money"] = ToFloat64(price) * ToFloat64(amount) - } - if orderSide == "buy" { - body["order_side"] = "1" - } else if orderSide == "sell" { - body["order_side"] = "2" - } - body["price"] = price - body["amount"] = amount - params["body"] = body - - cmd, _ := json.Marshal(params) - cmds := "[" + string(cmd) + "]" - sign := c.buildSigned(cmds) - - param := make(map[string]string) - //param["cmds"] = strconv.Quote("["+string(cmds)+"]") - param["cmds"] = cmds - param["apikey"] = c.accessKey - param["sign"] = sign - resp, err := HttpPostForm4(c.httpClient, path, param, nil) - if err != nil { - return nil, err - } - respmap := make(map[string]interface{}) - json.Unmarshal(resp, &respmap) - - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return nil, errors.New(errcode["msg"].(string)) - } - - orderId := ToInt(respmap["result"]) - if orderId <= 0 { - return nil, errors.New(string(resp)) - } - - side := BUY - if orderSide == "sell" { - side = SELL - } - - return &Order{ - Currency: pair, - OrderID: ToInt(respmap["index"]), - OrderID2: respmap["result"].(string), - Price: ToFloat64(price), - Amount: ToFloat64(amount), - DealAmount: 0, - AvgPrice: 0, - Side: TradeSide(side), - Status: ORDER_UNFINISH, - OrderTime: int(time.Now().Unix())}, nil -} - -func (c *Cpk) LimitBuy(amount, price string, currency CurrencyPair) (*Order, error) { - return c.placeOrder("limit", "buy", amount, price, currency) -} - -func (c *Cpk) LimitSell(amount, price string, currency CurrencyPair) (*Order, error) { - return c.placeOrder("limit", "sell", amount, price, currency) -} - -func (c *Cpk) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { - return c.placeOrder("market", "buy", amount, price, currency) -} -func (c *Cpk) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { - return c.placeOrder("market", "sell", amount, price, currency) -} - -func (c *Cpk) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { - path := API_URL + TRADE_URL - params := make(map[string]interface{}) - params["cmd"] = "orderpending/cancelTrade" - params["index"] = strconv.Itoa(rand.Intn(1000)) - - body := make(map[string]interface{}) - body["orders_id"] = orderId - params["body"] = body - - cmd, _ := json.Marshal(params) - cmds := "[" + string(cmd) + "]" - sign := c.buildSigned(cmds) - - param := make(map[string]string) - //param["cmds"] = strconv.Quote("["+string(cmds)+"]") - param["cmds"] = cmds - param["apikey"] = c.accessKey - param["sign"] = sign - resp, err := HttpPostForm4(c.httpClient, path, param, nil) - if err != nil { - return false, err - } - respmap := make(map[string]interface{}) - json.Unmarshal(resp, &respmap) - - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return false, errors.New(errcode["msg"].(string)) - } - - status := respmap["result"].(string) - if strings.Contains(status, "撤销中") { - return true, nil - } - return false, errors.New("fail") -} - -func (c *Cpk) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) { - path := API_URL + TRADE_URL - params := make(map[string]interface{}) - params["cmd"] = "orderpending/order" - - body := make(map[string]interface{}) - body["id"] = orderId - params["body"] = body - - cmd, _ := json.Marshal(params) - cmds := "[" + string(cmd) + "]" - sign := c.buildSigned(cmds) - - param := make(map[string]string) - //param["cmds"] = strconv.Quote("["+string(cmds)+"]") - param["cmds"] = cmds - param["apikey"] = c.accessKey - param["sign"] = sign - resp, err := HttpPostForm4(c.httpClient, path, param, nil) - if err != nil { - return nil, err - } - respmap := make(map[string]interface{}) - json.Unmarshal(resp, &respmap) - - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return nil, errors.New(errcode["msg"].(string)) - } - - orderInfo := respmap["result"].(map[string]interface{}) - status := ToInt(orderInfo["status"]) - var orderState TradeStatus - switch status { - case 1: - orderState = ORDER_UNFINISH - case 2: - orderState = ORDER_PART_FINISH - case 3: - orderState = ORDER_FINISH - case 4, 5: - orderState = ORDER_CANCEL - case 6: - orderState = ORDER_CANCEL_ING - } - var side TradeSide - switch ToInt(orderInfo["order_side"]) { - case 1: - side = BUY - case 2: - side = SELL - } - return &Order{ - Currency: currency, - OrderID2: orderInfo["id"].(string), - Price: ToFloat64(orderInfo["price"]), - Amount: ToFloat64(orderInfo["amount"]), - DealAmount: ToFloat64(orderInfo["deal_amount"]), - AvgPrice: 0, - Side: side, - Status: orderState, - OrderTime: int(time.Now().Unix())}, nil - -} - -func (c *Cpk) GetOrdersList() { - panic("not implement") -} - -func (c *Cpk) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { - panic("not implement") -} - -func (c *Cpk) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - panic("not implement") -} - -func (c *Cpk) GetAccount() (*Account, error) { - path := API_URL + GET_ACCOUNT_API - //cmds := "[{\"cmd\":\"transfer/assets\",\"body\":{\"select\":1}}]" - - params := make(map[string]interface{}) - params["cmd"] = "transfer/assets" - - body := make(map[string]interface{}) - body["select"] = 1 - - params["body"] = body - - cmd, _ := json.Marshal(params) - cmds := "[" + string(cmd) + "]" - sign := c.buildSigned(cmds) - - param := make(map[string]string) - param["cmds"] = cmds - param["apikey"] = c.accessKey - param["sign"] = sign - resp, err := HttpPostForm4(c.httpClient, path, param, nil) - - //cmds := `[{cmd:transfer/assets,body:{}}]` - //cmds := "[{\"cmd\":\"transfer/assets\",\"body\":{\"select\":1}}]" - //sign := c.buildSigned(cmds) - - //params := make(map[string]string) - //params["cmds"] = cmds - //params["apikey"]= c.accessKey - //params["sign"]= sign - - //resp, err := HttpPostForm4(c.httpClient, path, params, nil) - - if err != nil { - return nil, err - } - respmap := make(map[string]interface{}) - json.Unmarshal(resp, &respmap) - log.Println(string(resp)) - //log.Println(respmap) - - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return nil, errors.New(errcode["msg"].(string)) - } - ba := respmap["result"].([]interface{}) - ba1 := ba[0].(map[string]interface{}) - balances := ba1["result"].(map[string]interface{}) - acc := new(Account) - - acc.Asset = ToFloat64(balances["total_btc"]) - acc.SubAccounts = make(map[Currency]SubAccount) - acc.Exchange = c.GetExchangeName() - assets_list, isok := balances["assets_list"].([]interface{}) - if isok != true { - return acc, nil - } - for _, v := range assets_list { - //log.Println(v) - vv := v.(map[string]string) - currency := NewCurrency(vv["coin_symbol"], "") - acc.SubAccounts[currency] = SubAccount{ - Currency: currency, - Amount: ToFloat64(vv["balance"]), - ForzenAmount: ToFloat64(vv["freeze"]), - } - } - - return acc, nil - -} - -func (c *Cpk) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - panic("not implement") -} - -//非个人,整个交易所的交易记录 -func (c *Cpk) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - panic("not implement") -} - -func (c *Cpk) GetPairList() ([]CurrencyPair, error) { - url := API_URL + Pair_List - respmap, err := HttpGet(c.httpClient, url) - if err != nil { - return nil, err - } - log.Println("respmap:", respmap) - errcode, isok := respmap["error"].(map[string]interface{}) - if isok == true { - return nil, errors.New(errcode["msg"].(string)) - } - - list, ok := respmap["result"].([]interface{}) - if !ok { - return nil, errors.New("tick assert error") - } - pairlist := make([]CurrencyPair, 0) - for _, v := range list { - vv := v.(map[string]interface{}) - pairlist = append(pairlist, NewCurrencyPair2(vv["pair"].(string))) - } - - return pairlist, nil - -} diff --git a/coinpark/coinpark_test.go b/coinpark/coinpark_test.go deleted file mode 100644 index c33040ac..00000000 --- a/coinpark/coinpark_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package coinpark - -import ( - "github.com/nntaoli-project/goex" - "net/http" - "testing" -) - -var cpk = New(http.DefaultClient, "", "") - -func TestCpk_buildSigned(t *testing.T) { - //[{"cmd":"user/userInfo","body":{}}] - return - cmds := "[{\"cmd\":\"user/userInfo\",\"body\":{}}]" - t.Log(cpk.buildSigned(cmds)) -} -func TestCpk_GetAccount(t *testing.T) { - //return - t.Log(cpk.GetAccount()) -} - -func TestCpk_LimitBuy(t *testing.T) { - return - t.Log(cpk.LimitBuy("1", "1", goex.BTC_USDT)) -} -func TestCpk_LimitSell(t *testing.T) { - return - t.Log(cpk.LimitSell("1", "999999", goex.BTC_USDT)) -} - -func TestCpk_CancelOrder(t *testing.T) { - return - t.Log(cpk.CancelOrder("123", goex.BTC_USDT)) -} - -func TestCpk_GetPairList(t *testing.T) { - return - t.Log(cpk.GetPairList()) -} diff --git a/cryptopia/Cryptopia.go b/cryptopia/Cryptopia.go deleted file mode 100644 index c645d021..00000000 --- a/cryptopia/Cryptopia.go +++ /dev/null @@ -1,402 +0,0 @@ -package cryptopia - -import ( - "crypto/hmac" - "crypto/md5" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "log" - "strconv" - "strings" - - . "github.com/nntaoli-project/goex" - - "net/http" - "net/http/httputil" - "net/url" - "time" -) - -const ( - API_BASE_URL = "https://www.cryptopia.co.nz/api/" - - TICKERS_URI = "GetMarkets" - TICKER_URI = "GetMarket/" - DEPTH_URI = "GetMarketOrders" - CANCEL_URI = "CancelTrade" - SUBMIT_ORDER_URI = "SubmitTrade" - DEFAULT_HTTPCLIENT_TIMEOUT = 30 // HTTP client timeout -) - -type Cryptopia struct { - accessKey, - secretKey string - httpClient *http.Client - debug bool -} -type jsonResponse struct { - Success bool `json:"success"` - Error string `json:"error"` - Message string `json:"message"` - Result json.RawMessage `json:"data"` -} - -type placedOrderResponse struct { - OrderId int `json:"OrderId"` -} - -type cancelOrderPayload struct { - OrderId int `json:"OrderId"` - TradePairId int `json:"TradePairId"` - Type string -} - -func New(client *http.Client, accessKey, secretKey string) *Cryptopia { - debug := false - return &Cryptopia{accessKey, secretKey, client, debug} -} - -func (cta *Cryptopia) GetExchangeName() string { - return CRYPTOPIA -} - -func (cta *Cryptopia) GetTickers(currency CurrencyPair) (*Ticker, error) { - return cta.GetTicker(currency) - - //tickerUri := API_BASE_URL + TICKERS_URI - ////log.Println("tickerUrl:", tickerUri) - //bodyDataMap, err := HttpGet(cta.httpClient, tickerUri) - ////log.Println("Cryptopia bodyDataMap:", tickerUri, bodyDataMap) - // - //if err != nil { - // log.Println(err) - // return nil, err - //} - // - //if result, isok := bodyDataMap["success"].(bool); isok == true && result != true { - // log.Println("bodyDataMap[\"success\"]", isok, result) - // return nil, errors.New("err") - //} - ////timestamp := time.Now().Unix() - // - //panic("not implement") - //return nil, nil -} - -func (cta *Cryptopia) GetTicker(currency CurrencyPair) (*Ticker, error) { - currency = cta.adaptCurrencyPair(currency) - - tickerUri := API_BASE_URL + TICKER_URI + currency.ToSymbol("_") - //log.Println("tickerUrl:", tickerUri) - bodyDataMap, err := HttpGet(cta.httpClient, tickerUri) - //log.Println("Cryptopia bodyDataMap:", tickerUri, bodyDataMap) - - if err != nil { - //log.Println(err) - return nil, err - } - tickerMap, isok := bodyDataMap["Data"].(map[string]interface{}) - if isok != true { - //log.Println("Cryptopia bodyDataMap:", tickerUri, bodyDataMap) - //log.Println("bodyDataMap[\"Error\"]", bodyDataMap["Error"].(string)) - //return nil, errors.New(bodyDataMap["Error"].(string)) - return nil, errors.New("ERR") - } - var ticker Ticker - - timestamp := time.Now().Unix() - - //fmt.Println(bodyDataMap) - ticker.Date = uint64(timestamp) - ticker.Last, _ = tickerMap["LastPrice"].(float64) - - ticker.Buy, _ = tickerMap["BidPrice"].(float64) - ticker.Sell, _ = tickerMap["AskPrice"].(float64) - ticker.Vol, _ = tickerMap["Volume"].(float64) - //log.Println("Cryptopia", currency, "ticker:", ticker) - return &ticker, nil -} - -func (cta *Cryptopia) GetTickerInBuf(currency CurrencyPair) (*Ticker, error) { - return cta.GetTicker(currency) -} - -//GetDepth get orderbook -func (cta *Cryptopia) GetDepth(size int, currency CurrencyPair) (*Depth, error) { - currency = cta.adaptCurrencyPair(currency) - - depthURI := fmt.Sprintf("%s%s/%s/%d", API_BASE_URL, DEPTH_URI, currency.ToSymbol("_"), size) - bodyDataMap, err := HttpGet(cta.httpClient, depthURI) - if err != nil { - return nil, err - } - depthMap, isok := bodyDataMap["Data"].(map[string]interface{}) - if isok != true { - return nil, err - } - bids := depthMap["Buy"].([]interface{}) - asks := depthMap["Sell"].([]interface{}) - - depth := new(Depth) - for _, bid := range bids { - _bid := bid.(map[string]interface{}) - amount := ToFloat64(_bid["Volume"]) - price := ToFloat64(_bid["Price"]) - dr := DepthRecord{Amount: amount, Price: price} - depth.BidList = append(depth.BidList, dr) - } - for _, ask := range asks { - _ask := ask.(map[string]interface{}) - amount := ToFloat64(_ask["Volume"]) - price := ToFloat64(_ask["Price"]) - dr := DepthRecord{Amount: amount, Price: price} - depth.AskList = append(depth.AskList, dr) - } - - return depth, nil -} - -func (cta *Cryptopia) adaptCurrencyPair(pair CurrencyPair) CurrencyPair { - var currencyA Currency - var currencyB Currency - - if pair.CurrencyA == BCC { - currencyA = BCH - } else { - currencyA = pair.CurrencyA - } - currencyB = pair.CurrencyB - //if pair.BaseCurrency == USDT { - // currencyB = USD - //} else { - // currencyB = pair.BaseCurrency - //} - - return NewCurrencyPair(currencyA, currencyB) -} - -func (cta *Cryptopia) currencyPairToSymbol(currencyPair CurrencyPair) string { - return strings.ToUpper(currencyPair.ToSymbol("_")) -} - -func (cta *Cryptopia) placeOrder(orderType, side, amount, price string, pair CurrencyPair) (*Order, error) { - payload := map[string]interface{}{ - "Market": cta.currencyPairToSymbol(pair), - "Amount": amount, - "Rate": price, - "Type": orderType, - "Exchange": "cryptopia"} - p, err := json.Marshal(&payload) - if err != nil { - return nil, err - } - resp, err := cta.do("POST", SUBMIT_ORDER_URI, string(p), true) - if err != nil { - return nil, err - } - - var jsonResp jsonResponse - err = json.Unmarshal(resp, &jsonResp) - if err != nil { - log.Println(string(resp)) - return nil, err - } - order := new(Order) - - if jsonResp.Success { - data := new(placedOrderResponse) - err = json.Unmarshal(jsonResp.Result, &data) - if err != nil { - return nil, err - } - order.Currency = pair - order.OrderID = data.OrderId - order.Amount = ToFloat64(amount) - order.Price = ToFloat64(price) - } else { - return nil, errors.New(jsonResp.Error) - } - - switch side { - case "buy": - order.Side = BUY - case "sell": - order.Side = SELL - } - return order, nil -} - -//LimitBuy ... -func (cta *Cryptopia) LimitBuy(amount, price string, currency CurrencyPair) (*Order, error) { - return cta.placeOrder("Buy", "Buy", amount, price, currency) -} - -//LimitSell ... -func (cta *Cryptopia) LimitSell(amount, price string, currency CurrencyPair) (*Order, error) { - return cta.placeOrder("Sell", "Sell", amount, price, currency) -} - -func (cta *Cryptopia) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { - panic("not implements") -} - -func (cta *Cryptopia) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { - panic("not implements") -} - -func (cta *Cryptopia) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { - payload := map[string]interface{}{ - "OrderId": ToInt(orderId), - } - p, err := json.Marshal(&payload) - if err != nil { - return false, err - } - resp, err := cta.do("POST", CANCEL_URI, string(p), true) - if err != nil { - return false, err - } - var jsonResp jsonResponse - err = json.Unmarshal(resp, &jsonResp) - if err != nil { - log.Println(string(resp)) - return false, err - } - return jsonResp.Success, nil -} - -func (cta *Cryptopia) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) { - panic("not implements") -} -func (cta *Cryptopia) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { - panic("not implements") -} - -func (cta *Cryptopia) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - panic("not implements") -} - -func (cta *Cryptopia) GetAccount() (*Account, error) { - panic("not implements") -} -func (cta *Cryptopia) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - panic("not implements") -} -func (cta *Cryptopia) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - panic("not implements") -} - -func (cta *Cryptopia) do(method string, resource string, payload string, authNeeded bool) (response []byte, err error) { - connectTimer := time.NewTimer(DEFAULT_HTTPCLIENT_TIMEOUT * time.Second) - - var rawurl string - if strings.HasPrefix(resource, "http") { - rawurl = resource - } else { - rawurl = fmt.Sprintf("%s%s", API_BASE_URL, resource) - } - - req, err := http.NewRequest(method, rawurl, strings.NewReader(payload)) - if err != nil { - return - } - if method == "POST" || method == "PUT" { - req.Header.Add("Content-Type", "application/json;charset=utf-8") - } - req.Header.Add("Accept", "application/json") - - // Auth - if authNeeded { - if len(cta.accessKey) == 0 || len(cta.secretKey) == 0 { - err = errors.New("You need to set API Key and API Secret to call this method") - return - } - nonce := strconv.FormatInt(time.Now().UnixNano(), 10) - md5 := md5.Sum([]byte(payload)) - signature := cta.accessKey + method + strings.ToLower(url.QueryEscape(req.URL.String())) + - nonce + base64.StdEncoding.EncodeToString(md5[:]) - secret, _ := base64.StdEncoding.DecodeString(cta.secretKey) - mac := hmac.New(sha256.New, secret) - _, err = mac.Write([]byte(signature)) - sig := base64.StdEncoding.EncodeToString(mac.Sum(nil)) - auth := "amx " + cta.accessKey + ":" + sig + ":" + nonce - req.Header.Add("Authorization", auth) - } - - resp, err := cta.doTimeoutRequest(connectTimer, req) - if err != nil { - return - } - - defer resp.Body.Close() - response, err = ioutil.ReadAll(resp.Body) - //fmt.Println(fmt.Sprintf("reponse %s", response), err) - if err != nil { - return response, err - } - if resp.StatusCode != 200 { - err = errors.New(resp.Status) - } - return response, err -} - -func (cta Cryptopia) dumpRequest(r *http.Request) { - if r == nil { - log.Print("dumpReq ok: ") - return - } - dump, err := httputil.DumpRequest(r, true) - if err != nil { - log.Print("dumpReq err:", err) - } else { - log.Print("dumpReq ok:", string(dump)) - } -} - -func (cta Cryptopia) dumpResponse(r *http.Response) { - if r == nil { - log.Print("dumpResponse ok: ") - return - } - dump, err := httputil.DumpResponse(r, true) - if err != nil { - log.Print("dumpResponse err:", err) - } else { - log.Print("dumpResponse ok:", string(dump)) - } -} - -// doTimeoutRequest do a HTTP request with timeout -func (cta *Cryptopia) doTimeoutRequest(timer *time.Timer, req *http.Request) (*http.Response, error) { - // Do the request in the background so we can check the timeout - type result struct { - resp *http.Response - err error - } - done := make(chan result, 1) - go func() { - if cta.debug { - cta.dumpRequest(req) - } - resp, err := cta.httpClient.Do(req) - if cta.debug { - cta.dumpResponse(resp) - } - done <- result{resp, err} - }() - // Wait for the read or the timeout - select { - case r := <-done: - return r.resp, r.err - case <-timer.C: - return nil, errors.New("timeout on reading data from Bittrex API") - } -} - -func (cta *Cryptopia) SetDebug(enable bool) { - cta.debug = enable -} diff --git a/cryptopia/Cryptopia_test.go b/cryptopia/Cryptopia_test.go deleted file mode 100644 index edfe0857..00000000 --- a/cryptopia/Cryptopia_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package cryptopia - -import ( - "github.com/nntaoli-project/goex" - "net/http" - "testing" -) - -var ctp = New(http.DefaultClient, "", "") - -func TestCryptopia_GetTicker(t *testing.T) { - ticker, err := ctp.GetTicker(goex.BTC_USDT) - t.Log("err=>", err) - t.Log("ticker=>", ticker) -} - -func TestCryptopia_GetDepth(t *testing.T) { - depCtp, err := ctp.GetDepth(goex.BTC_USDT) - t.Log(err) - t.Log("AskList=>", depCtp.AskList) - t.Log("BidList=>", depCtp.BidList) -} diff --git a/cryptopia/README.md b/cryptopia/README.md deleted file mode 100644 index cb9dca01..00000000 --- a/cryptopia/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Reference to Cryptopia API documentation: -https://support.cryptopia.co.nz/csm?id=kb_article&sys_id=a75703dcdbb9130084ed147a3a9619bc \ No newline at end of file diff --git a/huobi/Hbdm.go b/huobi/Hbdm.go index 83133261..9ad23327 100644 --- a/huobi/Hbdm.go +++ b/huobi/Hbdm.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/nntaoli-project/goex/internal/logger" + "net/http" "net/url" "sort" "strings" @@ -51,6 +53,67 @@ const ( defaultBaseUrl = "https://api.hbdm.com" ) +var ( + FuturesContractInfos []FuturesContractInfo +) + +func init() { + go func() { + interval := time.Second + intervalTimer := time.NewTimer(interval) + + for { + select { + case <-intervalTimer.C: + var response struct { + Status string `json:"status"` + Data []struct { + Symbol string `json:"symbol"` + ContractCode string `json:"contract_code"` + ContractType string `json:"contract_type"` + ContractSize float64 `json:"contract_size"` + PriceTick float64 `json:"price_tick"` + DeliveryDate string `json:"delivery_date"` + CreateDate string `json:"create_date"` + ContractStatus int `json:"contract_status"` + } `json:"data"` + } + urlPath := "http://api.hbdm.pro/api/v1/contract_contract_info" + respBody, err := HttpGet5(http.DefaultClient, urlPath, map[string]string{}) + if err != nil { + logger.Error("[hbdm] get contract info error=", err) + goto reset + } + err = json.Unmarshal(respBody, &response) + if err != nil { + logger.Errorf("[hbdm] json unmarshal contract info error=%s", err) + goto reset + } + FuturesContractInfos = FuturesContractInfos[:0] + for _, info := range response.Data { + FuturesContractInfos = append(FuturesContractInfos, FuturesContractInfo{ + TickSize: &TickSize{ + InstrumentID: info.ContractCode, + UnderlyingIndex: info.Symbol, + QuoteCurrency: "", + PriceTickSize: info.PriceTick, + AmountTickSize: 0, + }, + ContractVal: info.ContractSize, + Delivery: info.DeliveryDate, + ContractType: info.ContractType, + }) + } + interval = 10 * time.Minute + reset: + intervalTimer.Reset(interval) + } + + } + + }() +} + func NewHbdm(conf *APIConfig) *Hbdm { if conf.Endpoint == "" { conf.Endpoint = defaultBaseUrl @@ -179,7 +242,7 @@ func (dm *Hbdm) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, params.Set("order_price_type", "opponent") //对手价下单 } else { params.Set("order_price_type", "limit") - params.Add("price", price) + params.Add("price", dm.formatPriceSize(contractType, currencyPair.CurrencyA, price)) } direction, offset := dm.adaptOpenType(openType) @@ -340,15 +403,20 @@ func (dm *Hbdm) GetFutureTicker(currencyPair CurrencyPair, contractType string) return nil, errors.New(ret["err_msg"].(string)) } - tick := ret["tick"].(map[string]interface{}) + tick, ok1 := ret["tick"].(map[string]interface{}) + ask, ok2 := tick["ask"].([]interface{}) + bid, ok3 := tick["bid"].([]interface{}) + if !ok1 || !ok2 || !ok3 { + return nil, errors.New("no tick data") + } return &Ticker{ Pair: currencyPair, Last: ToFloat64(tick["close"]), Vol: ToFloat64(tick["amount"]), Low: ToFloat64(tick["low"]), High: ToFloat64(tick["high"]), - Sell: ToFloat64(tick["ask"].([]interface{})[0]), - Buy: ToFloat64(tick["bid"].([]interface{})[0]), + Sell: ToFloat64(ask[0]), + Buy: ToFloat64(bid[0]), Date: ToUint64(ret["ts"])}, nil } @@ -600,3 +668,20 @@ func (dm *Hbdm) doRequest(path string, params *url.Values, data interface{}) err return json.Unmarshal(ret.Data, data) } + +func (dm *Hbdm) formatPriceSize(contract string, currency Currency, price string) string { + var tickSize = 0 + for _, v := range FuturesContractInfos { + if (v.ContractType == contract || v.InstrumentID == contract) && v.UnderlyingIndex == currency.Symbol { + if v.PriceTickSize == 0 { + break + } + for v.PriceTickSize < 1 { + tickSize++ + v.PriceTickSize *= 10 + } + break + } + } + return FloatToString(ToFloat64(price), tickSize) +} diff --git a/huobi/Hbdm_Ws.go b/huobi/Hbdm_Ws.go index cee722fa..19a67d74 100644 --- a/huobi/Hbdm_Ws.go +++ b/huobi/Hbdm_Ws.go @@ -1,12 +1,12 @@ package huobi import ( + "bytes" "encoding/json" "errors" "fmt" . "github.com/nntaoli-project/goex" - "log" - "sort" + "github.com/nntaoli-project/goex/internal/logger" "strings" "sync" "time" @@ -54,6 +54,7 @@ type DetailResponse struct { type DepthResponse struct { Bids [][]float64 Asks [][]float64 + Ts int64 `json:"ts"` } type HbdmWs struct { @@ -73,7 +74,7 @@ func NewHbdmWs() *HbdmWs { AutoReconnect(). //Heartbeat([]byte("{\"event\": \"ping\"} "), 30*time.Second). //Heartbeat(func() []byte { return []byte("{\"op\":\"ping\"}") }(), 5*time.Second). - UnCompressFunc(GzipUnCompress). + DecompressFunc(GzipDecompress). ProtoHandleFunc(hbdmWs.handle) return hbdmWs } @@ -86,6 +87,17 @@ func (hbdmWs *HbdmWs) SetCallbacks(tickerCallback func(*FutureTicker), hbdmWs.tradeCallback = tradeCallback } +func (hbdmWs *HbdmWs) TickerCallback(call func(ticker *FutureTicker)) { + hbdmWs.tickerCallback = call +} +func (hbdmWs *HbdmWs) TradeCallback(call func(trade *Trade, contract string)) { + hbdmWs.tradeCallback = call +} + +func (hbdmWs *HbdmWs) DepthCallback(call func(depth *Depth)) { + hbdmWs.depthCallback = call +} + func (hbdmWs *HbdmWs) SubscribeTicker(pair CurrencyPair, contract string) error { if hbdmWs.tickerCallback == nil { return errors.New("please set ticker callback func") @@ -95,13 +107,13 @@ func (hbdmWs *HbdmWs) SubscribeTicker(pair CurrencyPair, contract string) error "sub": fmt.Sprintf("market.%s_%s.detail", pair.CurrencyA.Symbol, hbdmWs.adaptContractSymbol(contract))}) } -func (hbdmWs *HbdmWs) SubscribeDepth(pair CurrencyPair, contract string, size int) error { +func (hbdmWs *HbdmWs) SubscribeDepth(pair CurrencyPair, contract string) error { if hbdmWs.depthCallback == nil { return errors.New("please set depth callback func") } return hbdmWs.subscribe(map[string]interface{}{ - "id": "depth_2", - "sub": fmt.Sprintf("market.%s_%s.depth.step0", pair.CurrencyA.Symbol, hbdmWs.adaptContractSymbol(contract))}) + "id": "futures.depth", + "sub": fmt.Sprintf("market.%s_%s.depth.size_20.high_freq", pair.CurrencyA.Symbol, hbdmWs.adaptContractSymbol(contract))}) } func (hbdmWs *HbdmWs) SubscribeTrade(pair CurrencyPair, contract string) error { @@ -114,7 +126,7 @@ func (hbdmWs *HbdmWs) SubscribeTrade(pair CurrencyPair, contract string) error { } func (hbdmWs *HbdmWs) subscribe(sub map[string]interface{}) error { -// log.Println(sub) + // log.Println(sub) hbdmWs.connectWs() return hbdmWs.wsConn.Subscribe(sub) } @@ -127,17 +139,9 @@ func (hbdmWs *HbdmWs) connectWs() { func (hbdmWs *HbdmWs) handle(msg []byte) error { //心跳 - if strings.Contains(string(msg), "ping") { - var ping struct { - Ping int64 - } - json.Unmarshal(msg, &ping) - - pong := struct { - Pong int64 `json:"pong"` - }{ping.Ping} - - hbdmWs.wsConn.SendJsonMessage(pong) + if bytes.Contains(msg, []byte("ping")) { + pong := bytes.ReplaceAll(msg, []byte("ping"), []byte("pong")) + hbdmWs.wsConn.SendMessage(pong) return nil } @@ -148,29 +152,16 @@ func (hbdmWs *HbdmWs) handle(msg []byte) error { } if resp.Ch == "" { - log.Println(string(msg)) + logger.Warnf("[%s] ch == \"\" , msg=%s", hbdmWs.wsConn.WsUrl, string(msg)) return nil } pair, contract, err := hbdmWs.parseCurrencyAndContract(resp.Ch) if err != nil { + logger.Errorf("[%s] parse currency and contract err=%s", hbdmWs.wsConn.WsUrl, err) return err } - if strings.HasSuffix(resp.Ch, "trade.detail") { - var tradeResp TradeResponse - err := json.Unmarshal(resp.Tick, &tradeResp) - if err != nil { - return err - } - trades := hbdmWs.parseTrade(tradeResp) - for _, v := range trades { - v.Pair = pair - hbdmWs.tradeCallback(&v, contract) - } - return nil - } - if strings.Contains(resp.Ch, ".depth.") { var depResp DepthResponse err := json.Unmarshal(resp.Tick, &depResp) @@ -178,15 +169,29 @@ func (hbdmWs *HbdmWs) handle(msg []byte) error { return err } - dep := hbdmWs.parseDepth(depResp) + dep := ParseDepthFromResponse(depResp) dep.ContractType = contract dep.Pair = pair - dep.UTime = time.Unix(resp.Ts/1000, 0) + dep.UTime = time.Unix(0, resp.Ts*int64(time.Millisecond)) hbdmWs.depthCallback(&dep) return nil } + if strings.HasSuffix(resp.Ch, "trade.detail") { + var tradeResp TradeResponse + err := json.Unmarshal(resp.Tick, &tradeResp) + if err != nil { + return err + } + trades := hbdmWs.parseTrade(tradeResp) + for _, v := range trades { + v.Pair = pair + hbdmWs.tradeCallback(&v, contract) + } + return nil + } + if strings.HasSuffix(resp.Ch, ".detail") { var detail DetailResponse err := json.Unmarshal(resp.Tick, &detail) @@ -200,6 +205,8 @@ func (hbdmWs *HbdmWs) handle(msg []byte) error { return nil } + logger.Errorf("[%s] unknown message, msg=%s", hbdmWs.wsConn.WsUrl, string(msg)) + return nil } @@ -207,21 +214,6 @@ func (hbdmWs *HbdmWs) parseTicker(r DetailResponse) FutureTicker { return FutureTicker{Ticker: &Ticker{High: r.High, Low: r.Low, Vol: r.Amount}} } -func (hbdmWs *HbdmWs) parseDepth(r DepthResponse) Depth { - var dep Depth - for _, bid := range r.Bids { - dep.BidList = append(dep.BidList, DepthRecord{bid[0], bid[1]}) - } - - for _, ask := range r.Asks { - dep.AskList = append(dep.AskList, DepthRecord{ask[0], ask[1]}) - } - - sort.Sort(sort.Reverse(dep.BidList)) - sort.Sort(sort.Reverse(dep.AskList)) - return dep -} - func (hbdmWs *HbdmWs) parseCurrencyAndContract(ch string) (CurrencyPair, string, error) { el := strings.Split(ch, ".") if len(el) < 2 { diff --git a/huobi/HuobiPro.go b/huobi/HuobiPro.go index fd405241..314f8766 100644 --- a/huobi/HuobiPro.go +++ b/huobi/HuobiPro.go @@ -231,6 +231,7 @@ func (hbpro *HuoBiPro) placeOrder(amount, price string, pair CurrencyPair, order path := "/v1/order/orders/place" params := url.Values{} params.Set("account-id", hbpro.accountId) + params.Set("client-order-id", GenerateOrderClientId(32)) params.Set("amount", FloatToString(ToFloat64(amount), int(symbol.AmountPrecision))) params.Set("symbol", pair.AdaptUsdToUsdt().ToLower().ToSymbol("")) params.Set("type", orderType) diff --git a/huobi/HuobiSpot_Ws.go b/huobi/HuobiSpot_Ws.go new file mode 100644 index 00000000..d1f9e677 --- /dev/null +++ b/huobi/HuobiSpot_Ws.go @@ -0,0 +1,135 @@ +package huobi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "strings" + "sync" + "time" +) + +type SpotWs struct { + *WsBuilder + sync.Once + wsConn *WsConn + + tickerCallback func(*Ticker) + depthCallback func(*Depth) + tradeCallback func(*Trade) +} + +func NewSpotWs() *SpotWs { + ws := &SpotWs{ + WsBuilder: NewWsBuilder(), + } + ws.WsBuilder = ws.WsBuilder. + WsUrl("wss://api.huobi.pro/ws"). + AutoReconnect(). + DecompressFunc(GzipDecompress). + ProtoHandleFunc(ws.handle) + return ws +} + +func (ws *SpotWs) DepthCallback(call func(depth *Depth)) { + ws.depthCallback = call +} + +func (ws *SpotWs) TickerCallback(call func(ticker *Ticker)) { + ws.tickerCallback = call +} +func (ws *SpotWs) TradeCallback(call func(trade *Trade)) { + ws.tradeCallback = call +} + +func (ws *SpotWs) connectWs() { + ws.Do(func() { + ws.wsConn = ws.WsBuilder.Build() + }) +} + +func (ws *SpotWs) subscribe(sub map[string]interface{}) error { + ws.connectWs() + return ws.wsConn.Subscribe(sub) +} + +func (ws *SpotWs) SubscribeDepth(pair CurrencyPair) error { + if ws.depthCallback == nil { + return errors.New("please set depth callback func") + } + return ws.subscribe(map[string]interface{}{ + "id": "spot.depth", + "sub": fmt.Sprintf("market.%s.mbp.refresh.20", pair.ToLower().ToSymbol(""))}) +} + +func (ws *SpotWs) SubscribeTicker(pair CurrencyPair) error { + if ws.tickerCallback == nil { + return errors.New("please set ticker call back func") + } + return ws.subscribe(map[string]interface{}{ + "id": "spot.ticker", + "sub": fmt.Sprintf("market.%s.detail", pair.ToLower().ToSymbol("")), + }) + return nil +} + +func (ws *SpotWs) SubscribeTrade(pair CurrencyPair) error { + return nil +} + +func (ws *SpotWs) handle(msg []byte) error { + if bytes.Contains(msg, []byte("ping")) { + pong := bytes.ReplaceAll(msg, []byte("ping"), []byte("pong")) + ws.wsConn.SendMessage(pong) + return nil + } + + var resp WsResponse + err := json.Unmarshal(msg, &resp) + if err != nil { + return err + } + + currencyPair := ParseCurrencyPairFromSpotWsCh(resp.Ch) + if strings.Contains(resp.Ch, "mbp.refresh") { + var ( + depthResp DepthResponse + ) + + err := json.Unmarshal(resp.Tick, &depthResp) + if err != nil { + return err + } + + dep := ParseDepthFromResponse(depthResp) + dep.Pair = currencyPair + dep.UTime = time.Unix(0, resp.Ts*int64(time.Millisecond)) + ws.depthCallback(&dep) + + return nil + } + + if strings.Contains(resp.Ch, ".detail") { + var tickerResp DetailResponse + err := json.Unmarshal(resp.Tick, &tickerResp) + if err != nil { + return err + } + ws.tickerCallback(&Ticker{ + Pair: currencyPair, + Last: tickerResp.Close, + High: tickerResp.High, + Low: tickerResp.Low, + Vol: tickerResp.Amount, + Date: uint64(resp.Ts), + }) + return nil + } + + logger.Errorf("[%s] unknown message ch , msg=%s", ws.wsConn.WsUrl, string(msg)) + + return nil +} diff --git a/huobi/HuobiSpot_Ws_test.go b/huobi/HuobiSpot_Ws_test.go new file mode 100644 index 00000000..b64d2a85 --- /dev/null +++ b/huobi/HuobiSpot_Ws_test.go @@ -0,0 +1,28 @@ +package huobi + +import ( + "github.com/nntaoli-project/goex" + "os" + "testing" + "time" +) + +func TestNewSpotWs(t *testing.T) { + os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:1080") + spotWs := NewSpotWs() + spotWs.DepthCallback(func(depth *goex.Depth) { + t.Log("asks=", depth.AskList) + t.Log("bids=", depth.BidList) + }) + spotWs.TickerCallback(func(ticker *goex.Ticker) { + t.Log(ticker) + }) + spotWs.SubscribeTicker(goex.NewCurrencyPair2("BTC_USDT")) + spotWs.SubscribeTicker(goex.NewCurrencyPair2("USDT_HUSD")) + spotWs.SubscribeTicker(goex.NewCurrencyPair2("LTC_BTC")) + spotWs.SubscribeTicker(goex.NewCurrencyPair2("EOS_ETH")) + spotWs.SubscribeTicker(goex.NewCurrencyPair2("LTC_HT")) + spotWs.SubscribeTicker(goex.NewCurrencyPair2("BTT_TRX")) + //spotWs.SubscribeDepth(goex.BTC_USDT) + time.Sleep(time.Minute) +} diff --git a/huobi/Parser.go b/huobi/Parser.go new file mode 100644 index 00000000..b38b4639 --- /dev/null +++ b/huobi/Parser.go @@ -0,0 +1,65 @@ +package huobi + +import ( + "fmt" + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "sort" + "strings" +) + +func ParseDepthFromResponse(r DepthResponse) goex.Depth { + var dep goex.Depth + for _, bid := range r.Bids { + dep.BidList = append(dep.BidList, goex.DepthRecord{Price: bid[0], Amount: bid[1]}) + } + + for _, ask := range r.Asks { + dep.AskList = append(dep.AskList, goex.DepthRecord{Price: ask[0], Amount: ask[1]}) + } + + sort.Sort(sort.Reverse(dep.BidList)) + sort.Sort(sort.Reverse(dep.AskList)) + return dep +} + +func ParseCurrencyPairFromSpotWsCh(ch string) goex.CurrencyPair { + meta := strings.Split(ch, ".") + if len(meta) < 2 { + logger.Errorf("parse error, ch=%s", ch) + return goex.UNKNOWN_PAIR + } + + currencyPairStr := meta[1] + if strings.HasSuffix(currencyPairStr, "usdt") { + currencyA := strings.TrimSuffix(currencyPairStr, "usdt") + return goex.NewCurrencyPair2(fmt.Sprintf("%s_usdt", currencyA)) + } + + if strings.HasSuffix(currencyPairStr, "btc") { + currencyA := strings.TrimSuffix(currencyPairStr, "btc") + return goex.NewCurrencyPair2(fmt.Sprintf("%s_btc", currencyA)) + } + + if strings.HasSuffix(currencyPairStr, "eth") { + currencyA := strings.TrimSuffix(currencyPairStr, "eth") + return goex.NewCurrencyPair2(fmt.Sprintf("%s_eth", currencyA)) + } + + if strings.HasSuffix(currencyPairStr, "husd") { + currencyA := strings.TrimSuffix(currencyPairStr, "husd") + return goex.NewCurrencyPair2(fmt.Sprintf("%s_husd", currencyA)) + } + + if strings.HasSuffix(currencyPairStr, "ht") { + currencyA := strings.TrimSuffix(currencyPairStr, "ht") + return goex.NewCurrencyPair2(fmt.Sprintf("%s_ht", currencyA)) + } + + if strings.HasSuffix(currencyPairStr, "trx") { + currencyA := strings.TrimSuffix(currencyPairStr, "trx") + return goex.NewCurrencyPair2(fmt.Sprintf("%s_trx", currencyA)) + } + + return goex.UNKNOWN_PAIR +} diff --git a/ocx/ocx.go b/ocx/ocx.go deleted file mode 100644 index e07eb76d..00000000 --- a/ocx/ocx.go +++ /dev/null @@ -1,315 +0,0 @@ -package ocx - -import ( - "crypto" - "crypto/hmac" - "encoding/json" - "errors" - "fmt" - . "github.com/nntaoli-project/goex" - "log" - "net/http" - "net/url" - "strconv" - "strings" - "time" -) - -var TimeOffset int64 = 0 - -const ( - API_BASE_URL = "https://openapi.ocx.com" - V2 = "/api/v2/" - //API_URL = API_BASE_URL + V2 - - TICKER_API = "market/ticker/%s" - TICKERS_API = "market/tickers" - DEPTH_API = "depth?market_code=%s" - SERVER_TIME = "timestamp" - - TRADE_URL = "orders" - GET_ACCOUNT_API = "accounts" - GET_ORDER_API = "orders/%s" - - CANCEL_ORDER_API = "orders/:%s/cancel" - CANCEL_ALL_ORDER_API = "orders/clear" - - PLACE_ORDER_API = "orders" -) - -type Ocx struct { - httpClient *http.Client - accessKey, - secretKey string -} - -func New(client *http.Client, apikey, secretkey string) *Ocx { - return &Ocx{accessKey: apikey, secretKey: secretkey, httpClient: client} -} - -func (o *Ocx) GetExchangeName() string { - return "ocx.com" -} - -func (o *Ocx) GetServerTime() int64 { - url := API_BASE_URL + V2 + SERVER_TIME - respmap, err := HttpGet(o.httpClient, url) - if err != nil { - return 0 - } - data := respmap["data"].(interface{}) - d := data.(map[string]interface{}) - ts := d["timestamp"].(float64) - servertime := int64(ts) - now := time.Now().Unix() - TimeOffset = servertime - now - return servertime -} - -func (o *Ocx) GetTicker(currencyPair CurrencyPair) (*Ticker, error) { - url := API_BASE_URL + V2 + fmt.Sprintf(TICKER_API, strings.ToLower(currencyPair.ToSymbol(""))) - respmap, err := HttpGet(o.httpClient, url) - if err != nil { - return nil, err - } - //log.Println("ticker respmap:", respmap) - - tickmap, ok := respmap["data"].(map[string]interface{}) - if !ok { - return nil, errors.New("tick assert error") - } - ticker := new(Ticker) - ticker.Pair = currencyPair - ticker.Date = ToUint64(tickmap["timestamp"]) - ticker.Last = ToFloat64(tickmap["last"]) - ticker.Vol = ToFloat64(tickmap["volume"]) - ticker.Low = ToFloat64(tickmap["low"]) - ticker.High = ToFloat64(tickmap["high"]) - ticker.Buy = ToFloat64(tickmap["buy"]) - ticker.Sell = ToFloat64(tickmap["sell"]) - - return ticker, nil - -} - -func (o *Ocx) GetDepth(size int, currency CurrencyPair) (*Depth, error) { - url := API_BASE_URL + V2 + fmt.Sprintf(DEPTH_API, strings.ToLower(currency.ToSymbol(""))) - resp, err := HttpGet(o.httpClient, url) - if err != nil { - return nil, err - } - respmap, _ := resp["status"].(map[string]interface{}) - bids, ok1 := respmap["bids"].([]interface{}) - asks, ok2 := respmap["asks"].([]interface{}) - - if !ok1 || !ok2 { - return nil, errors.New("tick assert error") - } - - depth := new(Depth) - - for _, r := range asks { - var dr DepthRecord - rr := r.([]interface{}) - dr.Price = ToFloat64(rr[0]) - dr.Amount = ToFloat64(rr[1]) - depth.AskList = append(depth.AskList, dr) - } - - for _, r := range bids { - var dr DepthRecord - rr := r.([]interface{}) - dr.Price = ToFloat64(rr[0]) - dr.Amount = ToFloat64(rr[1]) - depth.BidList = append(depth.BidList, dr) - } - - return depth, nil -} - -func (o *Ocx) buildSigned(method, apiurl string, para *url.Values) string { - param := "" - if para != nil { - param = para.Encode() - } - - log.Println("param", param) - - sig_str := "" - if para != nil { - sig_str = method + "|" + apiurl + "|" + param - } else { - sig_str = method + "|" + apiurl - } - - log.Println("sig_str", sig_str) - - mac := hmac.New(crypto.SHA256.New, []byte("abc")) - - mac.Write([]byte(sig_str)) - - sum := mac.Sum(nil) - - return fmt.Sprintf("%x", sum) -} - -func (o *Ocx) placeOrder(orderType, orderSide, amount, price string, pair CurrencyPair) (*Order, error) { - uri := API_BASE_URL + V2 + TRADE_URL - method := "POST" - path := V2 + TRADE_URL - params := url.Values{} - tonce := strconv.Itoa(int(time.Now().UnixNano() / 1000000)) - params.Set("access_key", o.accessKey) - params.Set("tonce", tonce) - //params.Set("foo", "bar") - - signed := o.buildSigned(method, path, ¶ms) - - f := fmt.Sprintf("access_key=%s&tonce=%s&signature=%s&market_code=%s&price=%s&side=%s&volume=%s", - o.accessKey, tonce, signed, strings.ToLower(pair.ToSymbol("")), price, orderSide, amount) - resp, err := HttpPostForm3(o.httpClient, uri, f, nil) - //resp, err := HttpPostForm3(o.httpClient, uri, form.Encode(), nil) - log.Println("resp:", string(resp), "err:", err) - if err != nil { - return nil, err - } - - respmap := make(map[string]interface{}) - err = json.Unmarshal(resp, &respmap) - if err != nil { - log.Println(string(resp)) - return nil, err - } - - orderId := ToInt(respmap["orderId"]) - if orderId <= 0 { - return nil, errors.New(string(resp)) - } - - side := BUY - if orderSide == "SELL" { - side = SELL - } - - return &Order{ - Currency: pair, - OrderID: orderId, - OrderID2: fmt.Sprint(orderId), - Price: ToFloat64(price), - Amount: ToFloat64(amount), - DealAmount: 0, - AvgPrice: 0, - Side: TradeSide(side), - Status: ORDER_UNFINISH, - OrderTime: int(time.Now().Unix())}, nil - - panic("1") -} - -func (o *Ocx) LimitBuy(amount, price string, currency CurrencyPair) (*Order, error) { - return o.placeOrder("limit", "buy", amount, price, currency) -} - -func (o *Ocx) LimitSell(amount, price string, currency CurrencyPair) (*Order, error) { - return o.placeOrder("limit", "sell", amount, price, currency) -} - -func (o *Ocx) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { - panic("not implement") -} -func (o *Ocx) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { - panic("not implement") -} - -func (o *Ocx) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { - path := API_BASE_URL + V2 + fmt.Sprintf(CANCEL_ORDER_API, strings.ToLower(currency.ToSymbol(""))) - params := url.Values{} - - params.Set("order_id", orderId) - - sign := o.buildSigned("POST", path, ¶ms) - log.Println("path", path, "params", params.Encode(), "sign", sign) - - resp, err := HttpPostForm2(o.httpClient, path, params, nil) - log.Println("resp:", string(resp), "err:", err) - return true, nil -} - -func (o *Ocx) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) { - path := API_BASE_URL + V2 + fmt.Sprintf(GET_ORDER_API, orderId) - para := url.Values{} - para.Set("order_id", orderId) - - //sign := o.buildSigned("GET", path, ¶) - - respmap, err := HttpGet2(o.httpClient, path, nil) - - if err != nil { - return nil, err - } - - log.Println(respmap) - - return nil, nil - -} - -func (o *Ocx) GetOrdersList() { - panic("not implement") - -} - -func (o *Ocx) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { - panic("not implement") -} - -func (o *Ocx) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - panic("not implement") -} - -func (o *Ocx) GetAccount() (*Account, error) { - url := API_BASE_URL + V2 + GET_ACCOUNT_API - //timestamp := strconv.FormatInt((time.Now().UnixNano() / 1000000), 10) - - //sign := o.buildSigned("GET", url, nil) - - respmap, err := HttpGet2(o.httpClient, url, nil) - - if err != nil { - return nil, err - } - - log.Println(respmap) - - if respmap["status"].(float64) != 0 { - return nil, errors.New(respmap["msg"].(string)) - } - - acc := new(Account) - acc.SubAccounts = make(map[Currency]SubAccount) - acc.Exchange = o.GetExchangeName() - - balances := respmap["data"].([]interface{}) - for _, v := range balances { - //log.Println(v) - vv := v.(map[string]interface{}) - currency := NewCurrency(vv["currency"].(string), "") - acc.SubAccounts[currency] = SubAccount{ - Currency: currency, - Amount: ToFloat64(vv["available"]), - ForzenAmount: ToFloat64(vv["frozen"]), - } - } - - return acc, nil - -} - -func (o *Ocx) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - panic("not implement") -} - -//非个人,整个交易所的交易记录 -func (o *Ocx) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { - panic("not implement") -} diff --git a/ocx/ocx_test.go b/ocx/ocx_test.go deleted file mode 100644 index 0f333a5c..00000000 --- a/ocx/ocx_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package ocx - -import ( - "github.com/nntaoli-project/goex" - "net/http" - "net/url" - "testing" -) - -var o = New(&http.Client{}, "", "") - -func TestOcx_GetServerTime(t *testing.T) { - return - t.Log(o.GetServerTime()) -} - -func TestOcx_buildSigned(t *testing.T) { - return - method := "GET" - path := "/api/v2/markets" - para := url.Values{} - para.Set("access_key", "xxx") - para.Set("tonce", "123456789") - para.Set("foo", "bar") - t.Log(o.buildSigned(method, path, ¶)) - -} - -func TestOcx_LimitSell(t *testing.T) { - //return - - o.LimitBuy("1", "1", goex.BTC_USDT) -} diff --git a/okex/OKEx.go b/okex/OKEx.go index c55c9a3a..1cd8cadb 100644 --- a/okex/OKEx.go +++ b/okex/OKEx.go @@ -52,8 +52,7 @@ func (ok *OKEx) UUID() string { func (ok *OKEx) DoRequest(httpMethod, uri, reqBody string, response interface{}) error { url := ok.config.Endpoint + uri sign, timestamp := ok.doParamSign(httpMethod, uri, reqBody) - logger.Log.Debug("sign=", sign) - logger.Log.Debug("timestamp=", timestamp) + logger.Log.Debug("timestamp=", timestamp, ", sign=", sign) resp, err := NewHttpRequest(ok.config.HttpClient, httpMethod, url, reqBody, map[string]string{ CONTENT_TYPE: APPLICATION_JSON_UTF8, ACCEPT: APPLICATION_JSON, diff --git a/okex/OKExFuture.go b/okex/OKExFuture.go index 2faaf389..b4303cff 100644 --- a/okex/OKExFuture.go +++ b/okex/OKExFuture.go @@ -113,7 +113,7 @@ func (ok *OKExFuture) GetFutureContractId(pair CurrencyPair, contractAlias strin time.Sleep(120 * time.Millisecond) //retry contractInfo, err = ok.GetAllFutureContractInfo() if err != nil { - logger.Warnf(fmt.Sprintf("Get Futures Contract Alias Error [%s] ???", err.Error())) + logger.Errorf(fmt.Sprintf("Get Futures Contract Id Error [%s] ???", err.Error())) } } } diff --git a/okex/OKExFuturesWs.go b/okex/OKExFuturesWs.go index db3defa9..05423d00 100644 --- a/okex/OKExFuturesWs.go +++ b/okex/OKExFuturesWs.go @@ -19,7 +19,6 @@ type OKExV3FuturesWs struct { depthCallback func(*Depth) tradeCallback func(*Trade, string) klineCallback func(*FutureKline, int) - orderCallback func(*FutureOrder, string) } func NewOKExV3FuturesWs(base *OKEx) *OKExV3FuturesWs { @@ -30,41 +29,30 @@ func NewOKExV3FuturesWs(base *OKEx) *OKExV3FuturesWs { return okV3Ws } -func (okV3Ws *OKExV3FuturesWs) TickerCallback(tickerCallback func(*FutureTicker)) *OKExV3FuturesWs { +func (okV3Ws *OKExV3FuturesWs) TickerCallback(tickerCallback func(*FutureTicker)) { okV3Ws.tickerCallback = tickerCallback - return okV3Ws } -func (okV3Ws *OKExV3FuturesWs) DepthCallback(depthCallback func(*Depth)) *OKExV3FuturesWs { +func (okV3Ws *OKExV3FuturesWs) DepthCallback(depthCallback func(*Depth)) { okV3Ws.depthCallback = depthCallback - return okV3Ws } -func (okV3Ws *OKExV3FuturesWs) TradeCallback(tradeCallback func(*Trade, string)) *OKExV3FuturesWs { +func (okV3Ws *OKExV3FuturesWs) TradeCallback(tradeCallback func(*Trade, string)) { okV3Ws.tradeCallback = tradeCallback - return okV3Ws } -func (okV3Ws *OKExV3FuturesWs) OrderCallback(orderCallback func(*FutureOrder, string)) *OKExV3FuturesWs { - okV3Ws.orderCallback = orderCallback - return okV3Ws -} - -func (okV3Ws *OKExV3FuturesWs) KlineCallback(klineCallback func(*FutureKline, int)) *OKExV3FuturesWs { +func (okV3Ws *OKExV3FuturesWs) KlineCallback(klineCallback func(*FutureKline, int)) { okV3Ws.klineCallback = klineCallback - return okV3Ws } func (okV3Ws *OKExV3FuturesWs) SetCallbacks(tickerCallback func(*FutureTicker), depthCallback func(*Depth), tradeCallback func(*Trade, string), - klineCallback func(*FutureKline, int), - orderCallback func(*FutureOrder, string)) { + klineCallback func(*FutureKline, int)) { okV3Ws.tickerCallback = tickerCallback okV3Ws.depthCallback = depthCallback okV3Ws.tradeCallback = tradeCallback okV3Ws.klineCallback = klineCallback - okV3Ws.orderCallback = orderCallback } func (okV3Ws *OKExV3FuturesWs) getChannelName(currencyPair CurrencyPair, contractType string) string { @@ -80,6 +68,7 @@ func (okV3Ws *OKExV3FuturesWs) getChannelName(currencyPair CurrencyPair, contrac } else { prefix = "futures" contractId = okV3Ws.base.OKExFuture.GetFutureContractId(currencyPair, contractType) + logger.Info("contractid=", contractId) } channelName = prefix + "/%s:" + contractId @@ -87,11 +76,7 @@ func (okV3Ws *OKExV3FuturesWs) getChannelName(currencyPair CurrencyPair, contrac return channelName } -func (okV3Ws *OKExV3FuturesWs) SubscribeDepth(currencyPair CurrencyPair, contractType string, size int) error { - if (size > 0) && (size != 5) { - return errors.New("only support depth 5") - } - +func (okV3Ws *OKExV3FuturesWs) SubscribeDepth(currencyPair CurrencyPair, contractType string) error { if okV3Ws.depthCallback == nil { return errors.New("please set depth callback func") } @@ -139,17 +124,6 @@ func (okV3Ws *OKExV3FuturesWs) SubscribeKline(currencyPair CurrencyPair, contrac "args": []string{fmt.Sprintf(chName, fmt.Sprintf("candle%ds", seconds))}}) } -func (okV3Ws *OKExV3FuturesWs) SubscribeOrder(currencyPair CurrencyPair, contractType string) error { - if okV3Ws.orderCallback == nil { - return errors.New("place set order callback func") - } - okV3Ws.v3Ws.Login() - chName := okV3Ws.getChannelName(currencyPair, contractType) - return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ - "op": "subscribe", - "args": []string{fmt.Sprintf(chName, "order")}}) -} - func (okV3Ws *OKExV3FuturesWs) getContractAliasAndCurrencyPairFromInstrumentId(instrumentId string) (alias string, pair CurrencyPair) { if strings.HasSuffix(instrumentId, "SWAP") { ar := strings.Split(instrumentId, "-") @@ -166,9 +140,10 @@ func (okV3Ws *OKExV3FuturesWs) getContractAliasAndCurrencyPairFromInstrumentId(i } } -func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { +func (okV3Ws *OKExV3FuturesWs) handle(channel string, data json.RawMessage) error { var ( err error + ch string tickers []tickerResponse depthResp []depthResponse dep Depth @@ -180,15 +155,22 @@ func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { InstrumentId string `json:"instrument_id"` Timestamp string `json:"timestamp"` } - orderResp []futureOrderResponse klineResponse []struct { Candle []string `json:"candle"` InstrumentId string `json:"instrument_id"` } ) - if strings.Contains(ch, "candle") { + + if strings.Contains(channel, "futures/candle") { ch = "candle" + } else { + ch, err = okV3Ws.v3Ws.parseChannel(channel) + if err != nil { + logger.Errorf("[%s] parse channel err=%s , originChannel=%s", okV3Ws.base.GetExchangeName(), err, ch) + return nil + } } + switch ch { case "ticker": err = json.Unmarshal(data, &tickers) @@ -296,32 +278,6 @@ func (okV3Ws *OKExV3FuturesWs) handle(ch string, data json.RawMessage) error { }, 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("[%s] unknown websocket message: %s", ch, string(data)) diff --git a/okex/OKExFuturesWs_test.go b/okex/OKExFuturesWs_test.go index 81ea6b50..c266021c 100644 --- a/okex/OKExFuturesWs_test.go +++ b/okex/OKExFuturesWs_test.go @@ -1,12 +1,9 @@ package okex import ( - "context" "github.com/nntaoli-project/goex" "github.com/nntaoli-project/goex/internal/logger" - "net" "net/http" - "net/url" "os" "testing" "time" @@ -18,17 +15,6 @@ var ( 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) { @@ -45,12 +31,8 @@ func TestNewOKExV3FuturesWs(t *testing.T) { 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.SubscribeTicker(goex.EOS_USD, goex.QUARTER_CONTRACT) - ok.OKExV3FutureWs.SubscribeDepth(goex.EOS_USDT, goex.QUARTER_CONTRACT, 5) + ok.OKExV3FutureWs.SubscribeDepth(goex.EOS_USD, goex.QUARTER_CONTRACT) //ok.OKExV3FutureWs.SubscribeTrade(goex.EOS_USD, goex.QUARTER_CONTRACT) - //ok.OKExV3FutureWs.SubscribeOrder(goex.BSV_USD, goex.NEXT_WEEK_CONTRACT) time.Sleep(1 * time.Minute) } diff --git a/okex/OKExSpotWs.go b/okex/OKExSpotWs.go index f2e304fc..f0a5f527 100644 --- a/okex/OKExSpotWs.go +++ b/okex/OKExSpotWs.go @@ -19,7 +19,6 @@ type OKExV3SpotWs struct { depthCallback func(*Depth) tradeCallback func(*Trade) klineCallback func(*Kline, KlinePeriod) - orderCallback func(*Order) } func NewOKExSpotV3Ws(base *OKEx) *OKExV3SpotWs { @@ -30,43 +29,33 @@ func NewOKExSpotV3Ws(base *OKEx) *OKExV3SpotWs { return okV3Ws } -func (okV3Ws *OKExV3SpotWs) TickerCallback(tickerCallback func(*Ticker)) *OKExV3SpotWs { +func (okV3Ws *OKExV3SpotWs) TickerCallback(tickerCallback func(*Ticker)) { okV3Ws.tickerCallback = tickerCallback - return okV3Ws } -func (okV3Ws *OKExV3SpotWs) DepthCallback(depthCallback func(*Depth)) *OKExV3SpotWs { +func (okV3Ws *OKExV3SpotWs) DepthCallback(depthCallback func(*Depth)) { okV3Ws.depthCallback = depthCallback - return okV3Ws } -func (okV3Ws *OKExV3SpotWs) TradeCallback(tradeCallback func(*Trade)) *OKExV3SpotWs { +func (okV3Ws *OKExV3SpotWs) TradeCallback(tradeCallback func(*Trade)) { 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) KLineCallback(klineCallback func(kline *Kline, period KlinePeriod)) { + okV3Ws.klineCallback = klineCallback } func (okV3Ws *OKExV3SpotWs) SetCallbacks(tickerCallback func(*Ticker), depthCallback func(*Depth), tradeCallback func(*Trade), - klineCallback func(*Kline, KlinePeriod), - orderCallback func(*Order)) { + klineCallback func(*Kline, KlinePeriod)) { okV3Ws.tickerCallback = tickerCallback okV3Ws.depthCallback = depthCallback okV3Ws.tradeCallback = tradeCallback okV3Ws.klineCallback = klineCallback - okV3Ws.orderCallback = orderCallback } -func (okV3Ws *OKExV3SpotWs) SubscribeDepth(currencyPair CurrencyPair, size int) error { +func (okV3Ws *OKExV3SpotWs) SubscribeDepth(currencyPair CurrencyPair) error { if okV3Ws.depthCallback == nil { return errors.New("please set depth callback func") } @@ -109,16 +98,6 @@ func (okV3Ws *OKExV3SpotWs) SubscribeKline(currencyPair CurrencyPair, period int "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, "-") } @@ -141,11 +120,10 @@ func (okV3Ws *OKExV3SpotWs) handle(ch string, data json.RawMessage) error { Candle []string `json:"candle"` InstrumentId string `json:"instrument_id"` } - orderResp []futureOrderResponse ) switch ch { - case "ticker": + case "spot/ticker": err = json.Unmarshal(data, &tickers) if err != nil { return err @@ -165,7 +143,7 @@ func (okV3Ws *OKExV3SpotWs) handle(ch string, data json.RawMessage) error { }) } return nil - case "depth5": + case "spot/depth5": err := json.Unmarshal(data, &depthResp) if err != nil { logger.Error(err) @@ -191,7 +169,7 @@ func (okV3Ws *OKExV3SpotWs) handle(ch string, data json.RawMessage) error { //call back func okV3Ws.depthCallback(&dep) return nil - case "trade": + case "spot/trade": err := json.Unmarshal(data, &tradeResponse) if err != nil { logger.Error("unmarshal error :", err) @@ -220,19 +198,13 @@ func (okV3Ws *OKExV3SpotWs) handle(ch string, data json.RawMessage) error { }) } return nil - case "order": - err := json.Unmarshal(data, &orderResp) - if err != nil { - return err - } - return nil default: - if strings.HasPrefix(ch, "candle") { + if strings.HasPrefix(ch, "spot/candle") { err := json.Unmarshal(data, &candleResponse) if err != nil { return err } - periodMs := strings.TrimPrefix(ch, "candle") + periodMs := strings.TrimPrefix(ch, "spot/candle") periodMs = strings.TrimSuffix(periodMs, "s") for _, k := range candleResponse { pair := okV3Ws.getCurrencyPair(k.InstrumentId) diff --git a/okex/OKExWs.go b/okex/OKExWs.go index 595fc825..763c1b3e 100644 --- a/okex/OKExWs.go +++ b/okex/OKExWs.go @@ -24,13 +24,9 @@ type wsResp struct { 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{} + once *sync.Once + WsConn *WsConn + respHandle func(channel string, data json.RawMessage) error } func NewOKExV3Ws(base *OKEx, handle func(channel string, data json.RawMessage) error) *OKExV3Ws { @@ -39,15 +35,12 @@ func NewOKExV3Ws(base *OKEx, handle func(channel string, data json.RawMessage) e 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). + ReconnectInterval(time.Second). AutoReconnect(). Heartbeat(func() []byte { return []byte("ping") }, 28*time.Second). - UnCompressFunc(FlateUnCompress).ProtoHandleFunc(okV3Ws.handle) + DecompressFunc(FlateDecompress).ProtoHandleFunc(okV3Ws.handle) return okV3Ws } @@ -68,20 +61,9 @@ func (okV3Ws *OKExV3Ws) getTablePrefix(currencyPair CurrencyPair, contractType s 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() { +func (okV3Ws *OKExV3Ws) ConnectWs() { okV3Ws.once.Do(func() { - okV3Ws.wsConn = okV3Ws.WsBuilder.Build() + okV3Ws.WsConn = okV3Ws.WsBuilder.Build() }) } @@ -125,52 +107,16 @@ func (okV3Ws *OKExV3Ws) handle(msg []byte) error { 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) - } + logger.Errorf(string(msg)) + default: + logger.Info(string(msg)) } return fmt.Errorf("unknown websocket 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) + err = okV3Ws.respHandle(wsResp.Table, wsResp.Data) if err != nil { logger.Error("handle ws data error:", err) } @@ -180,43 +126,7 @@ func (okV3Ws *OKExV3Ws) handle(msg []byte) error { return fmt.Errorf("unknown websocket 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) + okV3Ws.ConnectWs() + return okV3Ws.WsConn.Subscribe(sub) } diff --git a/okex/SwapResponse.go b/okex/SwapResponse.go index 08b9c036..fbbf3704 100644 --- a/okex/SwapResponse.go +++ b/okex/SwapResponse.go @@ -11,7 +11,7 @@ type SwapPositionHolding struct { LiquidationPrice float64 `json:"liquidation_price , string"` Position float64 `json:"position,string"` AvailPosition float64 `json:"avail_position,string"` - AvgCost float64 `json:"avg_cost , string"` + AvgCost float64 `json:"avg_cost,string"` SettlementPrice float64 `json:"settlement_price,string"` InstrumentId string `json:"instrument_id"` Leverage string `json:"leverage"` @@ -169,7 +169,8 @@ type BaseTickerInfo struct { Volume24h float64 `json:"volume_24h,string"` Low24h float64 `json:"low_24h,string"` BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"`} + BestAsk float64 `json:"best_ask,string"` +} type SwapTickerList []BaseTickerInfo diff --git a/websocket.go b/websocket.go index 444614c8..9d674a96 100644 --- a/websocket.go +++ b/websocket.go @@ -14,30 +14,37 @@ import ( ) type WsConfig struct { - WsUrl string - ProxyUrl string - ReqHeaders map[string][]string //连接的时候加入的头部信息 - HeartbeatIntervalTime time.Duration // - HeartbeatData func() []byte //心跳数据2 - IsAutoReconnect bool - ProtoHandleFunc func([]byte) error //协议处理函数 - UnCompressFunc func([]byte) ([]byte, error) //解压函数 - ErrorHandleFunc func(err error) - IsDump bool - readDeadLineTime time.Duration - reconnectInterval time.Duration + WsUrl string + ProxyUrl string + ReqHeaders map[string][]string //连接的时候加入的头部信息 + HeartbeatIntervalTime time.Duration // + HeartbeatData func() []byte //心跳数据2 + IsAutoReconnect bool + ProtoHandleFunc func([]byte) error //协议处理函数 + DecompressFunc func([]byte) ([]byte, error) //解压函数 + ErrorHandleFunc func(err error) + ConnectSuccessAfterSendMessage func() []byte //for reconnect + IsDump bool + readDeadLineTime time.Duration + reconnectInterval time.Duration +} + +var dialer = &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 30 * time.Second, + EnableCompression: true, } type WsConn struct { c *websocket.Conn - sync.Mutex WsConfig writeBufferChan chan []byte pingMessageBufferChan chan []byte pongMessageBufferChan chan []byte closeMessageBufferChan chan []byte - subs []interface{} + subs [][]byte close chan bool + reConnectLock *sync.Mutex } type WsBuilder struct { @@ -92,8 +99,8 @@ func (b *WsBuilder) ProtoHandleFunc(f func([]byte) error) *WsBuilder { return b } -func (b *WsBuilder) UnCompressFunc(f func([]byte) ([]byte, error)) *WsBuilder { - b.wsConfig.UnCompressFunc = f +func (b *WsBuilder) DecompressFunc(f func([]byte) ([]byte, error)) *WsBuilder { + b.wsConfig.DecompressFunc = f return b } @@ -102,15 +109,17 @@ func (b *WsBuilder) ErrorHandleFunc(f func(err error)) *WsBuilder { return b } +func (b *WsBuilder) ConnectSuccessAfterSendMessage(msg func() []byte) *WsBuilder { + b.wsConfig.ConnectSuccessAfterSendMessage = msg + return b +} + func (b *WsBuilder) Build() *WsConn { wsConn := &WsConn{WsConfig: *b.wsConfig} return wsConn.NewWs() } func (ws *WsConn) NewWs() *WsConn { - ws.Lock() - defer ws.Unlock() - if ws.HeartbeatIntervalTime == 0 { ws.readDeadLineTime = time.Minute } else { @@ -126,16 +135,21 @@ func (ws *WsConn) NewWs() *WsConn { ws.pongMessageBufferChan = make(chan []byte, 10) ws.closeMessageBufferChan = make(chan []byte, 10) ws.writeBufferChan = make(chan []byte, 10) + ws.reConnectLock = new(sync.Mutex) go ws.writeRequest() go ws.receiveMessage() + if ws.ConnectSuccessAfterSendMessage != nil { + msg := ws.ConnectSuccessAfterSendMessage() + ws.SendMessage(msg) + Log.Infof("[ws] [%s] execute the connect success after send message=%s", ws.WsUrl, string(msg)) + } + return ws } func (ws *WsConn) connect() error { - dialer := websocket.DefaultDialer - if ws.ProxyUrl != "" { proxy, err := url.Parse(ws.ProxyUrl) if err == nil { @@ -156,22 +170,21 @@ func (ws *WsConn) connect() error { return err } - ws.c = wsConn - - if ws.HeartbeatIntervalTime > 0 { - wsConn.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) - } + wsConn.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) if ws.IsDump { dumpData, _ := httputil.DumpResponse(resp, true) Log.Debugf("[ws][%s] %s", ws.WsUrl, string(dumpData)) } Log.Infof("[ws][%s] connected", ws.WsUrl) - + ws.c = wsConn return nil } func (ws *WsConn) reconnect() { + ws.reConnectLock.Lock() + defer ws.reConnectLock.Unlock() + ws.c.Close() //主动关闭一次 var err error for retry := 1; retry <= 100; retry++ { @@ -185,18 +198,23 @@ func (ws *WsConn) reconnect() { } if err != nil { - Log.Errorf("[ws] [%s] retry reconnect fail , begin exiting. ", ws.WsUrl) + Log.Errorf("[ws] [%s] retry connect 100 count fail , begin exiting. ", ws.WsUrl) ws.CloseWs() if ws.ErrorHandleFunc != nil { ws.ErrorHandleFunc(errors.New("retry reconnect fail")) } } else { //re subscribe - var tmp []interface{} - copy(tmp, ws.subs) - ws.subs = ws.subs[:0] - for _, sub := range tmp { - ws.Subscribe(sub) + if ws.ConnectSuccessAfterSendMessage != nil { + msg := ws.ConnectSuccessAfterSendMessage() + ws.SendMessage(msg) + Log.Infof("[ws] [%s] execute the connect success after send message=%s", ws.WsUrl, string(msg)) + time.Sleep(time.Second) //wait response + } + + for _, sub := range ws.subs { + Log.Info("[ws] re subscribe: ", string(sub)) + ws.SendMessage(sub) } } } @@ -228,15 +246,14 @@ func (ws *WsConn) writeRequest() { err = ws.c.WriteMessage(websocket.CloseMessage, d) case <-heartTimer.C: if ws.HeartbeatIntervalTime > 0 { - //Log.Debug("send heartbeat data") err = ws.c.WriteMessage(websocket.TextMessage, ws.HeartbeatData()) heartTimer.Reset(ws.HeartbeatIntervalTime) } } if err != nil { - Log.Errorf("[ws][%s] %s", ws.WsUrl, err.Error()) - time.Sleep(time.Second) + Log.Errorf("[ws][%s] write message %s", ws.WsUrl, err.Error()) + //time.Sleep(time.Second) } } } @@ -248,7 +265,7 @@ func (ws *WsConn) Subscribe(subEvent interface{}) error { return err } ws.writeBufferChan <- data - ws.subs = append(ws.subs, subEvent) + ws.subs = append(ws.subs, data) return nil } @@ -280,8 +297,8 @@ func (ws *WsConn) SendJsonMessage(m interface{}) error { func (ws *WsConn) receiveMessage() { //exit ws.c.SetCloseHandler(func(code int, text string) error { - Log.Infof("[ws][%s] websocket exiting [code=%d , text=%s]", ws.WsUrl, code, text) - ws.CloseWs() + Log.Warnf("[ws][%s] websocket exiting [code=%d , text=%s]", ws.WsUrl, code, text) + //ws.CloseWs() return nil }) @@ -304,14 +321,11 @@ func (ws *WsConn) receiveMessage() { return default: t, msg, err := ws.c.ReadMessage() - if err != nil { Log.Errorf("[ws][%s] %s", ws.WsUrl, err.Error()) if ws.IsAutoReconnect { - // if _, ok := err.(*websocket.CloseError); ok { Log.Infof("[ws][%s] Unexpected Closed , Begin Retry Connect.", ws.WsUrl) ws.reconnect() - // } continue } @@ -321,26 +335,24 @@ func (ws *WsConn) receiveMessage() { return } - + // Log.Debug(string(msg)) ws.c.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) - switch t { case websocket.TextMessage: ws.ProtoHandleFunc(msg) case websocket.BinaryMessage: - if ws.UnCompressFunc == nil { + if ws.DecompressFunc == nil { ws.ProtoHandleFunc(msg) } else { - msg2, err := ws.UnCompressFunc(msg) + msg2, err := ws.DecompressFunc(msg) if err != nil { - Log.Errorf("[ws][%s] uncompress error %s", ws.WsUrl, err.Error()) + Log.Errorf("[ws][%s] decompress error %s", ws.WsUrl, err.Error()) } else { ws.ProtoHandleFunc(msg2) } } - case websocket.CloseMessage: - ws.CloseWs() - return + // case websocket.CloseMessage: + // ws.CloseWs() default: Log.Errorf("[ws][%s] error websocket message type , content is :\n %s \n", ws.WsUrl, string(msg)) } @@ -349,11 +361,16 @@ func (ws *WsConn) receiveMessage() { } func (ws *WsConn) CloseWs() { - ws.close <- true + //ws.close <- true close(ws.close) + close(ws.writeBufferChan) + close(ws.closeMessageBufferChan) + close(ws.pingMessageBufferChan) + close(ws.pongMessageBufferChan) + err := ws.c.Close() if err != nil { - Log.Error("[ws][", ws.WsUrl, "]close websocket error ,", err) + Log.Error("[ws][", ws.WsUrl, "] close websocket error ,", err) } }