300 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			300 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								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
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								}
							 |