package excservice import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "net/url" "strconv" "strings" "time" "go.uber.org/zap" "go-admin/common/global" "go-admin/common/helper" "go-admin/models" "go-admin/pkg/httputils" "go-admin/pkg/utility" "github.com/bytedance/sonic" log "github.com/go-admin-team/go-admin-core/logger" ) const ( TICKER_URI = "/api/v3/ticker/24hr?symbol=%s" TICKERS_URI = "ticker/allBookTickers" DEPTH_URI = "/api/v3/depth?symbol=%s&limit=%d" ACCOUNT_URI = "/api/v3/account?" ORDER_URI = "/api/v3/order" UNFINISHED_ORDERS_INFO = "openOrders?" KLINE_URI = "klines" SERVER_TIME_URL = "/api/v3/time" ) var ( apiUrl = "https://api.binance.com" //如果上面的baseURL访问有性能问题,请访问下面的API集群: //https://api1.binance.com //https://api2.binance.com //https://api3.binance.com ) func init() { //err := setTimeOffsetPro() //if err != nil { // fmt.Println("setTimeOffsetPro,err:", err) //} } func buildParamsSigned(postForm *url.Values, secretKey string) { postForm.Set("recvWindow", "60000") tonce := strconv.FormatInt(time.Now().UnixNano()+timeOffset, 10)[0:13] postForm.Set("timestamp", tonce) payload := postForm.Encode() sign, _ := GetHmacSHA256Sign(secretKey, payload) postForm.Set("signature", sign) } func GetHmacSHA256Sign(secret, params string) (string, error) { mac := hmac.New(sha256.New, []byte(secret)) _, err := mac.Write([]byte(params)) if err != nil { return "", err } return hex.EncodeToString(mac.Sum(nil)), nil } // 获取服务器时间 func setTimeOffsetPro() error { respData, err := httputils.NewHttpRequestWithFasthttp("GET", apiUrl+SERVER_TIME_URL, "", nil) if err != nil { return err } var bodyDataMap map[string]interface{} err = json.Unmarshal(respData, &bodyDataMap) if err != nil { log.Error(string(respData)) return err } stime := int64(utility.ToInt(bodyDataMap["serverTime"])) st := time.Unix(stime/1000, 1000000*(stime%1000)) lt := time.Now() offset := st.Sub(lt).Nanoseconds() timeOffset = offset return nil } // GetTicker 获取24小时行情 func GetTicker(coin, currency string) (models.Ticker24, error) { par := strings.ToUpper(coin + currency) tickerUri := apiUrl + fmt.Sprintf(TICKER_URI, par) var ticker models.Ticker24 respData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerUri, "", nil) if err != nil { log.Error("GetTicker", zap.Error(err)) return ticker, err } var tickerMap map[string]interface{} err = json.Unmarshal(respData, &tickerMap) if err != nil { log.Error("GetTicker", zap.ByteString("respData", respData), zap.Error(err)) return ticker, err } ticker.LastPrice = tickerMap["lastPrice"].(string) ticker.LowPrice = tickerMap["lowPrice"].(string) ticker.HighPrice = tickerMap["highPrice"].(string) ticker.Volume = tickerMap["volume"].(string) ticker.QuoteVolume = tickerMap["quoteVolume"].(string) ticker.ChangePercent = tickerMap["priceChangePercent"].(string) ticker.OpenPrice = tickerMap["openPrice"].(string) return ticker, nil } // GetTickerBySymbols 获取24小时行情 symbols symbols参数可接受的格式: ["BTCUSDT","BNBUSDT"] func GetTickerBySymbols(symbols string) ([]models.Ticker24, error) { tickerUri := apiUrl + "/api/v3/ticker/24hr" respData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerUri, "", nil) if err != nil { log.Error("GetTicker", zap.Error(err)) return nil, err } var tickerList []interface{} err = json.Unmarshal(respData, &tickerList) if err != nil { log.Error("GetTickerBySymbols", zap.ByteString("respData", respData), zap.Error(err)) return nil, err } list := make([]models.Ticker24, 0, len(tickerList)) for _, t := range tickerList { tickerMap := t.(map[string]interface{}) if tickerMap == nil { continue } var ticker models.Ticker24 ticker.LastPrice = tickerMap["lastPrice"].(string) ticker.LowPrice = tickerMap["lowPrice"].(string) ticker.HighPrice = tickerMap["highPrice"].(string) ticker.Volume = tickerMap["volume"].(string) ticker.QuoteVolume = tickerMap["quoteVolume"].(string) ticker.ChangePercent = tickerMap["priceChangePercent"].(string) ticker.OpenPrice = tickerMap["openPrice"].(string) ticker.Symbol = tickerMap["symbol"].(string) list = append(list, ticker) } return list, nil } // GetKlinePro 获取k线--现货行情接口 func GetKlinePro(coin, currency string, period string, size int) ([]models.Kline, error) { par := strings.ToUpper(coin + currency) periodS := period //, isOk := INERNAL_KLINE_PERIOD_CONVERTER[period] //if isOk != true { // periodS = "M1" //} key := fmt.Sprintf("%s:%s:%s", global.K_SPOT, par, period) //获取缓存 klineStrs, err := helper.DefaultRedis.GetAllSortSet(key) if err != nil { return nil, err } if len(klineStrs) > 0 && len(klineStrs) >= 500 { klines := make([]models.Kline, 0) for _, item := range klineStrs { var kline models.Kline err := sonic.Unmarshal([]byte(item), &kline) if err == nil { klines = append(klines, kline) } } return klines, nil } //没有缓存 重新获取 urlKline := apiUrl + "/api/v3/klines?symbol=" + par + "&interval=" + periodS + "&limit=" + utility.IntTostring(size) respData, err := httputils.NewHttpRequestWithFasthttp("GET", urlKline, "", nil) if err != nil { return nil, err } var bodyDataMap []interface{} err = json.Unmarshal(respData, &bodyDataMap) if err != nil { log.Error("GetKlinePro", zap.ByteString("respData", respData), zap.Error(err)) return nil, err } var klines []models.Kline for _, _record := range bodyDataMap { r := models.Kline{} record := _record.([]interface{}) times := utility.ToFloat64(record[0]) //to unix timestramp // 超出10位的 处理为 if times > 9999999999 { r.Timestamp = int64(times) / 1000 } else { r.Timestamp = int64(times) } r.Open = record[1].(string) r.High = record[2].(string) r.Low = record[3].(string) r.Close = record[4].(string) r.Vol = record[5].(string) r.QuoteVolume = record[7].(string) klines = append(klines, r) member, err := sonic.Marshal(r) if err == nil { err = helper.DefaultRedis.SignelAdd(key, float64(r.Timestamp), string(member)) if err != nil { log.Error("保存k线数据失败:", key, err) } } } return klines, nil } // GetTrades 非个人,整个交易所的交易记录 // 注意:since is fromId func GetTrades(coin, currency string) ([]models.NewDealPush, error) { param := url.Values{} param.Set("symbol", strings.ToUpper(coin+currency)) param.Set("limit", "50") //if since > 0 { // param.Set("fromId", strconv.Itoa(int(since))) //} urlTrade := apiUrl + "/api/v3/trades?" + param.Encode() resp, err := httputils.NewHttpRequestWithFasthttp("GET", urlTrade, "", nil) if err != nil { return nil, err } var bodyDataMap []interface{} err = json.Unmarshal(resp, &bodyDataMap) if err != nil { log.Error("GetTrades", zap.ByteString("respData", resp), zap.Error(err)) return nil, err } var trades []models.NewDealPush for _, v := range bodyDataMap { m := v.(map[string]interface{}) ty := 2 if m["isBuyerMaker"].(bool) { ty = 1 } trades = append(trades, models.NewDealPush{ DealId: utility.ToInt64(m["id"]), Type: ty, Num: utility.ToFloat64(m["qty"]), Price: utility.ToFloat64(m["price"]), CreateTime: utility.ToInt64(m["time"]), }) } return trades, nil } // GetDepth 获取深度 func GetDepth(size int, coin, currency string) (models.DepthBin, error) { if size <= 5 { size = 5 } else if size <= 10 { size = 10 } else if size <= 20 { size = 20 } else if size <= 50 { size = 50 } else if size <= 100 { size = 100 } else if size <= 500 { size = 500 } else { size = 1000 } urlDep := fmt.Sprintf(apiUrl+DEPTH_URI, strings.ToUpper(coin+currency), size) respFive, err := httputils.NewHttpRequestWithFasthttp("GET", urlDep, "", nil) if err != nil { return models.DepthBin{}, err } d := models.DepthBin{} err = sonic.Unmarshal(respFive, &d) if err != nil { fmt.Println("GetDepth json unmarshal error for ", string(respFive), zap.Error(err)) return models.DepthBin{}, err } return d, nil }