diff --git a/API.go b/API.go index 80009eba..310d6082 100644 --- a/API.go +++ b/API.go @@ -10,12 +10,12 @@ type API interface { CancelOrder(orderId string, currency CurrencyPair) (bool, error) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) - GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) + GetOrderHistorys(currency CurrencyPair, opt ...OptionalParameter) ([]Order, error) GetAccount() (*Account, error) GetTicker(currency CurrencyPair) (*Ticker, error) GetDepth(size int, currency CurrencyPair) (*Depth, error) - GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) + GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) //非个人,整个交易所的交易记录 GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) diff --git a/CurrencyPair.go b/CurrencyPair.go index c4055085..e11a59cd 100644 --- a/CurrencyPair.go +++ b/CurrencyPair.go @@ -60,6 +60,10 @@ var ( TRX = Currency{"TRX", ""} GBP = Currency{"GBP", ""} XLM = Currency{"XLM", ""} + DOT = Currency{"DOT", ""} + DASH = Currency{"DASH", ""} + CRV = Currency{"CRV", ""} + ALGO = Currency{"ALGO", ""} //currency pair BTC_KRW = CurrencyPair{CurrencyA: BTC, CurrencyB: KRW, AmountTickSize: 2, PriceTickSize: 1} @@ -68,33 +72,41 @@ var ( LTC_KRW = CurrencyPair{CurrencyA: LTC, CurrencyB: KRW, AmountTickSize: 2, PriceTickSize: 2} BCH_KRW = CurrencyPair{CurrencyA: BCH, CurrencyB: KRW, AmountTickSize: 2, PriceTickSize: 2} - BTC_USD = CurrencyPair{CurrencyA: BTC, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 1} - LTC_USD = CurrencyPair{CurrencyA: LTC, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} - ETH_USD = CurrencyPair{CurrencyA: ETH, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} - ETC_USD = CurrencyPair{CurrencyA: ETC, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} - BCH_USD = CurrencyPair{CurrencyA: BCH, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} - XRP_USD = CurrencyPair{CurrencyA: XRP, CurrencyB: USD, AmountTickSize: 3, PriceTickSize: 3} - BCD_USD = CurrencyPair{CurrencyA: BCD, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 3} - EOS_USD = CurrencyPair{CurrencyA: EOS, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} - BTG_USD = CurrencyPair{CurrencyA: BTG, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} - BSV_USD = CurrencyPair{CurrencyA: BSV, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + BTC_USD = CurrencyPair{CurrencyA: BTC, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 1} + LTC_USD = CurrencyPair{CurrencyA: LTC, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + ETH_USD = CurrencyPair{CurrencyA: ETH, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + ETC_USD = CurrencyPair{CurrencyA: ETC, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + BCH_USD = CurrencyPair{CurrencyA: BCH, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + XRP_USD = CurrencyPair{CurrencyA: XRP, CurrencyB: USD, AmountTickSize: 3, PriceTickSize: 3} + BCD_USD = CurrencyPair{CurrencyA: BCD, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 3} + EOS_USD = CurrencyPair{CurrencyA: EOS, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + BTG_USD = CurrencyPair{CurrencyA: BTG, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + BSV_USD = CurrencyPair{CurrencyA: BSV, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + DOT_USD = CurrencyPair{CurrencyA: DOT, CurrencyB: USD, AmountTickSize: 3, PriceTickSize: 2} + DASH_USD = CurrencyPair{CurrencyA: DASH, CurrencyB: USD, AmountTickSize: 2, PriceTickSize: 2} + CRV_USD = CurrencyPair{CurrencyA: CRV, CurrencyB: USD, AmountTickSize: 4, PriceTickSize: 3} + ALGO_USD = CurrencyPair{CurrencyA: ALGO, CurrencyB: USD, AmountTickSize: 4, PriceTickSize: 4} - BTC_USDT = CurrencyPair{CurrencyA: BTC, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 1} - LTC_USDT = CurrencyPair{CurrencyA: LTC, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - BCH_USDT = CurrencyPair{CurrencyA: BCH, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - ETC_USDT = CurrencyPair{CurrencyA: ETC, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 3} - ETH_USDT = CurrencyPair{CurrencyA: ETH, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - BCD_USDT = CurrencyPair{CurrencyA: BCD, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - NEO_USDT = CurrencyPair{CurrencyA: NEO, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - EOS_USDT = CurrencyPair{CurrencyA: EOS, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - XRP_USDT = CurrencyPair{CurrencyA: XRP, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - HSR_USDT = CurrencyPair{CurrencyA: HSR, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - BSV_USDT = CurrencyPair{CurrencyA: BSV, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - OKB_USDT = CurrencyPair{CurrencyA: OKB, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - HT_USDT = CurrencyPair{CurrencyA: HT, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 4} - BNB_USDT = CurrencyPair{CurrencyA: BNB, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} - PAX_USDT = CurrencyPair{CurrencyA: PAX, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 3} - TRX_USDT = CurrencyPair{CurrencyA: TRX, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 3} + BTC_USDT = CurrencyPair{CurrencyA: BTC, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 1} + LTC_USDT = CurrencyPair{CurrencyA: LTC, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + BCH_USDT = CurrencyPair{CurrencyA: BCH, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + ETC_USDT = CurrencyPair{CurrencyA: ETC, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 3} + ETH_USDT = CurrencyPair{CurrencyA: ETH, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + BCD_USDT = CurrencyPair{CurrencyA: BCD, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + NEO_USDT = CurrencyPair{CurrencyA: NEO, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + EOS_USDT = CurrencyPair{CurrencyA: EOS, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + XRP_USDT = CurrencyPair{CurrencyA: XRP, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + HSR_USDT = CurrencyPair{CurrencyA: HSR, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + BSV_USDT = CurrencyPair{CurrencyA: BSV, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + OKB_USDT = CurrencyPair{CurrencyA: OKB, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + HT_USDT = CurrencyPair{CurrencyA: HT, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 4} + BNB_USDT = CurrencyPair{CurrencyA: BNB, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + PAX_USDT = CurrencyPair{CurrencyA: PAX, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 3} + TRX_USDT = CurrencyPair{CurrencyA: TRX, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 3} + DOT_USDT = CurrencyPair{CurrencyA: DOT, CurrencyB: USDT, AmountTickSize: 3, PriceTickSize: 2} + DASH_USDT = CurrencyPair{CurrencyA: DASH, CurrencyB: USDT, AmountTickSize: 2, PriceTickSize: 2} + CRV_USDT = CurrencyPair{CurrencyA: CRV, CurrencyB: USDT, AmountTickSize: 3, PriceTickSize: 3} + ALGO_USDT = CurrencyPair{CurrencyA: ALGO, CurrencyB: USDT, AmountTickSize: 3, PriceTickSize: 4} XRP_EUR = CurrencyPair{CurrencyA: XRP, CurrencyB: EUR, AmountTickSize: 2, PriceTickSize: 4} @@ -121,6 +133,7 @@ var ( HT_BTC = CurrencyPair{CurrencyA: HT, CurrencyB: BTC, AmountTickSize: 2, PriceTickSize: 7} BNB_BTC = CurrencyPair{CurrencyA: BNB, CurrencyB: BTC, AmountTickSize: 2, PriceTickSize: 6} TRX_BTC = CurrencyPair{CurrencyA: TRX, CurrencyB: BTC, AmountTickSize: 2, PriceTickSize: 7} + DOT_BTC = CurrencyPair{CurrencyA: DOT, CurrencyB: BTC, AmountTickSize: 3, PriceTickSize: 6} ETC_ETH = CurrencyPair{CurrencyA: ETC, CurrencyB: ETH, AmountTickSize: 2, PriceTickSize: 4} EOS_ETH = CurrencyPair{CurrencyA: EOS, CurrencyB: ETH, AmountTickSize: 2, PriceTickSize: 4} @@ -196,6 +209,8 @@ func NewCurrency(symbol, desc string) Currency { return BNB case "trx", "TRX": return TRX + case "dot", "DOT": + return DOT default: return Currency{strings.ToUpper(symbol), desc} } diff --git a/FutureRestAPI.go b/FutureRestAPI.go index befa94dd..2b6c1a34 100644 --- a/FutureRestAPI.go +++ b/FutureRestAPI.go @@ -88,6 +88,11 @@ type FutureRestAPI interface { */ GetUnfinishFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) + /** + * 获取个人订单历史,默认获取最近的订单历史列表,返回多少条订单数据,需根据平台接口定义而定 + */ + GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) + /** *获取交易费 */ @@ -111,7 +116,7 @@ type FutureRestAPI interface { /** * 获取K线数据 */ - GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) + GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]FutureKline, error) /** * 获取Trade数据 diff --git a/HttpUtils.go b/HttpUtils.go index 5dd1e89f..49967bb0 100644 --- a/HttpUtils.go +++ b/HttpUtils.go @@ -5,9 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/nntaoli-project/goex/internal/logger" - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttpproxy" "io/ioutil" "log" "net/http" @@ -15,6 +12,10 @@ import ( "os" "strings" "time" + + "github.com/nntaoli-project/goex/internal/logger" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpproxy" ) var ( diff --git a/Models.go b/Models.go index de2a4920..5e277d22 100644 --- a/Models.go +++ b/Models.go @@ -1,6 +1,7 @@ package goex import ( + "fmt" "net/http" "time" ) @@ -261,3 +262,37 @@ type DepositWithdrawHistory struct { Status int `json:"status,string"` Timestamp time.Time `json:"timestamp"` } + +type OptionalParameter map[string]interface{} + +func (optional OptionalParameter) Optional(name string, value interface{}) OptionalParameter { + optional[name] = value + return optional +} + +func (optional OptionalParameter) GetString(name string) string { + return fmt.Sprint(optional[name]) +} + +func (optional OptionalParameter) GetInt(name string) int { + return ToInt(optional[name]) +} + +func (optional OptionalParameter) GetInt64(name string) int64 { + return ToInt64(optional[name]) +} + +func (optional OptionalParameter) GetFloat64(name string) float64 { + return ToFloat64(optional[name]) +} + +func (optional OptionalParameter) GetTime(name string) *time.Time { + val := optional["name"] + if val != nil { + t, ok := val.(time.Time) + if ok { + return &t + } + } + return nil +} diff --git a/Utils.go b/Utils.go index 4d196637..3a450d53 100644 --- a/Utils.go +++ b/Utils.go @@ -114,6 +114,15 @@ func ValuesToJson(v url.Values) ([]byte, error) { return json.Marshal(parammap) } +func MergeOptionalParameter(values *url.Values, opts ...OptionalParameter) url.Values { + for _, opt := range opts { + for k, v := range opt { + values.Set(k, fmt.Sprint(v)) + } + } + return *values +} + func GzipDecompress(data []byte) ([]byte, error) { r, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { diff --git a/allcoin/allcoin.go b/allcoin/allcoin.go index b6440a7a..3c71ac15 100755 --- a/allcoin/allcoin.go +++ b/allcoin/allcoin.go @@ -454,7 +454,7 @@ func (ac *Allcoin) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, e panic("not implements") } -func (ac *Allcoin) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (ac *Allcoin) GetOrderHistorys(currency CurrencyPair, opt ...OptionalParameter) ([]Order, error) { panic("not implements") } func (ba *Allcoin) adaptCurrencyPair(pair CurrencyPair) CurrencyPair { diff --git a/atop/atop.go b/atop/atop.go index 2c6c0c38..bd781b80 100644 --- a/atop/atop.go +++ b/atop/atop.go @@ -113,7 +113,7 @@ const ( Withdrawal = "/trade/api/v1/withdraw" ) -var KlinePeriodConverter = map[int]string{ +var KlinePeriodConverter = map[KlinePeriod]string{ KLINE_PERIOD_1MIN: "1min", KLINE_PERIOD_3MIN: "3min", KLINE_PERIOD_5MIN: "5min", @@ -472,13 +472,13 @@ func (at *Atop) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, error) { } //hao -func (at *Atop) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (at *Atop) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { pair := at.adaptCurrencyPair(currency) params := url.Values{} params.Set("market", pair.ToLower().String()) //params.Set("type", "1min") //1min,5min,15min,30min,1hour,6hour,1day,7day,30day params.Set("type", KlinePeriodConverter[period]) //1min,5min,15min,30min,1hour,6hour,1day,7day,30day - params.Set("since", fmt.Sprintf("%d", size)) //The first time is 0, followed by the value of the response since + MergeOptionalParameter(¶ms, opt...) klineUrl := ApiBaseUrl + GetKLine + "?" + params.Encode() kLines, err := HttpGet(at.httpClient, klineUrl) @@ -544,18 +544,17 @@ func (at *Atop) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, erro return trades, nil } -func (at *Atop) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (at *Atop) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { //panic("not support") pair := at.adaptCurrencyPair(currency) path := ApiBaseUrl + GetHistorys params := url.Values{} params.Set("market", pair.ToLower().String()) - //params.Set("type", "1") - //params.Set("status", "0") - params.Set("page", fmt.Sprint(currentPage)) - params.Set("pageSize", fmt.Sprint(pageSize)) + + MergeOptionalParameter(¶ms, optional...) at.buildPostForm(¶ms) + resp, err := HttpPostForm(at.httpClient, path, params) if err != nil { return nil, err diff --git a/bigone/Bigone.go b/bigone/Bigone.go index e0ca9920..6b31342b 100644 --- a/bigone/Bigone.go +++ b/bigone/Bigone.go @@ -347,7 +347,7 @@ func (bo *Bigone) GetOneOrder(orderId string, currencyPair goex.CurrencyPair) (* func (bo *Bigone) GetUnfinishOrders(currencyPair goex.CurrencyPair) ([]goex.Order, error) { return bo.getOrdersList(currencyPair, -1, goex.ORDER_UNFINISH) } -func (bo *Bigone) GetOrderHistorys(currencyPair goex.CurrencyPair, currentPage, pageSize int) ([]goex.Order, error) { +func (bo *Bigone) GetOrderHistorys(currencyPair goex.CurrencyPair, opt ...goex.OptionalParameter) ([]goex.Order, error) { return bo.getOrdersList(currencyPair, -1, goex.ORDER_FINISH) } @@ -471,7 +471,7 @@ func (bo *Bigone) GetDepth(size int, currencyPair goex.CurrencyPair) (*goex.Dept return depth, nil } -func (bo *Bigone) GetKlineRecords(currency goex.CurrencyPair, period, size, since int) ([]goex.Kline, error) { +func (bo *Bigone) GetKlineRecords(currency goex.CurrencyPair, period goex.KlinePeriod, size int, opt ...goex.OptionalParameter) ([]goex.Kline, error) { panic("not implements") } diff --git a/bigone/BigoneV3.go b/bigone/BigoneV3.go index ce4a946e..982c532a 100644 --- a/bigone/BigoneV3.go +++ b/bigone/BigoneV3.go @@ -355,7 +355,7 @@ func (bo *BigoneV3) GetOneOrder(orderId string, currencyPair goex.CurrencyPair) func (bo *BigoneV3) GetUnfinishOrders(currencyPair goex.CurrencyPair) ([]goex.Order, error) { return bo.getOrdersList(currencyPair, 200, goex.ORDER_UNFINISH) } -func (bo *BigoneV3) GetOrderHistorys(currencyPair goex.CurrencyPair, currentPage, pageSize int) ([]goex.Order, error) { +func (bo *BigoneV3) GetOrderHistorys(currencyPair goex.CurrencyPair, opt goex.OptionalParameter) ([]goex.Order, error) { return bo.getOrdersList(currencyPair, 200, goex.ORDER_FINISH) } diff --git a/bigone/Bigone_test.go b/bigone/Bigone_test.go index d0758457..9ea336ee 100644 --- a/bigone/Bigone_test.go +++ b/bigone/Bigone_test.go @@ -30,7 +30,7 @@ func TestBigone_GetUnfinishOrders(t *testing.T) { func TestBigone_GetOrderHistorys(t *testing.T) { return TCT_BTC := NewCurrencyPair2("TCT_BTC") - t.Log(bo.GetOrderHistorys(TCT_BTC, 1, 1)) + t.Log(bo.GetOrderHistorys(TCT_BTC)) } func TestBigone_LimitSell(t *testing.T) { return diff --git a/binance/Adapter.go b/binance/Adapter.go new file mode 100644 index 00000000..fdb82cdd --- /dev/null +++ b/binance/Adapter.go @@ -0,0 +1,66 @@ +package binance + +import ( + "fmt" + "github.com/nntaoli-project/goex" + "strings" +) + +func adaptStreamToCurrencyPair(stream string) goex.CurrencyPair { + symbol := strings.Split(stream, "@")[0] + + if strings.HasSuffix(symbol, "usdt") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_usdt", strings.TrimSuffix(symbol, "usdt"))) + } + + if strings.HasSuffix(symbol, "usd") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_usd", strings.TrimSuffix(symbol, "usd"))) + } + + if strings.HasSuffix(symbol, "btc") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_btc", strings.TrimSuffix(symbol, "btc"))) + } + + return goex.UNKNOWN_PAIR +} + +func adaptSymbolToCurrencyPair(symbol string) goex.CurrencyPair { + symbol = strings.ToUpper(symbol) + + if strings.HasSuffix(symbol, "USD") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_USD", strings.TrimSuffix(symbol, "USD"))) + } + + if strings.HasSuffix(symbol, "USDT") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_USDT", strings.TrimSuffix(symbol, "USDT"))) + } + + if strings.HasSuffix(symbol, "PAX") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_PAX", strings.TrimSuffix(symbol, "PAX"))) + } + + if strings.HasSuffix(symbol, "BTC") { + return goex.NewCurrencyPair2(fmt.Sprintf("%s_BTC", strings.TrimSuffix(symbol, "BTC"))) + } + + return goex.UNKNOWN_PAIR +} + +func adaptOrderStatus(status string) goex.TradeStatus { + var tradeStatus goex.TradeStatus + switch status { + case "NEW": + tradeStatus = goex.ORDER_UNFINISH + case "FILLED": + tradeStatus = goex.ORDER_FINISH + case "PARTIALLY_FILLED": + tradeStatus = goex.ORDER_PART_FINISH + case "CANCELED": + tradeStatus = goex.ORDER_CANCEL + case "PENDING_CANCEL": + tradeStatus = goex.ORDER_CANCEL_ING + case "REJECTED": + tradeStatus = goex.ORDER_REJECT + } + return tradeStatus +} diff --git a/binance/Binance.go b/binance/Binance.go index 9f7e1e71..01bbcdf8 100644 --- a/binance/Binance.go +++ b/binance/Binance.go @@ -1,6 +1,7 @@ package binance import ( + "encoding/json" "errors" "fmt" . "github.com/nntaoli-project/goex" @@ -29,7 +30,7 @@ const ( SERVER_TIME_URL = "time" ) -var _INERNAL_KLINE_PERIOD_CONVERTER = map[int]string{ +var _INERNAL_KLINE_PERIOD_CONVERTER = map[KlinePeriod]string{ KLINE_PERIOD_1MIN: "1m", KLINE_PERIOD_3MIN: "3m", KLINE_PERIOD_5MIN: "5m", @@ -428,7 +429,7 @@ func (bn *Binance) CancelOrder(orderId string, currencyPair CurrencyPair) (bool, resp, err := HttpDeleteForm(bn.httpClient, path, params, map[string]string{"X-MBX-APIKEY": bn.accessKey}) if err != nil { - return false, err + return false, bn.adaptError(err) } respmap := make(map[string]interface{}) @@ -461,48 +462,9 @@ func (bn *Binance) GetOneOrder(orderId string, currencyPair CurrencyPair) (*Orde return nil, err } - status := respmap["status"].(string) - side := respmap["side"].(string) - - ord := Order{} - ord.Currency = currencyPair - ord.OrderID = ToInt(orderId) - ord.OrderID2 = orderId - ord.Cid, _ = respmap["clientOrderId"].(string) - ord.Type = respmap["type"].(string) - - if side == "SELL" { - ord.Side = SELL - } else { - ord.Side = BUY - } - - switch status { - case "NEW": - 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(respmap["origQty"].(string)) - ord.Price = ToFloat64(respmap["price"].(string)) - ord.DealAmount = ToFloat64(respmap["executedQty"]) - ord.OrderTime = ToInt(respmap["time"]) + order := bn.adaptOrder(currencyPair, respmap) - cummulativeQuoteQty := ToFloat64(respmap["cummulativeQuoteQty"]) - if cummulativeQuoteQty > 0 { - ord.AvgPrice = cummulativeQuoteQty / ord.DealAmount - } - - return &ord, nil + return &order, nil } func (bn *Binance) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, error) { @@ -520,34 +482,18 @@ func (bn *Binance) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, error) orders := make([]Order, 0) for _, v := range respmap { ord := v.(map[string]interface{}) - side := ord["side"].(string) - orderSide := SELL - if side == "BUY" { - orderSide = BUY - } - ordId := ToInt(ord["orderId"]) - orders = append(orders, Order{ - OrderID: ordId, - OrderID2: strconv.Itoa(ordId), - Currency: currencyPair, - Price: ToFloat64(ord["price"]), - Amount: ToFloat64(ord["origQty"]), - Side: TradeSide(orderSide), - Status: ORDER_UNFINISH, - OrderTime: ToInt(ord["time"])}) + orders = append(orders, bn.adaptOrder(currencyPair, ord)) } + return orders, nil } -func (bn *Binance) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (bn *Binance) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { params := url.Values{} params.Set("symbol", currency.ToSymbol("")) params.Set("interval", _INERNAL_KLINE_PERIOD_CONVERTER[period]) - if since > 0 { - params.Set("startTime", strconv.Itoa(since)) - } - //params.Set("endTime", strconv.Itoa(int(time.Now().UnixNano()/1000000))) params.Set("limit", fmt.Sprintf("%d", size)) + MergeOptionalParameter(¶ms, optional...) klineUrl := bn.apiV3 + KLINE_URI + "?" + params.Encode() klines, err := HttpGet3(bn.httpClient, klineUrl, nil) @@ -609,11 +555,12 @@ func (bn *Binance) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, e return trades, nil } -func (bn *Binance) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (bn *Binance) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { params := url.Values{} - params.Set("symbol", currency.ToSymbol("")) - + params.Set("symbol", currency.AdaptUsdToUsdt().ToSymbol("")) + MergeOptionalParameter(¶ms, optional...) bn.buildParamsSigned(¶ms) + path := bn.apiV3 + "allOrders?" + params.Encode() respmap, err := HttpGet3(bn.httpClient, path, map[string]string{"X-MBX-APIKEY": bn.accessKey}) @@ -623,40 +570,10 @@ func (bn *Binance) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize orders := make([]Order, 0) for _, v := range respmap { - ord := v.(map[string]interface{}) - side := ord["side"].(string) - orderSide := SELL - if side == "BUY" { - orderSide = BUY - } - ordId := ToInt(ord["orderId"]) - status := ord["status"].(string) - var tradeStatus TradeStatus - switch status { - case "NEW": - tradeStatus = ORDER_UNFINISH - case "FILLED": - tradeStatus = ORDER_FINISH - case "PARTIALLY_FILLED": - tradeStatus = ORDER_PART_FINISH - case "CANCELED": - tradeStatus = ORDER_CANCEL - case "PENDING_CANCEL": - tradeStatus = ORDER_CANCEL_ING - case "REJECTED": - tradeStatus = ORDER_REJECT - } - - orders = append(orders, Order{ - OrderID: ToInt(ord["orderId"]), - OrderID2: strconv.Itoa(ordId), - Currency: currency, - Price: ToFloat64(ord["price"]), - Amount: ToFloat64(ord["origQty"]), - Side: TradeSide(orderSide), - Status: tradeStatus, - OrderTime: ToInt(ord["time"])}) + orderMap := v.(map[string]interface{}) + orders = append(orders, bn.adaptOrder(currency, orderMap)) } + return orders, nil } @@ -706,3 +623,46 @@ func (bn *Binance) GetTradeSymbol(currencyPair CurrencyPair) (*TradeSymbol, erro } return nil, errors.New("symbol not found") } + +func (bn *Binance) adaptError(err error) error { + errStr := err.Error() + + if strings.Contains(errStr, "Order does not exist") || + strings.Contains(errStr, "Unknown order sent") { + return EX_ERR_NOT_FIND_ORDER.OriginErr(errStr) + } + + if strings.Contains(errStr, "Too much request") { + return EX_ERR_API_LIMIT.OriginErr(errStr) + } + + if strings.Contains(errStr, "insufficient") { + return EX_ERR_INSUFFICIENT_BALANCE.OriginErr(errStr) + } + + return err +} + +func (bn *Binance) adaptOrder(currencyPair CurrencyPair, orderMap map[string]interface{}) Order { + side := orderMap["side"].(string) + + orderSide := SELL + if side == "BUY" { + orderSide = BUY + } + + return Order{ + OrderID: ToInt(orderMap["orderId"]), + OrderID2: fmt.Sprintf("%.0f",orderMap["orderId"]), + Cid: orderMap["clientOrderId"].(string), + Currency: currencyPair, + Price: ToFloat64(orderMap["price"]), + Amount: ToFloat64(orderMap["origQty"]), + DealAmount: ToFloat64(orderMap["executedQty"]), + AvgPrice: FloatToFixed(ToFloat64(orderMap["cummulativeQuoteQty"])/ToFloat64(orderMap["executedQty"]), 8), + Side: TradeSide(orderSide), + Status: adaptOrderStatus(orderMap["status"].(string)), + OrderTime: ToInt(orderMap["time"]), + FinishedTime: ToInt64(orderMap["updateTime"]), + } +} diff --git a/binance/BinanceFutures.go b/binance/BinanceFutures.go index 9c361810..dabe4e6d 100644 --- a/binance/BinanceFutures.go +++ b/binance/BinanceFutures.go @@ -1,6 +1,7 @@ package binance import ( + "encoding/json" "errors" "fmt" . "github.com/nntaoli-project/goex" @@ -25,6 +26,7 @@ type AccountResponse struct { Assets []struct { Asset string `json:"asset"` WalletBalance float64 `json:"walletBalance,string"` + MarginBalance float64 `json:"marginBalance,string"` UnrealizedProfit float64 `json:"unrealizedProfit,string"` MaintMargin float64 `json:"maintMargin,string"` } `json:"assets"` @@ -261,7 +263,7 @@ func (bs *BinanceFutures) GetFutureUserinfo(currencyPair ...CurrencyPair) (*Futu currency := NewCurrency(asset.Asset, "") futureAccounts.FutureSubAccounts[currency] = FutureSubAccount{ Currency: NewCurrency(asset.Asset, ""), - AccountRights: asset.WalletBalance, + AccountRights: asset.MarginBalance, KeepDeposit: asset.MaintMargin, ProfitReal: 0, ProfitUnreal: asset.UnrealizedProfit, @@ -541,6 +543,10 @@ func (bs *BinanceFutures) GetUnfinishFutureOrders(currencyPair CurrencyPair, con return orders, nil } +func (bs *BinanceFutures) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + panic("implement me") +} + func (bs *BinanceFutures) GetFee() (float64, error) { panic("not supported.") } @@ -558,7 +564,7 @@ func (bs *BinanceFutures) GetDeliveryTime() (int, int, int, int) { panic("not supported.") } -func (bs *BinanceFutures) GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { +func (bs *BinanceFutures) GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { panic("not supported.") } @@ -620,6 +626,10 @@ func (bs *BinanceFutures) adaptToSymbol(pair CurrencyPair, contractType string) if info.ContractType == "NEXT_QUARTER" && contractType == BI_QUARTER_CONTRACT { return info.Symbol, nil } + + if info.Symbol == contractType { + return info.Symbol, nil + } } } diff --git a/binance/BinanceSwap.go b/binance/BinanceSwap.go index ef590498..075e076a 100644 --- a/binance/BinanceSwap.go +++ b/binance/BinanceSwap.go @@ -1,15 +1,15 @@ package binance import ( + "encoding/json" "errors" "fmt" + . "github.com/nntaoli-project/goex" "net/url" "strconv" "strings" "sync" "time" - - . "github.com/nntaoli-project/goex" ) const ( @@ -194,6 +194,10 @@ func (bs *BinanceSwap) GetFutureDepth(currency CurrencyPair, contractType string return depth, nil } +func (bs *BinanceSwap) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + panic("implement me") +} + func (bs *BinanceSwap) GetTrades(contractType string, currencyPair CurrencyPair, since int64) ([]Trade, error) { if contractType == SWAP_CONTRACT { return bs.f.GetTrades(SWAP_CONTRACT, currencyPair.AdaptUsdtToUsd(), since) @@ -246,9 +250,6 @@ func (bs *BinanceSwap) GetFutureIndex(currencyPair CurrencyPair) (float64, error return ToFloat64(respmap["markPrice"]), nil } -/** - *全仓账户 - */ func (bs *BinanceSwap) GetFutureUserinfo(currencyPair ...CurrencyPair) (*FutureAccount, error) { acc, err := bs.f.GetFutureUserinfo(currencyPair...) if err != nil { @@ -273,10 +274,9 @@ func (bs *BinanceSwap) GetFutureUserinfo(currencyPair ...CurrencyPair) (*FutureA currency := NewCurrency(vv["asset"].(string), "").AdaptBccToBch() acc.FutureSubAccounts[currency] = FutureSubAccount{ Currency: currency, - AccountRights: ToFloat64(vv["walletBalance"]), - KeepDeposit: ToFloat64(vv["marginBalance"]), + AccountRights: ToFloat64(vv["marginBalance"]), + KeepDeposit: ToFloat64(vv["maintMargin"]), ProfitUnreal: ToFloat64(vv["unrealizedProfit"]), - RiskRate: ToFloat64(vv["unrealizedProfit"]), } } @@ -758,9 +758,9 @@ func (bs *BinanceSwap) GetDeliveryTime() (int, int, int, int) { panic("not supported.") } -func (bs *BinanceSwap) GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { +func (bs *BinanceSwap) GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { if contractType == SWAP_CONTRACT { - return bs.f.GetKlineRecords(contractType, currency.AdaptUsdtToUsd(), period, since, since) + return bs.f.GetKlineRecords(contractType, currency.AdaptUsdtToUsd(), period, size, opt...) } if contractType != SWAP_USDT_CONTRACT { @@ -770,12 +770,10 @@ func (bs *BinanceSwap) GetKlineRecords(contractType string, currency CurrencyPai currency2 := bs.adaptCurrencyPair(currency) params := url.Values{} params.Set("symbol", currency2.ToSymbol("")) - params.Set("interval", _INERNAL_KLINE_PERIOD_CONVERTER[period]) - if since > 0 { - params.Set("startTime", strconv.Itoa(since)) - } + params.Set("interval", _INERNAL_KLINE_PERIOD_CONVERTER[KlinePeriod(period)]) //params.Set("endTime", strconv.Itoa(int(time.Now().UnixNano()/1000000))) params.Set("limit", strconv.Itoa(size)) + MergeOptionalParameter(¶ms, opt...) klineUrl := bs.apiV1 + KLINE_URI + "?" + params.Encode() klines, err := HttpGet3(bs.httpClient, klineUrl, nil) diff --git a/binance/BinanceWs.go b/binance/BinanceWs.go deleted file mode 100644 index 4c3a5213..00000000 --- a/binance/BinanceWs.go +++ /dev/null @@ -1,402 +0,0 @@ -package binance - -import ( - "errors" - "fmt" - "github.com/json-iterator/go" - . "github.com/nntaoli-project/goex" - "strconv" - "strings" - "time" - "unsafe" -) - -var json = jsoniter.ConfigCompatibleWithStandardLibrary - -type BinanceWs struct { - baseURL string - combinedBaseURL string - proxyUrl string - tickerCallback func(*Ticker) - depthCallback func(*Depth) - tradeCallback func(*Trade) - klineCallback func(*Kline, int) - wsConns []*WsConn -} - -type AggTrade struct { - Trade - FirstBreakdownTradeID int64 `json:"f"` - LastBreakdownTradeID int64 `json:"l"` - TradeTime int64 `json:"T"` -} - -type RawTrade struct { - Trade - BuyerOrderID int64 `json:"b"` - SellerOrderID int64 `json:"a"` -} - -type DiffDepth struct { - Depth - UpdateID int64 `json:"u"` - FirstUpdateID int64 `json:"U"` -} - -var _INERNAL_KLINE_PERIOD_REVERTER = map[string]int{ - "1m": KLINE_PERIOD_1MIN, - "3m": KLINE_PERIOD_3MIN, - "5m": KLINE_PERIOD_5MIN, - "15m": KLINE_PERIOD_15MIN, - "30m": KLINE_PERIOD_30MIN, - "1h": KLINE_PERIOD_60MIN, - "2h": KLINE_PERIOD_2H, - "4h": KLINE_PERIOD_4H, - "6h": KLINE_PERIOD_6H, - "8h": KLINE_PERIOD_8H, - "12h": KLINE_PERIOD_12H, - "1d": KLINE_PERIOD_1DAY, - "3d": KLINE_PERIOD_3DAY, - "1w": KLINE_PERIOD_1WEEK, - "1M": KLINE_PERIOD_1MONTH, -} - -func NewBinanceWs() *BinanceWs { - bnWs := &BinanceWs{} - bnWs.baseURL = "wss://stream.binance.com:9443/ws" - bnWs.combinedBaseURL = "wss://stream.binance.com:9443/stream?streams=" - return bnWs -} - -func (bnWs *BinanceWs) ProxyUrl(proxyUrl string) { - bnWs.proxyUrl = proxyUrl -} - -func (bnWs *BinanceWs) SetBaseUrl(baseURL string) { - bnWs.baseURL = baseURL -} - -func (bnWs *BinanceWs) SetCombinedBaseURL(combinedBaseURL string) { - bnWs.combinedBaseURL = combinedBaseURL -} - -func (bnWs *BinanceWs) SetCallbacks( - tickerCallback func(*Ticker), - depthCallback func(*Depth), - tradeCallback func(*Trade), - klineCallback func(*Kline, int), -) { - bnWs.tickerCallback = tickerCallback - bnWs.depthCallback = depthCallback - bnWs.tradeCallback = tradeCallback - bnWs.klineCallback = klineCallback -} - -func (bnWs *BinanceWs) Subscribe(endpoint string, handle func(msg []byte) error) *WsConn { - wsConn := NewWsBuilder(). - WsUrl(endpoint). - AutoReconnect(). - ProtoHandleFunc(handle). - ProxyUrl(bnWs.proxyUrl). - ReconnectInterval(time.Millisecond * 5). - Build() - bnWs.wsConns = append(bnWs.wsConns, wsConn) - go bnWs.exitHandler(wsConn) - return wsConn -} - -func (bnWs *BinanceWs) Close() { - for _, con := range bnWs.wsConns { - con.CloseWs() - } -} - -func (bnWs *BinanceWs) SubscribeDepth(pair CurrencyPair, size int) error { - if bnWs.depthCallback == nil { - return errors.New("please set depth callback func") - } - if size != 5 && size != 10 && size != 20 { - return errors.New("please set depth size as 5 / 10 / 20") - } - endpoint := fmt.Sprintf("%s/%s@depth%d@100ms", bnWs.baseURL, strings.ToLower(pair.ToSymbol("")), size) - - handle := func(msg []byte) error { - rawDepth := struct { - LastUpdateID int64 `json:"lastUpdateId"` - Bids [][]interface{} `json:"bids"` - Asks [][]interface{} `json:"asks"` - }{} - err := json.Unmarshal(msg, &rawDepth) - if err != nil { - fmt.Println("json unmarshal error for ", string(msg)) - return err - } - depth := bnWs.parseDepthData(rawDepth.Bids, rawDepth.Asks) - depth.Pair = pair - depth.UTime = time.Now() - bnWs.depthCallback(depth) - return nil - } - bnWs.Subscribe(endpoint, handle) - return nil -} - -func (bnWs *BinanceWs) SubscribeTicker(pair CurrencyPair) error { - if bnWs.tickerCallback == nil { - return errors.New("please set ticker callback func") - } - endpoint := fmt.Sprintf("%s/%s@ticker", bnWs.baseURL, strings.ToLower(pair.ToSymbol(""))) - - handle := func(msg []byte) error { - datamap := make(map[string]interface{}) - err := json.Unmarshal(msg, &datamap) - if err != nil { - fmt.Println("json unmarshal error for ", string(msg)) - return err - } - - msgType, isOk := datamap["e"].(string) - if !isOk { - return errors.New("no message type") - } - - switch msgType { - case "24hrTicker": - tick := bnWs.parseTickerData(datamap) - tick.Pair = pair - bnWs.tickerCallback(tick) - return nil - default: - return errors.New("unknown message " + msgType) - } - } - bnWs.Subscribe(endpoint, handle) - return nil -} - -func (bnWs *BinanceWs) SubscribeTrade(pair CurrencyPair) error { - if bnWs.tradeCallback == nil { - return errors.New("please set trade callback func") - } - endpoint := fmt.Sprintf("%s/%s@trade", bnWs.baseURL, strings.ToLower(pair.ToSymbol(""))) - - handle := func(msg []byte) error { - datamap := make(map[string]interface{}) - err := json.Unmarshal(msg, &datamap) - if err != nil { - fmt.Println("json unmarshal error for ", string(msg)) - return err - } - - msgType, isOk := datamap["e"].(string) - if !isOk { - return errors.New("no message type") - } - - switch msgType { - case "trade": - side := BUY - if datamap["m"].(bool) == false { - side = SELL - } - trade := &RawTrade{ - Trade: Trade{ - Tid: int64(ToUint64(datamap["t"])), - Type: TradeSide(side), - Amount: ToFloat64(datamap["q"]), - Price: ToFloat64(datamap["p"]), - Date: int64(ToUint64(datamap["T"])), - }, - BuyerOrderID: ToInt64(datamap["b"]), - SellerOrderID: ToInt64(datamap["a"]), - } - trade.Pair = pair - bnWs.tradeCallback((*Trade)(unsafe.Pointer(trade))) - return nil - default: - return errors.New("unknown message " + msgType) - } - } - bnWs.Subscribe(endpoint, handle) - return nil -} - -func (bnWs *BinanceWs) SubscribeKline(pair CurrencyPair, period int) error { - if bnWs.klineCallback == nil { - return errors.New("place set kline callback func") - } - periodS, isOk := _INERNAL_KLINE_PERIOD_CONVERTER[period] - if isOk != true { - periodS = "M1" - } - endpoint := fmt.Sprintf("%s/%s@kline_%s", bnWs.baseURL, strings.ToLower(pair.ToSymbol("")), periodS) - - handle := func(msg []byte) error { - datamap := make(map[string]interface{}) - err := json.Unmarshal(msg, &datamap) - if err != nil { - fmt.Println("json unmarshal error for ", string(msg)) - return err - } - - msgType, isOk := datamap["e"].(string) - if !isOk { - return errors.New("no message type") - } - - switch msgType { - case "kline": - k := datamap["k"].(map[string]interface{}) - period := _INERNAL_KLINE_PERIOD_REVERTER[k["i"].(string)] - kline := bnWs.parseKlineData(k) - kline.Pair = pair - bnWs.klineCallback(kline, period) - return nil - default: - return errors.New("unknown message " + msgType) - } - } - bnWs.Subscribe(endpoint, handle) - return nil -} - -func (bnWs *BinanceWs) parseTickerData(tickmap map[string]interface{}) *Ticker { - t := new(Ticker) - t.Date = ToUint64(tickmap["E"]) - t.Last = ToFloat64(tickmap["c"]) - t.Vol = ToFloat64(tickmap["v"]) - t.Low = ToFloat64(tickmap["l"]) - t.High = ToFloat64(tickmap["h"]) - t.Buy = ToFloat64(tickmap["b"]) - t.Sell = ToFloat64(tickmap["a"]) - - return t -} - -func (bnWs *BinanceWs) parseDepthData(bids, asks [][]interface{}) *Depth { - depth := new(Depth) - for _, v := range bids { - depth.BidList = append(depth.BidList, DepthRecord{ToFloat64(v[0]), ToFloat64(v[1])}) - } - - for _, v := range asks { - depth.AskList = append(depth.AskList, DepthRecord{ToFloat64(v[0]), ToFloat64(v[1])}) - } - return depth -} - -func (bnWs *BinanceWs) parseKlineData(k map[string]interface{}) *Kline { - kline := &Kline{ - Timestamp: int64(ToInt(k["t"])) / 1000, - Open: ToFloat64(k["o"]), - Close: ToFloat64(k["c"]), - High: ToFloat64(k["h"]), - Low: ToFloat64(k["l"]), - Vol: ToFloat64(k["v"]), - } - return kline -} - -func (bnWs *BinanceWs) SubscribeAggTrade(pair CurrencyPair, tradeCallback func(*Trade)) error { - if tradeCallback == nil { - return errors.New("please set trade callback func") - } - endpoint := fmt.Sprintf("%s/%s@aggTrade", bnWs.baseURL, strings.ToLower(pair.ToSymbol(""))) - - handle := func(msg []byte) error { - datamap := make(map[string]interface{}) - err := json.Unmarshal(msg, &datamap) - if err != nil { - fmt.Println("json unmarshal error for ", string(msg)) - return err - } - - msgType, isOk := datamap["e"].(string) - if !isOk { - return errors.New("no message type") - } - - switch msgType { - case "aggTrade": - side := BUY - if datamap["m"].(bool) == false { - side = SELL - } - aggTrade := &AggTrade{ - Trade: Trade{ - Tid: int64(ToUint64(datamap["a"])), - Type: TradeSide(side), - Amount: ToFloat64(datamap["q"]), - Price: ToFloat64(datamap["p"]), - Date: int64(ToUint64(datamap["E"])), - }, - FirstBreakdownTradeID: int64(ToUint64(datamap["f"])), - LastBreakdownTradeID: int64(ToUint64(datamap["l"])), - TradeTime: int64(ToUint64(datamap["T"])), - } - aggTrade.Pair = pair - tradeCallback((*Trade)(unsafe.Pointer(aggTrade))) - return nil - default: - return errors.New("unknown message " + msgType) - } - } - bnWs.Subscribe(endpoint, handle) - return nil -} - -func (bnWs *BinanceWs) SubscribeDiffDepth(pair CurrencyPair, depthCallback func(*Depth)) error { - if depthCallback == nil { - return errors.New("please set depth callback func") - } - endpoint := fmt.Sprintf("%s/%s@depth", bnWs.baseURL, strings.ToLower(pair.ToSymbol(""))) - - handle := func(msg []byte) error { - rawDepth := struct { - Type string `json:"e"` - Time int64 `json:"E"` - Symbol string `json:"s"` - UpdateID int `json:"u"` - Bids [][]interface{} `json:"b"` - Asks [][]interface{} `json:"a"` - }{} - - err := json.Unmarshal(msg, &rawDepth) - if err != nil { - fmt.Println("json unmarshal error for ", string(msg)) - return err - } - diffDepth := new(DiffDepth) - for _, v := range rawDepth.Bids { - diffDepth.BidList = append(diffDepth.BidList, DepthRecord{ToFloat64(v[0]), ToFloat64(v[1])}) - } - - for _, v := range rawDepth.Asks { - diffDepth.AskList = append(diffDepth.AskList, DepthRecord{ToFloat64(v[0]), ToFloat64(v[1])}) - } - - diffDepth.Pair = pair - diffDepth.UTime = time.Unix(0, rawDepth.Time*int64(time.Millisecond)) - depthCallback((*Depth)(unsafe.Pointer(diffDepth))) - return nil - } - bnWs.Subscribe(endpoint, handle) - return nil -} - -func (bnWs *BinanceWs) exitHandler(c *WsConn) { - pingTicker := time.NewTicker(10 * time.Minute) - pongTicker := time.NewTicker(time.Second) - defer pingTicker.Stop() - defer pongTicker.Stop() - defer c.CloseWs() - - for { - select { - case t := <-pingTicker.C: - c.SendPingMessage([]byte(strconv.Itoa(int(t.UnixNano() / int64(time.Millisecond))))) - case t := <-pongTicker.C: - c.SendPongMessage([]byte(strconv.Itoa(int(t.UnixNano() / int64(time.Millisecond))))) - } - } -} diff --git a/binance/BinanceWs_test.go b/binance/BinanceWs_test.go deleted file mode 100644 index b31d127b..00000000 --- a/binance/BinanceWs_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package binance - -import ( - "github.com/nntaoli-project/goex" - "log" - "testing" - "time" - "unsafe" -) - -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) -} - -func printfTicker(ticker *goex.Ticker) { - log.Println("ticker:", ticker) -} - -func printfDepth(depth *goex.Depth) { - log.Println("depth:", depth) -} - -func printfTrade(trade *goex.Trade) { - log.Println("trade:", trade) - log.Println("trade:", (*RawTrade)(unsafe.Pointer(trade))) -} - -func printfAggTrade(aggTrade *goex.Trade) { - log.Println("trade:", (*AggTrade)(unsafe.Pointer(aggTrade))) -} -func printfKline(kline *goex.Kline, period int) { - log.Println("kline:", kline) -} - -func TestBinanceWs_SubscribeTicker(t *testing.T) { - bnWs.SubscribeTicker(goex.BTC_USDT) - time.Sleep(time.Second * 5) -} - -func TestBinanceWs_GetDepthWithWs(t *testing.T) { - bnWs.SubscribeDepth(goex.BTC_USDT, 5) - time.Sleep(time.Second * 10) -} - -func TestBinanceWs_GetKLineWithWs(t *testing.T) { - return - bnWs.SubscribeKline(goex.BTC_USDT, goex.KLINE_PERIOD_1MIN) - time.Sleep(time.Second * 10) -} - -func TestBinanceWs_GetTradesWithWs(t *testing.T) { - bnWs.SubscribeTrade(goex.BTC_USDT) - time.Sleep(time.Second * 5) -} - -func TestBinanceWs_SubscribeAggTrade(t *testing.T) { - bnWs.SubscribeAggTrade(goex.BTC_USDT, printfAggTrade) - time.Sleep(time.Second * 5) -} - -func TestBinanceWs_SubscribeDiffDepth(t *testing.T) { - bnWs.SubscribeDiffDepth(goex.BTC_USDT, printfDepth) - time.Sleep(time.Second * 10) -} - -func TestBinanceWs_SubscribeDepth(t *testing.T) { - bnWs.SubscribeDepth(goex.BTC_USDT, 5) - bnWs.SubscribeDepth(goex.LTC_USDT, 5) - bnWs.SubscribeDepth(goex.ETC_USDT, 5) - time.Sleep(time.Second * 60) -} diff --git a/binance/Binance_test.go b/binance/Binance_test.go index b4defdc6..ce64a7c0 100644 --- a/binance/Binance_test.go +++ b/binance/Binance_test.go @@ -1,31 +1,17 @@ package binance import ( + "fmt" "github.com/nntaoli-project/goex" - "net" "net/http" - "net/url" "testing" "time" ) var ba = NewWithConfig( &goex.APIConfig{ - HttpClient: &http.Client{ - Transport: &http.Transport{ - Proxy: func(req *http.Request) (*url.URL, error) { - return url.Parse("socks5://127.0.0.1:1080") - return nil, nil - }, - Dial: (&net.Dialer{ - Timeout: 10 * time.Second, - }).Dial, - }, - Timeout: 10 * time.Second, - }, - Endpoint: GLOBAL_API_BASE_URL, - ApiKey: "q6y6Gr7fF3jSJLncpfn2PmAA0xu4XRiRFHpFkyJy3d7K68WUxY0Gt8rrajCDUfbI", - ApiSecretKey: "AP8C2kh4RyISN3fpRCFMZJddf233XbPcYWQ1S7gBan3pGjCQg2JnyQFSJrIaNzRh", + HttpClient: http.DefaultClient, + Endpoint: "https://api.binancezh.pro", }) func TestBinance_GetTicker(t *testing.T) { @@ -34,26 +20,30 @@ func TestBinance_GetTicker(t *testing.T) { } func TestBinance_LimitBuy(t *testing.T) { - order, err := ba.LimitBuy("0.005", "8000", goex.BTC_USDT) + order, err := ba.LimitBuy("3", "68.5", goex.LTC_USDT) t.Log(order, err) } func TestBinance_LimitSell(t *testing.T) { - order, err := ba.LimitSell("0.01", "0.1", goex.LTC_BTC) + order, err := ba.LimitSell("1", "90", goex.LTC_USDT) t.Log(order, err) } func TestBinance_CancelOrder(t *testing.T) { - t.Log(ba.CancelOrder("1156274704", goex.BTC_USDT)) + r, er := ba.CancelOrder("3848718241", goex.BTC_USDT) + if !r { + t.Log((er.(goex.ApiError)).ErrCode) + } } func TestBinance_GetOneOrder(t *testing.T) { - t.Log(ba.GetOneOrder("1156274704", goex.BTC_USDT)) + odr, err := ba.GetOneOrder("3874087228", goex.BTC_USDT) + t.Log(err, odr) } func TestBinance_GetDepth(t *testing.T) { //return - dep, err := ba.GetDepth(5, goex.ETH_BTC) + dep, err := ba.GetDepth(5, goex.NewCurrencyPair2("BTC_USDT")) t.Log(err) if err == nil { t.Log(dep.AskList) @@ -63,17 +53,21 @@ func TestBinance_GetDepth(t *testing.T) { func TestBinance_GetAccount(t *testing.T) { account, err := ba.GetAccount() - t.Log(account, err) + t.Log(err, account) } func TestBinance_GetUnfinishOrders(t *testing.T) { - orders, err := ba.GetUnfinishOrders(goex.ETH_BTC) + orders, err := ba.GetUnfinishOrders(goex.NewCurrencyPair2("BTC_USDT")) t.Log(orders, err) } func TestBinance_GetKlineRecords(t *testing.T) { - before := time.Now().Add(-time.Hour).Unix() * 1000 - kline, _ := ba.GetKlineRecords(goex.ETH_BTC, goex.KLINE_PERIOD_5MIN, 100, int(before)) + startTime := time.Now().Add(-24*time.Hour).Unix() * 1000 + endTime := time.Now().Add(-5*time.Hour).Unix() * 1000 + + kline, _ := ba.GetKlineRecords(goex.ETH_BTC, goex.KLINE_PERIOD_5MIN, 100, + goex.OptionalParameter{}.Optional("startTime", fmt.Sprint(startTime)).Optional("endTime", fmt.Sprint(endTime))) + for _, k := range kline { tt := time.Unix(k.Timestamp, 0) t.Log(tt, k.Open, k.Close, k.High, k.Low, k.Vol) @@ -94,5 +88,8 @@ func TestBinance_SetTimeOffset(t *testing.T) { } func TestBinance_GetOrderHistorys(t *testing.T) { - t.Log(ba.GetOrderHistorys(goex.BTC_USDT, 1, 1)) + t.Log(ba.GetOrderHistorys(goex.BTC_USDT, + goex.OptionalParameter{}. + Optional("startTime", "1607656034333"). + Optional("limit", "5"))) } diff --git a/binance/FuturesWs.go b/binance/FuturesWs.go new file mode 100644 index 00000000..41eef8ea --- /dev/null +++ b/binance/FuturesWs.go @@ -0,0 +1,180 @@ +package binance + +import ( + "encoding/json" + "errors" + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "net/http" + "net/url" + "os" + "sort" + "strings" + "time" +) + +type FuturesWs struct { + base *BinanceFutures + f *goex.WsConn + d *goex.WsConn + + depthCallFn func(depth *goex.Depth) + tickerCallFn func(ticker *goex.FutureTicker) + tradeCalFn func(trade *goex.Trade, contract string) +} + +func NewFuturesWs() *FuturesWs { + futuresWs := new(FuturesWs) + + wsBuilder := goex.NewWsBuilder(). + ProxyUrl(os.Getenv("HTTPS_PROXY")). + ProtoHandleFunc(futuresWs.handle).AutoReconnect() + futuresWs.f = wsBuilder.WsUrl("wss://fstream.binance.com/ws").Build() + futuresWs.d = wsBuilder.WsUrl("wss://dstream.binance.com/ws").Build() + futuresWs.base = NewBinanceFutures(&goex.APIConfig{ + HttpClient: &http.Client{ + Transport: &http.Transport{ + Proxy: func(r *http.Request) (*url.URL, error) { + return url.Parse(os.Getenv("HTTPS_PROXY")) + }, + }, + Timeout: 10 * time.Second, + }, + }) + + return futuresWs +} + +func (s *FuturesWs) DepthCallback(f func(depth *goex.Depth)) { + s.depthCallFn = f +} + +func (s *FuturesWs) TickerCallback(f func(ticker *goex.FutureTicker)) { + s.tickerCallFn = f +} + +func (s *FuturesWs) TradeCallback(f func(trade *goex.Trade, contract string)) { + s.tradeCalFn = f +} + +func (s *FuturesWs) SubscribeDepth(pair goex.CurrencyPair, contractType string) error { + switch contractType { + case goex.SWAP_USDT_CONTRACT: + return s.f.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{pair.AdaptUsdToUsdt().ToLower().ToSymbol("") + "@depth10@100ms"}, + Id: 1, + }) + default: + sym, _ := s.base.adaptToSymbol(pair.AdaptUsdtToUsd(), contractType) + return s.d.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{strings.ToLower(sym) + "@depth10@100ms"}, + Id: 2, + }) + } + return errors.New("contract is error") +} + +func (s *FuturesWs) SubscribeTicker(pair goex.CurrencyPair, contractType string) error { + switch contractType { + case goex.SWAP_USDT_CONTRACT: + return s.f.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{pair.AdaptUsdToUsdt().ToLower().ToSymbol("") + "@ticker"}, + Id: 1, + }) + default: + sym, _ := s.base.adaptToSymbol(pair.AdaptUsdtToUsd(), contractType) + return s.d.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{strings.ToLower(sym) + "@ticker"}, + Id: 2, + }) + } + return errors.New("contract is error") +} + +func (s *FuturesWs) SubscribeTrade(pair goex.CurrencyPair, contractType string) error { + panic("implement me") +} + +func (s *FuturesWs) handle(data []byte) error { + var m = make(map[string]interface{}, 4) + err := json.Unmarshal(data, &m) + if err != nil { + return err + } + + if e, ok := m["e"].(string); ok && e == "depthUpdate" { + dep := s.depthHandle(m["b"].([]interface{}), m["a"].([]interface{})) + dep.ContractType = m["s"].(string) + symbol, ok := m["ps"].(string) + + if ok { + dep.Pair = adaptSymbolToCurrencyPair(symbol) + } else { + dep.Pair = adaptSymbolToCurrencyPair(dep.ContractType) //usdt swap + } + + dep.UTime = time.Unix(0, goex.ToInt64(m["T"])*int64(time.Millisecond)) + s.depthCallFn(dep) + + return nil + } + + if e, ok := m["e"].(string); ok && e == "24hrTicker" { + s.tickerCallFn(s.tickerHandle(m)) + return nil + } + + logger.Warn("unknown ws response:", string(data)) + + return nil +} + +func (s *FuturesWs) depthHandle(bids []interface{}, asks []interface{}) *goex.Depth { + var dep goex.Depth + + for _, item := range bids { + bid := item.([]interface{}) + dep.BidList = append(dep.BidList, + goex.DepthRecord{ + Price: goex.ToFloat64(bid[0]), + Amount: goex.ToFloat64(bid[1]), + }) + } + + for _, item := range asks { + ask := item.([]interface{}) + dep.AskList = append(dep.AskList, goex.DepthRecord{ + Price: goex.ToFloat64(ask[0]), + Amount: goex.ToFloat64(ask[1]), + }) + } + + sort.Sort(sort.Reverse(dep.AskList)) + + return &dep +} + +func (s *FuturesWs) tickerHandle(m map[string]interface{}) *goex.FutureTicker { + var ticker goex.FutureTicker + ticker.Ticker = new(goex.Ticker) + + symbol, ok := m["ps"].(string) + if ok { + ticker.Pair = adaptSymbolToCurrencyPair(symbol) + } else { + ticker.Pair = adaptSymbolToCurrencyPair(m["s"].(string)) //usdt swap + } + + ticker.ContractType = m["s"].(string) + ticker.Date = goex.ToUint64(m["E"]) + ticker.High = goex.ToFloat64(m["h"]) + ticker.Low = goex.ToFloat64(m["l"]) + ticker.Last = goex.ToFloat64(m["c"]) + ticker.Vol = goex.ToFloat64(m["v"]) + + return &ticker +} diff --git a/binance/FuturesWs_test.go b/binance/FuturesWs_test.go new file mode 100644 index 00000000..599908dd --- /dev/null +++ b/binance/FuturesWs_test.go @@ -0,0 +1,45 @@ +package binance + +import ( + "github.com/nntaoli-project/goex" + "log" + "os" + "testing" + "time" +) + +var futuresWs *FuturesWs + +func createFuturesWs() { + os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:1080") + + futuresWs = NewFuturesWs() + + futuresWs.DepthCallback(func(depth *goex.Depth) { + log.Println(depth) + }) + + futuresWs.TickerCallback(func(ticker *goex.FutureTicker) { + log.Println(ticker.Ticker, ticker.ContractType) + }) +} + +func TestFuturesWs_DepthCallback(t *testing.T) { + createFuturesWs() + + futuresWs.SubscribeDepth(goex.LTC_USDT, goex.SWAP_USDT_CONTRACT) + futuresWs.SubscribeDepth(goex.LTC_USDT, goex.SWAP_CONTRACT) + futuresWs.SubscribeDepth(goex.LTC_USDT, goex.QUARTER_CONTRACT) + + time.Sleep(30 * time.Second) +} + +func TestFuturesWs_SubscribeTicker(t *testing.T) { + createFuturesWs() + + futuresWs.SubscribeTicker(goex.BTC_USDT, goex.SWAP_USDT_CONTRACT) + futuresWs.SubscribeTicker(goex.BTC_USDT, goex.SWAP_CONTRACT) + futuresWs.SubscribeTicker(goex.BTC_USDT, goex.QUARTER_CONTRACT) + + time.Sleep(30 * time.Second) +} diff --git a/binance/SpotWs.go b/binance/SpotWs.go new file mode 100644 index 00000000..47169cb2 --- /dev/null +++ b/binance/SpotWs.go @@ -0,0 +1,180 @@ +package binance + +import ( + json2 "encoding/json" + "fmt" + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "os" + "sort" + "strings" + "time" +) + +type req struct { + Method string `json:"method"` + Params []string `json:"params"` + Id int `json:"id"` +} + +type resp struct { + Stream string `json:"stream"` + Data json2.RawMessage `json:"data"` +} + +type depthResp struct { + LastUpdateId int `json:"lastUpdateId"` + Bids [][]interface{} `json:"bids"` + Asks [][]interface{} `json:"asks"` +} + +type SpotWs struct { + c *goex.WsConn + + reqId int + + depthCallFn func(depth *goex.Depth) + tickerCallFn func(ticker *goex.Ticker) + tradeCallFn func(trade *goex.Trade) +} + +func NewSpotWs() *SpotWs { + spotWs := &SpotWs{} + logger.Debugf("proxy url: %s", os.Getenv("HTTPS_PROXY")) + + wsBuilder := goex.NewWsBuilder(). + WsUrl("wss://stream.binance.com:9443/stream?streams=depth/miniTicker/ticker/trade"). + ProxyUrl(os.Getenv("HTTPS_PROXY")). + ProtoHandleFunc(spotWs.handle).AutoReconnect() + + spotWs.c = wsBuilder.Build() + spotWs.reqId = 1 + + return spotWs +} + +func (s *SpotWs) DepthCallback(f func(depth *goex.Depth)) { + s.depthCallFn = f +} + +func (s *SpotWs) TickerCallback(f func(ticker *goex.Ticker)) { + s.tickerCallFn = f +} + +func (s *SpotWs) TradeCallback(f func(trade *goex.Trade)) { + s.tradeCallFn = f +} + +func (s *SpotWs) SubscribeDepth(pair goex.CurrencyPair) error { + defer func() { + s.reqId++ + }() + + return s.c.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{ + fmt.Sprintf("%s@depth10@100ms", pair.ToLower().ToSymbol("")), + }, + Id: s.reqId, + }) +} + +func (s *SpotWs) SubscribeTicker(pair goex.CurrencyPair) error { + defer func() { + s.reqId++ + }() + + return s.c.Subscribe(req{ + Method: "SUBSCRIBE", + Params: []string{pair.ToLower().ToSymbol("") + "@ticker"}, + Id: s.reqId, + }) +} + +func (s *SpotWs) SubscribeTrade(pair goex.CurrencyPair) error { + panic("implement me") +} + +func (s *SpotWs) handle(data []byte) error { + var r resp + err := json2.Unmarshal(data, &r) + if err != nil { + logger.Errorf("json unmarshal ws response error [%s] , response data = %s", err, string(data)) + return err + } + + if strings.HasSuffix(r.Stream, "@depth10@100ms") { + return s.depthHandle(r.Data, adaptStreamToCurrencyPair(r.Stream)) + } + + if strings.HasSuffix(r.Stream, "@ticker") { + return s.tickerHandle(r.Data, adaptStreamToCurrencyPair(r.Stream)) + } + + logger.Warn("unknown ws response:", string(data)) + + return nil +} + +func (s *SpotWs) depthHandle(data json2.RawMessage, pair goex.CurrencyPair) error { + var ( + depthR depthResp + dep goex.Depth + err error + ) + + err = json2.Unmarshal(data, &depthR) + if err != nil { + logger.Errorf("unmarshal depth response error %s[] , response data = %s", err, string(data)) + return err + } + + dep.UTime = time.Now() + dep.Pair = pair + + for _, bid := range depthR.Bids { + dep.BidList = append(dep.BidList, goex.DepthRecord{ + Price: goex.ToFloat64(bid[0]), + Amount: goex.ToFloat64(bid[1]), + }) + } + + for _, ask := range depthR.Asks { + dep.AskList = append(dep.AskList, goex.DepthRecord{ + Price: goex.ToFloat64(ask[0]), + Amount: goex.ToFloat64(ask[1]), + }) + } + + sort.Sort(sort.Reverse(dep.AskList)) + + s.depthCallFn(&dep) + + return nil +} + +func (s *SpotWs) tickerHandle(data json2.RawMessage, pair goex.CurrencyPair) error { + var ( + tickerData = make(map[string]interface{}, 4) + ticker goex.Ticker + ) + + err := json2.Unmarshal(data, &tickerData) + if err != nil { + logger.Errorf("unmarshal ticker response data error [%s] , data = %s", err, string(data)) + return err + } + + ticker.Pair = pair + ticker.Vol = goex.ToFloat64(tickerData["v"]) + ticker.Last = goex.ToFloat64(tickerData["c"]) + ticker.Sell = goex.ToFloat64(tickerData["a"]) + ticker.Buy = goex.ToFloat64(tickerData["b"]) + ticker.High = goex.ToFloat64(tickerData["h"]) + ticker.Low = goex.ToFloat64(tickerData["l"]) + ticker.Date = goex.ToUint64(tickerData["E"]) + + s.tickerCallFn(&ticker) + + return nil +} diff --git a/binance/SpotWs_test.go b/binance/SpotWs_test.go new file mode 100644 index 00000000..0cf92d8c --- /dev/null +++ b/binance/SpotWs_test.go @@ -0,0 +1,37 @@ +package binance + +import ( + "github.com/nntaoli-project/goex" + "log" + "os" + "testing" + "time" +) + +var spotWs *SpotWs + +func createSpotWs() { + os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:1080") + spotWs = NewSpotWs() + spotWs.DepthCallback(func(depth *goex.Depth) { + log.Println(depth) + }) + spotWs.TickerCallback(func(ticker *goex.Ticker) { + log.Println(ticker) + }) +} + +func TestSpotWs_DepthCallback(t *testing.T) { + createSpotWs() + + spotWs.SubscribeDepth(goex.BTC_USDT) + spotWs.SubscribeTicker(goex.LTC_USDT) + time.Sleep(11 * time.Minute) +} + +func TestSpotWs_SubscribeTicker(t *testing.T) { + createSpotWs() + + spotWs.SubscribeTicker(goex.LTC_USDT) + time.Sleep(30 * time.Minute) +} diff --git a/binance/Wallet.go b/binance/Wallet.go index 5d2c7cb5..a21eefa8 100644 --- a/binance/Wallet.go +++ b/binance/Wallet.go @@ -1,9 +1,11 @@ package binance import ( + "encoding/json" "errors" "fmt" . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" "net/url" ) @@ -48,7 +50,7 @@ func (w *Wallet) Transfer(param TransferParameter) error { } w.ba.buildParamsSigned(&postParam) - + resp, err := HttpPostForm2(w.ba.httpClient, transferUrl, postParam, map[string]string{"X-MBX-APIKEY": w.ba.accessKey}) @@ -70,9 +72,46 @@ func (w *Wallet) Transfer(param TransferParameter) error { } func (w *Wallet) GetWithDrawHistory(currency *Currency) ([]DepositWithdrawHistory, error) { - return nil, errors.New("not implement") + //historyUrl := w.conf.Endpoint + "/wapi/v3/withdrawHistory.html" + historyUrl := w.conf.Endpoint + "/sapi/v1/accountSnapshot" + postParam := url.Values{} + postParam.Set("type", "SPOT") + w.ba.buildParamsSigned(&postParam) + + resp, err := HttpGet5(w.ba.httpClient, historyUrl+"?"+postParam.Encode(), + map[string]string{"X-MBX-APIKEY": w.ba.accessKey}) + + if err != nil { + return nil, err + } + logger.Debugf("response body: %s", string(resp)) + respmap := make(map[string]interface{}) + err = json.Unmarshal(resp, &respmap) + if err != nil { + return nil, err + } + + return nil, nil } func (w *Wallet) GetDepositHistory(currency *Currency) ([]DepositWithdrawHistory, error) { - return nil, errors.New("not implement") + historyUrl := w.conf.Endpoint + "/wapi/v3/depositHistory.html" + postParam := url.Values{} + postParam.Set("asset", currency.Symbol) + w.ba.buildParamsSigned(&postParam) + + resp, err := HttpGet5(w.ba.httpClient, historyUrl+"?"+postParam.Encode(), + map[string]string{"X-MBX-APIKEY": w.ba.accessKey}) + + if err != nil { + return nil, err + } + logger.Debugf("response body: %s", string(resp)) + respmap := make(map[string]interface{}) + err = json.Unmarshal(resp, &respmap) + if err != nil { + return nil, err + } + + return nil, nil } diff --git a/bitfinex/bitfinex.go b/bitfinex/bitfinex.go index ce2025e5..e5422e67 100644 --- a/bitfinex/bitfinex.go +++ b/bitfinex/bitfinex.go @@ -89,17 +89,17 @@ func (bfx *Bitfinex) GetDepth(size int, currencyPair CurrencyPair) (*Depth, erro return depth, nil } -func (bfx *Bitfinex) GetKlineRecords(currencyPair CurrencyPair, klinePeriod, size, since int) ([]Kline, error) { +func (bfx *Bitfinex) GetKlineRecords(currencyPair CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { symbol := convertPairToBitfinexSymbol("t", currencyPair) if size == 0 { size = 100 } - period, ok := klinePeriods[KlinePeriod(klinePeriod)] + periodStr, ok := klinePeriods[period] if !ok { return nil, fmt.Errorf("invalid period") } - apiURL := fmt.Sprintf("%s/candles/trade:%s:%s/hist?limit=%d", apiURLV2, period, symbol, size) + apiURL := fmt.Sprintf("%s/candles/trade:%s:%s/hist?limit=%d", apiURLV2, periodStr, symbol, size) respRaw, err := NewHttpRequest(bfx.httpClient, "GET", apiURL, "", nil) if err != nil { @@ -309,7 +309,7 @@ func (bfx *Bitfinex) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, erro return orders, nil } -func (bfx *Bitfinex) GetOrderHistorys(currencyPair CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (bfx *Bitfinex) GetOrderHistorys(currencyPair CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("not implement") } diff --git a/bitfinex/bitfinex_test.go b/bitfinex/bitfinex_test.go index 14b9154b..eac37abd 100644 --- a/bitfinex/bitfinex_test.go +++ b/bitfinex/bitfinex_test.go @@ -21,7 +21,7 @@ func TestBitfinex_GetDepth(t *testing.T) { } func TestBitfinex_GetKline(t *testing.T) { - kline, _ := bfx.GetKlineRecords(goex.BTC_USD, goex.KLINE_PERIOD_1MONTH, 10, 0) + kline, _ := bfx.GetKlineRecords(goex.BTC_USD, goex.KLINE_PERIOD_1MONTH, 10) for _, k := range kline { t.Log(k) } diff --git a/bithumb/bithumb.go b/bithumb/bithumb.go index 1b6f85c2..149c3358 100644 --- a/bithumb/bithumb.go +++ b/bithumb/bithumb.go @@ -11,6 +11,7 @@ import ( "net/url" "sort" "strconv" + "strings" "time" ) @@ -199,7 +200,7 @@ func (bit *Bithumb) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return orders, nil } -func (bit *Bithumb) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (bit *Bithumb) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("not implement") } @@ -212,36 +213,17 @@ func (bit *Bithumb) GetAccount() (*Account, error) { datamap := retmap["data"].(map[string]interface{}) acc := new(Account) acc.SubAccounts = make(map[Currency]SubAccount) - acc.SubAccounts[LTC] = SubAccount{ - Currency: LTC, - Amount: ToFloat64(datamap["available_ltc"]), - ForzenAmount: ToFloat64(datamap["in_use_ltc"]), - LoanAmount: 0} - acc.SubAccounts[BTC] = SubAccount{ - Currency: BTC, - Amount: ToFloat64(datamap["available_btc"]), - ForzenAmount: ToFloat64(datamap["in_use_etc"]), - LoanAmount: 0} - acc.SubAccounts[ETH] = SubAccount{ - Currency: ETH, - Amount: ToFloat64(datamap["available_eth"]), - ForzenAmount: ToFloat64(datamap["in_use_eth"]), - LoanAmount: 0} - acc.SubAccounts[ETC] = SubAccount{ - Currency: ETC, - Amount: ToFloat64(datamap["available_etc"]), - ForzenAmount: ToFloat64(datamap["in_use_etc"]), - LoanAmount: 0} - acc.SubAccounts[BCH] = SubAccount{ - Currency: BCH, - Amount: ToFloat64(datamap["available_bch"]), - ForzenAmount: ToFloat64(datamap["in_use_bch"]), - LoanAmount: 0} - acc.SubAccounts[KRW] = SubAccount{ - Currency: KRW, - Amount: ToFloat64(datamap["available_krw"]), - ForzenAmount: ToFloat64(datamap["in_use_krw"]), - LoanAmount: 0} + for key := range datamap { + if strings.HasPrefix(key, "available_") { + t := strings.Split(key, "_") + currency := NewCurrency(strings.ToUpper(t[len(t)-1]), "") + acc.SubAccounts[currency] = SubAccount{ + Currency: currency, + Amount: ToFloat64(datamap[key]), + ForzenAmount: ToFloat64(datamap[fmt.Sprintf("in_use_%s", strings.ToLower(currency.String()))]), + LoanAmount: 0} + } + } //log.Println(datamap) acc.Exchange = bit.GetExchangeName() return acc, nil @@ -335,7 +317,7 @@ func (bit *Bithumb) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return dep, nil } -func (bit *Bithumb) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (bit *Bithumb) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { panic("not implement") } diff --git a/bitmex/Adapter.go b/bitmex/Adapter.go new file mode 100644 index 00000000..3c78efa4 --- /dev/null +++ b/bitmex/Adapter.go @@ -0,0 +1,57 @@ +package bitmex + +import ( + "fmt" + . "github.com/nntaoli-project/goex" + "strings" +) + +func AdaptCurrencyPairToSymbol(pair CurrencyPair, contract string) string { + if contract == "" || contract == SWAP_CONTRACT { + if pair.CurrencyA.Eq(BTC) { + pair = NewCurrencyPair(XBT, USD) + } + if pair.CurrencyB.Eq(BTC) { + pair = NewCurrencyPair(pair.CurrencyA, XBT) + } + return pair.AdaptUsdtToUsd().ToSymbol("") + } + + coin := pair.CurrencyA.Symbol + if pair.CurrencyA.Eq(BTC) { + coin = XBT.Symbol + } + return fmt.Sprintf("%s%s", coin, strings.ToUpper(contract)) +} + +func AdaptWsSymbol(symbol string) (pair CurrencyPair, contract string) { + symbol = strings.ToUpper(symbol) + + if symbol == "XBTCUSD" { + return BTC_USD, SWAP_CONTRACT + } + + if symbol == "BCHUSD" { + return BCH_USD, SWAP_CONTRACT + } + + if symbol == "ETHUSD" { + return ETH_USD, SWAP_CONTRACT + } + + if symbol == "LTCUSD" { + return LTC_USD, SWAP_CONTRACT + } + + if symbol == "LINKUSDT" { + return NewCurrencyPair2("LINK_USDT"), SWAP_CONTRACT + } + + pair = NewCurrencyPair(NewCurrency(symbol[0:3], ""), USDT) + contract = symbol[3:] + if pair.CurrencyA.Eq(XBT) { + return NewCurrencyPair(BTC, USDT), contract + } + + return pair, contract +} diff --git a/bitmex/bitmex.go b/bitmex/Bitmex.go similarity index 97% rename from bitmex/bitmex.go rename to bitmex/Bitmex.go index b124d257..a6e05bab 100644 --- a/bitmex/bitmex.go +++ b/bitmex/Bitmex.go @@ -21,6 +21,10 @@ type bitmex struct { *APIConfig } +func (bm *bitmex) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + panic("implement me") +} + func New(config *APIConfig) *bitmex { bm := &bitmex{config} if bm.Endpoint == "" { @@ -309,6 +313,7 @@ func (bm *bitmex) GetFee() (float64, error) { func (bm *bitmex) GetFutureDepth(currencyPair CurrencyPair, contractType string, size int) (*Depth, error) { sym := bm.adaptCurrencyPairToSymbol(currencyPair, contractType) uri := fmt.Sprintf("/api/v1/orderBook/L2?symbol=%s&depth=%d", sym, size) + resp, err := HttpGet3(bm.HttpClient, bm.Endpoint+uri, nil) if err != nil { return nil, HTTP_ERR_CODE.OriginErr(err.Error()) @@ -406,14 +411,9 @@ func (bm *bitmex) GetFutureEstimatedPrice(currencyPair CurrencyPair) (float64, e panic("no support") } -func (bm *bitmex) GetKlineRecords(contract_type string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { +func (bm *bitmex) GetKlineRecords(contract_type string, currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]FutureKline, error) { urlPath := "/api/v1/trade/bucketed?binSize=%s&partial=false&symbol=%s&count=%d&startTime=%s&reverse=true" contractId := bm.adaptCurrencyPairToSymbol(currency, contract_type) - sinceTime := time.Unix(int64(since), 0).UTC() - - if since/int(time.Second) != 1 { - sinceTime = time.Unix(int64(since)/int64(time.Second), 0).UTC() - } var granularity string switch period { @@ -429,6 +429,11 @@ func (bm *bitmex) GetKlineRecords(contract_type string, currency CurrencyPair, p granularity = "5m" } + sinceTime := time.Now() + if len(optional) > 0 && optional[0].GetTime("startTime") != nil { + sinceTime = *optional[0].GetTime("startTime") + } + uri := fmt.Sprintf(urlPath, granularity, contractId, size, sinceTime.Format(time.RFC3339)) response, err := HttpGet3(bm.HttpClient, bm.Endpoint+uri, nil) if err != nil { diff --git a/bitmex/bitmex_test.go b/bitmex/Bitmex_test.go similarity index 92% rename from bitmex/bitmex_test.go rename to bitmex/Bitmex_test.go index 4f577438..e5f84a0a 100644 --- a/bitmex/bitmex_test.go +++ b/bitmex/Bitmex_test.go @@ -28,16 +28,15 @@ var httpProxyClient = &http.Client{ func init() { logger.Log.SetLevel(logger.DEBUG) mex = New(&goex.APIConfig{ - Endpoint: "https://testnet.bitmex.com/", - HttpClient: httpProxyClient, - ApiKey: "", - ApiSecretKey: ""}) + Endpoint: "https://testnet.bitmex.com/", + HttpClient: httpProxyClient, + }) } var mex *bitmex func TestBitmex_GetFutureDepth(t *testing.T) { - dep, err := mex.GetFutureDepth(goex.BTC_USD, "Z19", 5) + dep, err := mex.GetFutureDepth(goex.ETH_USDT, goex.SWAP_CONTRACT, 5) assert.Nil(t, err) t.Log(dep.AskList) t.Log(dep.BidList) diff --git a/bitmex/SwapWs.go b/bitmex/SwapWs.go new file mode 100644 index 00000000..00a42ac2 --- /dev/null +++ b/bitmex/SwapWs.go @@ -0,0 +1,217 @@ +package bitmex + +import ( + "encoding/json" + "fmt" + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "sort" + "time" +) + +type SubscribeOp struct { + Op string `json:"op"` + Args []string `json:"args"` +} + +type wsMessage struct { + Table string `json:"table"` + Action string `json:"action"` + Data json.RawMessage +} + +type tickerData struct { + Symbol string `json:"symbol"` + MakerFee float64 `json:"makerFee"` + TakerFee float64 `json:"takerFee"` + LastPrice float64 `json:"lastPrice"` + HighPrice float64 `json:"highPrice"` + LowPrice float64 `json:"lowPrice"` + AskPrice float64 `json:"askPrice"` + BidPrice float64 `json:"bidPrice"` + HomeNotional24h float64 `json:"homeNotional24h"` + Turnover24h float64 `json:"turnover24h"` + Timestamp string `json:"timestamp"` +} + +type depthData struct { + Symbol string `json:"symbol"` + Bids [][]interface{} `json:"bids"` + Asks [][]interface{} `json:"asks"` + Timestamp string `json:"timestamp"` +} + +type SwapWs struct { + c *WsConn + + depthCall func(depth *Depth) + tickerCall func(ticker *FutureTicker) + + tickerCacheMap map[string]FutureTicker +} + +func NewSwapWs() *SwapWs { + s := new(SwapWs) + wsBuilder := NewWsBuilder().DisableEnableCompression().WsUrl("wss://www.bitmex.com/realtime") + wsBuilder = wsBuilder.Heartbeat(func() []byte { return []byte("ping") }, 5*time.Second) + wsBuilder = wsBuilder.ProtoHandleFunc(s.handle).AutoReconnect() + s.c = wsBuilder.Build() + s.tickerCacheMap = make(map[string]FutureTicker, 10) + return s +} + +func (s *SwapWs) DepthCallback(f func(depth *Depth)) { + s.depthCall = f +} + +func (s *SwapWs) TickerCallback(f func(ticker *FutureTicker)) { + s.tickerCall = f +} + +func (s *SwapWs) TradeCallback(f func(trade *Trade, contract string)) { + panic("implement me") +} + +func (s *SwapWs) SubscribeDepth(pair CurrencyPair, contractType string) error { + //{"op": "subscribe", "args": ["orderBook10:XBTUSD"]} + op := SubscribeOp{ + Op: "subscribe", + Args: []string{ + fmt.Sprintf("orderBook10:%s", AdaptCurrencyPairToSymbol(pair, contractType)), + }, + } + return s.c.Subscribe(op) +} + +func (s *SwapWs) SubscribeTicker(pair CurrencyPair, contractType string) error { + return s.c.Subscribe(SubscribeOp{ + Op: "subscribe", + Args: []string{ + "instrument:" + AdaptCurrencyPairToSymbol(pair, contractType), + }, + }) +} + +func (s *SwapWs) SubscribeTrade(pair CurrencyPair, contractType string) error { + panic("implement me") +} + +func (s *SwapWs) handle(data []byte) error { + if string(data) == "pong" { + return nil + } + + var msg wsMessage + err := json.Unmarshal(data, &msg) + if err != nil { + logger.Errorf("unmarshal error , message: %s", string(data)) + return err + } + + switch msg.Table { + case "orderBook10": + if msg.Action != "update" { + return nil + } + + var ( + depthData []depthData + dep Depth + ) + + err = json.Unmarshal(msg.Data, &depthData) + if err != nil { + logger.Errorf("unmarshal depth data error , data: %s", string(msg.Data)) + return nil + } + + if len(depthData) == 0 { + logger.Warn("depth data len==0 ??") + return nil + } + + dep.UTime, _ = time.Parse(time.RFC3339, depthData[0].Timestamp) + dep.Pair, dep.ContractType = AdaptWsSymbol(depthData[0].Symbol) + + for _, item := range depthData[0].Bids { + dep.BidList = append(dep.BidList, DepthRecord{ + Price: ToFloat64(item[0]), + Amount: ToFloat64(item[1]), + }) + } + + for _, item := range depthData[0].Asks { + dep.AskList = append(dep.AskList, DepthRecord{ + Price: ToFloat64(item[0]), + Amount: ToFloat64(item[1]), + }) + } + + sort.Sort(sort.Reverse(dep.AskList)) + + s.depthCall(&dep) + case "instrument": + var tickerData []tickerData + + err = json.Unmarshal(msg.Data, &tickerData) + if err != nil { + logger.Errorf("ticker data unmarshal error , data: %s", string(msg.Data)) + return err + } + + if msg.Action == "partial" { + ticker := s.tickerCacheMap[tickerData[0].Symbol] + ticker.Ticker = new(Ticker) + ticker.Pair, ticker.ContractType = AdaptWsSymbol(tickerData[0].Symbol) + ticker.Vol = tickerData[0].HomeNotional24h + ticker.Last = tickerData[0].LastPrice + ticker.Sell = tickerData[0].AskPrice + ticker.Buy = tickerData[0].BidPrice + ticker.High = tickerData[0].HighPrice + ticker.Low = tickerData[0].LowPrice + + tickerTime, _ := time.Parse(time.RFC3339, tickerData[0].Timestamp) + ticker.Date = uint64(tickerTime.Unix()) + + s.tickerCacheMap[tickerData[0].Symbol] = ticker + s.tickerCall(&ticker) + } + + if msg.Action == "update" { + ticker := s.tickerCacheMap[tickerData[0].Symbol] + tickerTime, _ := time.Parse(time.RFC3339, tickerData[0].Timestamp) + ticker.Date = uint64(tickerTime.Unix()) + + if tickerData[0].LastPrice > 0 { + ticker.Last = tickerData[0].LastPrice + } + + if tickerData[0].AskPrice > 0 { + ticker.Sell = tickerData[0].AskPrice + } + + if tickerData[0].BidPrice > 0 { + ticker.Buy = tickerData[0].BidPrice + } + + if tickerData[0].HighPrice > 0 { + ticker.High = tickerData[0].HighPrice + } + + if tickerData[0].LowPrice > 0 { + ticker.Low = tickerData[0].LowPrice + } + + if tickerData[0].HomeNotional24h > 0 { + ticker.Vol = tickerData[0].HomeNotional24h + } + + s.tickerCacheMap[tickerData[0].Symbol] = ticker + s.tickerCall(&ticker) + } + default: + logger.Warnf("unknown ws message: %s", string(data)) + } + + return nil +} diff --git a/bitmex/SwapWs_test.go b/bitmex/SwapWs_test.go new file mode 100644 index 00000000..6026309e --- /dev/null +++ b/bitmex/SwapWs_test.go @@ -0,0 +1,23 @@ +package bitmex + +import ( + "github.com/nntaoli-project/goex" + "os" + "testing" + "time" +) + +func TestNewSwapWs(t *testing.T) { + os.Setenv("HTTPS_PROXY", "socks5://127.0.0.1:1080") + ws := NewSwapWs() + ws.DepthCallback(func(depth *goex.Depth) { + t.Log(depth) + }) + ws.TickerCallback(func(ticker *goex.FutureTicker) { + t.Logf("%s %v", ticker.ContractType, ticker.Ticker) + }) + //ws.SubscribeDepth(goex.NewCurrencyPair2("LTC_USD"), goex.SWAP_CONTRACT) + ws.SubscribeTicker(goex.LTC_USDT , goex.SWAP_CONTRACT) + + time.Sleep(5 * time.Minute) +} diff --git a/bitstamp/Bitstamp.go b/bitstamp/Bitstamp.go index 80b3a943..2d994e30 100644 --- a/bitstamp/Bitstamp.go +++ b/bitstamp/Bitstamp.go @@ -4,19 +4,39 @@ import ( "encoding/json" "errors" "fmt" - . "github.com/nntaoli-project/goex" "net/http" "net/url" "sort" "strconv" "strings" "time" + + . "github.com/nntaoli-project/goex" ) var ( BASE_URL = "https://www.bitstamp.net/api/" ) +var _INTERNAL_KLINE_PERIOD_CONVERTER = map[KlinePeriod]string{ + KLINE_PERIOD_1MIN: "60", + KLINE_PERIOD_3MIN: "180", + KLINE_PERIOD_5MIN: "300", + KLINE_PERIOD_15MIN: "900", + KLINE_PERIOD_30MIN: "1800", + KLINE_PERIOD_60MIN: "3600", + KLINE_PERIOD_1H: "3600", + KLINE_PERIOD_2H: "7200", + KLINE_PERIOD_4H: "14400", + KLINE_PERIOD_6H: "21600", + // KLINE_PERIOD_8H: "28800", // Not supported + KLINE_PERIOD_12H: "43200", + KLINE_PERIOD_1DAY: "86400", + KLINE_PERIOD_3DAY: "259200", + // KLINE_PERIOD_1WEEK: "604800", //Not supported + // KLINE_PERIOD_1MONTH: "1M", // Not supported +} + type Bitstamp struct { client *http.Client clientId, @@ -324,7 +344,7 @@ func (bitstamp *Bitstamp) GetUnfinishOrders(currency CurrencyPair) ([]Order, err return orders, nil } -func (bitstamp *Bitstamp) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (bitstamp *Bitstamp) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("not implement") } @@ -388,8 +408,53 @@ func (bitstamp *Bitstamp) GetDepth(size int, currency CurrencyPair) (*Depth, err return dep, nil } -func (bitstamp *Bitstamp) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - panic("not implement") +func (bitstamp *Bitstamp) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { + + params := url.Values{} + params.Set("step", _INTERNAL_KLINE_PERIOD_CONVERTER[period]) + params.Set("limit", fmt.Sprintf("%d", size)) + MergeOptionalParameter(¶ms, optional...) + + urlStr := BASE_URL + "v2/ohlc/" + strings.ToLower(currency.ToSymbol("")) + "?" + params.Encode() + + fmt.Println(urlStr) + + type ohlcResp struct { + Data struct { + Pair string `json:"pair"` + Ohlc []struct { + High string `json:"high"` + Timestamp string `json:"timestamp"` + Volume string `json:"volume"` + Low string `json:"low"` + Close string `json:"close"` + Open string `json:"open"` + } `json:ohlc` + } `json:"data"` + } + + resp := ohlcResp{} + err := HttpGet4(bitstamp.client, urlStr, nil, &resp) + if err != nil { + return nil, err + } + + var klineRecords []Kline + + for _, _record := range resp.Data.Ohlc { + r := Kline{Pair: currency} + r.Timestamp, _ = strconv.ParseInt(_record.Timestamp, 10, 64) //to unix timestramp + r.Open = ToFloat64(_record.Open) + r.High = ToFloat64(_record.High) + r.Low = ToFloat64(_record.Low) + r.Close = ToFloat64(_record.Close) + r.Vol = ToFloat64(_record.Volume) + + klineRecords = append(klineRecords, r) + } + + return klineRecords, nil + } ////非个人,整个交易所的交易记录 diff --git a/bittrex/bittrex.go b/bittrex/bittrex.go index 71f51cf1..5d2be31a 100644 --- a/bittrex/bittrex.go +++ b/bittrex/bittrex.go @@ -40,7 +40,7 @@ func (bx *Bittrex) GetOneOrder(orderId string, currency CurrencyPair) (*Order, e func (bx *Bittrex) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { panic("not implement") } -func (bx *Bittrex) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (bx *Bittrex) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("not implement") } func (bx *Bittrex) GetAccount() (*Account, error) { @@ -105,7 +105,7 @@ func (bx *Bittrex) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return dep, nil } -func (bx *Bittrex) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (bx *Bittrex) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { panic("not implement") } diff --git a/builder/APIBuilder.go b/builder/APIBuilder.go index b6aaed2c..a76eb748 100644 --- a/builder/APIBuilder.go +++ b/builder/APIBuilder.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - . "github.com/nntaoli-project/goex" "github.com/nntaoli-project/goex/bigone" "github.com/nntaoli-project/goex/binance" @@ -317,7 +316,6 @@ func (builder *APIBuilder) BuildFuture(exName string) (api FutureRestAPI) { ApiKey: builder.apiKey, ApiSecretKey: builder.secretkey, }) - default: println(fmt.Sprintf("%s not support future", exName)) return nil @@ -333,6 +331,12 @@ func (builder *APIBuilder) BuildFuturesWs(exName string) (FuturesWsApi, error) { })), nil case HBDM: return huobi.NewHbdmWs(), nil + case HBDM_SWAP: + return huobi.NewHbdmSwapWs(), nil + case BINANCE, BINANCE_FUTURES, BINANCE_SWAP: + return binance.NewFuturesWs(), nil + case BITMEX: + return bitmex.NewSwapWs(), nil } return nil, errors.New("not support the exchange " + exName) } @@ -343,6 +347,8 @@ func (builder *APIBuilder) BuildSpotWs(exName string) (SpotWsApi, error) { return okex.NewOKExSpotV3Ws(nil), nil case HUOBI_PRO, HUOBI: return huobi.NewSpotWs(), nil + case BINANCE: + return binance.NewSpotWs(), nil } return nil, errors.New("not support the exchange " + exName) } diff --git a/coinbene/CoinbeneSwap.go b/coinbene/CoinbeneSwap.go index 26602381..49e2d25b 100644 --- a/coinbene/CoinbeneSwap.go +++ b/coinbene/CoinbeneSwap.go @@ -38,6 +38,10 @@ type CoinbeneSwap struct { config APIConfig } +func (swap *CoinbeneSwap) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + panic("implement me") +} + func NewCoinbeneSwap(config APIConfig) *CoinbeneSwap { if config.Endpoint == "" { config.Endpoint = "http://openapi-contract.coinbene.com" @@ -351,7 +355,7 @@ func (swap *CoinbeneSwap) GetUnfinishFutureOrders(currencyPair CurrencyPair, con func (swap *CoinbeneSwap) GetFee() (float64, error) { panic("") } func (swap *CoinbeneSwap) GetContractValue(currencyPair CurrencyPair) (float64, error) { panic("") } func (swap *CoinbeneSwap) GetDeliveryTime() (int, int, int, int) { panic("") } -func (swap *CoinbeneSwap) GetKlineRecords(contract_type string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { +func (swap *CoinbeneSwap) GetKlineRecords(contract_type string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { panic("") } func (swap *CoinbeneSwap) GetTrades(contract_type string, currencyPair CurrencyPair, since int64) ([]Trade, error) { diff --git a/coinbig/coinbig.go b/coinbig/coinbig.go index 9feabc71..376258a8 100644 --- a/coinbig/coinbig.go +++ b/coinbig/coinbig.go @@ -344,7 +344,7 @@ func (cb *CoinBig) GetUnfinishOrders(currencyPair CurrencyPair) ([]Order, error) } return orders, nil } -func (cb *CoinBig) GetOrderHistorys(currencyPair CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (cb *CoinBig) GetOrderHistorys(currencyPair CurrencyPair, optional ...OptionalParameter) ([]Order, error) { return nil, nil } func (cb *CoinBig) GetDepth(size int, currencyPair CurrencyPair) (*Depth, error) { diff --git a/coinex/coinex.go b/coinex/coinex.go index 499b59ad..3c0b6232 100644 --- a/coinex/coinex.go +++ b/coinex/coinex.go @@ -178,7 +178,7 @@ func (coinex *CoinEx) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) return coinex.GetPendingOrders(1, 100, currency) } -func (coinex *CoinEx) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (coinex *CoinEx) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("not implement") } @@ -238,7 +238,7 @@ func (coinex *CoinEx) GetAccount() (*Account, error) { return acc, nil } -func (coinex *CoinEx) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (coinex *CoinEx) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { panic("not implement") } diff --git a/exx/exx.go b/exx/exx.go index 643c0c48..38a68a3f 100644 --- a/exx/exx.go +++ b/exx/exx.go @@ -15,7 +15,7 @@ import ( ) const ( - EXX = "EXX" + EXX = "EXX" API_BASE_URL = "https://api.exx.com/" MARKET_URL = "http://api.exx.com/data/v1/" TICKER_API = "ticker?currency=%s" @@ -46,7 +46,7 @@ func (exx *Exx) GetExchangeName() string { } func (exx *Exx) GetTicker(currency CurrencyPair) (*Ticker, error) { - symbol := currency.AdaptBchToBcc().AdaptUsdToUsdt().ToLower().ToSymbol("_") + symbol := currency.ToLower().ToSymbol("_") path := MARKET_URL + fmt.Sprintf(TICKER_API, symbol) resp, err := HttpGet(exx.httpClient, path) if err != nil { @@ -73,7 +73,7 @@ func (exx *Exx) GetTicker(currency CurrencyPair) (*Ticker, error) { } func (exx *Exx) GetDepth(size int, currency CurrencyPair) (*Depth, error) { - symbol := currency.AdaptBchToBcc().AdaptUsdToUsdt().ToSymbol("_") + symbol := currency.ToSymbol("_") resp, err := HttpGet(exx.httpClient, MARKET_URL+fmt.Sprintf(DEPTH_API, symbol)) if err != nil { return nil, err @@ -177,7 +177,7 @@ func (exx *Exx) GetAccount() (*Account, error) { } func (exx *Exx) placeOrder(amount, price string, currency CurrencyPair, tradeType int) (*Order, error) { - symbol := currency.AdaptBchToBcc().AdaptUsdToUsdt().ToSymbol("_") + symbol := currency.ToSymbol("_") params := url.Values{} params.Set("method", "order") params.Set("price", price) @@ -237,7 +237,7 @@ func (exx *Exx) LimitSell(amount, price string, currency CurrencyPair, opt ...Li func (exx *Exx) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { - symbol := currency.AdaptBchToBcc().AdaptUsdToUsdt().ToSymbol("_") + symbol := currency.ToSymbol("_") params := url.Values{} params.Set("method", "cancelOrder") params.Set("id", orderId) @@ -309,7 +309,7 @@ func parseOrder(order *Order, ordermap map[string]interface{}) { } func (exx *Exx) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error) { - symbol := currency.AdaptBchToBcc().AdaptUsdToUsdt().ToSymbol("_") + symbol := currency.ToSymbol("_") params := url.Values{} params.Set("method", "getOrder") params.Set("id", orderId) @@ -339,7 +339,7 @@ func (exx *Exx) GetOneOrder(orderId string, currency CurrencyPair) (*Order, erro func (exx *Exx) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { params := url.Values{} - symbol := currency.AdaptBchToBcc().AdaptUsdToUsdt().ToSymbol("_") + symbol := currency.ToSymbol("_") params.Set("method", "getUnfinishedOrdersIgnoreTradeType") params.Set("currency", symbol) params.Set("pageIndex", "1") @@ -379,7 +379,7 @@ func (exx *Exx) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return orders, nil } -func (exx *Exx) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (exx *Exx) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { return nil, nil } diff --git a/gdax/gdax.go b/gdax/gdax.go index 462242f9..b01a147a 100644 --- a/gdax/gdax.go +++ b/gdax/gdax.go @@ -45,7 +45,7 @@ func (g *Gdax) GetOneOrder(orderId string, currency CurrencyPair) (*Order, error func (g *Gdax) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { panic("not implement") } -func (g *Gdax) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (g *Gdax) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("not implement") } func (g *Gdax) GetAccount() (*Account, error) { @@ -116,7 +116,7 @@ func (g *Gdax) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return dep, nil } -func (g *Gdax) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (g *Gdax) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { urlpath := fmt.Sprintf("%s/products/%s/candles", g.baseUrl, currency.AdaptUsdtToUsd().ToSymbol("-")) granularity := -1 switch period { diff --git a/hitbtc/Hitbtc.go b/hitbtc/Hitbtc.go index 4c0c8d63..d2430a6d 100644 --- a/hitbtc/Hitbtc.go +++ b/hitbtc/Hitbtc.go @@ -291,9 +291,10 @@ func (hitbtc *Hitbtc) GetUnfinishOrders(currency goex.CurrencyPair) ([]goex.Orde // TODO // https://api.hitbtc.com/#orders-history -func (hitbtc *Hitbtc) GetOrderHistorys(currency goex.CurrencyPair, currentPage, pageSize int) ([]goex.Order, error) { +func (hitbtc *Hitbtc) GetOrderHistorys(currency goex.CurrencyPair, optional ...goex.OptionalParameter) ([]goex.Order, error) { params := url.Values{} params.Set("symbol", currency.ToSymbol("")) + resp := []map[string]interface{}{} err := hitbtc.doRequest("GET", ORDER_URI+"?"+params.Encode(), &resp) if err != nil { @@ -395,7 +396,7 @@ func (hitbtc *Hitbtc) GetDepth(size int, currency goex.CurrencyPair) (*goex.Dept return &goex.Depth{AskList: askList, BidList: bidList}, nil } -func (hitbtc *Hitbtc) GetKlineRecords(currency goex.CurrencyPair, period, size, since int) ([]goex.Kline, error) { +func (hitbtc *Hitbtc) GetKlineRecords(currency goex.CurrencyPair, period goex.KlinePeriod, size int, opt ...goex.OptionalParameter) ([]goex.Kline, error) { panic("not implement") } diff --git a/hitbtc/Hitbtc_test.go b/hitbtc/Hitbtc_test.go index c66794af..e1e743cd 100644 --- a/hitbtc/Hitbtc_test.go +++ b/hitbtc/Hitbtc_test.go @@ -87,7 +87,7 @@ func TestGetOneOrder(t *testing.T) { } func TestGetOrders(t *testing.T) { - res, err := htb.GetOrderHistorys(YCC_BTC, 1, 10) + res, err := htb.GetOrderHistorys(YCC_BTC) require := require.New(t) require.Nil(err) t.Log(res) diff --git a/huobi/Hbdm.go b/huobi/Hbdm.go index 50bd7143..72a025a1 100644 --- a/huobi/Hbdm.go +++ b/huobi/Hbdm.go @@ -32,6 +32,7 @@ type OrderInfo struct { ClientOrderId int64 `json:"client_order_id"` OrderSource string `json:"order_source"` CreatedAt int64 `json:"created_at"` + CreateDate int64 `json:"create_date"` //for swap contract TradeVolume float64 `json:"trade_volume"` TradeTurnover float64 `json:"trade_turnover"` Fee float64 `json:"fee"` @@ -457,6 +458,51 @@ func (dm *Hbdm) GetFutureOrders(orderIds []string, currencyPair CurrencyPair, co } +func (dm *Hbdm) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + path := "/api/v1/contract_hisorders_exact" + + param := url.Values{} + param.Set("symbol", pair.CurrencyA.Symbol) + param.Set("type", "1") + param.Set("trade_type", "0") + param.Set("status", "0") + param.Set("size", "50") + + MergeOptionalParameter(¶m, optional...) + + var data struct { + Orders []OrderInfo `json:"orders"` + RemainSize int `json:"remain_size"` + NextId int `json:"next_id"` + } + + err := dm.doRequest(path, ¶m, &data) + if err != nil { + return nil, err + } + + var ords []FutureOrder + for _, ord := range data.Orders { + ords = append(ords, FutureOrder{ + ContractName: ord.ContractType, + Currency: pair, + OType: dm.adaptOffsetDirectionToOpenType(ord.Offset, ord.Direction), + OrderID2: fmt.Sprint(ord.OrderId), + OrderID: ord.OrderId, + Amount: ord.Volume, + Price: ord.Price, + AvgPrice: ord.TradeAvgPrice, + DealAmount: ord.TradeVolume, + Status: dm.adaptOrderStatus(ord.Status), + Fee: ord.Fee, + LeverRate: ord.LeverRate, + OrderTime: ord.CreateDate, + }) + } + + return ords, nil +} + func (dm *Hbdm) GetContractValue(currencyPair CurrencyPair) (float64, error) { switch currencyPair.CurrencyA { case BTC: @@ -566,7 +612,7 @@ func (dm *Hbdm) GetFutureIndex(currencyPair CurrencyPair) (float64, error) { return ToFloat64(index), nil } -func (dm *Hbdm) GetKlineRecords(contract_type string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { +func (dm *Hbdm) GetKlineRecords(contract_type string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { symbol := dm.adaptSymbol(currency, contract_type) periodS := dm.adaptKLinePeriod(period) url := fmt.Sprintf("%s/market/history/kline?symbol=%s&period=%s&size=%d", dm.config.Endpoint, symbol, periodS, size) @@ -640,7 +686,7 @@ func (dm *Hbdm) adaptSymbol(pair CurrencyPair, contractType string) string { return symbol } -func (dm *Hbdm) adaptKLinePeriod(period int) string { +func (dm *Hbdm) adaptKLinePeriod(period KlinePeriod) string { switch period { case KLINE_PERIOD_1MIN: return "1min" diff --git a/huobi/Hbdm_Swap.go b/huobi/Hbdm_Swap.go index 3c9df609..6dcbe19f 100644 --- a/huobi/Hbdm_Swap.go +++ b/huobi/Hbdm_Swap.go @@ -27,6 +27,7 @@ const ( cancelOrderApiPath = "/swap-api/v1/swap_cancel" getOpenOrdersApiPath = "/swap-api/v1/swap_openorders" getOrderInfoApiPath = "/swap-api/v1/swap_order_info" + getHistoryOrderPath = "/swap-api/v1/swap_hisorders_exact" ) func NewHbdmSwap(c *APIConfig) *HbdmSwap { @@ -381,6 +382,54 @@ func (swap *HbdmSwap) GetFutureOrder(orderId string, currencyPair CurrencyPair, }, nil } +func (swap *HbdmSwap) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + params := url.Values{} + params.Add("status", "0") //all + params.Add("type", "1") //all + params.Add("trade_type", "0") //all + + if contractType == "" || contractType == SWAP_CONTRACT { + params.Add("contract_code", pair.AdaptUsdtToUsd().ToSymbol("-")) + } else { + return nil, errors.New("contract type is error") + } + + MergeOptionalParameter(¶ms, optional...) + + var historyOrderResp struct { + Orders []OrderInfo `json:"orders"` + RemainSize int64 `json:"remain_size"` + NextId int64 `json:"next_id"` + } + + err := swap.base.doRequest(getHistoryOrderPath, ¶ms, &historyOrderResp) + if err != nil { + return nil, err + } + + var historyOrders []FutureOrder + + for _, ord := range historyOrderResp.Orders { + historyOrders = append(historyOrders, FutureOrder{ + OrderID: ord.OrderId, + OrderID2: fmt.Sprintf("%d", ord.OrderId), + Price: ord.Price, + Amount: ord.Volume, + AvgPrice: ord.TradeAvgPrice, + DealAmount: ord.TradeVolume, + OrderTime: ord.CreateDate, + Status: swap.base.adaptOrderStatus(ord.Status), + Currency: pair, + OType: swap.base.adaptOffsetDirectionToOpenType(ord.Offset, ord.Direction), + LeverRate: ord.LeverRate, + Fee: ord.Fee, + ContractName: ord.ContractCode, + }) + } + + return historyOrders, nil +} + func (swap *HbdmSwap) GetUnfinishFutureOrders(currencyPair CurrencyPair, contractType string) ([]FutureOrder, error) { param := url.Values{} param.Set("contract_code", currencyPair.ToSymbol("-")) @@ -426,7 +475,7 @@ func (swap *HbdmSwap) GetContractValue(currencyPair CurrencyPair) (float64, erro } } -func (swap *HbdmSwap) GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { +func (swap *HbdmSwap) GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { panic("not implement") } diff --git a/huobi/Hbdm_Swap_Ws.go b/huobi/Hbdm_Swap_Ws.go new file mode 100644 index 00000000..0e1e60d9 --- /dev/null +++ b/huobi/Hbdm_Swap_Ws.go @@ -0,0 +1,246 @@ +package huobi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + . "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "strings" + "sync" + "time" +) + +type HbdmSwapWs struct { + *WsBuilder + sync.Once + wsConn *WsConn + + tickerCallback func(*FutureTicker) + depthCallback func(*Depth) + tradeCallback func(*Trade, string) +} + +func NewHbdmSwapWs() *HbdmSwapWs { + ws := &HbdmSwapWs{WsBuilder: NewWsBuilder()} + ws.WsBuilder = ws.WsBuilder. + WsUrl("wss://api.hbdm.com/swap-ws"). + //ProxyUrl("socks5://127.0.0.1:1080"). + AutoReconnect(). + DecompressFunc(GzipDecompress). + ProtoHandleFunc(ws.handle) + return ws +} + +//构建usdt本位永续合约ws +func NewHbdmLinearSwapWs() *HbdmSwapWs { + ws := &HbdmSwapWs{WsBuilder: NewWsBuilder()} + ws.WsBuilder = ws.WsBuilder. + WsUrl("wss://api.hbdm.com/linear-swap-ws"). + //ProxyUrl("socks5://127.0.0.1:1080"). + AutoReconnect(). + DecompressFunc(GzipDecompress). + ProtoHandleFunc(ws.handle) + return ws +} + +func (ws *HbdmSwapWs) SetCallbacks(tickerCallback func(*FutureTicker), + depthCallback func(*Depth), + tradeCallback func(*Trade, string)) { + ws.tickerCallback = tickerCallback + ws.depthCallback = depthCallback + ws.tradeCallback = tradeCallback +} + +func (ws *HbdmSwapWs) TickerCallback(call func(ticker *FutureTicker)) { + ws.tickerCallback = call +} +func (ws *HbdmSwapWs) TradeCallback(call func(trade *Trade, contract string)) { + ws.tradeCallback = call +} + +func (ws *HbdmSwapWs) DepthCallback(call func(depth *Depth)) { + ws.depthCallback = call +} + +func (ws *HbdmSwapWs) SubscribeTicker(pair CurrencyPair, contract string) error { + if ws.tickerCallback == nil { + return errors.New("please set ticker callback func") + } + + if contract == SWAP_CONTRACT || contract == SWAP_USDT_CONTRACT { + return ws.subscribe(map[string]interface{}{ + "id": "ticker_1", + "sub": fmt.Sprintf("market.%s.detail", pair.ToSymbol("-"))}) + } + + return errors.New("not implement") +} + +func (ws *HbdmSwapWs) SubscribeDepth(pair CurrencyPair, contract string) error { + if ws.depthCallback == nil { + return errors.New("please set depth callback func") + } + + if contract == SWAP_CONTRACT || contract == SWAP_USDT_CONTRACT { + return ws.subscribe(map[string]interface{}{ + "id": "swap.depth", + "sub": fmt.Sprintf("market.%s.depth.step6", pair.ToSymbol("-"))}) + } + + return errors.New("not implement") +} + +func (ws *HbdmSwapWs) SubscribeTrade(pair CurrencyPair, contract string) error { + if ws.tradeCallback == nil { + return errors.New("please set trade callback func") + } + + if contract == SWAP_CONTRACT || contract == SWAP_USDT_CONTRACT { + return ws.subscribe(map[string]interface{}{ + "id": "swap_trade_3", + "sub": fmt.Sprintf("market.%s.trade.detail", pair.ToSymbol("-"))}) + } + + return errors.New("not implement") +} + +func (ws *HbdmSwapWs) subscribe(sub map[string]interface{}) error { + // log.Println(sub) + ws.connectWs() + return ws.wsConn.Subscribe(sub) +} + +func (ws *HbdmSwapWs) connectWs() { + ws.Do(func() { + ws.wsConn = ws.WsBuilder.Build() + }) +} + +func (ws *HbdmSwapWs) handle(msg []byte) error { + logger.Debug("ws message data:", string(msg)) + //心跳 + 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 + } + + if resp.Ch == "" { + logger.Warnf("[%s] ch == \"\" , msg=%s", ws.wsConn.WsUrl, string(msg)) + return nil + } + + ts := time.Now() + if resp.Ts > 0 { + ts = time.Unix(0, resp.Ts*int64(time.Millisecond)) + } + + pair, contract, err := ws.parseCurrencyAndContract(resp.Ch) + if err != nil { + logger.Errorf("[%s] parse currency and contract err=%s", ws.wsConn.WsUrl, err) + return err + } + + if strings.Contains(resp.Ch, ".depth.") { + var depResp DepthResponse + err := json.Unmarshal(resp.Tick, &depResp) + if err != nil { + return err + } + + dep := ParseDepthFromResponse(depResp) + dep.ContractType = contract + dep.Pair = pair + dep.UTime = ts + + ws.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 := ws.parseTrade(tradeResp) + for _, v := range trades { + v.Pair = pair + ws.tradeCallback(&v, contract) + } + + return nil + } + + if strings.HasSuffix(resp.Ch, ".detail") { + var detail DetailResponse + err := json.Unmarshal(resp.Tick, &detail) + if err != nil { + return err + } + + ticker := ws.parseTicker(detail) + ticker.ContractType = contract + ticker.Pair = pair + ticker.Date = uint64(detail.Id) + + ws.tickerCallback(&ticker) + + return nil + } + + logger.Errorf("[%s] unknown message, msg=%s", ws.wsConn.WsUrl, string(msg)) + + return nil +} + +func (ws *HbdmSwapWs) parseTicker(r DetailResponse) FutureTicker { + return FutureTicker{Ticker: &Ticker{Last: r.Close, High: r.High, Low: r.Low, Vol: r.Amount}} +} + +func (ws *HbdmSwapWs) parseCurrencyAndContract(ch string) (CurrencyPair, string, error) { + el := strings.Split(ch, ".") + + if len(el) < 2 { + return UNKNOWN_PAIR, "", errors.New(ch) + } + + pair := NewCurrencyPair3(el[1], "-") + if pair.CurrencyB.Eq(USD) { + return pair, SWAP_CONTRACT, nil + } + + return pair, SWAP_USDT_CONTRACT, nil +} + +func (ws *HbdmSwapWs) parseTrade(r TradeResponse) []Trade { + var trades []Trade + for _, v := range r.Data { + trades = append(trades, Trade{ + Tid: v.Id, + Price: v.Price, + Amount: v.Amount, + Type: AdaptTradeSide(v.Direction), + Date: v.Ts}) + } + return trades +} + +func (ws *HbdmSwapWs) adaptTime(tm string) int64 { + format := "2006-01-02 15:04:05" + day := time.Now().Format("2006-01-02") + local, _ := time.LoadLocation("Asia/Chongqing") + t, _ := time.ParseInLocation(format, day+" "+tm, local) + return t.UnixNano() / 1e6 + +} diff --git a/huobi/Hbdm_Swap_Ws_test.go b/huobi/Hbdm_Swap_Ws_test.go new file mode 100644 index 00000000..cba7b6a0 --- /dev/null +++ b/huobi/Hbdm_Swap_Ws_test.go @@ -0,0 +1,30 @@ +package huobi + +import ( + "github.com/nntaoli-project/goex" + "github.com/nntaoli-project/goex/internal/logger" + "testing" + "time" +) + +func TestNewHbdmSwapWs(t *testing.T) { + logger.SetLevel(logger.DEBUG) + + ws := NewHbdmSwapWs() + + ws.DepthCallback(func(depth *goex.Depth) { + t.Log(depth) + }) + ws.TickerCallback(func(ticker *goex.FutureTicker) { + t.Log(ticker.Date, ticker.Last, ticker.Buy, ticker.Sell, ticker.High, ticker.Low, ticker.Vol) + }) + ws.TradeCallback(func(trade *goex.Trade, contract string) { + t.Log(trade, contract) + }) + + //t.Log(ws.SubscribeDepth(goex.BTC_USD, goex.SWAP_CONTRACT)) + //t.Log(ws.SubscribeTicker(goex.BTC_USD, goex.SWAP_CONTRACT)) + t.Log(ws.SubscribeTrade(goex.BTC_USD , goex.SWAP_CONTRACT)) + + time.Sleep(time.Minute) +} diff --git a/huobi/Hbdm_Swap_test.go b/huobi/Hbdm_Swap_test.go index 27b899fe..f5619875 100644 --- a/huobi/Hbdm_Swap_test.go +++ b/huobi/Hbdm_Swap_test.go @@ -4,6 +4,7 @@ import ( "github.com/nntaoli-project/goex" "net/http" "testing" + "time" ) var swap *HbdmSwap @@ -53,3 +54,9 @@ func TestHbdmSwap_GetUnfinishFutureOrders(t *testing.T) { func TestHbdmSwap_GetFutureOrder(t *testing.T) { t.Log(swap.GetFutureOrder("784118017750929408", goex.NewCurrencyPair2("DOT_USD"), goex.SWAP_CONTRACT)) } + +func TestHbdmSwap_GetFutureOrderHistory(t *testing.T) { + t.Log(swap.GetFutureOrderHistory(goex.NewCurrencyPair2("KSM_USD"), goex.SWAP_CONTRACT, + goex.OptionalParameter{}.Optional("start_time", time.Now().Add(-5*24*time.Hour).Unix()*1000), + goex.OptionalParameter{}.Optional("end_time", time.Now().Unix()*1000))) +} diff --git a/huobi/HuobiPro.go b/huobi/HuobiPro.go index 484d3de0..5c450656 100644 --- a/huobi/HuobiPro.go +++ b/huobi/HuobiPro.go @@ -17,7 +17,7 @@ import ( var HBPOINT = NewCurrency("HBPOINT", "") -var _INERNAL_KLINE_PERIOD_CONVERTER = map[int]string{ +var _INERNAL_KLINE_PERIOD_CONVERTER = map[KlinePeriod]string{ KLINE_PERIOD_1MIN: "1min", KLINE_PERIOD_5MIN: "5min", KLINE_PERIOD_15MIN: "15min", @@ -348,6 +348,7 @@ func (hbpro *HuoBiPro) MarketSell(amount, price string, currency CurrencyPair) ( func (hbpro *HuoBiPro) parseOrder(ordmap map[string]interface{}) Order { ord := Order{ + Cid: fmt.Sprint(ordmap["client-order-id"]), OrderID: ToInt(ordmap["id"]), OrderID2: fmt.Sprint(ToInt(ordmap["id"])), Amount: ToFloat64(ordmap["amount"]), @@ -410,12 +411,9 @@ func (hbpro *HuoBiPro) GetOneOrder(orderId string, currency CurrencyPair) (*Orde } func (hbpro *HuoBiPro) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { - return hbpro.getOrders(queryOrdersParams{ - pair: currency, - states: "pre-submitted,submitted,partial-filled", - size: 100, - //direct:"" - }) + return hbpro.getOrders(currency, OptionalParameter{}. + Optional("states", "pre-submitted,submitted,partial-filled"). + Optional("size", "100")) } func (hbpro *HuoBiPro) CancelOrder(orderId string, currency CurrencyPair) (bool, error) { @@ -441,13 +439,14 @@ func (hbpro *HuoBiPro) CancelOrder(orderId string, currency CurrencyPair) (bool, return true, nil } -func (hbpro *HuoBiPro) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - return hbpro.getOrders(queryOrdersParams{ - pair: currency, - size: pageSize, - states: "partial-canceled,filled", - direct: "next", - }) +func (hbpro *HuoBiPro) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { + var optionals []OptionalParameter + optionals = append(optionals, OptionalParameter{}. + Optional("states", "canceled,partial-canceled,filled"). + Optional("size", "100"). + Optional("direct", "next")) + optionals = append(optionals, optional...) + return hbpro.getOrders(currency, optionals...) } type queryOrdersParams struct { @@ -461,20 +460,12 @@ type queryOrdersParams struct { pair CurrencyPair } -func (hbpro *HuoBiPro) getOrders(queryparams queryOrdersParams) ([]Order, error) { +func (hbpro *HuoBiPro) getOrders(pair CurrencyPair, optional ...OptionalParameter) ([]Order, error) { path := "/v1/order/orders" params := url.Values{} - params.Set("symbol", strings.ToLower(queryparams.pair.AdaptUsdToUsdt().ToSymbol(""))) - params.Set("states", queryparams.states) - - if queryparams.direct != "" { - params.Set("direct", queryparams.direct) - } - - if queryparams.size > 0 { - params.Set("size", fmt.Sprint(queryparams.size)) - } - + params.Set("symbol", strings.ToLower(pair.AdaptUsdToUsdt().ToSymbol(""))) + MergeOptionalParameter(¶ms, optional...) + Log.Info(params) hbpro.buildPostForm("GET", path, ¶ms) respmap, err := HttpGet(hbpro.httpClient, fmt.Sprintf("%s%s?%s", hbpro.baseUrl, path, params.Encode())) if err != nil { @@ -490,7 +481,7 @@ func (hbpro *HuoBiPro) getOrders(queryparams queryOrdersParams) ([]Order, error) for _, v := range datamap { ordmap := v.(map[string]interface{}) ord := hbpro.parseOrder(ordmap) - ord.Currency = queryparams.pair + ord.Currency = pair orders = append(orders, ord) } @@ -568,7 +559,7 @@ func (hbpro *HuoBiPro) GetDepth(size int, currency CurrencyPair) (*Depth, error) } //倒序 -func (hbpro *HuoBiPro) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (hbpro *HuoBiPro) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { url := hbpro.baseUrl + "/market/history/kline?period=%s&size=%d&symbol=%s" symbol := strings.ToLower(currency.AdaptUsdToUsdt().ToSymbol("")) periodS, isOk := _INERNAL_KLINE_PERIOD_CONVERTER[period] diff --git a/huobi/HuobiPro_test.go b/huobi/HuobiPro_test.go index 0f7662e4..0dc6dd7f 100644 --- a/huobi/HuobiPro_test.go +++ b/huobi/HuobiPro_test.go @@ -31,10 +31,11 @@ var ( ) // -var hbpro = NewHuoBiProSpot(httpProxyClient, apikey, secretkey) +var hbpro *HuoBiPro -func init() { +func init() { logger.Log.SetLevel(logger.DEBUG) + hbpro = NewHuoBiProSpot(httpProxyClient, apikey, secretkey) } func TestHuobiPro_GetTicker(t *testing.T) { @@ -117,14 +118,15 @@ func TestHuobiPro_CancelOrder(t *testing.T) { } func TestHuobiPro_GetOneOrder(t *testing.T) { - return - ord, err := hbpro.GetOneOrder("1116237737", goex.LTC_BTC) + ord, err := hbpro.GetOneOrder("165062634284339", goex.BTC_USDT) assert.Nil(t, err) t.Log(ord) } func TestHuobiPro_GetOrderHistorys(t *testing.T) { - ords, err := hbpro.GetOrderHistorys(goex.NewCurrencyPair2("HT_USDT"), 1, 3) + ords, err := hbpro.GetOrderHistorys( + goex.NewCurrencyPair2("BTC_USDT"), + goex.OptionalParameter{}.Optional("start-date","2020-11-30")) t.Log(err) t.Log(ords) } diff --git a/kraken/Kraken.go b/kraken/Kraken.go index 34ed1b05..d0db770c 100644 --- a/kraken/Kraken.go +++ b/kraken/Kraken.go @@ -178,7 +178,7 @@ func (k *Kraken) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return orders, nil } -func (k *Kraken) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (k *Kraken) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { panic("") } @@ -270,7 +270,7 @@ func (k *Kraken) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return &dep, nil } -func (k *Kraken) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (k *Kraken) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { panic("") } diff --git a/kucoin/kucoin.go b/kucoin/kucoin.go index e9c726fd..d6833d78 100644 --- a/kucoin/kucoin.go +++ b/kucoin/kucoin.go @@ -9,7 +9,7 @@ import ( func New(apiKey, apiSecret, apiPassphrase string) *KuCoin { return NewWithConfig(&APIConfig{ - Endpoint: "https://api.kcs.top", + Endpoint: "https://api.kucoin.com", ApiKey: apiKey, ApiSecretKey: apiSecret, ApiPassphrase: apiPassphrase, @@ -46,7 +46,7 @@ type KuCoin struct { service *kucoin.ApiService } -var inernalKlinePeriodConverter = map[int]string{ +var inernalKlinePeriodConverter = map[KlinePeriod]string{ KLINE_PERIOD_1MIN: "1min", KLINE_PERIOD_3MIN: "3min", KLINE_PERIOD_5MIN: "5min", @@ -94,12 +94,12 @@ func (kc *KuCoin) GetTicker(currency CurrencyPair) (*Ticker, error) { func (kc *KuCoin) LimitBuy(amount, price string, currency CurrencyPair, opt ...LimitOrderOptionalParameter) (*Order, error) { clientID := GenerateOrderClientId(32) in := kucoin.CreateOrderModel{ - ClientOid: clientID, - Side: "buy", - Symbol: currency.ToSymbol("-"), - Type: "limit", - Price: price, - Size: amount, + ClientOid: clientID, + Side: "buy", + Symbol: currency.ToSymbol("-"), + Type: "limit", + Price: price, + Size: amount, } resp, err := kc.service.CreateOrder(&in) if err != nil { @@ -124,12 +124,12 @@ func (kc *KuCoin) LimitBuy(amount, price string, currency CurrencyPair, opt ...L func (kc *KuCoin) LimitSell(amount, price string, currency CurrencyPair, opt ...LimitOrderOptionalParameter) (*Order, error) { clientID := GenerateOrderClientId(32) in := kucoin.CreateOrderModel{ - ClientOid: clientID, - Side: "sell", - Symbol: currency.ToSymbol("-"), - Type: "limit", - Price: price, - Size: amount, + ClientOid: clientID, + Side: "sell", + Symbol: currency.ToSymbol("-"), + Type: "limit", + Price: price, + Size: amount, } resp, err := kc.service.CreateOrder(&in) if err != nil { @@ -154,12 +154,12 @@ func (kc *KuCoin) LimitSell(amount, price string, currency CurrencyPair, opt ... func (kc *KuCoin) MarketBuy(amount, price string, currency CurrencyPair) (*Order, error) { clientID := GenerateOrderClientId(32) in := kucoin.CreateOrderModel{ - ClientOid: clientID, - Side: "buy", - Symbol: currency.ToSymbol("-"), - Type: "market", - Price: price, - Size: amount, + ClientOid: clientID, + Side: "buy", + Symbol: currency.ToSymbol("-"), + Type: "market", + Price: price, + Size: amount, } resp, err := kc.service.CreateOrder(&in) @@ -185,12 +185,12 @@ func (kc *KuCoin) MarketBuy(amount, price string, currency CurrencyPair) (*Order func (kc *KuCoin) MarketSell(amount, price string, currency CurrencyPair) (*Order, error) { clientID := GenerateOrderClientId(32) in := kucoin.CreateOrderModel{ - ClientOid: clientID, - Side: "sell", - Symbol: currency.ToSymbol("-"), - Type: "market", - Price: price, - Size: amount, + ClientOid: clientID, + Side: "sell", + Symbol: currency.ToSymbol("-"), + Type: "market", + Price: price, + Size: amount, } resp, err := kc.service.CreateOrder(&in) if err != nil { @@ -314,15 +314,19 @@ func (kc *KuCoin) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return orders, nil } -func (kc *KuCoin) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (kc *KuCoin) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { params := map[string]string{ "status": "done", "symbol": currency.ToSymbol("-"), } - pagination := kucoin.PaginationParam{ - CurrentPage: int64(currentPage), - PageSize: int64(pageSize), + + pagination := kucoin.PaginationParam{} + + if len(optional) > 0 { + pagination.CurrentPage = ToInt64(optional[0]["currentPage"]) + pagination.PageSize = ToInt64(optional[0]["pageSize"]) } + resp, err := kc.service.Orders(params, &pagination) if err != nil { log.Error("KuCoin GetOrderHistorys error:", err) @@ -341,7 +345,34 @@ func (kc *KuCoin) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize } func (kc *KuCoin) GetAccount() (*Account, error) { - var account Account + accs, err := kc.Accounts("", "") + if err != nil { + log.Error("KuCoin GetAccount error:", err) + return nil, err + } + + account := Account{} + account.Exchange = kc.GetExchangeName() + account.SubAccounts = make(map[Currency]SubAccount) + + for _, v := range accs { + currency := NewCurrency(v.Currency, "").AdaptBccToBch() + // KuCoin同一币种可能有多种账户类型 + if sub, exist := account.SubAccounts[currency]; !exist { + account.SubAccounts[currency] = SubAccount{ + Currency: currency, + Amount: ToFloat64(v.Available), + ForzenAmount: ToFloat64(v.Holds), + } + } else { + account.SubAccounts[currency] = SubAccount{ + Currency: currency, + Amount: sub.Amount + ToFloat64(v.Available), + ForzenAmount: sub.ForzenAmount + ToFloat64(v.Holds), + } + } + + } return &account, nil } @@ -387,8 +418,9 @@ func (kc *KuCoin) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return &depth, nil } -func (kc *KuCoin) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - resp, err := kc.service.KLines(currency.ToSymbol("-"), inernalKlinePeriodConverter[period], int64(since), time.Now().UnixNano()/int64(time.Millisecond)) +func (kc *KuCoin) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { + resp, err := kc.service.KLines(currency.ToSymbol("-"), inernalKlinePeriodConverter[period], 0, 0) + if err != nil { log.Error("KuCoin GetKlineRecords error:", err) return nil, err diff --git a/kucoin/kucoin_test.go b/kucoin/kucoin_test.go index c538a706..ea419fdb 100644 --- a/kucoin/kucoin_test.go +++ b/kucoin/kucoin_test.go @@ -3,7 +3,6 @@ package kucoin import ( "github.com/nntaoli-project/goex" "testing" - "time" ) var kc = New("", "", "") @@ -19,7 +18,7 @@ func TestKuCoin_GetDepth(t *testing.T) { } func TestKuCoin_GetKlineRecords(t *testing.T) { - kLines, _ := kc.GetKlineRecords(goex.BTC_USDT, goex.KLINE_PERIOD_1MIN, 10, int(time.Now().Unix()-3600)) + kLines, _ := kc.GetKlineRecords(goex.BTC_USDT, goex.KLINE_PERIOD_1MIN, 10) t.Log(kLines) } @@ -28,3 +27,7 @@ func TestKuCoin_GetTrades(t *testing.T) { t.Log(trades) } +func TestKuCoin_GetAccount(t *testing.T) { + acc, _ := kc.GetAccount() + t.Log(acc) +} diff --git a/okex/OKEx.go b/okex/OKEx.go index 4256ef6b..194847b1 100644 --- a/okex/OKEx.go +++ b/okex/OKEx.go @@ -159,8 +159,8 @@ func (ok *OKEx) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return ok.OKExSpot.GetUnfinishOrders(currency) } -func (ok *OKEx) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - return ok.OKExSpot.GetOrderHistorys(currency, currentPage, pageSize) +func (ok *OKEx) GetOrderHistorys(currency CurrencyPair, opt ...OptionalParameter) ([]Order, error) { + return ok.OKExSpot.GetOrderHistorys(currency, opt...) } func (ok *OKEx) GetAccount() (*Account, error) { @@ -175,8 +175,8 @@ func (ok *OKEx) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return ok.OKExSpot.GetDepth(size, currency) } -func (ok *OKEx) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { - return ok.OKExSpot.GetKlineRecords(currency, period, size, since) +func (ok *OKEx) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { + return ok.OKExSpot.GetKlineRecords(currency, period, size, optional...) } func (ok *OKEx) GetTrades(currencyPair CurrencyPair, since int64) ([]Trade, error) { diff --git a/okex/OKExFuture.go b/okex/OKExFuture.go index 128bafa5..188efc78 100644 --- a/okex/OKExFuture.go +++ b/okex/OKExFuture.go @@ -3,6 +3,7 @@ package okex import ( "errors" "fmt" + "net/url" "sort" "sync" "time" @@ -532,6 +533,39 @@ func (ok *OKExFuture) GetFutureOrders(orderIds []string, currencyPair CurrencyPa panic("") } +func (ok *OKExFuture) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + urlPath := fmt.Sprintf("/api/futures/v3/orders/%s?", ok.GetFutureContractId(pair, contractType)) + + param := url.Values{} + param.Set("limit", "100") + param.Set("state", "7") + MergeOptionalParameter(¶m, optional...) + urlPath += param.Encode() + + var response struct { + Result bool + OrderInfo []futureOrderResponse `json:"order_info"` + } + + err := ok.DoRequest("GET", urlPath, "", &response) + if err != nil { + return nil, err + } + + if !response.Result { + return nil, errors.New(fmt.Sprintf("%v", response)) + } + + orders := make([]FutureOrder, 0, 100) + for _, info := range response.OrderInfo { + ord := ok.adaptOrder(info) + ord.Currency = pair + orders = append(orders, ord) + } + + return orders, nil +} + type futureOrderResponse struct { InstrumentId string `json:"instrument_id"` ClientOid string `json:"client_oid"` @@ -624,25 +658,16 @@ func (ok *OKExFuture) GetDeliveryTime() (int, int, int, int) { return 4, 16, 0, 0 //星期五,下午4点交割 } -/** - since : 单位秒,开始时间 -*/ -func (ok *OKExFuture) GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { - urlPath := "/api/futures/v3/instruments/%s/candles?start=%s&granularity=%d" +func (ok *OKExFuture) GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { + urlPath := "/api/futures/v3/instruments/%s/candles?granularity=%d" contractId := ok.GetFutureContractId(currency, contractType) - sinceTime := time.Unix(int64(since), 0).UTC() - - if since/int(time.Second) != 1 { //如果不为秒,转为秒 - sinceTime = time.Unix(int64(since)/int64(time.Second), 0).UTC() - } - granularity := adaptKLinePeriod(KlinePeriod(period)) if granularity == -1 { return nil, errors.New("kline period parameter is error") } var response [][]interface{} - err := ok.DoRequest("GET", fmt.Sprintf(urlPath, contractId, sinceTime.Format(time.RFC3339), granularity), "", &response) + err := ok.DoRequest("GET", fmt.Sprintf(urlPath, contractId, granularity), "", &response) if err != nil { return nil, err } diff --git a/okex/OKExSpot.go b/okex/OKExSpot.go index 60d813e7..d291503e 100644 --- a/okex/OKExSpot.go +++ b/okex/OKExSpot.go @@ -5,6 +5,7 @@ import ( "github.com/go-openapi/errors" . "github.com/nntaoli-project/goex" "github.com/nntaoli-project/goex/internal/logger" + "net/url" "sort" "strings" "time" @@ -324,8 +325,16 @@ func (ok *OKExSpot) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return ords, nil } -func (ok *OKExSpot) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { - urlPath := fmt.Sprintf("/api/spot/v3/orders?instrument_id=%s&state=7", currency.AdaptUsdToUsdt().ToSymbol("-")) +func (ok *OKExSpot) GetOrderHistorys(currency CurrencyPair, optional ...OptionalParameter) ([]Order, error) { + urlPath := "/api/spot/v3/orders" + + param := url.Values{} + param.Set("instrument_id", currency.AdaptUsdToUsdt().ToSymbol("-")) + param.Set("state", "7") + MergeOptionalParameter(¶m, optional...) + + urlPath += "?" + param.Encode() + var response []OrderResponse err := ok.OKEx.DoRequest("GET", urlPath, "", &response) if err != nil { @@ -414,16 +423,12 @@ func (ok *OKExSpot) GetDepth(size int, currency CurrencyPair) (*Depth, error) { return dep, nil } -func (ok *OKExSpot) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (ok *OKExSpot) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { urlPath := "/api/spot/v3/instruments/%s/candles?granularity=%d" - if since > 0 { - sinceTime := time.Unix(int64(since), 0).UTC() - if since/int(time.Second) != 1 { //如果不为秒,转为秒 - sinceTime = time.Unix(int64(since)/int64(time.Second), 0).UTC() - } - urlPath += "&start=" + sinceTime.Format(time.RFC3339) - } + optParam := url.Values{} + MergeOptionalParameter(&optParam, optional...) + urlPath += "&" + optParam.Encode() granularity := 60 switch period { diff --git a/okex/OKExSwap.go b/okex/OKExSwap.go index aeebe046..daebe7ef 100644 --- a/okex/OKExSwap.go +++ b/okex/OKExSwap.go @@ -362,6 +362,32 @@ func (ok *OKExSwap) FutureCancelOrder(currencyPair CurrencyPair, contractType, o return resp.Result, nil } +func (ok *OKExSwap) GetFutureOrderHistory(pair CurrencyPair, contractType string, optional ...OptionalParameter) ([]FutureOrder, error) { + urlPath := fmt.Sprintf("/api/swap/v3/orders/%s?", ok.adaptContractType(pair)) + + param := url.Values{} + param.Set("limit", "100") + param.Set("state", "7") + MergeOptionalParameter(¶m, optional...) + + var response SwapOrdersInfo + + err := ok.DoRequest("GET", urlPath+param.Encode(), "", &response) + if err != nil { + return nil, err + } + + orders := make([]FutureOrder, 0, 100) + for _, info := range response.OrderInfo { + ord := ok.parseOrder(info) + ord.Currency = pair + ord.ContractName = contractType + orders = append(orders, ord) + } + + return orders, nil +} + func (ok *OKExSwap) parseOrder(ord BaseOrderInfo) FutureOrder { oTime, _ := time.Parse(time.RFC3339, ord.Timestamp) return FutureOrder{ @@ -537,19 +563,50 @@ func (ok *OKExSwap) GetDeliveryTime() (int, int, int, int) { panic("not support") } -func (ok *OKExSwap) GetKlineRecords(contractType string, currency CurrencyPair, period, size, since int) ([]FutureKline, error) { - - sinceTime := time.Unix(int64(since), 0).UTC() - - if since/int(time.Second) != 1 { //如果不为秒,转为秒 - sinceTime = time.Unix(int64(since)/int64(time.Second), 0).UTC() +func (ok *OKExSwap) GetKlineRecords(contractType string, currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]FutureKline, error) { + granularity := adaptKLinePeriod(KlinePeriod(period)) + if granularity == -1 { + return nil, errors.New("kline period parameter is error") } + return ok.GetKlineRecords2(contractType, currency, "", "", strconv.Itoa(granularity)) +} +/** + since : 单位秒,开始时间 + to : 单位秒,结束时间 +*/ +func (ok *OKExSwap) GetKlineRecordsByRange(currency CurrencyPair, period, since, to int) ([]FutureKline, error) { + urlPath := "/api/swap/v3/instruments/%s/candles?start=%s&end=%s&granularity=%d" + sinceTime := time.Unix(int64(since), 0).UTC().Format(time.RFC3339) + toTime := time.Unix(int64(to), 0).UTC().Format(time.RFC3339) + contractId := ok.adaptContractType(currency) granularity := adaptKLinePeriod(KlinePeriod(period)) if granularity == -1 { return nil, errors.New("kline period parameter is error") } - return ok.GetKlineRecords2(contractType, currency, sinceTime.Format(time.RFC3339), "", strconv.Itoa(granularity)) + + var response [][]interface{} + err := ok.DoRequest("GET", fmt.Sprintf(urlPath, contractId, sinceTime, toTime, granularity), "", &response) + if err != nil { + return nil, err + } + + var klines []FutureKline + for _, itm := range response { + t, _ := time.Parse(time.RFC3339, fmt.Sprint(itm[0])) + klines = append(klines, FutureKline{ + Kline: &Kline{ + Timestamp: t.Unix(), + Pair: currency, + Open: ToFloat64(itm[1]), + High: ToFloat64(itm[2]), + Low: ToFloat64(itm[3]), + Close: ToFloat64(itm[4]), + Vol: ToFloat64(itm[5])}, + Vol2: ToFloat64(itm[6])}) + } + + return klines, nil } /** diff --git a/okex/OKEx_test.go b/okex/OKEx_test.go index ffff25f3..a41f67fe 100644 --- a/okex/OKEx_test.go +++ b/okex/OKEx_test.go @@ -15,20 +15,8 @@ func init() { // var config2 = &goex.APIConfig{ - Endpoint: "https://www.okex.me", - //HttpClient: &http.Client{ - // Transport: &http.Transport{ - // Proxy: func(req *http.Request) (*url.URL, error) { - // return &url.URL{ - // Scheme: "socks5", - // Host: "127.0.0.1:1080"}, nil - // }, - // }, - //}, //需要代理的这样配置 - HttpClient: http.DefaultClient, - ApiKey: "", - ApiSecretKey: "", - ApiPassphrase: "", + Endpoint: "https://www.okex.me", + HttpClient: http.DefaultClient, } var okex = NewOKEx(config2) //线上请用APIBuilder构建 @@ -245,5 +233,10 @@ func TestOKExSpot_GetCurrenciesPrecision(t *testing.T) { } func TestOKExSpot_GetOrderHistorys(t *testing.T) { - t.Log(okex.OKExSpot.GetOrderHistorys(goex.BTC_USDT, 1, 10)) + orders, err := okex.OKExSpot.GetOrderHistorys(goex.NewCurrencyPair2("DASH_USDT")) + if err != nil { + t.Log(err) + t.FailNow() + } + t.Log(len(orders)) } diff --git a/poloniex/Poloniex.go b/poloniex/Poloniex.go index a2b7200f..0581e464 100644 --- a/poloniex/Poloniex.go +++ b/poloniex/Poloniex.go @@ -115,7 +115,7 @@ func (poloniex *Poloniex) GetDepth(size int, currency CurrencyPair) (*Depth, err return &depth, nil } -func (Poloniex *Poloniex) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (poloniex *Poloniex) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, optional ...OptionalParameter) ([]Kline, error) { return nil, nil } @@ -336,7 +336,7 @@ func (poloniex *Poloniex) GetUnfinishOrders(currency CurrencyPair) ([]Order, err //log.Println(orders) return orders, nil } -func (Poloniex *Poloniex) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (poloniex *Poloniex) GetOrderHistorys(currency CurrencyPair, opt ...OptionalParameter) ([]Order, error) { return nil, nil } @@ -386,7 +386,7 @@ func (poloniex *Poloniex) GetAccount() (*Account, error) { return acc, nil } -func (p *Poloniex) Withdraw(amount string, currency Currency, fees, receiveAddr, safePwd string) (string, error) { +func (poloniex *Poloniex) Withdraw(amount string, currency Currency, fees, receiveAddr, safePwd string) (string, error) { if currency == BCC { currency = BCH } @@ -396,16 +396,16 @@ func (p *Poloniex) Withdraw(amount string, currency Currency, fees, receiveAddr, params.Add("amount", amount) params.Add("currency", strings.ToUpper(currency.String())) - sign, err := p.buildPostForm(¶ms) + sign, err := poloniex.buildPostForm(¶ms) if err != nil { return "", err } headers := map[string]string{ - "Key": p.accessKey, + "Key": poloniex.accessKey, "Sign": sign} - resp, err := HttpPostForm2(p.client, TRADE_API, params, headers) + resp, err := HttpPostForm2(poloniex.client, TRADE_API, params, headers) if err != nil { log.Println(err) diff --git a/websocket.go b/websocket.go index 9d674a96..9f1ade55 100644 --- a/websocket.go +++ b/websocket.go @@ -25,6 +25,7 @@ type WsConfig struct { ErrorHandleFunc func(err error) ConnectSuccessAfterSendMessage func() []byte //for reconnect IsDump bool + DisableEnableCompression bool readDeadLineTime time.Duration reconnectInterval time.Duration } @@ -99,6 +100,11 @@ func (b *WsBuilder) ProtoHandleFunc(f func([]byte) error) *WsBuilder { return b } +func (b *WsBuilder) DisableEnableCompression() *WsBuilder { + b.wsConfig.DisableEnableCompression = true + return b +} + func (b *WsBuilder) DecompressFunc(f func([]byte) ([]byte, error)) *WsBuilder { b.wsConfig.DecompressFunc = f return b @@ -160,6 +166,10 @@ func (ws *WsConn) connect() error { } } + if ws.DisableEnableCompression { + dialer.EnableCompression = false + } + wsConn, resp, err := dialer.Dial(ws.WsUrl, http.Header(ws.ReqHeaders)) if err != nil { Log.Errorf("[ws][%s] %s", ws.WsUrl, err.Error()) @@ -264,6 +274,7 @@ func (ws *WsConn) Subscribe(subEvent interface{}) error { Log.Errorf("[ws][%s] json encode error , %s", ws.WsUrl, err) return err } + Log.Debug(string(data)) ws.writeBufferChan <- data ws.subs = append(ws.subs, data) return nil @@ -310,6 +321,7 @@ func (ws *WsConn) receiveMessage() { ws.c.SetPingHandler(func(ping string) error { Log.Debugf("[%s] received [ping] %s", ws.WsUrl, ping) + ws.SendPongMessage([]byte(ping)) ws.c.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) return nil }) diff --git a/zb/Zb.go b/zb/Zb.go index 28de51ea..0816a56c 100644 --- a/zb/Zb.go +++ b/zb/Zb.go @@ -371,11 +371,11 @@ func (zb *Zb) GetUnfinishOrders(currency CurrencyPair) ([]Order, error) { return orders, nil } -func (zb *Zb) GetOrderHistorys(currency CurrencyPair, currentPage, pageSize int) ([]Order, error) { +func (zb *Zb) GetOrderHistorys(currency CurrencyPair, opt ...OptionalParameter) ([]Order, error) { return nil, nil } -func (zb *Zb) GetKlineRecords(currency CurrencyPair, period, size, since int) ([]Kline, error) { +func (zb *Zb) GetKlineRecords(currency CurrencyPair, period KlinePeriod, size int, opt ...OptionalParameter) ([]Kline, error) { return nil, nil }