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" ) 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) } // 处理订单 func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) { 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 { e.TriggerOrder(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) //价格没有变动 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 } // 触发委托单 func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisList, symbolType int) error { 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") } var mainOrder models.LinePreOrder for _, v := range orders { if v.MainId == 0 { mainOrder = v break } } GetOrderByPid(&mainOrder, orders, mainOrder.Id) return nil } // 重新计算订单单价、数量 // 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_id =?", mainOrder.Id).Find(&exts).Error; err != nil { 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() remainQuantity := totalNum for index := range mainOrder.Childs { 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) } 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 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.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)) //todo 计算子订单 //减仓单 case mainOrder.Child[index].OrderType == 4: // todo 计算 //减库存 lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Child[index].Num)) //todo 计算子订单 } } } 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) } } }