diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 00000000..1ecea0aa --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +name: Go + +on: + push: + branches: [ dev ] + pull_request: + branches: [ dev ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.18 + + - name: Build + run: go build -v ./... + + # - name: Test + # run: go test -v ./... diff --git a/Const.go b/Const.go index 2ef285af..a214e663 100644 --- a/Const.go +++ b/Const.go @@ -193,4 +193,5 @@ const ( PostOnly LimitOrderOptionalParameter = iota + 1 Ioc Fok + Futures_Twoway_Position_Mode //币安双向持仓模式 ) diff --git a/Models.go b/Models.go index 5e277d22..ef78a556 100644 --- a/Models.go +++ b/Models.go @@ -263,6 +263,21 @@ type DepositWithdrawHistory struct { Timestamp time.Time `json:"timestamp"` } +type PoloniexCurrency struct { + ID int `json:"id"` + Name string `json:"name"` + HumanType string `json:"humanType"` + CurrencyType string `json:"currencyType"` + TxFee string `json:"txFee"` + MinConf int `json:"minConf"` + DepositAddress string `json:"depositAddress"` + Disabled int `json:"disabled"` //Designates whether (1) or not (0) deposits and withdrawals are disabled. + Frozen int `json:"frozen"` //Designates whether (1) or not (0) trading for this currency is disabled for trading. + Blockchain string `json:"blockchain"` + Delisted int `json:"delisted"` + IsGeofenced int `json:"isGeofenced"` +} + type OptionalParameter map[string]interface{} func (optional OptionalParameter) Optional(name string, value interface{}) OptionalParameter { @@ -287,7 +302,7 @@ func (optional OptionalParameter) GetFloat64(name string) float64 { } func (optional OptionalParameter) GetTime(name string) *time.Time { - val := optional["name"] + val := optional[name] if val != nil { t, ok := val.(time.Time) if ok { diff --git a/README.md b/README.md index b3e94763..6598bc1c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ -
-goex - -
+![build](https://github.com/WymA/goex/actions/workflows/go.yml/badge.svg?branch=dev) ### goex目标 @@ -59,12 +56,10 @@ require ( donate ----------------- -BTC:13cBHLk6B7t3Uj7caJbCwv1UaiuiA6Qx8z +BTC: 1GoExWZop4JCJQkjb1UgtVGpjBKmP4DvG8 -LTC:LVxM7y1K2dnpuNBU42ei3dKzPySf4VAm1H - -ETH:0x98573ddb33cdddce480c3bc1f9279ccd88ca1e93 +USDT(TRC20): TGoExC6xvzE4wSA9cYZnwcPaXEjibA5Vtc ### 欢迎为作者付一碗面钱 -![微信](wx_pay.JPG) ![支付宝](IMG_1177.jpg) \ No newline at end of file +![微信](wx_pay.JPG) ![支付宝](IMG_1177.jpg) diff --git a/README_en.md b/README_en.md index f4a70cec..bdf2b1de 100644 --- a/README_en.md +++ b/README_en.md @@ -2,6 +2,8 @@ goex +![build](https://github.com/WymA/goex/actions/workflows/go.yml/badge.svg?branch=dev) + ### goex goex project is designed to unify and standardize the interfaces of each digital asset trading platform. The same strategy can be switched to any trading platform at any time without changing any code. diff --git a/bigone/BigoneV3_test.go b/bigone/BigoneV3_test.go index 3e36c129..8cb53be5 100644 --- a/bigone/BigoneV3_test.go +++ b/bigone/BigoneV3_test.go @@ -1,10 +1,11 @@ package bigone import ( - . "github.com/nntaoli-project/goex" "net/http" "testing" + . "github.com/nntaoli-project/goex" + "net" "net/url" "time" @@ -52,7 +53,7 @@ func TestBigoneV3_GetUnfinishOrders(t *testing.T) { } func TestBigoneV3_GetOrderHistorys(t *testing.T) { return - t.Log(b1.GetOrderHistorys(BTC_USDT, 1, 1)) + t.Log(b1.GetOrderHistorys(BTC_USDT, OptionalParameter{"test": 1})) } func TestBigoneV3_LimitSell(t *testing.T) { return diff --git a/binance/Binance.go b/binance/Binance.go index e0c82615..72bddf57 100644 --- a/binance/Binance.go +++ b/binance/Binance.go @@ -4,19 +4,30 @@ import ( "encoding/json" "errors" "fmt" - . "github.com/nntaoli-project/goex" "net/http" "net/url" "sort" "strconv" "strings" "time" + + . "github.com/nntaoli-project/goex" ) const ( GLOBAL_API_BASE_URL = "https://api.binance.com" US_API_BASE_URL = "https://api.binance.us" JE_API_BASE_URL = "https://api.binance.je" + + FUTURE_USD_WS_BASE_URL = "wss://fstream.binance.com/ws" + FUTURE_COIN_WS_BASE_URL = "wss://dstream.binance.com/ws" + + TESTNET_SPOT_API_BASE_URL = "https://api.binance.com" + TESTNET_SPOT_WS_BASE_URL = "wss://testnet.binance.vision/ws" + TESTNET_SPOT_STREAM_BASE_URL = "wss://testnet.binance.vision/stream" + TESTNET_FUTURE_USD_BASE_URL = "https://testnet.binancefuture.com" + TESTNET_FUTURE_USD_WS_BASE_URL = "wss://fstream.binance.com/ws" + TESTNET_FUTURE_COIN_WS_BASE_URL = "wss://dstream.binance.com/ws" //API_V1 = API_BASE_URL + "api/v1/" //API_V3 = API_BASE_URL + "api/v3/" diff --git a/binance/BinanceFutures.go b/binance/BinanceFutures.go index dabe4e6d..25ea1bef 100644 --- a/binance/BinanceFutures.go +++ b/binance/BinanceFutures.go @@ -4,8 +4,6 @@ import ( "encoding/json" "errors" "fmt" - . "github.com/nntaoli-project/goex" - "github.com/nntaoli-project/goex/internal/logger" "math" "net/http" "net/url" @@ -13,6 +11,9 @@ import ( "strings" "sync" "time" + + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" ) type BaseResponse struct { @@ -275,6 +276,10 @@ func (bs *BinanceFutures) GetFutureUserinfo(currencyPair ...CurrencyPair) (*Futu } func (bs *BinanceFutures) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice int, leverRate float64) (string, error) { + return bs.PlaceFutureOrder2(currencyPair, contractType, price, amount, openType, matchPrice) +} + +func (bs *BinanceFutures) PlaceFutureOrder2(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice int, opt ...LimitOrderOptionalParameter) (string, error) { apiPath := "order" symbol, err := bs.adaptToSymbol(currencyPair, contractType) if err != nil { @@ -298,8 +303,14 @@ func (bs *BinanceFutures) PlaceFutureOrder(currencyPair CurrencyPair, contractTy switch openType { case OPEN_BUY, CLOSE_SELL: param.Set("side", "BUY") + if len(opt) > 0 && opt[0] == Futures_Twoway_Position_Mode { + param.Set("positionSide", "LONG") + } case OPEN_SELL, CLOSE_BUY: param.Set("side", "SELL") + if len(opt) > 0 && opt[0] == Futures_Twoway_Position_Mode { + param.Set("positionSide", "SHORT") + } } bs.base.buildParamsSigned(¶m) @@ -331,7 +342,7 @@ func (bs *BinanceFutures) PlaceFutureOrder(currencyPair CurrencyPair, contractTy } func (bs *BinanceFutures) LimitFuturesOrder(currencyPair CurrencyPair, contractType, price, amount string, openType int, opt ...LimitOrderOptionalParameter) (*FutureOrder, error) { - orderId, err := bs.PlaceFutureOrder(currencyPair, contractType, price, amount, openType, 0, 10) + orderId, err := bs.PlaceFutureOrder2(currencyPair, contractType, price, amount, openType, 0, opt...) return &FutureOrder{ OrderID2: orderId, Currency: currencyPair, @@ -343,7 +354,7 @@ func (bs *BinanceFutures) LimitFuturesOrder(currencyPair CurrencyPair, contractT } func (bs *BinanceFutures) MarketFuturesOrder(currencyPair CurrencyPair, contractType, amount string, openType int) (*FutureOrder, error) { - orderId, err := bs.PlaceFutureOrder(currencyPair, contractType, "", amount, openType, 1, 10) + orderId, err := bs.PlaceFutureOrder2(currencyPair, contractType, "", amount, openType, 1) return &FutureOrder{ OrderID2: orderId, Currency: currencyPair, diff --git a/binance/BinanceSwap.go b/binance/BinanceSwap.go index 075e076a..b1fa749b 100644 --- a/binance/BinanceSwap.go +++ b/binance/BinanceSwap.go @@ -4,12 +4,13 @@ import ( "encoding/json" "errors" "fmt" - . "github.com/nntaoli-project/goex" "net/url" "strconv" "strings" "sync" "time" + + . "github.com/nntaoli-project/goex" ) const ( @@ -309,13 +310,13 @@ func (bs *BinanceSwap) Transfer(currency Currency, transferType int, amount floa } func (bs *BinanceSwap) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice int, leverRate float64) (string, error) { - fOrder, err := bs.PlaceFutureOrder2(currencyPair, contractType, price, amount, openType, matchPrice, leverRate) + fOrder, err := bs.PlaceFutureOrder2(currencyPair, contractType, price, amount, openType, matchPrice) return fOrder.OrderID2, err } -func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice int, leverRate float64) (*FutureOrder, error) { +func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice int, opt ...LimitOrderOptionalParameter) (*FutureOrder, error) { if contractType == SWAP_CONTRACT { - orderId, err := bs.f.PlaceFutureOrder(currencyPair.AdaptUsdtToUsd(), contractType, price, amount, openType, matchPrice, leverRate) + orderId, err := bs.f.PlaceFutureOrder2(currencyPair.AdaptUsdtToUsd(), contractType, price, amount, openType, matchPrice, opt...) return &FutureOrder{ OrderID2: orderId, Price: ToFloat64(price), @@ -323,7 +324,7 @@ func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType Status: ORDER_UNFINISH, Currency: currencyPair, OType: openType, - LeverRate: leverRate, + LeverRate: 0, ContractName: contractType, }, err } @@ -338,7 +339,7 @@ func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType Price: ToFloat64(price), Amount: ToFloat64(amount), OrderType: openType, - LeverRate: leverRate, + LeverRate: 0, ContractName: contractType, } @@ -352,9 +353,16 @@ func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType switch openType { case OPEN_BUY, CLOSE_SELL: params.Set("side", "BUY") + if len(opt) > 0 && opt[0] == Futures_Twoway_Position_Mode { + params.Set("positionSide", "LONG") + } case OPEN_SELL, CLOSE_BUY: params.Set("side", "SELL") + if len(opt) > 0 && opt[0] == Futures_Twoway_Position_Mode { + params.Set("positionSide", "SHORT") + } } + if matchPrice == 0 { params.Set("type", "LIMIT") params.Set("price", price) @@ -364,6 +372,7 @@ func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType } bs.buildParamsSigned(¶ms) + resp, err := HttpPostForm2(bs.httpClient, path, params, map[string]string{"X-MBX-APIKEY": bs.accessKey}) if err != nil { @@ -386,11 +395,11 @@ func (bs *BinanceSwap) PlaceFutureOrder2(currencyPair CurrencyPair, contractType } func (bs *BinanceSwap) LimitFuturesOrder(currencyPair CurrencyPair, contractType, price, amount string, openType int, opt ...LimitOrderOptionalParameter) (*FutureOrder, error) { - return bs.PlaceFutureOrder2(currencyPair, contractType, price, amount, openType, 0, 10) + return bs.PlaceFutureOrder2(currencyPair, contractType, price, amount, openType, 0, opt...) } func (bs *BinanceSwap) MarketFuturesOrder(currencyPair CurrencyPair, contractType, amount string, openType int) (*FutureOrder, error) { - return bs.PlaceFutureOrder2(currencyPair, contractType, "0", amount, openType, 1, 10) + return bs.PlaceFutureOrder2(currencyPair, contractType, "0", amount, openType, 1) } func (bs *BinanceSwap) FutureCancelOrder(currencyPair CurrencyPair, contractType, orderId string) (bool, error) { @@ -406,7 +415,12 @@ func (bs *BinanceSwap) FutureCancelOrder(currencyPair CurrencyPair, contractType path := bs.apiV1 + ORDER_URI params := url.Values{} params.Set("symbol", bs.adaptCurrencyPair(currencyPair).ToSymbol("")) - params.Set("orderId", orderId) + + if strings.HasPrefix(orderId, "goex") { //goex default clientOrderId Features + params.Set("origClientOrderId", orderId) + } else { + params.Set("orderId", orderId) + } bs.buildParamsSigned(¶ms) diff --git a/binance/BinanceSwap_test.go b/binance/BinanceSwap_test.go index ccc65145..0554ea49 100644 --- a/binance/BinanceSwap_test.go +++ b/binance/BinanceSwap_test.go @@ -1,12 +1,13 @@ package binance import ( - goex "github.com/nntaoli-project/goex" "net" "net/http" "net/url" "testing" "time" + + goex "github.com/nntaoli-project/goex" ) var bs = NewBinanceSwap(&goex.APIConfig{ @@ -15,7 +16,6 @@ var bs = NewBinanceSwap(&goex.APIConfig{ Transport: &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse("socks5://127.0.0.1:1080") - return nil, nil }, Dial: (&net.Dialer{ Timeout: 10 * time.Second, @@ -40,7 +40,7 @@ func TestBinanceSwap_GetFutureIndex(t *testing.T) { } func TestBinanceSwap_GetKlineRecords(t *testing.T) { - kline, err := bs.GetKlineRecords("", goex.BTC_USDT, goex.KLINE_PERIOD_4H, 1, 0) + kline, err := bs.GetKlineRecords("", goex.BTC_USDT, goex.KLINE_PERIOD_4H, 1, goex.OptionalParameter{"test": 0}) t.Log(err, kline[0].Kline) } diff --git a/binance/FuturesWs.go b/binance/FuturesWs.go index d5128cbe..2d612806 100644 --- a/binance/FuturesWs.go +++ b/binance/FuturesWs.go @@ -2,9 +2,6 @@ package binance import ( "encoding/json" - "errors" - "github.com/nntaoli-project/goex" - "github.com/nntaoli-project/goex/internal/logger" "net/http" "net/url" "os" @@ -12,6 +9,9 @@ import ( "strings" "sync" "time" + + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" ) type FuturesWs struct { @@ -59,13 +59,13 @@ func NewFuturesWs() *FuturesWs { func (s *FuturesWs) connectUsdtFutures() { s.fOnce.Do(func() { - s.f = s.wsBuilder.WsUrl("wss://fstream.binance.com/ws").Build() + s.f = s.wsBuilder.WsUrl(TESTNET_FUTURE_USD_WS_BASE_URL).Build() }) } func (s *FuturesWs) connectFutures() { s.dOnce.Do(func() { - s.d = s.wsBuilder.WsUrl("wss://dstream.binance.com/ws").Build() + s.d = s.wsBuilder.WsUrl(TESTNET_FUTURE_COIN_WS_BASE_URL).Build() }) } @@ -84,12 +84,14 @@ func (s *FuturesWs) TradeCallback(f func(trade *goex.Trade, contract string)) { func (s *FuturesWs) SubscribeDepth(pair goex.CurrencyPair, contractType string) error { switch contractType { case goex.SWAP_USDT_CONTRACT: + s.connectUsdtFutures() return s.f.Subscribe(req{ Method: "SUBSCRIBE", Params: []string{pair.AdaptUsdToUsdt().ToLower().ToSymbol("") + "@depth10@100ms"}, Id: 1, }) default: + s.connectFutures() sym, _ := s.base.adaptToSymbol(pair.AdaptUsdtToUsd(), contractType) return s.d.Subscribe(req{ Method: "SUBSCRIBE", @@ -97,7 +99,6 @@ func (s *FuturesWs) SubscribeDepth(pair goex.CurrencyPair, contractType string) Id: 2, }) } - return errors.New("contract is error") } func (s *FuturesWs) SubscribeTicker(pair goex.CurrencyPair, contractType string) error { @@ -118,11 +119,26 @@ func (s *FuturesWs) SubscribeTicker(pair goex.CurrencyPair, contractType string) Id: 2, }) } - return errors.New("contract is error") } func (s *FuturesWs) SubscribeTrade(pair goex.CurrencyPair, contractType string) error { - panic("implement me") + switch contractType { + case goex.SWAP_USDT_CONTRACT: + s.connectUsdtFutures() + return s.f.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{pair.AdaptUsdToUsdt().ToLower().ToSymbol("") + "@aggTrade"}, + Id: 1, + }) + default: + s.connectFutures() + sym, _ := s.base.adaptToSymbol(pair.AdaptUsdtToUsd(), contractType) + return s.d.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{strings.ToLower(sym) + "@aggTrade"}, + Id: 1, + }) + } } func (s *FuturesWs) handle(data []byte) error { @@ -154,6 +170,13 @@ func (s *FuturesWs) handle(data []byte) error { return nil } + if e, ok := m["e"].(string); ok && e == "aggTrade" { + + contractType := m["s"].(string) + s.tradeCalFn(s.tradeHandle(m), contractType) + return nil + } + logger.Warn("unknown ws response:", string(data)) return nil @@ -204,3 +227,25 @@ func (s *FuturesWs) tickerHandle(m map[string]interface{}) *goex.FutureTicker { return &ticker } + +func (s *FuturesWs) tradeHandle(m map[string]interface{}) *goex.Trade { + var trade goex.Trade + + symbol, ok := m["s"].(string) // Symbol + if ok { + trade.Pair = adaptSymbolToCurrencyPair(symbol) //usdt swap + } + + trade.Tid = goex.ToInt64(m["a"]) // Aggregate trade ID + trade.Date = goex.ToInt64(m["E"]) // Event time + trade.Amount = goex.ToFloat64(m["q"]) // Quantity + trade.Price = goex.ToFloat64(m["p"]) // Price + + if m["m"].(bool) { + trade.Type = goex.BUY_MARKET + } else { + trade.Type = goex.SELL_MARKET + } + + return &trade +} diff --git a/binance/SpotWs.go b/binance/SpotWs.go index ad33efe8..01c27123 100644 --- a/binance/SpotWs.go +++ b/binance/SpotWs.go @@ -3,13 +3,14 @@ package binance import ( json2 "encoding/json" "fmt" - "github.com/nntaoli-project/goex" - "github.com/nntaoli-project/goex/internal/logger" "os" "sort" "strings" "sync" "time" + + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" ) type req struct { @@ -46,7 +47,7 @@ func NewSpotWs() *SpotWs { logger.Debugf("proxy url: %s", os.Getenv("HTTPS_PROXY")) spotWs.wsBuilder = goex.NewWsBuilder(). - WsUrl("wss://stream.binance.com:9443/stream?streams=depth/miniTicker/ticker/trade"). + WsUrl(TESTNET_SPOT_STREAM_BASE_URL + "?streams=depth/miniTicker/ticker/trade"). ProxyUrl(os.Getenv("HTTPS_PROXY")). ProtoHandleFunc(spotWs.handle).AutoReconnect() @@ -103,8 +104,20 @@ func (s *SpotWs) SubscribeTicker(pair goex.CurrencyPair) error { }) } +// TODO: test func (s *SpotWs) SubscribeTrade(pair goex.CurrencyPair) error { - panic("implement me") + + defer func() { + s.reqId++ + }() + + s.connect() + + return s.c.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{pair.ToLower().ToSymbol("") + "@aggTrade"}, + Id: s.reqId, + }) } func (s *SpotWs) handle(data []byte) error { @@ -123,6 +136,10 @@ func (s *SpotWs) handle(data []byte) error { return s.tickerHandle(r.Data, adaptStreamToCurrencyPair(r.Stream)) } + if strings.HasSuffix(r.Stream, "@aggTrade") { + return s.tradeHandle(r.Data, adaptStreamToCurrencyPair(r.Stream)) + } + logger.Warn("unknown ws response:", string(data)) return nil @@ -190,3 +207,31 @@ func (s *SpotWs) tickerHandle(data json2.RawMessage, pair goex.CurrencyPair) err return nil } + +func (s *SpotWs) tradeHandle(data json2.RawMessage, pair goex.CurrencyPair) error { + + var ( + tradeData = make(map[string]interface{}, 4) + trade goex.Trade + ) + + err := json2.Unmarshal(data, &tradeData) + if err != nil { + logger.Errorf("unmarshal ticker response data error [%s] , data = %s", err, string(data)) + return err + } + + trade.Pair = pair //Symbol + trade.Tid = goex.ToInt64(tradeData["a"]) // Aggregate trade ID + trade.Date = goex.ToInt64(tradeData["E"]) // Event time + trade.Amount = goex.ToFloat64(tradeData["q"]) // Quantity + trade.Price = goex.ToFloat64(tradeData["p"]) // Price + + if tradeData["m"].(bool) { + trade.Type = goex.BUY_MARKET + } else { + trade.Type = goex.SELL_MARKET + } + + return nil +} diff --git a/bitfinex/bitfinex_ws.go b/bitfinex/bitfinex_ws.go index 6d6320d8..e9bd0be0 100644 --- a/bitfinex/bitfinex_ws.go +++ b/bitfinex/bitfinex_ws.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math" + "os" "sync" "time" @@ -23,6 +24,7 @@ type BitfinexWs struct { eventMap map[int64]SubscribeEvent tickerCallback func(*Ticker) + depthCallback func(*Depth) tradeCallback func(*Trade) candleCallback func(*Kline) } @@ -46,7 +48,7 @@ func NewWs() *BitfinexWs { bws := &BitfinexWs{WsBuilder: NewWsBuilder(), eventMap: make(map[int64]SubscribeEvent)} bws.WsBuilder = bws.WsBuilder. WsUrl("wss://api-pub.bitfinex.com/ws/2"). - AutoReconnect(). + AutoReconnect().ProxyUrl(os.Getenv("HTTPS_PROXY")).DisableEnableCompression(). ProtoHandleFunc(bws.handle) return bws } @@ -57,6 +59,15 @@ func (bws *BitfinexWs) SetCallbacks(tickerCallback func(*Ticker), tradeCallback bws.candleCallback = candleCallback } +func (bws *BitfinexWs) TickerCallback(tickerCallback func(*Ticker)) { + bws.tickerCallback = tickerCallback +} +func (bws *BitfinexWs) DepthCallback(depthCallback func(*Depth)) { + bws.depthCallback = depthCallback +} +func (bws *BitfinexWs) TradeCallback(tradeCallback func(*Trade)) { + bws.tradeCallback = tradeCallback +} func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { if bws.tickerCallback == nil { return fmt.Errorf("please set ticker callback func") @@ -67,6 +78,10 @@ func (bws *BitfinexWs) SubscribeTicker(pair CurrencyPair) error { "symbol": convertPairToBitfinexSymbol("t", pair)}) } +func (bws *BitfinexWs) SubscribeDepth(pair CurrencyPair) error { + panic("not implements") + return nil +} func (bws *BitfinexWs) SubscribeTrade(pair CurrencyPair) error { if bws.tradeCallback == nil { return fmt.Errorf("please set trade callback func") diff --git a/builder/APIBuilder.go b/builder/APIBuilder.go index a76eb748..1e044c96 100644 --- a/builder/APIBuilder.go +++ b/builder/APIBuilder.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + . "github.com/nntaoli-project/goex" "github.com/nntaoli-project/goex/bigone" "github.com/nntaoli-project/goex/binance" @@ -28,6 +29,7 @@ import ( "github.com/nntaoli-project/goex/huobi" "github.com/nntaoli-project/goex/kraken" "github.com/nntaoli-project/goex/okex" + okexV5 "github.com/nntaoli-project/goex/okex/v5" "github.com/nntaoli-project/goex/poloniex" "github.com/nntaoli-project/goex/zb" ) @@ -42,6 +44,7 @@ type APIBuilder struct { apiPassphrase string futuresEndPoint string endPoint string + futuresLever float64 } type HttpClientConfig struct { @@ -186,6 +189,11 @@ func (builder *APIBuilder) Endpoint(endpoint string) (_builer *APIBuilder) { return builder } +func (builder *APIBuilder) FuturesLever(lever float64) (_builder *APIBuilder) { + builder.futuresLever = lever + return builder +} + func (builder *APIBuilder) Build(exName string) (api API) { var _api API switch exName { @@ -206,7 +214,7 @@ func (builder *APIBuilder) Build(exName string) (api API) { Endpoint: builder.endPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey}) - case OKEX_V3, OKEX: + case OKEX_V3: _api = okex.NewOKEx(&APIConfig{ HttpClient: builder.client, ApiKey: builder.apiKey, @@ -214,6 +222,14 @@ func (builder *APIBuilder) Build(exName string) (api API) { ApiPassphrase: builder.apiPassphrase, Endpoint: builder.endPoint, }) + case OKEX: + _api = okexV5.NewOKExV5Spot(&APIConfig{ + HttpClient: builder.client, + ApiKey: builder.apiKey, + ApiSecretKey: builder.secretkey, + ApiPassphrase: builder.apiPassphrase, + Endpoint: builder.endPoint, + }) case BITFINEX: _api = bitfinex.New(builder.client, builder.apiKey, builder.secretkey) case KRAKEN: @@ -272,19 +288,22 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { Endpoint: builder.futuresEndPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, - ApiPassphrase: builder.apiPassphrase}).OKExFuture + ApiPassphrase: builder.apiPassphrase, + Lever: builder.futuresLever}).OKExFuture case HBDM: return huobi.NewHbdm(&APIConfig{ HttpClient: builder.client, Endpoint: builder.futuresEndPoint, ApiKey: builder.apiKey, - ApiSecretKey: builder.secretkey}) + ApiSecretKey: builder.secretkey, + Lever: builder.futuresLever}) case HBDM_SWAP: return huobi.NewHbdmSwap(&APIConfig{ HttpClient: builder.client, Endpoint: builder.endPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, + Lever: builder.futuresLever, }) case OKEX_SWAP: return okex.NewOKEx(&APIConfig{ @@ -292,7 +311,8 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { Endpoint: builder.futuresEndPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, - ApiPassphrase: builder.apiPassphrase}).OKExSwap + ApiPassphrase: builder.apiPassphrase, + Lever: builder.futuresLever}).OKExSwap case COINBENE: return coinbene.NewCoinbeneSwap(APIConfig{ HttpClient: builder.client, @@ -300,6 +320,7 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { Endpoint: builder.futuresEndPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, + Lever: builder.futuresLever, }) case BINANCE_SWAP: @@ -308,6 +329,7 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { Endpoint: builder.futuresEndPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, + Lever: builder.futuresLever, }) case BINANCE, BINANCE_FUTURES: return binance.NewBinanceFutures(&APIConfig{ @@ -315,6 +337,7 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { Endpoint: builder.futuresEndPoint, ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, + Lever: builder.futuresLever, }) default: println(fmt.Sprintf("%s not support future", exName)) @@ -349,6 +372,8 @@ func (builder *APIBuilder) BuildSpotWs(exName string) (SpotWsApi, error) { return huobi.NewSpotWs(), nil case BINANCE: return binance.NewSpotWs(), nil + case BITFINEX: + return bitfinex.NewWs(), nil } return nil, errors.New("not support the exchange " + exName) } diff --git a/examples/binance_futures_trade.go b/examples/binance_futures_trade.go new file mode 100644 index 00000000..34d4a114 --- /dev/null +++ b/examples/binance_futures_trade.go @@ -0,0 +1,102 @@ +package main + +import ( + "log" + + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/binance" + "github.com/nntaoli-project/goex/builder" +) + +const ( + BINANCE_TESTNET_API_KEY = "YOUR_KEY" + BINANCE_TESTNET_API_KEY_SECRET = "YOUR_KEY_SECRET" +) + +func fetchFutureDepthAndIndex() { + binanceApi := builder.DefaultAPIBuilder.APIKey(BINANCE_TESTNET_API_KEY).APISecretkey(BINANCE_TESTNET_API_KEY_SECRET).Endpoint(binance.TESTNET_SPOT_WS_BASE_URL).BuildFuture(goex.BINANCE_SWAP) + + depth, err := binanceApi.GetFutureDepth(goex.BTC_USD, goex.SWAP_USDT_CONTRACT, 100) + if err != nil { + log.Fatalln(err.Error()) + } + + askTotalAmount, bidTotalAmount := 0.0, 0.0 + askTotalVol, bidTotalVol := 0.0, 0.0 + + for _, v := range depth.AskList { + askTotalAmount += v.Amount + askTotalVol += v.Price * v.Amount + } + + for _, v := range depth.BidList { + bidTotalAmount += v.Amount + bidTotalVol += v.Price * v.Amount + } + + markPrice, err := binanceApi.GetFutureIndex(goex.BTC_USD) + if err != nil { + log.Fatalln(err.Error()) + } + + log.Printf("CURRENT mark price: %f", markPrice) + + log.Printf("ContractType: %s ContractId: %s Pair: %s UTime: %s AmountTickSize: %d\n", depth.ContractType, depth.ContractId, depth.Pair, depth.UTime.String(), depth.Pair.AmountTickSize) + log.Printf("askTotalAmount: %f, bidTotalAmount: %f, askTotalVol: %f, bidTotalVol: %f", askTotalAmount, bidTotalAmount, askTotalVol, bidTotalVol) + log.Printf("ask price averge: %f, bid price averge: %f,", askTotalVol/askTotalAmount, bidTotalVol/bidTotalAmount) + log.Printf("ask-bid spread: %f%%,", 100*(depth.AskList[0].Price-depth.BidList[0].Price)/markPrice) +} + +func subscribeFutureMarketData() { + binanceWs, err := builder.DefaultAPIBuilder.APIKey(BINANCE_TESTNET_API_KEY).APISecretkey(BINANCE_TESTNET_API_KEY_SECRET).Endpoint(binance.TESTNET_FUTURE_USD_WS_BASE_URL).BuildFuturesWs(goex.BINANCE_FUTURES) + + if err != nil { + log.Fatalln(err.Error()) + } + binanceWs.TickerCallback(func(ticker *goex.FutureTicker) { + //log.Printf("%+v\n", *ticker.Ticker) + }) + binanceWs.SubscribeTicker(goex.BTC_USD, goex.SWAP_USDT_CONTRACT) + binanceWs.DepthCallback(func(depth *goex.Depth) { + log.Printf("%+v\n", *depth) + }) + binanceWs.SubscribeDepth(goex.BTC_USDT, goex.SWAP_USDT_CONTRACT) + + binanceWs.TradeCallback(func(trade *goex.Trade, contractType string) { + log.Printf("%+v\n", *trade) + }) + binanceWs.SubscribeTrade(goex.BTC_USDT, goex.SWAP_USDT_CONTRACT) + + select {} +} + +func subscribeSpotMarketData() { + + binanceWs, err := builder.DefaultAPIBuilder.APIKey(BINANCE_TESTNET_API_KEY).APISecretkey(BINANCE_TESTNET_API_KEY_SECRET).Endpoint(binance.TESTNET_FUTURE_USD_BASE_URL).BuildSpotWs(goex.BINANCE) + + if err != nil { + log.Fatalln(err.Error()) + } + binanceWs.TickerCallback(func(ticker *goex.Ticker) { + log.Printf("%+v\n", *ticker) + }) + binanceWs.SubscribeTicker(goex.BTC_USDT) + binanceWs.DepthCallback(func(depth *goex.Depth) { + log.Printf("%+v\n", *depth) + }) + binanceWs.SubscribeDepth(goex.BTC_USDT) + + binanceWs.TradeCallback(func(trade *goex.Trade) { + log.Printf("%+v\n", *trade) + }) + binanceWs.SubscribeTrade(goex.BTC_USDT) + + select {} + +} + +func main() { + //subscribeFutureMarketData() + //subscribeFutureMarketData() + subscribeSpotMarketData() +} diff --git a/gdax/gdax_test.go b/gdax/gdax_test.go index 4836ba5f..d84ffe9a 100644 --- a/gdax/gdax_test.go +++ b/gdax/gdax_test.go @@ -31,5 +31,5 @@ func TestGdax_GetDepth(t *testing.T) { func TestGdax_GetKlineRecords(t *testing.T) { logger.SetLevel(logger.DEBUG) - t.Log(gdax.GetKlineRecords(goex.BTC_USD, goex.KLINE_PERIOD_1DAY, 0, 0)) + t.Log(gdax.GetKlineRecords(goex.BTC_USD, goex.KLINE_PERIOD_1DAY, 0, goex.OptionalParameter{"test": 0})) } diff --git a/go.mod b/go.mod index 50b7dc6c..4da86f34 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.12 require ( github.com/Kucoin/kucoin-go-sdk v1.2.7 github.com/go-openapi/errors v0.19.4 - github.com/google/uuid v1.1.1 - github.com/gorilla/websocket v1.4.1 - github.com/json-iterator/go v1.1.7 + github.com/google/uuid v1.3.0 + github.com/gorilla/websocket v1.5.0 + github.com/klauspost/compress v1.15.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/nubo/jwt v0.0.0-20150918093313-da5b79c3bbaf - github.com/stretchr/testify v1.4.0 - github.com/valyala/fasthttp v1.6.0 - golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect + github.com/stretchr/testify v1.7.0 + github.com/valyala/fasthttp v1.36.0 + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect ) diff --git a/go.sum b/go.sum index 8681b463..3db6d164 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,23 @@ github.com/Kucoin/kucoin-go-sdk v1.2.7 h1:lh74YnCmcswmnvkk0nMeodw+y17UEjMhyEzrIS14SDs= github.com/Kucoin/kucoin-go-sdk v1.2.7/go.mod h1:Wz3fTuM5gIct9chN6H6OBCXbku10XEcAjH5g/FL3wIY= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-openapi/errors v0.19.4 h1:fSGwO1tSYHFu70NKaWJt5Qh0qoBRtCm/mXS1yhf+0W0= github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw= +github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nubo/jwt v0.0.0-20150918093313-da5b79c3bbaf h1:mP7zQzhCrNQgSdCpxFxyZV/JMHbz4LJsyppAZMQVrI0= github.com/nubo/jwt v0.0.0-20150918093313-da5b79c3bbaf/go.mod h1:LuR7jHS+7SJ6EywD7zZiO6h0vwTBSevFk5wunVt3gf4= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -39,23 +31,32 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +github.com/valyala/fasthttp v1.36.0 h1:NhqfO/cB7Ajn1czkKnWkMHyPYr5nyND14ZGPk23g0/c= +github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/huobi/Hbdm_Ws_test.go b/huobi/Hbdm_Ws_test.go index e2f42b52..ff240ace 100644 --- a/huobi/Hbdm_Ws_test.go +++ b/huobi/Hbdm_Ws_test.go @@ -1,10 +1,11 @@ package huobi import ( - "github.com/nntaoli-project/goex" "log" "testing" "time" + + "github.com/nntaoli-project/goex" ) func TestNewHbdmWs(t *testing.T) { @@ -24,7 +25,7 @@ func TestNewHbdmWs(t *testing.T) { }) t.Log(ws.SubscribeTicker(goex.BTC_USD, goex.QUARTER_CONTRACT)) - t.Log(ws.SubscribeDepth(goex.BTC_USD, goex.NEXT_WEEK_CONTRACT, 0)) + t.Log(ws.SubscribeDepth(goex.BTC_USD, goex.NEXT_WEEK_CONTRACT)) t.Log(ws.SubscribeTrade(goex.LTC_USD, goex.THIS_WEEK_CONTRACT)) time.Sleep(time.Minute) } diff --git a/huobi/Hbdm_test.go b/huobi/Hbdm_test.go index 3dfe9f56..e1f372e9 100644 --- a/huobi/Hbdm_test.go +++ b/huobi/Hbdm_test.go @@ -1,9 +1,10 @@ package huobi import ( - "github.com/nntaoli-project/goex" "testing" "time" + + "github.com/nntaoli-project/goex" ) var dm = NewHbdm(&goex.APIConfig{ @@ -58,7 +59,7 @@ func TestHbdm_GetFutureEstimatedPrice(t *testing.T) { } func TestHbdm_GetKlineRecords(t *testing.T) { - klines, _ := dm.GetKlineRecords(goex.QUARTER_CONTRACT, goex.EOS_USD, goex.KLINE_PERIOD_1MIN, 20, 0) + klines, _ := dm.GetKlineRecords(goex.QUARTER_CONTRACT, goex.EOS_USD, goex.KLINE_PERIOD_1MIN, 20, goex.OptionalParameter{"test": 0}) for _, k := range klines { tt := time.Unix(k.Timestamp, 0) t.Log(k.Pair, tt, k.Open, k.Close, k.High, k.Low, k.Vol, k.Vol2) diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index daebe7ef..59542216 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -4,12 +4,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/nntaoli-project/goex/internal/logger" "net/url" "strconv" "strings" "time" + "github.com/nntaoli-project/goex/internal/logger" + . "github.com/nntaoli-project/goex" ) diff --git a/okex/OKExSwap_test.go b/okex/OKExSwap_test.go index 65afd1cb..64277d77 100644 --- a/okex/OKExSwap_test.go +++ b/okex/OKExSwap_test.go @@ -1,11 +1,12 @@ package okex import ( - "github.com/nntaoli-project/goex" "net/http" "net/url" "testing" "time" + + "github.com/nntaoli-project/goex" ) var config = &goex.APIConfig{ @@ -75,7 +76,7 @@ func TestOKExSwap_GetHistoricalFunding(t *testing.T) { func TestOKExSwap_GetKlineRecords(t *testing.T) { since := time.Now().Add(-24 * time.Hour).Unix() - kline, err := okExSwap.GetKlineRecords(goex.SWAP_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 0, int(since)) + kline, err := okExSwap.GetKlineRecords(goex.SWAP_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 10, goex.OptionalParameter{"since": since}) t.Log(err, kline[0].Kline) } diff --git a/okex/OKEx_test.go b/okex/OKEx_test.go index a41f67fe..02154355 100644 --- a/okex/OKEx_test.go +++ b/okex/OKEx_test.go @@ -1,12 +1,13 @@ package okex import ( - "github.com/nntaoli-project/goex" - "github.com/nntaoli-project/goex/internal/logger" - "github.com/stretchr/testify/assert" "net/http" "testing" "time" + + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "github.com/stretchr/testify/assert" ) func init() { @@ -137,7 +138,7 @@ func TestOKExFuture_GetRate(t *testing.T) { func TestOKExFuture_GetKlineRecords(t *testing.T) { since := time.Now().Add(-24 * time.Hour).Unix() - kline, err := okex.OKExFuture.GetKlineRecords(goex.QUARTER_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 0, int(since)) + kline, err := okex.OKExFuture.GetKlineRecords(goex.QUARTER_CONTRACT, goex.BTC_USD, goex.KLINE_PERIOD_4H, 10, goex.OptionalParameter{"since": since}) assert.Nil(t, err) for _, k := range kline { t.Logf("%+v", k.Kline) diff --git a/okex/v5/OKExV5.go b/okex/v5/OKExV5.go new file mode 100644 index 00000000..d03981f9 --- /dev/null +++ b/okex/v5/OKExV5.go @@ -0,0 +1,719 @@ +package okex + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" +) + +const ( + v5RestBaseUrl = "https://www.okex.com" + v5WsBaseUrl = "wss://ws.okex.com:8443/ws/v5" + + CONTENT_TYPE = "Content-Type" + ACCEPT = "Accept" + APPLICATION_JSON_UTF8 = "application/json; charset=UTF-8" + APPLICATION_JSON = "application/json" + OK_ACCESS_KEY = "OK-ACCESS-KEY" + OK_ACCESS_SIGN = "OK-ACCESS-SIGN" + OK_ACCESS_TIMESTAMP = "OK-ACCESS-TIMESTAMP" + OK_ACCESS_PASSPHRASE = "OK-ACCESS-PASSPHRASE" +) + +// base interface for okex v5 +type OKExV5 struct { + config *APIConfig + customCIDFunc func() string +} + +func NewOKExV5(config *APIConfig) *OKExV5 { + if config.Endpoint == "" { + config.Endpoint = v5RestBaseUrl + } + okex := &OKExV5{config: config} + return okex +} + +func (ok *OKExV5) ExchangeName() string { + return OKEX +} + +func (ok *OKExV5) SetCustomCID(f func() string) { + ok.customCIDFunc = f +} + +//获取所有产品行情信息 +//产品类型instType +// SPOT:币币 +// SWAP:永续合约 +// FUTURES:交割合约 +// OPTION:期权 +// func (ok *OKExV5) GetTickersV5(instType, uly string) ([]Ticker, error) { +// urlPath := fmt.Sprintf("/api/v5/market/tickers?instType=%s", instType) +// if instType == "SWAP" || instType == "FUTURES" || instType == "OPTION" { +// urlPath = fmt.Sprintf("%s&uly=%s", urlPath, uly) +// } +// var response spotTickerResponse +// err := ok.OKEx.DoAuthorRequest("GET", urlPath, "", &response) +// if err != nil { +// return nil, err +// } + +// date, _ := time.Parse(time.RFC3339, response.Timestamp) +// return &Ticker{ +// Pair: currency, +// Last: response.Last, +// High: response.High24h, +// Low: response.Low24h, +// Sell: response.BestAsk, +// Buy: response.BestBid, +// Vol: response.BaseVolume24h, +// Date: uint64(time.Duration(date.UnixNano() / int64(time.Millisecond)))}, nil + +// } + +type TickerV5 struct { + InstId string `json:"instId"` + Last float64 `json:"last,string"` + BuyPrice float64 `json:"bidPx,string"` + BuySize float64 `json:"bidSz,string"` + SellPrice float64 `json:"askPx,string"` + SellSize float64 `json:"askSz,string"` + Open float64 `json:"open24h,string"` + High float64 `json:"high24h,string"` + Low float64 `json:"low24h,string"` + Vol float64 `json:"volCcy24h,string"` + VolQuote float64 `json:"vol24h,string"` + Timestamp uint64 `json:"ts,string"` // 单位:ms +} + +func (ok *OKExV5) GetTickerV5(instId string) (*TickerV5, error) { + urlPath := fmt.Sprintf("%s/api/v5/market/ticker?instId=%s", ok.config.Endpoint, instId) + type TickerV5Response struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []TickerV5 `json:"data"` + } + var response TickerV5Response + err := HttpGet4(ok.config.HttpClient, urlPath, nil, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetTickerV5 error:%s", response.Msg) + } + return &response.Data[0], nil +} + +type DepthV5 struct { + Asks [][]string `json:"asks,string"` + Bids [][]string `json:"bids,string"` + Timestamp uint64 `json:"ts,string"` // 单位:ms +} + +func (ok *OKExV5) GetDepthV5(instId string, size int) (*DepthV5, error) { + + urlPath := fmt.Sprintf("%s/api/v5/market/books?instId=%s", ok.config.Endpoint, instId) + if size > 0 { + urlPath = fmt.Sprintf("%s&sz=%d", urlPath, size) + } + type DepthV5Response struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []DepthV5 `json:"data"` + } + var response DepthV5Response + err := HttpGet4(ok.config.HttpClient, urlPath, nil, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetDepthV5 error:%s", response.Msg) + } + return &response.Data[0], nil +} + +func (ok *OKExV5) GetKlineRecordsV5(instId string, period KlinePeriod, params *url.Values) ([][]string, error) { + urlPath := fmt.Sprintf("%s/api/v5/market/candles?instId=%s&bar=%s", ok.config.Endpoint, instId, ok.adaptKLineBar(period)) + + if params.Encode() != "" { + urlPath = fmt.Sprintf("%s&%s", urlPath, params.Encode()) + } + + logger.Debugf("[OKExV5] GetKlineRecordsV5 Url: %s", urlPath) + + type CandleResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data [][]string `json:"data"` + } + + var response CandleResponse + err := HttpGet4(ok.config.HttpClient, urlPath, nil, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetKlineRecordsV5 error:%s", response.Msg) + } + return response.Data, nil +} + +/* + Get a iso time + eg: 2018-03-16T18:02:48.284Z +*/ +func IsoTime() string { + utcTime := time.Now().UTC() + iso := utcTime.String() + isoBytes := []byte(iso) + iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z" + return iso +} + +/* + Get a http request body is a json string and a byte array. +*/ +func (ok *OKExV5) BuildRequestBody(params interface{}) (string, *bytes.Reader, error) { + if params == nil { + return "", nil, errors.New("illegal parameter") + } + data, err := json.Marshal(params) + if err != nil { + //log.Println(err) + return "", nil, errors.New("json convert string error") + } + + jsonBody := string(data) + binBody := bytes.NewReader(data) + + return jsonBody, binBody, nil +} + +func (ok *OKExV5) doParamSign(httpMethod, uri, requestBody string) (string, string) { + timestamp := IsoTime() + preText := fmt.Sprintf("%s%s%s%s", timestamp, strings.ToUpper(httpMethod), uri, requestBody) + //log.Println("preHash", preText) + sign, _ := GetParamHmacSHA256Base64Sign(ok.config.ApiSecretKey, preText) + return sign, timestamp +} + +func (ok *OKExV5) DoAuthorRequest(httpMethod, uri, reqBody string, response interface{}) error { + url := ok.config.Endpoint + uri + sign, timestamp := ok.doParamSign(httpMethod, uri, reqBody) + //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, + //COOKIE: LOCALE + "en_US", + OK_ACCESS_KEY: ok.config.ApiKey, + OK_ACCESS_PASSPHRASE: ok.config.ApiPassphrase, + OK_ACCESS_SIGN: sign, + OK_ACCESS_TIMESTAMP: fmt.Sprint(timestamp)}) + if err != nil { + //log.Println(err) + return err + } else { + logger.Log.Debug(string(resp)) + return json.Unmarshal(resp, &response) + } +} + +type CreateOrderParam struct { + Symbol string //产品ID + TradeMode string //交易模式, 保证金模式:isolated:逐仓 ;cross:全仓, 非保证金模式:cash:非保证金 + Side string // 订单方向 buy:买 sell:卖 + OrderType string //订单类型 + // market:市价单 + // limit:限价单 + // post_only:只做maker单 + // fok:全部成交或立即取消 + // ioc:立即成交并取消剩余 + + Size string // 委托数量 + PosSide string //持仓方向 在双向持仓模式下必填,且仅可选择 long 或 short + Price string //委托价格,仅适用于限价单 + CCY string // 保证金币种,仅适用于单币种保证金模式下的全仓杠杆订单 + ClientOrdId string //客户自定义订单ID 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。 + Tag string //订单标签 字母(区分大小写)与数字的组合,可以是纯字母、纯数字,且长度在1-8位之间。 + ReduceOnly bool //是否只减仓,true 或 false,默认false 仅适用于币币杠杆订单 +} + +type OrderSummaryV5 struct { + OrdId string `json:"ordId"` + ClientOrdId string `json:"clOrdId"` //客户自定义订单ID 字母(区分大小写)与数字的组合,可以是纯字母、纯数字且长度要在1-32位之间。 + Tag string `json:"tag"` + SCode string `json:"sCode"` + SMsg string `json:"sMsg"` +} + +func (ok *OKExV5) CreateOrder(param *CreateOrderParam) (*OrderSummaryV5, error) { + + reqBody := make(map[string]interface{}) + + reqBody["instId"] = param.Symbol + reqBody["tdMode"] = param.TradeMode + reqBody["side"] = param.Side + reqBody["ordType"] = param.OrderType + reqBody["sz"] = param.Size + + if param.CCY != "" { + reqBody["ccy"] = param.CCY + } + if param.ClientOrdId != "" { + reqBody["clOrdId"] = param.ClientOrdId + } else { + reqBody["clOrdId"] = param.ClientOrdId + } + if param.Tag != "" { + reqBody["tag"] = param.Tag + } + if param.PosSide != "" { + reqBody["posSide"] = param.PosSide + } + if param.Price != "" { + reqBody["px"] = param.Price + } + if param.ReduceOnly != false { + reqBody["reduceOnly"] = param.ReduceOnly + } + + type OrderResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []OrderSummaryV5 `json:"data"` + } + var response OrderResponse + + uri := "/api/v5/trade/order" + + jsonStr, _, _ := ok.BuildRequestBody(reqBody) + err := ok.DoAuthorRequest(http.MethodPost, uri, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + msg := "" + if len(response.Data) > 0 { + msg = fmt.Sprintf("code:%d, scode:%s, smsg:%s", response.Code, response.Data[0].SCode, response.Data[0].SMsg) + } else { + msg = fmt.Sprintf("code:%d", response.Code) + } + + return nil, fmt.Errorf("CreateOrder error:%s", msg) + } + return &response.Data[0], nil +} + +func (ok *OKExV5) CancelOrderV5(instId, ordId, clOrdId string) (*OrderSummaryV5, error) { + + reqBody := make(map[string]interface{}) + + reqBody["instId"] = instId + if ordId != "" { + reqBody["ordId"] = ordId + } + if clOrdId != "" { + reqBody["clOrdId"] = clOrdId + } + + type OrderResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []OrderSummaryV5 `json:"data"` + } + var response OrderResponse + + uri := "/api/v5/trade/cancel-order" + + jsonStr, _, _ := ok.BuildRequestBody(reqBody) + err := ok.DoAuthorRequest(http.MethodPost, uri, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + msg := response.Msg + if msg == "" { + if len(response.Data) > 0 { + msg = fmt.Sprintf("code:%d, scode:%s, smsg:%s", response.Code, response.Data[0].SCode, response.Data[0].SMsg) + } else { + msg = fmt.Sprintf("code:%d", response.Code) + } + } + return nil, fmt.Errorf("CancelOrderV5 error:%s", msg) + } + return &response.Data[0], nil +} + +func (ok *OKExV5) ClosePositions(instId, mgnMode, posSide string) (*OrderSummaryV5, error) { + reqBody := make(map[string]interface{}) + reqBody["instId"] = instId + reqBody["mgnMode"] = mgnMode + reqBody["posSide"] = posSide + + type OrderResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []OrderSummaryV5 `json:"data"` + } + var response OrderResponse + + uri := "/api/v5/trade/close-position" + + jsonStr, _, _ := ok.BuildRequestBody(reqBody) + err := ok.DoAuthorRequest(http.MethodPost, uri, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + msg := response.Msg + if msg == "" { + if len(response.Data) > 0 { + msg = fmt.Sprintf("code:%d, scode:%s, smsg:%s", response.Code, response.Data[0].SCode, response.Data[0].SMsg) + } else { + msg = fmt.Sprintf("code:%d", response.Code) + } + } + return nil, fmt.Errorf("ClosePositions error:%s", msg) + } + return &response.Data[0], nil +} + +type PendingOrderParam struct { + InstType string + Uly string + InstId string //产品ID + OrdType string + State string + After string + Before string + Limit string +} + +type OrderV5 struct { + AccFillSz string `json:"accFillSz"` + AvgPx string `json:"avgPx"` + CTime int `json:"cTime,string"` + Category string `json:"category"` + Ccy string `json:"ccy"` + ClOrdID string `json:"clOrdId"` + Fee float64 `json:"fee,string"` + FeeCcy string `json:"feeCcy"` + FillPx string `json:"fillPx"` + FillSz string `json:"fillSz"` + FillTime string `json:"fillTime"` + InstID string `json:"instId"` + InstType string `json:"instType"` + Lever string `json:"lever"` + OrdID string `json:"ordId"` + OrdType string `json:"ordType"` + Pnl string `json:"pnl"` + PosSide string `json:"posSide"` + Px float64 `json:"px,string"` + Rebate string `json:"rebate"` + RebateCcy string `json:"rebateCcy"` + Side string `json:"side"` + SlOrdPx string `json:"slOrdPx"` + SlTriggerPx string `json:"slTriggerPx"` + State string `json:"state"` + Sz float64 `json:"sz,string"` + Tag string `json:"tag"` + TdMode string `json:"tdMode"` + TpOrdPx string `json:"tpOrdPx"` + TpTriggerPx string `json:"tpTriggerPx"` + TradeID string `json:"tradeId"` + UTime int64 `json:"uTime,string"` +} + +func (ok *OKExV5) GetPendingOrders(param *PendingOrderParam) ([]OrderV5, error) { + + reqBody := make(map[string]string) + + if param.InstType != "" { + reqBody["instType"] = param.InstType + } + if param.Uly != "" { + reqBody["uly"] = param.Uly + } + if param.InstId != "" { + reqBody["instId"] = param.InstId + } + if param.OrdType != "" { + reqBody["ordType"] = param.OrdType + } + if param.State != "" { + reqBody["state"] = param.State + } + if param.Before != "" { + reqBody["before"] = param.Before + } + if param.After != "" { + reqBody["after"] = param.After + } + if param.Limit != "" { + reqBody["limit"] = param.Limit + } + + type OrderResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []OrderV5 `json:"data"` + } + var response OrderResponse + + uri := url.Values{} + for k, v := range reqBody { + uri.Set(k, v) + } + path := "/api/v5/trade/orders-pending" + if len(reqBody) > 0 { + path = fmt.Sprintf("%s?%s", path, uri.Encode()) + } + + jsonStr, _, _ := ok.BuildRequestBody(reqBody) + err := ok.DoAuthorRequest(http.MethodGet, path, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetPendingOrders error:%s", response.Msg) + } + return response.Data, nil +} + +func (ok *OKExV5) GetOrderV5(instId, ordId, clOrdId string) (*OrderV5, error) { + + reqBody := make(map[string]string) + + reqBody["instId"] = instId + if ordId != "" { + reqBody["ordId"] = ordId + } + if clOrdId != "" { + reqBody["clOrdId"] = clOrdId + } + + type OrderResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []OrderV5 `json:"data"` + } + var response OrderResponse + + uri := url.Values{} + for k, v := range reqBody { + uri.Set(k, v) + } + path := "/api/v5/trade/order" + if len(reqBody) > 0 { + path = fmt.Sprintf("%s?%s", path, uri.Encode()) + } + + jsonStr, _, _ := ok.BuildRequestBody(reqBody) + err := ok.DoAuthorRequest(http.MethodGet, path, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetOrderV5 error:%s", response.Msg) + } + return &response.Data[0], nil +} + +func (ok *OKExV5) GetOrderHistory(instType, instId, ordType, state, afterID, beforeID string) ([]OrderV5, error) { + + reqBody := make(map[string]string) + + reqBody["instType"] = instType + if instId != "" { + reqBody["instId"] = instId + } + if ordType != "" { + reqBody["ordType"] = ordType + } + if state != "" { + reqBody["state"] = state + } + if afterID != "" { + reqBody["after"] = afterID + } + if beforeID != "" { + reqBody["before"] = beforeID + } + // reqBody["limit"] = "100" + + type OrderResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []OrderV5 `json:"data"` + } + var response OrderResponse + + uri := url.Values{} + for k, v := range reqBody { + uri.Set(k, v) + } + path := "/api/v5/trade/orders-history-archive" + if len(reqBody) > 0 { + path = fmt.Sprintf("%s?%s", path, uri.Encode()) + } + + jsonStr, _, _ := ok.BuildRequestBody(reqBody) + err := ok.DoAuthorRequest(http.MethodGet, path, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetOrderV5 error:%s", response.Msg) + } + return response.Data, nil +} + +type AssetSummary struct { + Currency string `json:"ccy"` + Total float64 `json:"bal"` + Available float64 `json:"availBal,string"` + Frozen float64 `json:"frozenBal,string"` +} + +func (ok *OKExV5) GetAssetBalances(currency string) ([]AssetSummary, error) { + + reqBody := make(map[string]interface{}) + + path := "/api/v5/asset/balances" + jsonStr := "" + reqBody["ccy"] = currency + if currency != "" { + reqBody["ccy"] = currency + jsonStr, _, _ = ok.BuildRequestBody(reqBody) + path = fmt.Sprintf("%s?ccy=%s", path, currency) + } + + type AssetSummaryResponse struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []AssetSummary `json:"data"` + } + var response AssetSummaryResponse + + err := ok.DoAuthorRequest(http.MethodGet, path, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetAssetBalances error:%s", response.Msg) + } + return response.Data, nil +} + +type BalanceV5 struct { + AdjEq string `json:"adjEq"` + Details []BalanceDetail `json:"details"` + Imr string `json:"imr"` + IsoEq string `json:"isoEq"` + MgnRatio string `json:"mgnRatio"` + Mmr string `json:"mmr"` + OrdFroz string `json:"ordFroz"` + TotalEq string `json:"totalEq"` + UTime string `json:"uTime"` +} + +type BalanceDetail struct { + Available string `json:"availBal"` + AvailEq string `json:"availEq"` + CashBal string `json:"cashBal"` + Currency string `json:"ccy"` + DisEq string `json:"disEq"` + Eq string `json:"eq"` + Frozen string `json:"frozenBal"` + Interest string `json:"interest"` + IsoEq string `json:"isoEq"` + Liab string `json:"liab"` + MgnRatio string `json:"mgnRatio"` + OrdFrozen string `json:"ordFrozen"` + UTime string `json:"uTime"` + Upl string `json:"upl"` + UplLiab string `json:"uplLiab"` +} + +func (ok *OKExV5) GetAccountBalances(currency string) (*BalanceV5, error) { + + reqBody := make(map[string]interface{}) + + path := "/api/v5/account/balance" + jsonStr := "" + reqBody["ccy"] = currency + if currency != "" { + reqBody["ccy"] = currency + jsonStr, _, _ = ok.BuildRequestBody(reqBody) + path = fmt.Sprintf("%s?ccy=%s", path, currency) + } + + type BalanceV5Response struct { + Code int `json:"code,string"` + Msg string `json:"msg"` + Data []BalanceV5 `json:"data"` + } + var response BalanceV5Response + + err := ok.DoAuthorRequest(http.MethodGet, path, jsonStr, &response) + if err != nil { + return nil, err + } + + if response.Code != 0 { + return nil, fmt.Errorf("GetAccountBalances error:%s", response.Msg) + } + return &response.Data[0], nil +} + +func (ok *OKExV5) adaptKLineBar(period KlinePeriod) string { + bar := "1D" + switch period { + case KLINE_PERIOD_1MIN: + bar = "1m" + case KLINE_PERIOD_3MIN: + bar = "3m" + case KLINE_PERIOD_5MIN: + bar = "5m" + case KLINE_PERIOD_15MIN: + bar = "15m" + case KLINE_PERIOD_30MIN: + bar = "30m" + case KLINE_PERIOD_1H, KLINE_PERIOD_60MIN: + bar = "1H" + case KLINE_PERIOD_2H: + bar = "2H" + case KLINE_PERIOD_4H: + bar = "4H" + case KLINE_PERIOD_6H: + bar = "6H" + case KLINE_PERIOD_12H: + bar = "12H" + case KLINE_PERIOD_1DAY: + bar = "1D" + case KLINE_PERIOD_1WEEK: + bar = "1W" + default: + bar = "1D" + } + return bar +} diff --git a/okex/v5/OKExV5Spot.go b/okex/v5/OKExV5Spot.go new file mode 100644 index 00000000..0e6bd024 --- /dev/null +++ b/okex/v5/OKExV5Spot.go @@ -0,0 +1,355 @@ +package okex + +import ( + "fmt" + "math" + "net/url" + "time" + + "github.com/nntaoli-project/goex" + . "github.com/nntaoli-project/goex" +) + +type OKExV5Spot struct { + *OKExV5 +} + +func NewOKExV5Spot(config *APIConfig) *OKExV5Spot { + if config.Endpoint == "" { + config.Endpoint = v5RestBaseUrl + } + okex := &OKExV5Spot{OKExV5: NewOKExV5(config)} + return okex +} + +// private API +func (ok *OKExV5Spot) LimitBuy(amount, price string, currency CurrencyPair, opt ...LimitOrderOptionalParameter) (*Order, error) { + ty := "limit" + if len(opt) > 0 { + ty = opt[0].String() + } + + response, err := ok.CreateOrder(&CreateOrderParam{ + Symbol: currency.ToSymbol("-"), + TradeMode: "cash", + Side: "buy", + OrderType: ty, + Size: amount, + Price: price, + }) + if err != nil { + return nil, err + } + return &Order{ + Currency: currency, + Price: ToFloat64(price), + Amount: ToFloat64(amount), + Cid: response.ClientOrdId, + OrderID2: response.OrdId, + }, nil + +} +func (ok *OKExV5Spot) LimitSell(amount, price string, currency CurrencyPair, opt ...LimitOrderOptionalParameter) (*Order, error) { + ty := "limit" + if len(opt) > 0 { + ty = opt[0].String() + } + + response, err := ok.CreateOrder(&CreateOrderParam{ + Symbol: currency.ToSymbol("-"), + TradeMode: "cash", + Side: "sell", + OrderType: ty, + Size: amount, + Price: price, + }) + if err != nil { + return nil, err + } + return &Order{ + Currency: currency, + Price: ToFloat64(price), + Amount: ToFloat64(amount), + Cid: response.ClientOrdId, + OrderID2: response.OrdId, + }, nil + +} +func (ok *OKExV5Spot) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { + + response, err := ok.CreateOrder(&CreateOrderParam{ + Symbol: currency.ToSymbol("-"), + TradeMode: "cash", + Side: "buy", + OrderType: "market", + Size: amount, + }) + if err != nil { + return nil, err + } + return &Order{ + Currency: currency, + Amount: ToFloat64(amount), + Cid: response.ClientOrdId, + OrderID2: response.OrdId, + }, nil + +} +func (ok *OKExV5Spot) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { + + response, err := ok.CreateOrder(&CreateOrderParam{ + Symbol: currency.ToSymbol("-"), + TradeMode: "cash", + Side: "sell", + OrderType: "market", + Size: amount, + }) + if err != nil { + return nil, err + } + return &Order{ + Currency: currency, + Amount: ToFloat64(amount), + Cid: response.ClientOrdId, + OrderID2: response.OrdId, + }, nil + +} +func (ok *OKExV5Spot) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { + _, err := ok.CancelOrderV5(currency.ToSymbol("-"), orderId, "") + if err != nil { + return false, err + } + return true, nil + +} +func (ok *OKExV5Spot) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) { + response, err := ok.GetOrderV5(currency.ToSymbol("-"), orderId, "") + if err != nil { + return nil, err + } + status := ORDER_UNFINISH + switch response.State { + case "canceled": + status = ORDER_CANCEL + case "live": + status = ORDER_UNFINISH + case "partially_filled": + status = ORDER_PART_FINISH + case "filled": + status = ORDER_FINISH + default: + status = ORDER_UNFINISH + } + + side := BUY + if response.Side == "sell" || response.Side == "SELL" { + side = SELL + } + return &Order{ + Price: response.Px, + Amount: response.Sz, + AvgPrice: ToFloat64(response.AvgPx), + DealAmount: ToFloat64(response.AccFillSz), + Fee: response.Fee, + Cid: response.ClOrdID, + OrderID2: response.OrdID, + Status: status, + Currency: currency, + Side: side, + Type: response.OrdType, + OrderTime: response.CTime, + FinishedTime: response.UTime, + }, nil +} + +func (ok *OKExV5Spot) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { + response, err := ok.GetPendingOrders(&PendingOrderParam{ + InstType: "SPOT", + InstId: currency.ToSymbol("-"), + }) + if err != nil { + return nil, err + } + orders := make([]Order, 0) + for _, v := range response { + status := ORDER_UNFINISH + switch v.State { + case "canceled": + status = ORDER_CANCEL + case "live": + status = ORDER_UNFINISH + case "partially_filled": + status = ORDER_PART_FINISH + case "filled": + status = ORDER_FINISH + default: + status = ORDER_UNFINISH + } + + side := BUY + if v.Side == "sell" || v.Side == "SELL" { + side = SELL + } + orders = append(orders, Order{ + Price: v.Px, + Amount: v.Sz, + AvgPrice: ToFloat64(v.AvgPx), + DealAmount: ToFloat64(v.AccFillSz), + Fee: v.Fee, + Cid: v.ClOrdID, + OrderID2: v.OrdID, + Status: status, + Currency: currency, + Side: side, + Type: v.OrdType, + OrderTime: v.CTime, + FinishedTime: v.UTime, + }) + } + return orders, nil +} + +func (ok *OKExV5Spot) GetOrderHistorys(currency CurrencyPair, opt ...OptionalParameter) ([]Order, error) { + response, err := ok.GetOrderHistory( + "SPOT", + "", //currency.ToSymbol("-"), + "", "", "", "", + ) + if err != nil { + return nil, err + } + orders := make([]Order, 0) + for _, v := range response { + status := ORDER_UNFINISH + switch v.State { + case "canceled": + status = ORDER_CANCEL + case "live": + status = ORDER_UNFINISH + case "partially_filled": + status = ORDER_PART_FINISH + case "filled": + status = ORDER_FINISH + default: + status = ORDER_UNFINISH + } + + side := BUY + if v.Side == "sell" || v.Side == "SELL" { + side = SELL + } + + orders = append(orders, Order{ + Price: v.Px, + Amount: v.Sz, + AvgPrice: ToFloat64(v.AvgPx), + DealAmount: ToFloat64(v.AccFillSz), + Fee: v.Fee, + Cid: v.ClOrdID, + OrderID2: v.OrdID, + Status: status, + Currency: goex.NewCurrencyPair3(v.InstID, "-"), + Side: side, + Type: v.OrdType, + OrderTime: v.CTime, + FinishedTime: v.UTime, + }) + } + return orders, nil +} + +func (ok *OKExV5Spot) GetAccount() (*Account, error) { + response, err := ok.GetAccountBalances("") + if err != nil { + return nil, err + } + account := &Account{ + SubAccounts: make(map[Currency]SubAccount, 2)} + for _, itm := range response.Details { + currency := NewCurrency(itm.Currency, "") + account.SubAccounts[currency] = SubAccount{ + Currency: currency, + ForzenAmount: ToFloat64(itm.Frozen), + Amount: math.Max(ToFloat64(itm.Available), ToFloat64(itm.AvailEq)), + } + } + + return account, nil +} + +// public API + +func (ok *OKExV5Spot) GetTicker(currency CurrencyPair) (*Ticker, error) { + ticker, err := ok.GetTickerV5(currency.ToSymbol("-")) + if err != nil { + return nil, err + } + return &Ticker{ + Pair: currency, + Last: ticker.Last, + Buy: ticker.BuyPrice, + Sell: ticker.SellPrice, + High: ticker.High, + Low: ticker.Low, + Vol: ticker.Vol, + Date: ticker.Timestamp, + }, nil +} + +func (ok *OKExV5Spot) GetDepth(size int, currency CurrencyPair) (*Depth, error) { + d, err := ok.GetDepthV5(currency.ToSymbol("-"), size) + if err != nil { + return nil, err + } + + depth := &Depth{} + + for _, ask := range d.Asks { + depth.AskList = append(depth.AskList, DepthRecord{Price: ToFloat64(ask[0]), Amount: ToFloat64(ask[1])}) + } + for _, bid := range d.Bids { + depth.BidList = append(depth.BidList, DepthRecord{Price: ToFloat64(bid[0]), Amount: ToFloat64(bid[1])}) + } + depth.UTime = time.Unix(0, int64(d.Timestamp)*1000000) + depth.Pair = currency + return depth, nil +} + +func (ok *OKExV5Spot) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { + // [1m/3m/5m/15m/30m/1H/2H/4H/6H/12H/1D/1W/1M/3M/6M/1Y] + param := &url.Values{} + param.Set("limit", fmt.Sprint(size)) + MergeOptionalParameter(param, optional...) + + kl, err := ok.GetKlineRecordsV5(currency.ToSymbol("-"), period, param) + if err != nil { + return nil, err + } + + klines := make([]Kline, 0) + + for _, k := range kl { + klines = append(klines, Kline{ + Pair: currency, + Timestamp: ToInt64(k[0]), + Open: ToFloat64(k[1]), + High: ToFloat64(k[2]), + Low: ToFloat64(k[3]), + Close: ToFloat64(k[4]), + Vol: ToFloat64(k[5]), + }) + } + + return klines, nil + +} + +//非个人,整个交易所的交易记录 +func (ok *OKExV5Spot) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { + panic("not support") +} + +func (ok *OKExV5Spot) GetExchangeName() string { + return ok.ExchangeName() +} diff --git a/okex/v5/OKExV5Spot_test.go b/okex/v5/OKExV5Spot_test.go new file mode 100644 index 00000000..24eaee7f --- /dev/null +++ b/okex/v5/OKExV5Spot_test.go @@ -0,0 +1,71 @@ +package okex + +import ( + log "github.com/nntaoli-project/goex/internal/logger" + "testing" + + "github.com/nntaoli-project/goex" +) + +func newOKExV5SpotClient() *OKExV5Spot { + return NewOKExV5Spot(&goex.APIConfig{ + //HttpClient: &http.Client{ + // Transport: &http.Transport{ + // Proxy: func(req *http.Request) (*url.URL, error) { + // return &url.URL{ + // Scheme: "socks5", + // Host: "192.168.1.29:2222"}, nil + // }, + // }, + //}, + Endpoint: "https://www.okex.com", + ApiKey: "", + ApiSecretKey: "", + ApiPassphrase: "", + }) +} + +func init() { + log.SetLevel(log.DEBUG) +} + +func TestOKExV5Spot_GetTicker(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.GetTicker(goex.BTC_USDT)) +} + +func TestOKExV5Spot_GetDepth(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.GetDepth(5, goex.BTC_USDT)) +} + +func TestOKExV5SpotGetKlineRecords(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.GetKlineRecords(goex.BTC_USDT, goex.KLINE_PERIOD_1MIN, 10)) +} + +func TestOKExV5Spot_LimitBuy(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.LimitBuy("1", "1.0", goex.XRP_USDT)) + //{"code":"0","data":[{"clOrdId":"0bf60374efe445BC258eddf46df044c3","ordId":"305267682086109184","sCode":"0","sMsg":"","tag":""}],"msg":""}} +} + +func TestOKExV5Spot_CancelOrder(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.CancelOrder("305267682086109184", goex.XRP_USDT)) +} + +func TestOKExV5Spot_GetUnfinishOrders(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.GetUnfinishOrders(goex.XRP_USDT)) +} + +func TestOKExV5Spot_GetOneOrder(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.GetOneOrder("305267682086109184", goex.XRP_USDT)) +} + +func TestOKExV5Spot_GetAccount(t *testing.T) { + c := newOKExV5SpotClient() + t.Log(c.GetAccount()) +} diff --git a/okex/v5/OKExV5Swap.go b/okex/v5/OKExV5Swap.go new file mode 100644 index 00000000..1fe18ccf --- /dev/null +++ b/okex/v5/OKExV5Swap.go @@ -0,0 +1,162 @@ +package okex + +import ( + "fmt" + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "net/url" + "sort" + "time" +) + +type OKExV5Swap struct { + *OKExV5 +} + +func NewOKExV5Swap(config *APIConfig) *OKExV5Swap { + v5 := new(OKExV5Swap) + v5.OKExV5 = NewOKExV5(config) + return v5 +} + +func (ok *OKExV5Swap) GetExchangeName() string { + return OKEX_SWAP +} + +func (ok *OKExV5Swap) GetFutureEstimatedPrice(currencyPair CurrencyPair) (float64, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFutureTicker(currencyPair CurrencyPair, contractType string) (*Ticker, error) { + t, err := ok.OKExV5.GetTickerV5(fmt.Sprintf("%s-SWAP", currencyPair.ToSymbol("-"))) + + if err != nil { + return nil, err + } + + return &Ticker{ + Pair: currencyPair, + Last: t.Last, + Buy: t.BuyPrice, + Sell: t.SellPrice, + High: t.High, + Low: t.Low, + Vol: t.Vol, + Date: t.Timestamp, + }, nil +} + +func (ok *OKExV5Swap) GetFutureDepth(currencyPair CurrencyPair, contractType string, size int) (*Depth, error) { + instId := fmt.Sprintf("%s-SWAP", currencyPair.ToSymbol("-")) + dep, err := ok.OKExV5.GetDepthV5(instId, size) + + if err != nil { + return nil, err + } + + depth := &Depth{} + + for _, ask := range dep.Asks { + depth.AskList = append(depth.AskList, DepthRecord{Price: ToFloat64(ask[0]), Amount: ToFloat64(ask[1])}) + } + + for _, bid := range dep.Bids { + depth.BidList = append(depth.BidList, DepthRecord{Price: ToFloat64(bid[0]), Amount: ToFloat64(bid[1])}) + } + + sort.Sort(sort.Reverse(depth.AskList)) + + depth.Pair = currencyPair + depth.UTime = time.Unix(0, int64(dep.Timestamp)*1000000) + + return depth, nil +} + +func (ok *OKExV5Swap) GetFutureIndex(currencyPair CurrencyPair) (float64, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFutureUserinfo(currencyPair ...CurrencyPair) (*FutureAccount, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) PlaceFutureOrder(currencyPair CurrencyPair, contractType, price, amount string, openType, matchPrice int, leverRate float64) (string, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) LimitFuturesOrder(currencyPair CurrencyPair, contractType, price, amount string, openType int, opt ...LimitOrderOptionalParameter) (*FutureOrder, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) MarketFuturesOrder(currencyPair CurrencyPair, contractType, amount string, openType int) (*FutureOrder, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) FutureCancelOrder(currencyPair CurrencyPair, contractType, orderId string) (bool, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFuturePosition(currencyPair CurrencyPair, contractType string) ([]FuturePosition, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFutureOrders(orderIds []string, currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFutureOrder(orderId string, currencyPair CurrencyPair, contractType string) (*FutureOrder, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetUnfinishFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetFee() (float64, error) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetContractValue(currencyPair CurrencyPair) (float64, error) { + panic("implement me") +} + +func (ok OKExV5Swap) GetDeliveryTime() (int, int, int, int) { + panic("implement me") +} + +func (ok *OKExV5Swap) GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]FutureKline, error) { + param := &url.Values{} + param.Set("limit", fmt.Sprint(size)) + MergeOptionalParameter(param, optional...) + + data, err := ok.OKExV5.GetKlineRecordsV5(fmt.Sprintf("%s-SWAP", currency.ToSymbol("-")), period, param) + if err != nil { + return nil, err + } + logger.Debug("[okex v5] kline response data: ", data) + + var klines []FutureKline + for _, item := range data { + klines = append(klines, FutureKline{ + Kline: &Kline{ + Pair: currency, + Timestamp: ToInt64(item[0]) / 1000, + Open: ToFloat64(item[1]), + Close: ToFloat64(item[4]), + High: ToFloat64(item[2]), + Low: ToFloat64(item[3]), + Vol: ToFloat64(item[5]), + }, + }) + } + + return klines, nil +} + +func (ok *OKExV5Swap) GetTrades(contractType string, currencyPair CurrencyPair, since int64) ([]Trade, error) { + panic("implement me") +} diff --git a/okex/v5/OKExV5Swap_test.go b/okex/v5/OKExV5Swap_test.go new file mode 100644 index 00000000..22b3277e --- /dev/null +++ b/okex/v5/OKExV5Swap_test.go @@ -0,0 +1,55 @@ +package okex + +import ( + "github.com/nntaoli-project/goex" + log "github.com/nntaoli-project/goex/internal/logger" + "net/http" + "testing" +) + +func init() { + log.SetLevel(log.DEBUG) + //os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:2222") //local socks5 proxy +} + +func TestOKExV5Swap_GetFutureTicker(t *testing.T) { + swap := NewOKExV5Swap(&goex.APIConfig{ + HttpClient: http.DefaultClient, + ApiKey: "", + ApiSecretKey: "", + ApiPassphrase: "", + Lever: 0, + }) + t.Log(swap.GetFutureTicker(goex.BTC_USDT, goex.SWAP_CONTRACT)) +} + +func TestOKExV5Swap_GetFutureDepth(t *testing.T) { + swap := NewOKExV5Swap(&goex.APIConfig{ + HttpClient: http.DefaultClient, + }) + + dep, err := swap.GetFutureDepth(goex.BTC_USDT, goex.SWAP_CONTRACT, 2) + if err != nil { + t.Error(err) + return + } + + t.Log(dep.AskList) + t.Log(dep.BidList) +} + +func TestOKExV5Swap_GetKlineRecords(t *testing.T) { + swap := NewOKExV5Swap(&goex.APIConfig{ + HttpClient: http.DefaultClient, + }) + + klines, err := swap.GetKlineRecords(goex.SWAP_CONTRACT, goex.BTC_USDT, goex.KLINE_PERIOD_1H, 2) + if err != nil { + t.Error(err) + return + } + + for _, k := range klines { + t.Logf("%+v", k.Kline) + } +} diff --git a/okex/v5/OKExV5_test.go b/okex/v5/OKExV5_test.go new file mode 100644 index 00000000..c99314cf --- /dev/null +++ b/okex/v5/OKExV5_test.go @@ -0,0 +1,43 @@ +package okex + +import ( + "fmt" + "net/url" + "testing" + + "github.com/nntaoli-project/goex" +) + +func newOKExV5Client() *OKExV5 { + return NewOKExV5(&goex.APIConfig{ + //HttpClient: &http.Client{ + // Transport: &http.Transport{ + // Proxy: func(req *http.Request) (*url.URL, error) { + // return &url.URL{ + // Scheme: "socks5", + // Host: "127.0.0.1:2222"}, nil + // }, + // }, + //}, + Endpoint: "https://www.okex.com", + ApiKey: "", + ApiSecretKey: "", + ApiPassphrase: "", + }) +} + +func TestOKExV5_GetTicker(t *testing.T) { + o := newOKExV5Client() + fmt.Println(o.GetTickerV5("BTC-USD-SWAP")) +} + +func TestOKExV5_GetDepth(t *testing.T) { + o := newOKExV5Client() + fmt.Println(o.GetDepthV5("BTC-USD-SWAP", 0)) +} + +func TestOKExV5_GetKlineRecordsV5(t *testing.T) { + o := newOKExV5Client() + fmt.Println(o.GetKlineRecordsV5("BTC-USD-SWAP", goex.KLINE_PERIOD_1H, &url.Values{})) + +} diff --git a/poloniex/Poloniex.go b/poloniex/Poloniex.go index 0581e464..e1572b9b 100644 --- a/poloniex/Poloniex.go +++ b/poloniex/Poloniex.go @@ -20,6 +20,7 @@ const ( TRADE_API = BASE_URL + "tradingApi" PUBLIC_URL = BASE_URL + "public" TICKER_API = "?command=returnTicker" + CURRENCIES_API = "?command=returnCurrencies" ORDER_BOOK_API = "?command=returnOrderBook¤cyPair=%s&depth=%d" ) @@ -513,3 +514,33 @@ func (poloniex *Poloniex) MarketBuy(amount, price string, currency CurrencyPair) func (poloniex *Poloniex) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { panic("unsupport the market order") } + +func (poloniex *Poloniex) GetAllCurrencies() (map[string]*PoloniexCurrency, error) { + respmap, err := HttpGet(poloniex.client, PUBLIC_URL+CURRENCIES_API) + + if err != nil || respmap["error"] != nil { + log.Println(err) + return nil, err + } + + result := map[string]*PoloniexCurrency{} + for k, v := range respmap { + currencyMap := v.(map[string]interface{}) + poloniexCurrency := new(PoloniexCurrency) + poloniexCurrency.ID = int(currencyMap["id"].(float64)) + poloniexCurrency.Name, _ = currencyMap["name"].(string) + poloniexCurrency.TxFee, _ = currencyMap["txFee"].(string) + poloniexCurrency.MinConf = int(currencyMap["minConf"].(float64)) + poloniexCurrency.DepositAddress, _ = currencyMap["depositAddress"].(string) + poloniexCurrency.Disabled = int(currencyMap["disabled"].(float64)) + poloniexCurrency.Delisted = int(currencyMap["delisted"].(float64)) + poloniexCurrency.Frozen = int(currencyMap["frozen"].(float64)) + poloniexCurrency.HumanType, _ = currencyMap["humanType"].(string) + poloniexCurrency.CurrencyType, _ = currencyMap["currencyType"].(string) + poloniexCurrency.Blockchain, _ = currencyMap["blockchain"].(string) + poloniexCurrency.IsGeofenced = int(currencyMap["isGeofenced"].(float64)) + + result[k] = poloniexCurrency + } + return result, nil +} diff --git a/websocket.go b/websocket.go index 9f1ade55..753bd086 100644 --- a/websocket.go +++ b/websocket.go @@ -4,13 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "github.com/gorilla/websocket" - . "github.com/nntaoli-project/goex/internal/logger" "net/http" "net/http/httputil" "net/url" "sync" "time" + + "github.com/gorilla/websocket" + . "github.com/nntaoli-project/goex/internal/logger" ) type WsConfig struct {