2025-02-06 11:14:33 +08:00
package binanceservice
import (
"context"
"errors"
"fmt"
DbModels "go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
"go-admin/models"
"go-admin/models/futuresdto"
"go-admin/pkg/httputils"
"go-admin/pkg/utility"
"strconv"
"strings"
"time"
"github.com/shopspring/decimal"
"gorm.io/gorm"
"github.com/bytedance/sonic"
"github.com/go-redis/redis/v8"
"github.com/go-admin-team/go-admin-core/logger"
log "github.com/go-admin-team/go-admin-core/logger"
)
const (
futureApi = "https://fapi.binance.com"
)
type FutRestApi struct {
}
var FutErrorMaps = map [ float64 ] string {
- 2021 : "订单已拒绝。请调整触发价并重新下订单。 对于买入/做多,止盈订单触发价应低于市场价,止损订单的触发价应高于市场价。卖出/做空则与之相反" ,
- 4164 : "少于最小下单金额" ,
- 4061 : "持仓方向需要设置为单向持仓。" ,
- 2019 : "保证金不足" ,
- 1111 : "金额设置错误。精度错误" ,
- 1021 : "请求的时间戳在recvWindow之外" ,
- 1106 : "发送了不需要的参数。" ,
- 1109 : "非有效账户" ,
- 1110 : "交易对不正确" ,
- 1112 : "交易对没有挂单" ,
- 1114 : "发送的TimeInForce参数不需要。" ,
- 1115 : "无效的timeInForce" ,
- 1116 : "无效订单类型。" ,
- 1117 : "无效买卖方向。" ,
- 1121 : "无效的交易对。" ,
- 2010 : "新订单被拒绝" ,
- 2011 : "取消订单被拒绝" ,
- 2012 : "批量取消失败" ,
- 2013 : "订单不存在。" ,
- 2014 : "API-key 格式无效。" ,
- 2015 : "无效的API密钥, IP或操作权限。" ,
- 2018 : "余额不足" ,
- 2020 : "无法成交" ,
- 2022 : "ReduceOnly订单被拒绝" ,
- 2023 : "用户正处于被强平模式" ,
- 2024 : "持仓不足" ,
- 2025 : "挂单量达到上限" ,
- 4001 : "价格小于0" ,
- 4002 : "价格超过最大值" ,
- 4003 : "数量小于0" ,
- 4004 : "数量小于最小值" ,
- 4005 : "数量大于最大值" ,
- 4008 : "价格精度小于0" ,
- 4009 : "最大价格小于最小价格" ,
- 4029 : "价格精度小数点位数不正确。" ,
- 4031 : "不正确的参数类型。" ,
- 4047 : "如果有挂单,仓位模式不能切换。" ,
- 4048 : "如果有仓位,仓位模式不能切换。" ,
- 4059 : "无需变更仓位方向" ,
- 4117 : "stop订单在触发中" ,
}
var reduceOnlyRate = 0.95
/ *
获取资金费率
* /
func GetPremiumIndex ( ) ( [ ] futuresdto . FundingInfo , error ) {
url := fmt . Sprintf ( "%s%s" , futureApi , "/fapi/v1/premiumIndex" )
mapData , err := httputils . NewHttpRequestWithFasthttp ( "GET" , url , "" , map [ string ] string { } )
if err != nil {
return nil , err
}
if len ( mapData ) == 0 {
return nil , errors . New ( "获取交易对失败,或数量为空" )
}
var res [ ] futuresdto . FundingInfo
err = sonic . Unmarshal ( [ ] byte ( mapData ) , & res )
if err != nil {
return nil , err
}
for _ , item := range res {
key := fmt . Sprintf ( "%s:%s" , global . FUNDING_INFO_FUTURES , item . Symbol )
if item . NextFundingTime == 0 {
helper . DefaultRedis . DeleteString ( key )
continue
}
val , err := sonic . Marshal ( item )
if err != nil {
log . Error ( "序列化失败:" , item . Symbol , err )
continue
}
err = helper . DefaultRedis . SetString ( key , string ( val ) )
if err != nil {
log . Error ( "保存资金费率失败:" , item . Symbol , err )
continue
}
}
return nil , nil
}
/ *
获取指定交易对资金费率
- @ symbol 交易对名称
- @ data 结果
* /
func GetFundingInfoBySymbol ( symbol string , data * futuresdto . FundingInfo ) error {
key := fmt . Sprintf ( "%s:%s" , global . FUNDING_INFO_FUTURES , strings . ToUpper ( symbol ) )
resData , err := helper . DefaultRedis . GetString ( key )
if err != nil {
return err
}
if resData == "" {
shouldReturn , err := GetAndReloadSymbol ( symbol , data )
if shouldReturn {
return err
}
}
err = sonic . Unmarshal ( [ ] byte ( resData ) , data )
if err != nil {
return err
}
if data . NextFundingTime <= time . Now ( ) . Unix ( ) {
shouldReturn , err := GetAndReloadSymbol ( symbol , data )
if shouldReturn {
return err
}
}
return nil
}
// 获取并更新费率
func GetAndReloadSymbol ( symbol string , data * futuresdto . FundingInfo ) ( bool , error ) {
datas , err := GetPremiumIndex ( )
if err != nil {
return true , err
}
for _ , item := range datas {
if item . Symbol == strings . ToUpper ( symbol ) {
* data = item
return true , nil
}
}
return false , nil
}
// 获取合约交易对信息
func GetSymbols ( data * futuresdto . FutExchangeInfo ) error {
url := fmt . Sprintf ( "%s%s" , futureApi , "/fapi/v1/exchangeInfo" )
mapData , err := httputils . NewHttpRequestWithFasthttp ( "GET" , url , "" , map [ string ] string { } )
if err != nil {
return err
}
if len ( mapData ) == 0 {
return errors . New ( "获取合约交易对失败,或数量为空" )
}
err = sonic . Unmarshal ( mapData , data )
if err != nil {
return err
}
return nil
}
// 获取并更新交易对
func GetAndReloadSymbols ( data * map [ string ] models . TradeSet ) error {
var info futuresdto . FutExchangeInfo
err := GetSymbols ( & info )
if err != nil {
return err
}
maps := make ( map [ string ] string , 0 )
if len ( info . Symbols ) > 0 {
for _ , item := range info . Symbols {
if item . Status == "TRADING" && strings . HasSuffix ( item . Symbol , "USDT" ) {
tradeSet := models . TradeSet { }
tradeSet . Coin = item . BaseAsset
tradeSet . Currency = item . QuoteAsset
for _ , filter := range item . Filters {
switch filter . FilterType {
case "PRICE_FILTER" :
tradeSet . PriceDigit = utility . GetPrecision ( * filter . TickSize )
tradeSet . MinBuyVal = utility . StringAsFloat ( * filter . MinPrice )
case "LOT_SIZE" :
tradeSet . AmountDigit = utility . GetPrecision ( * filter . StepSize )
case "MIN_NOTIONAL" :
tradeSet . MinNotional = * filter . Notional
case "MAX_NOTIONAL" :
tradeSet . MaxNotional = * filter . Notional
}
}
( * data ) [ item . Symbol ] = tradeSet
key := fmt . Sprintf ( "%s:%s" , global . TICKER_FUTURES , item . Symbol )
val , err := sonic . Marshal ( tradeSet )
if err != nil {
log . Error ( "合约交易对序列化失败" , err )
} else {
maps [ key ] = string ( val )
}
}
}
}
if len ( maps ) > 0 {
if err = helper . DefaultRedis . BatchSet ( & maps ) ; err != nil {
return err
}
}
return nil
}
func ( e FutRestApi ) Ticker ( ) {
url := fmt . Sprintf ( "%s%s" , futureApi , "/fapi/v1/ticker/price" )
mapData , err := httputils . NewHttpRequestWithFasthttp ( "GET" , url , "" , map [ string ] string { } )
if err != nil {
log . Error ( "获取合约交易对行情失败:err:" , err )
}
if len ( mapData ) == 0 {
log . Error ( "获取合约交易对行情失败,或数量为空" )
}
helper . DefaultRedis . SetString ( rediskey . FutSymbolTicker , string ( mapData ) )
}
// 获取合约行情
func GetTicker24h ( ) ( [ ] futuresdto . FutureTicker24h , error ) {
url := fmt . Sprintf ( "%s%s" , futureApi , "/fapi/v1/ticker/24hr" )
mapData , err := httputils . NewHttpRequestWithFasthttp ( "GET" , url , "" , map [ string ] string { } )
if err != nil {
return nil , err
}
if len ( mapData ) == 0 {
return nil , errors . New ( "获取合约交易对24h行情失败, 或数量为空" )
}
var data [ ] futuresdto . FutureTicker24h
err = sonic . Unmarshal ( mapData , & data )
if err != nil {
return nil , err
}
return data , err
}
// 初始化交易对行情
func InitSymbolsTicker24h ( maps * map [ string ] models . TradeSet ) ( deletes [ ] string , err error ) {
tickers , err := GetTicker24h ( )
if err != nil {
return [ ] string { } , err
}
deleteSymbol := make ( [ ] string , 0 )
caches := make ( map [ string ] string , 0 )
priceChange := make ( [ ] * redis . Z , 0 )
for _ , item := range tickers {
symbol , exits := ( * maps ) [ item . Symbol ]
if ! exits {
continue
}
symbol . OpenPrice = utility . StringAsFloat ( item . OpenPrice )
symbol . PriceChange = utility . StringAsFloat ( item . PriceChangePercent )
symbol . LowPrice = item . LowPrice
symbol . HighPrice = item . HighPrice
symbol . Volume = item . Volume
symbol . QuoteVolume = item . QuoteVolume
symbol . LastPrice = item . LastPrice
2025-02-08 14:05:57 +08:00
key := fmt . Sprintf ( global . TICKER_FUTURES , global . EXCHANGE_BINANCE , item . Symbol )
2025-02-06 11:14:33 +08:00
if ! strings . HasSuffix ( item . Symbol , symbol . Currency ) || item . Count <= 0 || utility . StringToFloat64 ( item . QuoteVolume ) <= 0 {
helper . DefaultRedis . DeleteString ( key )
deleteSymbol = append ( deleteSymbol , item . Symbol )
continue
}
val , err := sonic . Marshal ( symbol )
if err != nil {
log . Error ( "设置行情序列化报错" , err )
}
2025-02-08 14:05:57 +08:00
tcKey := fmt . Sprintf ( global . TICKER_FUTURES , global . EXCHANGE_BINANCE , item . Symbol )
2025-02-06 11:14:33 +08:00
caches [ tcKey ] = string ( val )
priceChange = append ( priceChange , & redis . Z {
Score : symbol . PriceChange ,
Member : symbol . Coin + symbol . Currency ,
} )
}
if len ( caches ) > 0 {
err = helper . DefaultRedis . BatchSet ( & caches )
if err != nil {
log . Error ( "批量行情保存失败" , err )
return deleteSymbol , err
}
err = helper . DefaultRedis . BatchSortSet ( global . UFUTURES_PRICE_CHANGE , priceChange )
if err != nil {
log . Error ( "批量涨跌幅保存失败" , err )
return deleteSymbol , err
}
}
return deleteSymbol , nil
}
// 获取合约 24h行情
func GetTicker ( coin string , data * futuresdto . MarketResp ) error {
key := fmt . Sprintf ( "%s:%sUSDT" , global . TICKER_FUTURES , coin )
cache , _ := helper . DefaultRedis . GetString ( key )
if cache == "" {
} else {
var trade models . TradeSet
sonic . Unmarshal ( [ ] byte ( cache ) , & trade )
if trade . Coin != "" {
data . Coin = trade . Coin
data . HighPrice = trade . HighPrice
data . LowPrice = trade . LowPrice
data . NewPrice = trade . LastPrice
data . Ratio = utility . Float64ToString ( trade . PriceChange , - 1 )
data . AmountDigit = trade . AmountDigit
data . DealCoin = trade . Volume
data . DealAmt = trade . QuoteVolume
}
fiKey := fmt . Sprintf ( "%s:%sUSDT" , global . FUNDING_INFO_FUTURES , coin )
fi , _ := helper . DefaultRedis . GetString ( fiKey )
if fi != "" {
var fundingInfo futuresdto . FundingInfo
sonic . Unmarshal ( [ ] byte ( fi ) , & fundingInfo )
if fundingInfo . NextFundingTime > 0 {
data . FundRate = strconv . FormatFloat ( utility . ToFloat64 ( fundingInfo . LastFundingRate ) * 100 , 'f' , - 1 , 64 )
data . MarkPrice = utility . StringFloat64Cut ( fundingInfo . MarkPrice , int32 ( trade . PriceDigit ) )
data . IndexPrice = utility . StringFloat64Cut ( fundingInfo . IndexPrice , int32 ( trade . PriceDigit ) )
if fundingInfo . NextFundingTime > 999999999999 {
data . NextFundingSec = int ( fundingInfo . NextFundingTime / 1000 )
} else {
data . NextFundingSec = int ( fundingInfo . NextFundingTime )
}
}
}
}
return nil
}
// CheckKlineType 检查k线合法性
func CheckKlineType ( kLineType string ) bool {
for _ , v := range [ ] string { "1m" , "3m" , "5m" , "15m" , "30m" , "1h" , "2h" , "4h" , "6h" , "8h" , "12h" , "1d" , "3d" , "1w" , "1M" } {
if v == kLineType {
return true
}
}
return false
}
// OrderPlace 合约下单
func ( e FutRestApi ) OrderPlace ( orm * gorm . DB , params FutOrderPlace ) error {
if orm == nil {
return errors . New ( "数据库实例为空" )
}
err2 := params . CheckParams ( )
if err2 != nil {
return err2
}
var paramsMaps map [ string ] string
paramsMaps = map [ string ] string {
"symbol" : params . Symbol ,
"side" : params . Side ,
"quantity" : params . Quantity . String ( ) ,
"type" : strings . ToUpper ( params . OrderType ) ,
"newClientOrderId" : params . NewClientOrderId ,
}
if strings . ToUpper ( params . OrderType ) != "MARKET" { //不是市价
if strings . ToUpper ( params . OrderType ) == "LIMIT" {
paramsMaps [ "price" ] = params . Price . String ( )
paramsMaps [ "timeInForce" ] = "GTC"
}
if strings . ToUpper ( params . OrderType ) == "TAKE_PROFIT_MARKET" {
paramsMaps [ "timeInForce" ] = "GTC"
paramsMaps [ "stopprice" ] = params . Profit . String ( )
paramsMaps [ "workingType" ] = "MARK_PRICE"
}
if strings . ToUpper ( params . OrderType ) == "STOP_MARKET" {
paramsMaps [ "stopprice" ] = params . StopPrice . String ( )
paramsMaps [ "workingType" ] = "MARK_PRICE"
paramsMaps [ "timeInForce" ] = "GTC"
}
2025-02-10 17:55:34 +08:00
if strings . ToUpper ( params . OrderType ) == "STOP" {
paramsMaps [ "price" ] = params . Price . String ( )
paramsMaps [ "stopprice" ] = params . StopPrice . String ( )
paramsMaps [ "workingType" ] = "MARK_PRICE"
paramsMaps [ "timeInForce" ] = "GTC"
}
2025-02-06 11:14:33 +08:00
}
2025-02-10 17:55:34 +08:00
if strings . ToUpper ( params . OrderType ) != "STOP_MARKET" && strings . ToUpper ( params . OrderType ) != "TAKE_PROFIT_MARKET" && strings . ToUpper ( params . OrderType ) != "STOP" && strings . ToUpper ( params . OrderType ) != "TAKE_PROFIT" {
2025-02-06 11:14:33 +08:00
if strings . ToUpper ( params . Side ) == "BUY" {
paramsMaps [ "positionSide" ] = "LONG"
} else {
paramsMaps [ "positionSide" ] = "SHORT"
}
} else {
if strings . ToUpper ( params . Side ) == "BUY" {
paramsMaps [ "positionSide" ] = "SHORT"
} else {
paramsMaps [ "positionSide" ] = "LONG"
}
}
var apiUserInfo DbModels . LineApiUser
err := orm . Model ( & DbModels . LineApiUser { } ) . Where ( "id = ?" , params . ApiId ) . Find ( & apiUserInfo ) . Error
if err != nil {
return err
}
var client * helper . BinanceClient
if apiUserInfo . UserPass == "" {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "" , apiUserInfo . IpAddress )
} else {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "socks5" , apiUserInfo . UserPass + "@" + apiUserInfo . IpAddress )
}
_ , statusCode , err := client . SendFuturesRequestAuth ( "/fapi/v1/order" , "POST" , paramsMaps )
if err != nil {
var dataMap map [ string ] interface { }
if err2 := sonic . Unmarshal ( [ ] byte ( err . Error ( ) ) , & dataMap ) ; err2 != nil {
return fmt . Errorf ( "api_id:%d 交易对:%s statusCode:%v 下单失败:%s" , apiUserInfo . Id , params . Symbol , statusCode , err . Error ( ) )
}
code , ok := dataMap [ "code" ]
if ok {
paramsVal , _ := sonic . MarshalString ( & paramsMaps )
log . Error ( "下单失败 参数:" , paramsVal )
errContent := FutErrorMaps [ code . ( float64 ) ]
if errContent == "" {
errContent = err . Error ( )
}
return fmt . Errorf ( "api_id:%d 交易对:%s 下单失败:%s" , apiUserInfo . Id , params . Symbol , errContent )
}
if strings . Contains ( err . Error ( ) , "Margin is insufficient." ) {
return fmt . Errorf ( "api_id:%d 交易对:%s 下单失败:%s" , apiUserInfo . Id , params . Symbol , FutErrorMaps [ - 2019 ] )
}
return fmt . Errorf ( "api_id:%d 交易对:%s 下单失败:%s" , apiUserInfo . Id , params . Symbol , err . Error ( ) )
}
return nil
}
// ClosePositionB 平仓B对应的交易对
// bApiUserInfo B 账户api-user信息
// symbol 需要平仓的交易对
// closeType 平仓模式 ALL = 全平 reduceOnly = 只减仓
// func (e FutRestApi) ClosePositionB(orm *gorm.DB, bApiUserInfo *DbModels.LineApiUser, symbol string, closeType string) error {
// if bApiUserInfo == nil {
// return errors.New("缺失平仓账户信息")
// }
// if symbol == "" {
// return errors.New("缺失平仓交易对信息")
// }
// risks, err := e.GetPositionV3(bApiUserInfo, symbol)
// if err != nil {
// return err
// }
// for _, risk := range risks {
// if risk.Symbol == strings.ToUpper(symbol) {
// //持仓数量
// positionAmt, _ := decimal.NewFromString(risk.PositionAmt)
// side := "BUY"
// if positionAmt.GreaterThan(decimal.Zero) {
// side = "SELL"
// }
// var closeAmt decimal.Decimal
// if strings.ToUpper(closeType) == "ALL" { //全部平仓
// closeAmt = positionAmt
// } else {
// //如果是只减仓的话 数量=仓位数量*比例
// closeAmt = positionAmt.Mul(decimal.NewFromFloat(reduceOnlyRate))
// }
// err = e.ClosePosition(symbol, closeAmt, side, *bApiUserInfo, "MARKET", "", decimal.Zero)
// if err != nil {
// return err
// }
// orm.Model(&DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ?", bApiUserInfo.Id, symbol).Updates(map[string]interface{}{
// "status": "6",
// })
// }
// }
// return nil
// }
// 获取合约 持仓价格、数量
// symbol:交易对
// side:方向
// holdeData:持仓数据
func ( e FutRestApi ) GetHoldeData ( apiInfo * DbModels . LineApiUser , symbol , side string , holdeData * HoldeData ) error {
var holdes [ ] PositionRisk
var err error
for x := 0 ; x < 3 ; x ++ {
holdes , err = getSymbolHolde ( e , apiInfo , symbol , side , holdeData )
if err != nil {
return err
}
if len ( holdes ) == 0 {
logger . Error ( "获取持仓信息,未查询到持仓信息" )
time . Sleep ( time . Second * 1 )
} else {
break
}
}
//side=SELL&positionSide=LONG是平多,
//side=SELL&positionSide=SHORT是开空,
for _ , item := range holdes {
positionAmount , _ := decimal . NewFromString ( item . PositionAmt )
if side == "BUY" && ( item . PositionSide == "BOTH" || item . PositionSide == "LONG" ) { //多
holdeData . AveragePrice , _ = decimal . NewFromString ( item . EntryPrice )
holdeData . TotalQuantity = positionAmount . Abs ( )
} else if side == "SELL" && ( item . PositionSide == "BOTH" || item . PositionSide == "SHORT" ) { //空
holdeData . AveragePrice , _ = decimal . NewFromString ( item . EntryPrice )
holdeData . TotalQuantity = positionAmount . Abs ( )
}
}
if holdeData . AveragePrice . Cmp ( decimal . Zero ) == 0 {
holdesVal , _ := sonic . MarshalString ( & holdes )
log . Error ( "均价错误 symbol:" , symbol , " 数据:" , holdesVal )
}
return nil
}
// 获取代币持仓信息
func getSymbolHolde ( e FutRestApi , apiInfo * DbModels . LineApiUser , symbol string , side string , holdeData * HoldeData ) ( [ ] PositionRisk , error ) {
holdes , err := e . GetPositionV3 ( apiInfo , symbol )
if err != nil {
log . Error ( "订单回调-获取合约持仓信息失败:" , err )
if strings . Contains ( err . Error ( ) , " EOF" ) || strings . Contains ( err . Error ( ) , "无效的API密钥, IP或操作权限" ) {
return nil , e . GetHoldeData ( apiInfo , symbol , side , holdeData )
} else {
return nil , err
}
}
return holdes , nil
}
func ( e FutRestApi ) GetPositionV3 ( apiUserInfo * DbModels . LineApiUser , symbol string ) ( [ ] PositionRisk , error ) {
var client * helper . BinanceClient
if apiUserInfo . UserPass == "" {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "" , apiUserInfo . IpAddress )
} else {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "socks5" , apiUserInfo . UserPass + "@" + apiUserInfo . IpAddress )
}
params := map [ string ] string {
"symbol" : symbol ,
"recvWindow" : "5000" ,
}
resp , _ , err := client . SendFuturesRequestAuth ( "/fapi/v3/positionRisk" , "GET" , params )
if err != nil {
var dataMap map [ string ] interface { }
if err2 := sonic . Unmarshal ( [ ] byte ( err . Error ( ) ) , & dataMap ) ; err2 != nil {
return [ ] PositionRisk { } , fmt . Errorf ( "api_id:%d 交易对:%s 获取仓位:%s" , apiUserInfo . Id , symbol , err . Error ( ) )
}
code , ok := dataMap [ "code" ]
if ok {
errContent := FutErrorMaps [ code . ( float64 ) ]
if errContent == "" {
errContent = err . Error ( )
}
return [ ] PositionRisk { } , fmt . Errorf ( "api_id:%d 交易对:%s 获取仓位:%s" , apiUserInfo . Id , symbol , errContent )
}
}
risks := make ( [ ] PositionRisk , 0 )
err = sonic . Unmarshal ( resp , & risks )
if err != nil {
return [ ] PositionRisk { } , fmt . Errorf ( "api_id:%d 交易对:%s 解析用户持仓信息失败:%s" , apiUserInfo . Id , symbol , err . Error ( ) )
}
return risks , nil
}
// ClosePosition 合约平仓
// symbol 交易对
// orderSn 系统订单号
// quantity 平仓数量
// side 仓位方向 做多==BUY 做空 == SELL
// apiUserInfo 用户信息
// orderType 平仓是限价平 还是市价平 MARKET = 市价 LIMIT = 限价
// rate 限价平的价格比例 没有除100 的百分比
func ( e FutRestApi ) ClosePosition ( symbol string , orderSn string , quantity decimal . Decimal , side string , positionSide string ,
apiUserInfo DbModels . LineApiUser , orderType string , rate string , price decimal . Decimal ) error {
endpoint := "/fapi/v1/order"
params := map [ string ] string {
"symbol" : symbol ,
"side" : side ,
"positionSide" : positionSide ,
"type" : orderType ,
"quantity" : quantity . String ( ) ,
"newClientOrderId" : orderSn ,
}
if orderType == "LIMIT" {
key := fmt . Sprintf ( "%s:%s" , global . TICKER_FUTURES , symbol )
tradeSet , _ := helper . GetObjString [ models . TradeSet ] ( helper . DefaultRedis , key )
rateFloat , _ := decimal . NewFromString ( rate )
if rateFloat . GreaterThanOrEqual ( decimal . Zero ) {
if side == "SELL" { //仓位是空 平空的话
price = price . Mul ( decimal . NewFromInt ( 1 ) . Add ( rateFloat ) ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
} else {
price = price . Mul ( decimal . NewFromInt ( 1 ) . Sub ( rateFloat ) ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
}
params [ "price" ] = price . String ( )
}
}
if orderType == "LIMIT" {
params [ "timeInForce" ] = "GTC"
}
var client * helper . BinanceClient
if apiUserInfo . UserPass == "" {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "" , apiUserInfo . IpAddress )
} else {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "socks5" , apiUserInfo . UserPass + "@" + apiUserInfo . IpAddress )
}
resp , _ , err := client . SendFuturesRequestAuth ( endpoint , "POST" , params )
if err != nil {
var dataMap map [ string ] interface { }
if err2 := sonic . Unmarshal ( [ ] byte ( err . Error ( ) ) , & dataMap ) ; err2 != nil {
return fmt . Errorf ( "api_id:%d 交易对:%s 平仓出错:%s" , apiUserInfo . Id , symbol , err . Error ( ) )
}
code , ok := dataMap [ "code" ]
if ok {
errContent := FutErrorMaps [ code . ( float64 ) ]
if errContent == "" {
errContent = err . Error ( )
}
return fmt . Errorf ( "api_id:%d 交易对:%s 平仓出错:%s" , apiUserInfo . Id , symbol , errContent )
}
}
var orderResp FutOrderResp
err = sonic . Unmarshal ( resp , & orderResp )
if err != nil {
return fmt . Errorf ( "api_id:%d 交易对:%s 平仓出错:%s" , apiUserInfo . Id , symbol , err . Error ( ) )
}
if orderResp . Symbol == "" {
return fmt . Errorf ( "api_id:%d 交易对:%s 平仓出错:未找到订单信息" , apiUserInfo . Id , symbol )
}
return nil
}
/ *
判断合约触发
* /
func JudgeFuturesPrice ( tradeSet models . TradeSet ) {
2025-02-06 18:03:09 +08:00
preOrderVal , _ := helper . DefaultRedis . GetAllList ( fmt . Sprintf ( rediskey . PreFutOrderList , global . EXCHANGE_BINANCE ) )
2025-02-06 11:14:33 +08:00
db := GetDBConnection ( )
if len ( preOrderVal ) == 0 {
return
}
futApi := FutRestApi { }
for _ , item := range preOrderVal {
preOrder := dto . PreOrderRedisList { }
if err := sonic . Unmarshal ( [ ] byte ( item ) , & preOrder ) ; err != nil {
log . Error ( "反序列化失败" )
continue
}
if preOrder . Symbol != tradeSet . Coin + tradeSet . Currency {
continue
}
orderPrice , _ := decimal . NewFromString ( preOrder . Price )
tradePrice , _ := decimal . NewFromString ( tradeSet . LastPrice )
if orderPrice . Cmp ( decimal . Zero ) == 0 || tradePrice . Cmp ( decimal . Zero ) == 0 {
continue
}
//多
if ( strings . ToUpper ( preOrder . Site ) == "BUY" && orderPrice . Cmp ( tradePrice ) >= 0 ) ||
( strings . ToUpper ( preOrder . Site ) == "SELL" && orderPrice . Cmp ( tradePrice ) <= 0 ) {
futTriggerOrder ( db , & preOrder , item , futApi )
}
}
}
// 分布式锁下单
func futTriggerOrder ( db * gorm . DB , v * dto . PreOrderRedisList , item string , futApi FutRestApi ) {
lock := helper . NewRedisLock ( fmt . Sprintf ( rediskey . SpotTrigger , v . ApiId , v . Symbol ) , 200 , 5 , 100 * time . Millisecond )
if ok , err := lock . AcquireWait ( context . Background ( ) ) ; err != nil {
log . Debug ( "获取锁失败" , err )
return
} else if ok {
defer lock . Release ( )
2025-02-06 18:03:09 +08:00
key := fmt . Sprintf ( rediskey . PreFutOrderList , global . EXCHANGE_BINANCE )
preOrder := DbModels . LinePreOrder { }
2025-02-06 11:14:33 +08:00
if err := db . Where ( "id = ?" , v . Id ) . First ( & preOrder ) . Error ; err != nil {
log . Error ( "获取预下单失败" , err )
if errors . Is ( err , gorm . ErrRecordNotFound ) {
log . Error ( "不存在待触发主单" , item )
2025-02-06 18:03:09 +08:00
helper . DefaultRedis . LRem ( key , item )
2025-02-06 11:14:33 +08:00
}
return
}
2025-02-06 18:03:09 +08:00
hasrecord , _ := helper . DefaultRedis . IsElementInList ( key , item )
2025-02-06 11:14:33 +08:00
if ! hasrecord {
log . Debug ( "预下单缓存中不存在" , item )
return
}
price , _ := decimal . NewFromString ( v . Price )
num , _ := decimal . NewFromString ( preOrder . Num )
if price . Cmp ( decimal . Zero ) == 0 {
log . Error ( "价格不能为0" )
return
}
params := FutOrderPlace {
ApiId : v . ApiId ,
Symbol : v . Symbol ,
Side : v . Site ,
OrderType : preOrder . MainOrderType ,
SideType : preOrder . MainOrderType ,
Price : price ,
Quantity : num ,
NewClientOrderId : v . OrderSn ,
}
preOrderVal , _ := sonic . MarshalString ( & v )
if err := futApi . OrderPlace ( db , params ) ; err != nil {
log . Error ( "下单失败" , v . Symbol , " err:" , err )
err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id =? and status='0'" , preOrder . Id ) . Updates ( map [ string ] interface { } { "status" : "2" , "desc" : err . Error ( ) } ) . Error
if err != nil {
log . Error ( "更新预下单状态失败" )
}
if preOrderVal != "" {
2025-02-06 18:03:09 +08:00
if _ , err := helper . DefaultRedis . LRem ( key , preOrderVal ) ; err != nil {
2025-02-06 11:14:33 +08:00
log . Error ( "删除redis 预下单失败:" , err )
}
}
return
}
if preOrderVal != "" {
2025-02-06 18:03:09 +08:00
if _ , err := helper . DefaultRedis . LRem ( key , preOrderVal ) ; err != nil {
2025-02-06 11:14:33 +08:00
log . Error ( "删除redis 预下单失败:" , err )
}
}
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id =? AND status ='0'" , preOrder . Id ) . Update ( "status" , "1" ) . Error ; err != nil {
log . Error ( "更新预下单状态失败 ordersn:" , v . OrderSn , " status:1" )
}
return
} else {
log . Error ( "获取锁失败" )
return
}
}
// CancelFutOrder 通过单个订单号取消合约委托
// symbol 交易对
// newClientOrderId 系统自定义订单号
func ( e FutRestApi ) CancelFutOrder ( apiUserInfo DbModels . LineApiUser , symbol string , newClientOrderId string ) error {
endpoint := "/fapi/v1/order"
params := map [ string ] string {
"symbol" : symbol , //交易对
"origClientOrderId" : newClientOrderId , //用户自定义订单号
}
var client * helper . BinanceClient
if apiUserInfo . UserPass == "" {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "" , apiUserInfo . IpAddress )
} else {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "socks5" , apiUserInfo . UserPass + "@" + apiUserInfo . IpAddress )
}
_ , _ , err := client . SendFuturesRequestAuth ( endpoint , "DELETE" , params )
if err != nil {
var dataMap map [ string ] interface { }
if err2 := sonic . Unmarshal ( [ ] byte ( err . Error ( ) ) , & dataMap ) ; err2 != nil {
return errors . New ( fmt . Sprintf ( "api_id:%d 交易对:%s 撤销合约委托出错:%s" , apiUserInfo . Id , symbol , err . Error ( ) ) )
}
code , ok := dataMap [ "code" ]
if ok {
errContent := FutErrorMaps [ code . ( float64 ) ]
if errContent == "" {
errContent = err . Error ( )
}
return errors . New ( fmt . Sprintf ( "api_id:%d 交易对:%s 撤销合约委托出错:%s" , apiUserInfo . Id , symbol , errContent ) )
}
}
return nil
}
// CancelAllFutOrder 通过交易对取消合约委托
// symbol 交易对
func ( e FutRestApi ) CancelAllFutOrder ( apiUserInfo DbModels . LineApiUser , symbol string ) error {
endpoint := "/fapi/v1/allOpenOrders"
params := map [ string ] string {
"symbol" : symbol , //交易对
"recvWindow" : "5000" ,
}
var client * helper . BinanceClient
if apiUserInfo . UserPass == "" {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "" , apiUserInfo . IpAddress )
} else {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "socks5" , apiUserInfo . UserPass + "@" + apiUserInfo . IpAddress )
}
_ , _ , err := client . SendFuturesRequestAuth ( endpoint , "DELETE" , params )
if err != nil {
var dataMap map [ string ] interface { }
if err2 := sonic . Unmarshal ( [ ] byte ( err . Error ( ) ) , & dataMap ) ; err2 != nil {
return fmt . Errorf ( "api_id:%d 交易对:%s 撤销全部合约委托出错:%s" , apiUserInfo . Id , symbol , err . Error ( ) )
}
code , ok := dataMap [ "code" ]
if ok {
errContent := FutErrorMaps [ code . ( float64 ) ]
if errContent == "" {
errContent = err . Error ( )
}
return fmt . Errorf ( "api_id:%d 交易对:%s 撤销全部合约委托出错:%s" , apiUserInfo . Id , symbol , errContent )
}
}
return nil
}
// CancelBatchFutOrder 批量撤销订单
// symbol 交易对
// newClientOrderIdList 系统自定义的订单号, 最多支持10个订单
func ( e FutRestApi ) CancelBatchFutOrder ( apiUserInfo DbModels . LineApiUser , symbol string , newClientOrderIdList [ ] string ) error {
if len ( newClientOrderIdList ) > 10 {
return errors . New ( fmt . Sprintf ( "api_id:%d 交易对:%s 撤销批量合约委托出错:%s" , apiUserInfo . Id , symbol , "最多支持10个订单" ) )
}
endpoint := "/fapi/v1/batchOrders"
marshal , _ := sonic . Marshal ( newClientOrderIdList )
params := map [ string ] string {
"symbol" : symbol , //交易对
"origClientOrderIdList" : string ( marshal ) ,
}
var client * helper . BinanceClient
if apiUserInfo . UserPass == "" {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "" , apiUserInfo . IpAddress )
} else {
client , _ = helper . NewBinanceClient ( apiUserInfo . ApiKey , apiUserInfo . ApiSecret , "socks5" , apiUserInfo . UserPass + "@" + apiUserInfo . IpAddress )
}
_ , code , err := client . SendFuturesRequestAuth ( endpoint , "DELETE" , params )
if err != nil {
log . Error ( "取消合约委托失败 参数:" , params )
log . Error ( "取消合约委托失败 code:" , code )
log . Error ( "取消合约委托失败 err:" , err )
var dataMap map [ string ] interface { }
if err . Error ( ) != "" {
if err2 := sonic . Unmarshal ( [ ] byte ( err . Error ( ) ) , & dataMap ) ; err2 != nil {
return errors . New ( fmt . Sprintf ( "api_id:%d 交易对:%s 撤销批量合约委托出错:%s" , apiUserInfo . Id , symbol , err . Error ( ) ) )
}
}
code , ok := dataMap [ "code" ]
if ok {
errContent := FutErrorMaps [ code . ( float64 ) ]
if errContent == "" {
errContent = err . Error ( )
}
return fmt . Errorf ( "api_id:%d 交易对:%s 撤销批量合约委托出错:%s" , apiUserInfo . Id , symbol , errContent )
}
}
return nil
}
// CalcSymbolExchangeAmt 计算兑换成USDT后的交易对数量
// symbol 需要兑换的交易对 例如 ETHBTC
// quoteSymbol 计价货币 ETHBTC -> 计价货币就是BTC
// totalMoney 兑换的总金额
func ( e FutRestApi ) CalcSymbolExchangeAmt ( symbol string , quoteSymbol string , totalMoney decimal . Decimal ) decimal . Decimal {
tickerSymbol := helper . DefaultRedis . Get ( rediskey . FutSymbolTicker ) . Val ( )
tickerSymbolMaps := make ( [ ] dto . Ticker , 0 )
sonic . Unmarshal ( [ ] byte ( tickerSymbol ) , & tickerSymbolMaps )
var targetSymbol string
targetSymbol = strings . Replace ( symbol , quoteSymbol , "USDT" , 1 ) //ETHBTC -》 ETHUSDT
key := fmt . Sprintf ( "%s:%s" , global . TICKER_FUTURES , targetSymbol )
tradeSet , _ := helper . GetObjString [ models . TradeSet ] ( helper . DefaultRedis , key )
var targetPrice decimal . Decimal
quantity := decimal . Zero
for _ , symbolMap := range tickerSymbolMaps {
if symbolMap . Symbol == strings . ToUpper ( targetSymbol ) {
targetPrice , _ = decimal . NewFromString ( symbolMap . Price )
quantity = totalMoney . Div ( targetPrice ) . Truncate ( int32 ( tradeSet . AmountDigit ) )
}
}
return quantity
}
// 加仓主账号
func ( e FutRestApi ) CoverAccountA ( apiUserInfo DbModels . LineApiUser , symbol string ) {
}
// GetFutSymbolLastPrice 获取现货交易对最新价格
func ( e FutRestApi ) GetFutSymbolLastPrice ( targetSymbol string ) ( lastPrice decimal . Decimal ) {
tickerSymbol := helper . DefaultRedis . Get ( rediskey . FutSymbolTicker ) . Val ( )
tickerSymbolMaps := make ( [ ] dto . Ticker , 0 )
sonic . Unmarshal ( [ ] byte ( tickerSymbol ) , & tickerSymbolMaps )
for _ , symbolMap := range tickerSymbolMaps {
if symbolMap . Symbol == strings . ToUpper ( targetSymbol ) {
lastPrice = utility . StringToDecimal ( symbolMap . Price )
}
}
return lastPrice
}