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" "strings" "time" "github.com/bytedance/sonic" "github.com/go-admin-team/go-admin-core/logger" "github.com/go-admin-team/go-admin-core/sdk/service" "github.com/shopspring/decimal" "gorm.io/gorm" ) type BinanceStrategyOrderService struct { service.Service Debug bool } // 判断是否触发波段订单 func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) { //现货 orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType)) if len(orderStrs) > 0 { e.DoJudge(orderStrs, 1, exchangeType) } //合约 futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType)) if len(futOrdedrStrs) > 0 { e.DoJudge(futOrdedrStrs, 2, exchangeType) } } // 判断是否符合条件 func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) { db := GetDBConnection() // setting, _ := cacheservice.GetSystemSetting(db) 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.OrderSn), 60, 20, 300*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, symbolType, exchangeType) if err != nil { e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err) } if e.Debug || success { e.TriggerOrder(db, orderStr, orderItem, symbolType) } } } } // 判断是否符合触发条件 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 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]) //时间差超过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) logger.Infof("百分比:%s", percentag.Mul(decimal.NewFromInt(100)).String()) //价格没有变动 if percentag.Cmp(decimal.Zero) == 0 { return result, nil } //满足条件 switch { //涨价格大于0.5% 跌价格小于-0.5% case order.CompareType == 1 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, order.CompareType == 1 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0 case order.CompareType == 2 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, order.CompareType == 2 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0 case order.CompareType == 5 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, order.CompareType == 5 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0 } return result, nil } // 触发委托单 func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string, order dto.StrategyOrderRedisList, symbolType int) error { orders := make([]models.LinePreOrder, 0) if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =? or id =?", order.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("获取系统设置失败") } var tradeSet models2.TradeSet switch symbolType { case 1: tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 0) case 2: tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) default: return errors.New("获取交易对行情失败,交易对类型错误") } if tradeSet.Coin == "" { return errors.New("获取交易对行情失败") } lastPrice := utility.StrToDecimal(tradeSet.LastPrice) if lastPrice.Cmp(decimal.Zero) <= 0 { return errors.New("最新成交价小于等于0") } mainOrder := models.LinePreOrder{} for _, v := range orders { if v.MainId == 0 { mainOrder = v break } } if mainOrder.Id == 0 { return errors.New("获取主单失败") } GetOrderByPid(&mainOrder, orders, mainOrder.Id) e.RecalculateOrder(tradeSet, &mainOrder, setting) //事务保存 err := e.Orm.Transaction(func(tx *gorm.DB) error { if err1 := tx.Save(&mainOrder).Error; err1 != nil { return err1 } for _, v := range mainOrder.Childs { if err1 := tx.Save(&v).Error; err1 != nil { return err1 } for _, v2 := range v.Childs { if err1 := tx.Save(&v2).Error; err1 != nil { return err1 } } } return nil }) if err != nil { e.Log.Errorf("order_id:%d 波段触发保存委托单失败:%s", mainOrder.Id, err.Error()) return err } e.StrategyOrderPlace(db, cacheVal, mainOrder, tradeSet) return nil } // 策略触发订单 // 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) if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil { logger.Error("删除redis 预下单失败:", 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("更新预下单状态失败") } return } else { if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil { logger.Error("删除redis 预下单失败:", err) } } if err := db.Model(&models.LinePreOrder{}).Where("id =? ", mainOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil { logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1") } if err := db.Model(&models.LinePreOrder{}).Where("id =? AND status ='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "1"}).Error; err != nil { logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1") } } // 重新计算订单单价、数量 // tradeSet 交易对行情 // mainOrder 主订单 // setting 系统设置 func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet, mainOrder *models.LinePreOrder, setting models.LineSystemSetting) error { exts := make([]models.LinePreOrderExt, 0) if err := e.Orm.Model(models.LinePreOrderExt{}).Where("main_order_id =?", mainOrder.Id).Find(&exts).Error; err != nil { return errors.New("获取拓展信息失败") } var newPrice decimal.Decimal 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() remainQuantity := totalNum var totalLossAmount decimal.Decimal prePrice := lastPrice for index := range mainOrder.Childs { var ext models.LinePreOrderExt extOrderId := mainOrder.Childs[index].Id takeStopArray := []int{1, 2} //止盈止损 ext拓展id为 pid if utility.ContainsInt(takeStopArray, mainOrder.Childs[index].OrderType) { extOrderId = mainOrder.Childs[index].Pid } for _, v := range exts { if v.OrderId == extOrderId { ext = v break } } if ext.Id <= 0 { logger.Errorf("子订单ext不存在 id:%d", mainOrder.Childs[index].Id) continue } //主单止盈、止损 if mainOrder.Childs[index].Pid == mainOrder.Childs[index].MainId && (mainOrder.Childs[index].OrderType == 1 || mainOrder.Childs[index].OrderType == 2) { var percent decimal.Decimal switch { // 加价 case mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "SELL": percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio) //减价 case mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "SELL": percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio) } childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) mainOrder.Childs[index].Price = childPrice.String() mainOrder.Childs[index].Num = totalNum.String() } else { lastNum := remainQuantity //过期时间 if ext.ExpirateHour <= 0 { mainOrder.Childs[index].ExpireTime = time.Now().AddDate(10, 0, 0) } else { mainOrder.Childs[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour)) } switch { //加仓单 case mainOrder.Childs[index].OrderType == 1 && mainOrder.Childs[index].OrderCategory == 3: var percentage decimal.Decimal if mainOrder.Site == "BUY" { percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100))) } else { percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100))) } dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) mainOrder.Childs[index].Price = dataPrice.String() priceDiff := dataPrice.Sub(prePrice).Abs() totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff)) if ext.AddPositionType == 1 { buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2) mainOrder.Childs[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() } else { mainOrder.Childs[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() } //加库存 lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Childs[index].Num)) // 计算子订单 if len(mainOrder.Childs[index].Childs) > 0 { calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false) } //覆盖最近的订单价 prePrice = dataPrice //减仓单 case mainOrder.Childs[index].OrderType == 4: 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.Childs[index].Price = dataPrice.String() priceDiff := dataPrice.Sub(prePrice).Abs() totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff)) //百分比减仓 if ext.AddPositionType == 1 { mainOrder.Childs[index].Num = lastNum.Mul(ext.AddPositionVal.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String() } else { logger.Error("减仓不能是固定数值") } //减库存 lastNum = lastNum.Sub(utility.StrToDecimal(mainOrder.Childs[index].Num)) // 计算子订单 if len(mainOrder.Childs[index].Childs) > 0 { calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false) } //覆盖最近的订单价 prePrice = dataPrice } } } return nil } // 计算子订单信息 // isTpTp 是否是止盈后止损止盈 // totalLossAmount 累计亏损金额 func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeSet, ext models.LinePreOrderExt, lastNum decimal.Decimal, price decimal.Decimal, totalLossAmount 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)) } //止损 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) //做多止损、做空止盈 case (*orders)[index].OrderType == 2 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 1 && (*orders)[index].Site == "BUY": percentage = decimal.NewFromInt(100).Sub(addPercentage) } //止盈亏损回本百分比 if (*orders)[index].OrderType == 1 && totalLossAmount.Cmp(decimal.Zero) > 0 { lossPercent := totalLossAmount.Div(lastNum).Mul(decimal.NewFromInt(100)).Truncate(2) percentage = percentage.Add(lossPercent) } if percentage.Cmp(decimal.Zero) > 0 { percentage = percentage.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].Childs) > 0 && lastOrderQuantity.Cmp(decimal.Zero) > 0 { calculateChildOrder(&(*orders)[index].Childs, tradeSet, ext, lastOrderQuantity, orderPrice, decimal.Zero, true) } } 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) } } }