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
|
||
|
||
}
|