Files

300 lines
8.1 KiB
Go
Raw Permalink Normal View History

2025-02-06 11:14:33 +08:00
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
}