2025-04-03 18:32:23 +08:00
package binanceservice
import (
"context"
"errors"
"fmt"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
models2 "go-admin/models"
"go-admin/pkg/utility"
"go-admin/services/cacheservice"
2025-04-11 09:06:09 +08:00
"strings"
2025-04-03 18:32:23 +08:00
"time"
"github.com/bytedance/sonic"
2025-04-11 09:06:09 +08:00
"github.com/go-admin-team/go-admin-core/logger"
2025-04-03 18:32:23 +08:00
"github.com/go-admin-team/go-admin-core/sdk/service"
"github.com/shopspring/decimal"
2025-04-12 18:32:36 +08:00
"gorm.io/gorm"
2025-04-03 18:32:23 +08:00
)
type BinanceStrategyOrderService struct {
service . Service
}
// 判断是否触发波段订单
func ( e * BinanceStrategyOrderService ) TriggerStrategyOrder ( exchangeType string ) {
//现货
orderStrs , _ := helper . DefaultRedis . GetAllList ( fmt . Sprintf ( rediskey . StrategyFutOrderList , exchangeType ) )
e . DoJudge ( orderStrs , 1 , exchangeType )
//合约
futOrdedrStrs , _ := helper . DefaultRedis . GetAllList ( fmt . Sprintf ( rediskey . StrategyFutOrderList , exchangeType ) )
e . DoJudge ( futOrdedrStrs , 2 , exchangeType )
}
2025-04-12 18:32:36 +08:00
// 判断是否符合条件
2025-04-03 18:32:23 +08:00
func ( e * BinanceStrategyOrderService ) DoJudge ( orderStrs [ ] string , symbolType int , exchangeType string ) {
2025-04-12 18:32:36 +08:00
db := GetDBConnection ( )
2025-04-03 18:32:23 +08:00
for _ , orderStr := range orderStrs {
var lockKey string
orderItem := dto . StrategyOrderRedisList { }
err := sonic . Unmarshal ( [ ] byte ( orderStr ) , & orderItem )
if err != nil || orderItem . Symbol == "" {
continue
}
if symbolType == 1 {
lockKey = rediskey . StrategySpotTriggerLock
} else {
lockKey = rediskey . StrategyFutTriggerLock
}
lock := helper . NewRedisLock ( fmt . Sprintf ( lockKey , orderItem . ApiId , orderItem . Symbol ) , 200 , 50 , 100 * time . Millisecond )
if ok , err := lock . AcquireWait ( context . Background ( ) ) ; err != nil {
e . Log . Debug ( "获取锁失败" , err )
return
} else if ok {
defer lock . Release ( )
//判断是否符合条件
success , err := e . JudgeStrategy ( orderItem , 1 , exchangeType )
if err != nil {
e . Log . Errorf ( "order_id:%d err:%v" , orderItem . Id , err )
}
if success {
2025-04-12 18:32:36 +08:00
e . TriggerOrder ( db , orderStr , orderItem , symbolType )
2025-04-03 18:32:23 +08:00
}
}
}
}
// 判断是否符合触发条件
func ( e * BinanceStrategyOrderService ) JudgeStrategy ( order dto . StrategyOrderRedisList , symbolType int , exchangeType string ) ( bool , error ) {
var symbolKey string
result := false
nowUtc := time . Now ( ) . UnixMilli ( )
switch symbolType {
case 1 :
symbolKey = fmt . Sprintf ( rediskey . SpotTickerLastPrice , exchangeType , order . Symbol )
case 2 :
symbolKey = fmt . Sprintf ( rediskey . FutureTickerLastPrice , exchangeType , order . Symbol )
}
lastUtc := nowUtc - ( int64 ( order . TimeSlotStart ) * 60 * 1000 )
beforePrice , _ := helper . DefaultRedis . GetNextAfterScore ( symbolKey , float64 ( lastUtc ) )
lastPrices , _ := helper . DefaultRedis . GetLastSortSet ( symbolKey )
if beforePrice == "" || len ( lastPrices ) == 0 {
return result , errors . New ( "获取交易对起止价格失败" )
}
score := lastPrices [ 0 ] . Score
2025-04-11 09:06:09 +08:00
var startPrice decimal . Decimal
var lastPrice decimal . Decimal
startPriceArgs := strings . Split ( beforePrice , ":" )
endPricecArgs := strings . Split ( lastPrices [ 0 ] . Member . ( string ) , ":" )
if len ( startPriceArgs ) == 0 || len ( endPricecArgs ) == 0 {
return result , errors . New ( "获取交易对起止价格失败" )
}
startPrice = utility . StrToDecimal ( startPriceArgs [ len ( startPriceArgs ) - 1 ] )
lastPrice = utility . StrToDecimal ( endPricecArgs [ len ( endPricecArgs ) - 1 ] )
2025-04-03 18:32:23 +08:00
//时间差超过10s
if ( nowUtc - int64 ( score ) ) / 1000 > 10 {
return result , fmt . Errorf ( "时间差超过 %ss" , "10" )
}
if startPrice . Cmp ( decimal . Zero ) == 0 || lastPrice . Cmp ( decimal . Zero ) == 0 {
return result , errors . New ( "获取交易对起止价格有一个为0" )
}
percentag := lastPrice . Div ( startPrice ) . Sub ( decimal . NewFromInt ( 1 ) ) . Truncate ( 6 )
2025-04-12 18:32:36 +08:00
fmt . Printf ( "百分比:%s" , percentag . Mul ( decimal . NewFromInt ( 100 ) ) . String ( ) )
2025-04-03 18:32:23 +08:00
//价格没有变动
if percentag . Cmp ( decimal . Zero ) == 0 {
return result , nil
}
//满足条件
switch order . CompareType {
case 1 :
result = percentag . Mul ( decimal . NewFromInt ( 100 ) ) . Cmp ( order . Percentag ) > 0
case 2 :
result = percentag . Mul ( decimal . NewFromInt ( 100 ) ) . Cmp ( order . Percentag ) >= 0
case 5 :
result = percentag . Mul ( decimal . NewFromInt ( 100 ) ) . Cmp ( order . Percentag ) == 0
default :
return result , errors . New ( "没有对应的类型" )
}
return result , nil
}
// 触发委托单
2025-04-12 18:32:36 +08:00
func ( e * BinanceStrategyOrderService ) TriggerOrder ( db * gorm . DB , cacheVal string , order dto . StrategyOrderRedisList , symbolType int ) error {
2025-04-03 18:32:23 +08:00
orders := make ( [ ] models . LinePreOrder , 0 )
if err := e . Orm . Model ( & models . LinePreOrder { } ) . Where ( "main_id =?" , order . Id ) . Find ( & orders ) . Error ; err != nil {
e . Log . Errorf ( "order_id:%d 获取委托单失败:%s" , order . Id , err . Error ( ) )
return err
}
setting , _ := cacheservice . GetSystemSetting ( e . Orm )
if setting . Id == 0 {
return errors . New ( "获取系统设置失败" )
}
tradeSet , _ := cacheservice . GetTradeSet ( global . EXCHANGE_BINANCE , order . Symbol , symbolType )
if tradeSet . Coin == "" {
return errors . New ( "获取交易对行情失败" )
}
lastPrice := utility . StrToDecimal ( tradeSet . LastPrice )
if lastPrice . Cmp ( decimal . Zero ) <= 0 {
return errors . New ( "最新成交价小于等于0" )
}
2025-04-12 11:33:12 +08:00
mainOrder := models . LinePreOrder { }
2025-04-03 18:32:23 +08:00
for _ , v := range orders {
if v . MainId == 0 {
mainOrder = v
break
}
}
GetOrderByPid ( & mainOrder , orders , mainOrder . Id )
2025-04-12 11:33:12 +08:00
e . RecalculateOrder ( tradeSet , & mainOrder , setting )
if err := e . Orm . Save ( & mainOrder ) . Error ; err != nil {
e . Log . Errorf ( "order_id:%d 波段触发保存委托单失败:%s" , mainOrder . Id , err . Error ( ) )
2025-04-12 18:32:36 +08:00
return err
2025-04-12 11:33:12 +08:00
}
2025-04-12 18:32:36 +08:00
e . StrategyOrderPlace ( db , cacheVal , mainOrder , tradeSet )
2025-04-03 18:32:23 +08:00
return nil
}
2025-04-12 18:32:36 +08:00
// 策略触发订单
// cacheVal:缓存值
// mainOrder:主订单
// tradeSet:交易对行情
func ( e * BinanceStrategyOrderService ) StrategyOrderPlace ( db * gorm . DB , cacheVal string , mainOrder models . LinePreOrder , tradeSet models2 . TradeSet ) {
price := utility . StringToDecimal ( mainOrder . Price ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
num := utility . StringToDecimal ( mainOrder . Num ) . Truncate ( int32 ( tradeSet . AmountDigit ) )
futApi := FutRestApi { }
var key string
switch mainOrder . SymbolType {
case 1 :
key = fmt . Sprintf ( rediskey . StrategySpotOrderList , global . EXCHANGE_BINANCE )
case 2 :
key = fmt . Sprintf ( rediskey . StrategyFutOrderList , global . EXCHANGE_BINANCE )
default :
logger . Errorf ( "id:%d 交易对类型不存在:%d" , mainOrder . Id , mainOrder . SymbolType )
return
}
params := FutOrderPlace {
ApiId : mainOrder . ApiId ,
Symbol : mainOrder . Symbol ,
Side : mainOrder . Site ,
OrderType : mainOrder . MainOrderType ,
SideType : mainOrder . MainOrderType ,
Price : price ,
Quantity : num ,
NewClientOrderId : mainOrder . OrderSn ,
}
if err := futApi . OrderPlaceLoop ( db , params , 3 ) ; err != nil {
logger . Error ( "下单失败" , mainOrder . Symbol , " err:" , err )
err := db . Model ( & models . LinePreOrder { } ) . Where ( "id =? and status='0'" , mainOrder . Id ) . Updates ( map [ string ] interface { } { "status" : "2" , "desc" : err . Error ( ) } ) . Error
if err != nil {
logger . Error ( "更新预下单状态失败" )
}
if _ , err := helper . DefaultRedis . LRem ( key , cacheVal ) ; err != nil {
logger . Error ( "删除redis 预下单失败:" , err )
}
return
}
}
2025-04-03 18:32:23 +08:00
// 重新计算订单单价、数量
// tradeSet 交易对行情
// mainOrder 主订单
// setting 系统设置
func ( e * BinanceStrategyOrderService ) RecalculateOrder ( tradeSet models2 . TradeSet , mainOrder * models . LinePreOrder , setting models . LineSystemSetting ) error {
exts := make ( [ ] models . LinePreOrderExt , 0 )
2025-04-12 18:32:36 +08:00
if err := e . Orm . Model ( models . LinePreOrderExt { } ) . Where ( "main_order_id =?" , mainOrder . Id ) . Find ( & exts ) . Error ; err != nil {
2025-04-03 18:32:23 +08:00
return errors . New ( "获取拓展信息失败" )
}
lastPrice := utility . StrToDecimal ( tradeSet . LastPrice )
rate := utility . StrToDecimal ( mainOrder . Rate )
newPrice := lastPrice . Mul ( decimal . NewFromInt ( 1 ) . Add ( rate . Div ( decimal . NewFromInt ( 100 ) ) . Truncate ( 4 ) ) ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
buyPrice := utility . StrToDecimal ( mainOrder . BuyPrice )
totalNum := buyPrice . Div ( newPrice ) . Truncate ( int32 ( tradeSet . AmountDigit ) )
mainOrder . SignPrice = lastPrice . String ( )
mainOrder . Price = newPrice . String ( )
mainOrder . Num = totalNum . String ( )
2025-04-11 09:06:09 +08:00
remainQuantity := totalNum
2025-04-03 18:32:23 +08:00
for index := range mainOrder . Childs {
2025-04-11 09:06:09 +08:00
var ext models . LinePreOrderExt
for _ , v := range exts {
if v . OrderId == mainOrder . Child [ index ] . Id {
ext = v
break
}
}
if ext . Id <= 0 {
logger . Errorf ( "子订单ext不存在 id:%d" , mainOrder . Child [ index ] . Id )
continue
}
//主单止盈、止损
if mainOrder . Child [ index ] . Pid == mainOrder . Child [ index ] . MainId && ( mainOrder . Child [ index ] . OrderType == 1 || mainOrder . Child [ index ] . OrderType == 2 ) {
var percent decimal . Decimal
switch {
// 加价
case mainOrder . Child [ index ] . OrderType == 1 && mainOrder . Site == "BUY" , mainOrder . Child [ index ] . OrderType == 2 && mainOrder . Site == "SELL" :
percent = decimal . NewFromInt ( 100 ) . Add ( ext . TakeProfitRatio )
//减价
case mainOrder . Child [ index ] . OrderType == 2 && mainOrder . Site == "BUY" , mainOrder . Child [ index ] . OrderType == 1 && mainOrder . Site == "SELL" :
percent = decimal . NewFromInt ( 100 ) . Sub ( ext . StopLossRatio )
2025-04-03 18:32:23 +08:00
}
2025-04-11 09:06:09 +08:00
childPrice := lastPrice . Mul ( percent . Div ( decimal . NewFromInt ( 100 ) . Truncate ( 4 ) ) ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
mainOrder . Child [ index ] . Price = childPrice . String ( )
mainOrder . Child [ index ] . Num = totalNum . String ( )
} else {
//todo 重新计算
lastNum := remainQuantity
//过期时间
if ext . ExpirateHour <= 0 {
mainOrder . Child [ index ] . ExpireTime = time . Now ( ) . AddDate ( 10 , 0 , 0 )
} else {
mainOrder . Child [ index ] . ExpireTime = time . Now ( ) . Add ( time . Hour * time . Duration ( ext . ExpirateHour ) )
}
switch {
//加仓单
case mainOrder . Child [ index ] . OrderType == 1 && mainOrder . Child [ index ] . OrderCategory == 3 :
var percentage decimal . Decimal
2025-04-03 18:32:23 +08:00
if mainOrder . Site == "BUY" {
2025-04-11 09:06:09 +08:00
percentage = decimal . NewFromInt ( 1 ) . Sub ( ext . PriceRatio . Div ( decimal . NewFromInt ( 100 ) ) )
2025-04-03 18:32:23 +08:00
} else {
2025-04-11 09:06:09 +08:00
percentage = decimal . NewFromInt ( 1 ) . Add ( ext . PriceRatio . Div ( decimal . NewFromInt ( 100 ) ) )
2025-04-03 18:32:23 +08:00
}
2025-04-11 09:06:09 +08:00
dataPrice := utility . StrToDecimal ( mainOrder . Price ) . Mul ( percentage ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
mainOrder . Child [ index ] . Price = dataPrice . String ( )
if ext . AddPositionType == 1 {
buyPrice := utility . StrToDecimal ( mainOrder . BuyPrice ) . Mul ( utility . SafeDiv ( ext . AddPositionVal , decimal . NewFromInt ( 100 ) ) ) . Truncate ( 2 )
mainOrder . Child [ index ] . Num = utility . SafeDiv ( buyPrice , dataPrice ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
} else {
mainOrder . Child [ index ] . Num = utility . SafeDiv ( ext . AddPositionVal . Truncate ( 2 ) , dataPrice ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
}
//加库存
lastNum = lastNum . Add ( utility . StrToDecimal ( mainOrder . Child [ index ] . Num ) )
2025-04-12 11:33:12 +08:00
// 计算子订单
if len ( mainOrder . Child [ index ] . Child ) > 0 {
calculateChildOrder ( & mainOrder . Child , & tradeSet , ext , lastNum , dataPrice , false )
}
2025-04-11 09:06:09 +08:00
//减仓单
case mainOrder . Child [ index ] . OrderType == 4 :
2025-04-12 11:33:12 +08:00
percentage := decimal . NewFromInt ( 1 )
if mainOrder . Site == "BUY" && ext . PriceRatio . Cmp ( decimal . Zero ) > 0 {
percentage = decimal . NewFromInt ( 1 ) . Sub ( ext . PriceRatio . Div ( decimal . NewFromInt ( 100 ) ) )
} else if ext . PriceRatio . Cmp ( decimal . Zero ) > 0 {
percentage = decimal . NewFromInt ( 1 ) . Add ( ext . PriceRatio . Div ( decimal . NewFromInt ( 100 ) ) )
}
dataPrice := utility . StrToDecimal ( mainOrder . Price ) . Mul ( percentage ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
mainOrder . Child [ index ] . Price = dataPrice . String ( )
//百分比减仓
if ext . AddPositionType == 1 {
mainOrder . Child [ index ] . Num = lastNum . Mul ( ext . AddPositionVal . Div ( decimal . NewFromInt ( 100 ) ) ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
} else {
logger . Error ( "减仓不能是固定数值" )
}
2025-04-11 09:06:09 +08:00
//减库存
2025-04-12 11:33:12 +08:00
lastNum = lastNum . Sub ( utility . StrToDecimal ( mainOrder . Child [ index ] . Num ) )
// 计算子订单
if len ( mainOrder . Child [ index ] . Child ) > 0 {
calculateChildOrder ( & mainOrder . Child , & tradeSet , ext , lastNum , dataPrice , false )
}
}
}
}
return nil
}
// 计算子订单信息
// isTpTp 是否是止盈后止损止盈
func calculateChildOrder ( orders * [ ] models . LinePreOrder , tradeSet * models2 . TradeSet , ext models . LinePreOrderExt , lastNum decimal . Decimal , price decimal . Decimal , isTpTp bool ) error {
for index := range * orders {
orderQuantity := lastNum . Truncate ( int32 ( tradeSet . AmountDigit ) )
percentage := decimal . NewFromInt ( 1 )
var addPercentage decimal . Decimal
switch {
//止盈
case ! isTpTp && ( * orders ) [ index ] . OrderType == 1 :
addPercentage = ext . TakeProfitRatio
if ext . TakeProfitNumRatio . Cmp ( decimal . Zero ) > 0 {
orderQuantity = lastNum . Mul ( ext . TakeProfitNumRatio . Div ( decimal . NewFromInt ( 100 ) ) ) . Truncate ( int32 ( tradeSet . AmountDigit ) )
2025-04-03 18:32:23 +08:00
}
2025-04-12 11:33:12 +08:00
//止损
case ! isTpTp && ( * orders ) [ index ] . OrderType == 2 :
addPercentage = ext . StopLossRatio
//止盈后止盈
case isTpTp && ( * orders ) [ index ] . OrderType == 1 :
addPercentage = ext . TpTpPriceRatio
//止盈后止损
case isTpTp && ( * orders ) [ index ] . OrderType == 2 :
addPercentage = ext . TpSlPriceRatio
}
switch {
//做多止盈、做空止损
case ( * orders ) [ index ] . OrderType == 1 && ( * orders ) [ index ] . Site == "SELL" , ( * orders ) [ index ] . OrderType == 2 && ( * orders ) [ index ] . Site == "BUY" :
percentage = decimal . NewFromInt ( 100 ) . Add ( addPercentage ) . Div ( decimal . NewFromInt ( 100 ) )
//做多止损、做空止盈
case ( * orders ) [ index ] . OrderType == 2 && ( * orders ) [ index ] . Site == "SELL" , ( * orders ) [ index ] . OrderType == 1 && ( * orders ) [ index ] . Site == "BUY" :
percentage = decimal . NewFromInt ( 100 ) . Sub ( addPercentage ) . Div ( decimal . NewFromInt ( 100 ) )
}
orderPrice := price . Mul ( percentage ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
( * orders ) [ index ] . Price = orderPrice . String ( )
( * orders ) [ index ] . Num = orderQuantity . String ( )
lastOrderQuantity := lastNum . Sub ( orderQuantity ) . Truncate ( int32 ( tradeSet . AmountDigit ) )
//止盈后止盈、止盈后止损
if len ( ( * orders ) [ index ] . Child ) > 0 && lastOrderQuantity . Cmp ( decimal . Zero ) > 0 {
calculateChildOrder ( & ( * orders ) [ index ] . Child , tradeSet , ext , lastOrderQuantity , orderPrice , true )
2025-04-03 18:32:23 +08:00
}
}
2025-04-12 11:33:12 +08:00
2025-04-03 18:32:23 +08:00
return nil
}
// 递归订单树
func GetOrderByPid ( order * models . LinePreOrder , orders [ ] models . LinePreOrder , pid int ) {
for _ , v := range orders {
if v . Pid == pid {
GetOrderByPid ( & v , orders , v . Id )
order . Childs = append ( order . Childs , v )
}
}
}