package service

import (
	"errors"
	"fmt"
	"go-admin/common/const/rediskey"
	"go-admin/common/global"
	"go-admin/common/helper"
	models2 "go-admin/models"
	"go-admin/models/positiondto"
	"go-admin/pkg/utility"
	"go-admin/pkg/utility/snowflakehelper"
	"go-admin/services/binanceservice"
	"go-admin/services/cacheservice"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/bytedance/sonic"
	"github.com/jinzhu/copier"
	"github.com/shopspring/decimal"

	"github.com/go-admin-team/go-admin-core/logger"
	"github.com/go-admin-team/go-admin-core/sdk/service"
	"gorm.io/gorm"

	"go-admin/app/admin/models"
	"go-admin/app/admin/service/dto"
	"go-admin/app/admin/service/messages"
	"go-admin/common/actions"
	cDto "go-admin/common/dto"
)

type LinePreOrder struct {
	service.Service
}

// GetPage 获取LinePreOrder列表
func (e *LinePreOrder) GetPage(c *dto.LinePreOrderGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrder, count *int64) error {
	var err error
	var data models.LinePreOrder
	query := e.Orm.Model(&data).
		Scopes(
			cDto.MakeCondition(c.GetNeedSearch()),
			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
			actions.Permission(data.TableName(), p),
		).Where("pid = 0").Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id")

	if c.PercentStart != nil && *c.PercentStart > 0 {
		query.Where("rate >= ?", *c.PercentStart)
	}
	if c.PercentEnd != nil && *c.PercentEnd > 0 {
		query.Where("rate <= ?", *c.PercentEnd)
	}

	tx := query.
		Joins("JOIN line_pre_order_status on line_pre_order_status.order_sn = line_pre_order.order_sn").
		Select("line_pre_order.*,line_api_user.api_name,line_pre_order_status.add_position_status,line_pre_order_status.reduce_status")
	if c.AddPositionStatus >= 0 {
		tx.Where("line_pre_order_status.add_position_status = ?", c.AddPositionStatus)
	}

	if c.HedgeStatus >= 0 {
		tx.Where("line_pre_order_status.reduce_status = ?", c.HedgeStatus)
	}

	err = tx.
		Find(list).Limit(-1).Offset(-1).
		Count(count).Error
	if err != nil {
		e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err)
		return err
	}
	for i, order := range *list {
		var childNum int64
		e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", order.Id).Count(&childNum)
		(*list)[i].ChildNum = childNum
	}
	return nil
}

// GetOrderPage 获取LinePreOrder列表
func (e *LinePreOrder) GetOrderPage(c *dto.LinePreOrderGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrder, count *int64) error {
	var err error
	var data models.LinePreOrder

	query := e.Orm.Model(&data).
		Scopes(
			cDto.MakeCondition(c.GetNeedSearch()),
			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
			actions.Permission(data.TableName(), p),
		).Where("pid = 0 AND status != '0'").Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id")

	if c.PercentStart != nil && *c.PercentStart >= 0 {
		query.Where("rate >= ?", *c.PercentStart)
	}
	if c.PercentEnd != nil && *c.PercentEnd >= 0 {
		query.Where("rate <= ?", *c.PercentEnd)
	}
	tx := query.
		Joins("JOIN line_pre_order_status on line_pre_order_status.order_id = line_pre_order.id").
		Select("line_pre_order.*,line_api_user.api_name,line_pre_order_status.add_position_status,line_pre_order_status.reduce_status")
	if c.AddPositionStatus >= 0 {
		tx.Where("line_pre_order_status.add_position_status = ?", c.AddPositionStatus)
	}

	if c.HedgeStatus >= 0 {
		tx.Where("line_pre_order_status.reduce_status = ?", c.HedgeStatus)
	}
	err = tx.
		Find(list).Limit(-1).Offset(-1).
		Count(count).Error
	//err = e.Orm.Model(&data).
	//	Scopes(
	//		cDto.MakeCondition(c.GetNeedSearch()),
	//		cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
	//		actions.Permission(data.TableName(), p),
	//	).Where("pid = 0 AND status != '0'").Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id").Select("line_pre_order.*,line_api_user.api_name").
	//	Find(list).Limit(-1).Offset(-1).
	//	Count(count).Error
	if err != nil {
		e.Log.Errorf("LinePreOrderService GetChildPage error:%s \r\n", err)
		return err
	}
	for i, order := range *list {
		var childNum int64
		e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", order.Id).Count(&childNum)
		(*list)[i].ChildNum = childNum
	}
	return nil
}

// GetAllPage 获取LinePreOrder列表
func (e *LinePreOrder) GetAllPage(c *dto.LinePreOrderGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrder, count *int64) error {
	var err error
	var data models.LinePreOrder

	err = e.Orm.Model(&data).
		Scopes(
			cDto.MakeCondition(c.GetNeedSearch()),
			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
			actions.Permission(data.TableName(), p),
		).
		Find(list).Limit(-1).Offset(-1).
		Count(count).Error
	if err != nil {
		e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err)
		return err
	}
	return nil
}

// GetChildList 获取子订单列表
func (e *LinePreOrder) GetChildList(req *dto.GetChildOrderReq, p *actions.DataPermission, order *[]models.LinePreOrder) error {
	err := e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", req.Id).
		Order("id asc").
		Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id").Select("line_pre_order.*,line_api_user.api_name").
		Find(order).Error
	if err != nil {
		e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err)
		return err
	}
	ids := make([]int, 0)

	for _, v := range *order {
		ids = append(ids, v.Id)
	}

	if len(ids) > 0 {
		var counts []dto.LinePreOrderChildCount

		if err := e.Orm.Model(&models.LinePreOrder{}).Where("pid in ?", ids).Group("pid").Select("pid as id,count(*) as count").Find(&counts).Error; err != nil {
			logger.Errorf("获取子订单数量失败 %v", err)
			return nil
		}

		for index := range *order {
			for _, v := range counts {
				if v.Id == (*order)[index].Id {
					(*order)[index].ChildNum = int64(v.Count)
				}
			}
		}
	}

	return nil

}

// Get 获取LinePreOrder对象
func (e *LinePreOrder) Get(d *dto.LinePreOrderGetReq, p *actions.DataPermission, model *models.LinePreOrder) error {
	var data models.LinePreOrder

	err := e.Orm.Model(&data).
		Scopes(
			actions.Permission(data.TableName(), p),
		).
		First(model, d.GetId()).Error
	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
		err = errors.New("查看对象不存在或无权查看")
		e.Log.Errorf("Service GetLinePreOrder error:%s \r\n", err)
		return err
	}
	if err != nil {
		e.Log.Errorf("db error:%s", err)
		return err
	}
	return nil
}

// Insert 创建LinePreOrder对象
func (e *LinePreOrder) Insert(c *dto.LinePreOrderInsertReq) error {
	var err error
	var data models.LinePreOrder
	c.Generate(&data)
	err = e.Orm.Create(&data).Error
	if err != nil {
		e.Log.Errorf("LinePreOrderService Insert error:%s \r\n", err)
		return err
	}
	return nil
}

// Update 修改LinePreOrder对象
func (e *LinePreOrder) Update(c *dto.LinePreOrderUpdateReq, p *actions.DataPermission) error {
	var err error
	var data = models.LinePreOrder{}
	e.Orm.Scopes(
		actions.Permission(data.TableName(), p),
	).First(&data, c.GetId())
	c.Generate(&data)

	db := e.Orm.Save(&data)
	if err = db.Error; err != nil {
		e.Log.Errorf("LinePreOrderService Save error:%s \r\n", err)
		return err
	}
	if db.RowsAffected == 0 {
		return errors.New("无权更新该数据")
	}
	return nil
}

// Remove 删除LinePreOrder
func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermission) error {
	var data models.LinePreOrder
	var list []models.LinePreOrder
	e.Orm.Model(&models.LinePreOrder{}).Where("id in ?", d.GetId()).Find(&list)

	db := e.Orm.Model(&data).
		Scopes(
			actions.Permission(data.TableName(), p),
		).Unscoped().Delete(&data, d.GetId())
	if err := db.Error; err != nil {
		e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err)
		return err
	}
	ints := make([]int, 0)
	apiIds := make([]int, 0)

	if db.RowsAffected == 0 {
		return errors.New("无权删除该数据")
	}

	positions := map[string]positiondto.LinePreOrderPositioinDelReq{}
	addPosition := positiondto.AddPositionList{}
	reduceItem := positiondto.ReduceListItem{}
	futAddPosition := map[int]string{}
	spotAddPosition := map[int]string{}
	futReduces := map[int]string{}
	spotRedces := map[int]string{}

	futAddPositionKey := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE)
	spotAddPositionKey := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE)
	futReduceKey := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE)
	spotReduceKey := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE)
	futAddPositionVal, _ := helper.DefaultRedis.GetAllList(futAddPositionKey)
	futReduceVal, _ := helper.DefaultRedis.GetAllList(spotAddPositionKey)
	spotAddPositionVal, _ := helper.DefaultRedis.GetAllList(futReduceKey)
	spotReduceVal, _ := helper.DefaultRedis.GetAllList(spotReduceKey)

	for _, v := range futAddPositionVal {
		sonic.Unmarshal([]byte(v), &addPosition)
		if addPosition.MainId > 0 {
			futAddPosition[addPosition.MainId] = v
		}
	}

	for _, v := range spotAddPositionVal {
		sonic.Unmarshal([]byte(v), &addPosition)
		if addPosition.MainId > 0 {
			spotAddPosition[addPosition.MainId] = v
		}
	}

	for _, v := range futReduceVal {
		sonic.Unmarshal([]byte(v), &reduceItem)
		if reduceItem.MainId > 0 {
			futReduces[reduceItem.MainId] = v
		}
	}

	for _, v := range spotReduceVal {
		sonic.Unmarshal([]byte(v), &reduceItem)
		if reduceItem.MainId > 0 {
			spotRedces[reduceItem.MainId] = v
		}
	}
	//删除的缓存
	for _, order := range list {
		redisList := dto.PreOrderRedisList{
			Id:          order.Id,
			Symbol:      order.Symbol,
			Price:       order.Price,
			Site:        order.Site,
			ApiId:       order.ApiId,
			OrderSn:     order.OrderSn,
			QuoteSymbol: order.QuoteSymbol,
		}

		if !utility.ContainsInt(apiIds, order.ApiId) {
			apiIds = append(apiIds, order.ApiId)
		}

		//清除待加仓、待减仓
		if val, ok := futAddPosition[order.Id]; ok {
			helper.DefaultRedis.LRem(futAddPositionKey, val)
		}

		if val, ok := spotAddPosition[order.Id]; ok {
			helper.DefaultRedis.LRem(spotAddPositionKey, val)
		}
		if val, ok := futReduces[order.Id]; ok {
			helper.DefaultRedis.LRem(futReduceKey, val)
		}
		if val, ok := spotRedces[order.Id]; ok {
			helper.DefaultRedis.LRem(spotReduceKey, val)
		}

		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol))
		redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String()
		marshal, _ := sonic.Marshal(redisList)
		if order.SymbolType == 1 {
			listKey := fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType)
			helper.DefaultRedis.LRem(listKey, string(marshal))
		} else {
			listKey := fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType)
			helper.DefaultRedis.LRem(listKey, string(marshal))
		}

		//会影响持仓的
		removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType)

		if _, ok := positions[removeSymbolKey]; !ok {
			positions[removeSymbolKey] = positiondto.LinePreOrderPositioinDelReq{
				ApiId:        order.ApiId,
				Symbol:       order.Symbol,
				ExchangeType: order.ExchangeType,
				Side:         order.Site,
				SymbolType:   order.SymbolType,
			}
		}

		binanceservice.MainClosePositionClearCache(order.Id, order.SymbolType)

		ints = append(ints, order.Id)
	}

	if len(ints) > 0 {
		e.Orm.Model(&models.LinePreOrder{}).Where("main_id >0 AND main_id in ?", ints).Unscoped().Delete(&models.LinePreOrder{})
	}

	//清理仓位缓存
	for _, v := range positions {
		var count int64
		e.Orm.Model(&models.LinePreOrder{}).
			Where("api_id =? AND site=? AND symbol=? AND symbol_type =? AND exchange_type =? AND status =6",
				v.ApiId, v.Side, v.Symbol, v.SymbolType, v.ExchangeType).Count(&count)

		//没有已开仓的订单 直接清理仓位
		if count == 0 {
			var key string

			if v.SymbolType == 1 {
				key = fmt.Sprintf(rediskey.SpotPosition, v.ExchangeType, v.ApiId, v.Symbol, v.Side)
			} else {
				key = fmt.Sprintf(rediskey.FuturePosition, v.ExchangeType, v.ApiId, v.Symbol, v.Side)
			}

			helper.DefaultRedis.DeleteString(key)
		}
	}
	return nil
}

// 单个新增订单
func (e *LinePreOrder) AddPreOrderCheck(req *dto.LineAddPreOrderReq, p *actions.DataPermission, errs *[]error) error {
	apiUserService := LineApiUser{Service: e.Service}
	apiIds := []int{}
	apiUserIds := strings.Split(req.ApiUserId, ",")
	for _, v := range apiUserIds {
		apiId, _ := strconv.Atoi(v)
		apiIds = append(apiIds, apiId)
	}

	if req.StrategyTemplateType == 1 && req.StrategyTemplateId > 0 {
		cachePriceSymbols, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice)

		if !utility.ContainsStr(cachePriceSymbols, req.Symbol) {
			*errs = append(*errs, errors.New("交易对涨跌幅缓存不存在"))
			return nil
		}
	}

	activeApiIds, _ := apiUserService.GetActiveApis(apiIds)

	if len(activeApiIds) == 0 {
		*errs = append(*errs, errors.New("api用户不存在或未激活"))
	} else {
		for _, item := range apiIds {
			if !utility.ContainsInt(activeApiIds, item) {
				*errs = append(*errs, fmt.Errorf("api用户未激活:%v", item))
			}
		}
		e.AddPreOrder(req, activeApiIds, p, errs)
	}

	return nil
}

// AddPreOrder 单个添加
func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int, p *actions.DataPermission, errs *[]error) error {
	if req.SaveTemplate == "2" || req.SaveTemplate == "1" { //2 = 只保存模板 1= 保存模板并下单
		var templateLog dto.LineAddPreOrderReq
		copier.Copy(&templateLog, req)
		//templateLog := *req
		templateLog.SaveTemplate = "0"
		templateLog.TemplateName = ""
		marshal, _ := sonic.Marshal(templateLog)
		saveTemplateParams := models.LineOrderTemplateLogs{
			Name:   req.TemplateName,
			UserId: 0,
			Params: string(marshal),
			Type:   1,
			Switch: "0",
		}
		saveTemplateParams.CreateBy = req.CreateBy
		e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams)
	}

	linestrategyTemplate := models.LineStrategyTemplate{}

	if req.StrategyTemplateId > 0 {
		e.Orm.Where("id =?", req.StrategyTemplateId).First(&linestrategyTemplate)

		if linestrategyTemplate.Id == 0 {
			*errs = append(*errs, fmt.Errorf("策略不存在:%v", req.StrategyTemplateId))
			return nil
		}
	}
	if req.SaveTemplate == "2" {
		return nil
	}

	//重新排序下跌比例(顺序)
	sort.Slice(req.Ext, func(i, j int) bool {
		return req.Ext[i].PriceRatio.Cmp(req.Ext[j].PriceRatio) < 0
	})

	var key string
	if req.SymbolType == global.SYMBOL_SPOT {
		key = fmt.Sprintf(global.TICKER_SPOT, req.ExchangeType, req.Symbol)
	} else {
		key = fmt.Sprintf(global.TICKER_FUTURES, req.ExchangeType, req.Symbol)
	}

	for _, id := range apiUserIds {
		if req.Site == "SELL" && req.SymbolType == global.SYMBOL_SPOT {
			*errs = append(*errs, fmt.Errorf("api_id:%v 获取交易对:%s 现货不支持卖出操作", id, req.Symbol))
			continue
		}
		var AddOrder models.LinePreOrder
		var profitOrder models.LinePreOrder
		var reduceOrder models.LinePreOrder

		//获取交易对
		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key)
		tickerPrice := utility.StrToDecimal(tradeSet.LastPrice)
		if tickerPrice.Equal(decimal.Zero) { //redis 没有这个值
			*errs = append(*errs, fmt.Errorf("api_id:%d 获取交易对:%s 交易行情出错", id, req.Symbol))
			continue
		}

		AddOrder.CreateBy = req.CreateBy
		AddOrder.ExchangeType = req.ExchangeType
		AddOrder.OrderCategory = 1
		AddOrder.SignPriceType = "new"
		AddOrder.BuyPrice = req.BuyPrice     //购买多少U
		AddOrder.SymbolType = req.SymbolType //交易对类型1= 现货 2 = 合约
		AddOrder.OrderType = req.OrderType   //订单类型
		AddOrder.CoverType = req.CoverType   //对冲类型 0=无对冲 1= 现货对合约 2=合约对合约 3 合约对现货
		if req.ExpireHour == 0 {             //过期时间默认为4个小时
			req.ExpireHour = 4
		}
		AddOrder.ExpireTime = time.Now().Add(time.Duration(req.ExpireHour) * time.Hour) //过期时间
		AddOrder.MainOrderType = req.MainOrderType
		AddOrder.Site = req.Site
		AddOrder.SignPrice = tickerPrice.String()

		if req.PricePattern == "percentage" {
			AddOrder.Rate = req.Price
			orderPrice, _ := decimal.NewFromString(req.Price)                   //下单价百分比 10%
			priceRate := utility.SafeDiv(orderPrice, (decimal.NewFromInt(100))) //下单价除100 =0.1

			if strings.ToUpper(req.Site) == "BUY" { //购买方向
				//实际下单价格
				truncate := tickerPrice.Mul(decimal.NewFromInt(1).Sub(priceRate)).Truncate(int32(tradeSet.PriceDigit))
				AddOrder.Price = truncate.String()
			} else {
				truncate := tickerPrice.Mul(decimal.NewFromInt(1).Add(priceRate)).Truncate(int32(tradeSet.PriceDigit))
				AddOrder.Price = truncate.String()
			}

		} else { //实际价格下单
			AddOrder.Price = utility.StringToDecimal(req.Price).Truncate(int32(tradeSet.PriceDigit)).String()
			AddOrder.SignPriceType = req.PricePattern
			AddOrder.Rate = "0"
		}
		buyPrice := utility.StrToDecimal(req.BuyPrice) //购买多少U
		var symbolInfo models.LineSymbol
		e.Orm.Model(&models.LineSymbol{}).Where("type = ? AND symbol = ?", req.SymbolType, req.Symbol).Find(&symbolInfo)
		//计算购买数量  判断是否是否是U本位
		if symbolInfo.QuoteAsset != "USDT" { //不是U本位
			//获取币本位兑换u的价格
			ticker2 := models2.TradeSet{}
			tickerVal, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_SPOT, req.ExchangeType, strings.ToUpper(symbolInfo.QuoteAsset+"USDT")))

			if tickerVal == "" {
				logger.Error("查询行情失败")
				continue
			}

			err := sonic.Unmarshal([]byte(tickerVal), &ticker2)

			if ticker2.LastPrice == "" {
				logger.Errorf("查询行情失败 %s err:%v", strings.ToUpper(symbolInfo.QuoteAsset+"USDT"), err)
				continue
			}
			//LTCBTC --> LTCUSDT
			uTickerPrice, _ := decimal.NewFromString(ticker2.LastPrice) //94069
			//换算成U
			//div := decimal.NewFromInt(1).Div(uTickerPrice) //0.0000106365
			//在换算成对应交易对对应的价值
			//LTCBTC --> LTCUSDT == LTCUSDT -- 100.502
			div := utility.SafeDiv(utility.SafeDiv(tickerPrice, decimal.NewFromInt(1)), uTickerPrice)
			//计算下单数量
			AddOrder.Num = utility.SafeDiv(buyPrice, div).Truncate(int32(tradeSet.AmountDigit)).String()
		} else {
			fromString, _ := decimal.NewFromString(AddOrder.Price)
			AddOrder.Num = utility.SafeDiv(buyPrice, fromString).Truncate(int32(tradeSet.AmountDigit)).String()
		}
		if utility.StringToFloat64(AddOrder.Num) < tradeSet.MinQty {
			*errs = append(*errs, fmt.Errorf("api_id:%d 获取交易对:%s 小于最小下单数量", id, req.Symbol))
			continue
		}
		AddOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
		AddOrder.ApiId = id
		AddOrder.Symbol = req.Symbol
		AddOrder.QuoteSymbol = symbolInfo.QuoteAsset
		AddOrder.Pid = 0
		AddOrder.GroupId = "0"
		AddOrder.Status = 0
		copier.Copy(&profitOrder, &AddOrder)
		copier.Copy(&reduceOrder, &AddOrder)

		preOrderStatus := models.LinePreOrderStatus{}
		preOrderStatus.OrderSn = AddOrder.OrderSn

		//订单配置信息
		preOrderExts := make([]models.LinePreOrderExt, 0)
		defultExt := models.LinePreOrderExt{
			AddType:            1, //主单等同于加仓0
			AddPositionType:    1,
			AddPositionVal:     decimal.Zero,
			OrderType:          req.PriceType,
			TakeProfitRatio:    utility.StringToDecimal(req.Profit),
			TakeProfitNumRatio: req.ProfitNumRatio,
			StopLossRatio:      req.StopLoss,
			TpTpPriceRatio:     req.ProfitTpTpPriceRatio,
			TpSlPriceRatio:     req.ProfitTpSlPriceRatio,
		}
		//减仓单
		defultExt2 := models.LinePreOrderExt{
			AddType:            2,
			OrderType:          "LIMIT",
			PriceRatio:         req.ReducePriceRatio,
			AddPositionType:    1,
			AddPositionVal:     req.ReduceNumRatio,
			TakeProfitRatio:    req.ReduceTakeProfitRatio,
			TakeProfitNumRatio: decimal.NewFromInt(100), //减仓止盈默认100%
			StopLossRatio:      req.ReduceStopLossRatio,
		}
		mainPrice := utility.StringToDecimal(AddOrder.Price)
		mainAmount := utility.SafeDiv(buyPrice, mainPrice)
		defultExt.TotalAfter = utility.StrToDecimal(AddOrder.Num).Truncate(int32(tradeSet.AmountDigit))
		defultExt2.TotalBefore = defultExt.TotalAfter

		default2NumPercent := utility.SafeDiv(decimal.NewFromInt(100).Sub(req.ReduceNumRatio), decimal.NewFromInt(100))
		defultExt2.TotalAfter = mainAmount.Mul(default2NumPercent).Truncate(int32(tradeSet.AmountDigit))
		defultExt2.ReTakeRatio = utility.SafeDiv(req.ReducePriceRatio, default2NumPercent).Truncate(2)

		preOrderExts = append(preOrderExts, defultExt)
		preOrderExts = append(preOrderExts, defultExt2)

		calculateResp := dto.CalculateBreakEvenRatioResp{}
		mainParam := dto.CalculateBreakEevenRatioReq{
			AddType:          2,
			Price:            mainPrice,
			ExchangeType:     req.ExchangeType,
			Symbol:           req.Symbol,
			SymbolType:       req.SymbolType,
			BuyPrice:         buyPrice,
			LossBeginPercent: decimal.Zero,
			LossEndPercent:   req.ReducePriceRatio,
			AddPositionType:  1,
			AddPositionVal:   req.ReduceNumRatio,
		}

		//计算减仓后
		mainParam.LossEndPercent = req.ReducePriceRatio
		mainParam.RemainingQuantity = mainAmount
		e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet)
		mainParam.RemainingQuantity = calculateResp.RemainingQuantity //mainAmount.Mul(decimal.NewFromInt(100).Sub(req.ReduceNumRatio).Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit))
		mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU   //buyPrice.Mul(req.ReducePriceRatio.Div(decimal.NewFromInt(100)).Truncate(4)).Truncate(int32(tradeSet.PriceDigit))
		req.ReduceReTakeProfitRatio = calculateResp.Ratio
		mainParam.LossBeginPercent = req.ReducePriceRatio
		// defultExt.ReTakeRatio = calculateResp.Ratio

		for index, addPosition := range req.Ext {
			ext := models.LinePreOrderExt{
				AddType:            addPosition.AddType,
				OrderType:          addPosition.OrderType,
				PriceRatio:         addPosition.PriceRatio,
				TakeProfitRatio:    addPosition.TakeProfitRatio,
				TakeProfitNumRatio: addPosition.TakeProfitNumRatio,
				StopLossRatio:      addPosition.StopLossRatio,
				TpTpPriceRatio:     addPosition.TpTpPriceRatio,
				TpSlPriceRatio:     addPosition.TpSlPriceRatio,
				AddPositionType:    addPosition.AddPositionType,
				AddPositionVal:     addPosition.AddPositionVal,
				ExpirateHour:       addPosition.ExpirateHour,
			}

			mainParam.AddType = addPosition.AddType
			mainParam.LossEndPercent = req.Ext[index].PriceRatio
			mainParam.AddPositionType = req.Ext[index].AddPositionType
			mainParam.AddPositionVal = req.Ext[index].AddPositionVal

			if err := e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet); err != nil {
				*errs = append(*errs, err)
				return err
			}

			ext.TotalBefore = mainParam.RemainingQuantity    //初始数量
			ext.TotalAfter = calculateResp.RemainingQuantity //计算后数量
			ext.ReTakeRatio = calculateResp.Ratio
			ext.CreateBy = req.CreateBy
			mainParam.LossBeginPercent = addPosition.PriceRatio
			mainParam.RemainingQuantity = calculateResp.RemainingQuantity
			mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU
			preOrderExts = append(preOrderExts, ext)
		}

		//获取减仓策略
		var reduceStrategy models.LineReduceStrategy

		if req.ReduceStrategyId > 0 {
			reduceStrategyService := LineReduceStrategy{Service: e.Service}
			reduceStrategy, _ = reduceStrategyService.GetById(req.ReduceStrategyId)
		}

		//事务添加
		e.Orm.Transaction(func(tx *gorm.DB) error {
			reduceOrderStrategys := make([]models.LineOrderReduceStrategy, 0)
			err := tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&AddOrder).Error
			if err != nil {
				*errs = append(*errs, fmt.Errorf("api_id:%d 获取交易对:%s 生成订单失败", id, req.Symbol))
				return err
			}

			preOrderStatus.OrderId = AddOrder.Id

			//加仓、减仓状态
			tx.Model(&models.LinePreOrderStatus{}).Create(&preOrderStatus)
			preOrderExts[0].OrderId = AddOrder.Id

			childOrders, err := makeFuturesTakeAndReduce(&AddOrder, defultExt, tradeSet)

			if err != nil {
				logger.Errorf("构建主单止盈止损失败,err:%v", err)
			}

			if len(childOrders) > 0 {
				tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&childOrders)

				for _, childOrder := range childOrders {
					//不全部止盈的时候
					if childOrder.OrderType == 1 && req.ProfitNumRatio.Cmp(decimal.Zero) > 0 && req.ProfitNumRatio.Cmp(decimal.NewFromInt(100)) < 0 {
						reminQuantity := utility.StrToDecimal(AddOrder.Num).Sub(utility.StrToDecimal(childOrder.Num))

						childrens, err := makeTpOrder(&childOrder, reminQuantity, req.ProfitTpTpPriceRatio, req.ProfitTpSlPriceRatio, &tradeSet)

						if err != nil {
							logger.Error("生成止盈后子订单失败")
							return err
						}

						tx.Create(&childrens)
					}
				}
			}
			//减仓单
			if req.ReducePriceRatio.Cmp(decimal.Zero) > 0 {
				if req.PricePattern == "mixture" {
					if req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 {
						return errors.New("检查价格不能小于等于0")
					}

					reduceOrder.Price = req.ReducePriceRatio.Truncate(int32(tradeSet.PriceDigit)).String()
					reduceOrder.Rate = "0"
				} else {
					if strings.ToUpper(req.Site) == "BUY" {
						// stopOrder.Site = "SELL"
						reduceOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
					} else {
						// stopOrder.Site = "BUY"
						reduceOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Add(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
					}

					reduceOrder.Rate = req.ReducePriceRatio.String()
				}

				if strings.ToUpper(req.Site) == "BUY" {
					reduceOrder.Site = "SELL"
				} else {
					reduceOrder.Site = "BUY"
				}
				reduceOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
				reduceOrder.Pid = AddOrder.Id
				reduceOrder.MainId = AddOrder.Id
				reduceOrder.OrderType = 4
				reduceOrder.Status = 0
				reduceOrder.BuyPrice = "0"
				stopNum := utility.StrToDecimal(AddOrder.Num).Mul(req.ReduceNumRatio.Div(decimal.NewFromInt(100)).Truncate(4))
				reduceOrder.Num = stopNum.Truncate(int32(tradeSet.AmountDigit)).String()
				reduceOrder.ExpireTime = time.Now().AddDate(10, 0, 0)

				tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&reduceOrder)
				preOrderExts[1].OrderId = reduceOrder.Id
				if req.ReduceNumRatio.Cmp(decimal.Zero) > 0 && req.ReduceNumRatio.Cmp(decimal.NewFromInt(100)) < 0 {
					if newOrders, err := makeReduceTakeAndStoploss(&reduceOrder, defultExt2, tradeSet, false); err != nil {
						logger.Errorf("主单减仓生成止盈、减仓失败 err:%v", err)
						return err
					} else if len(newOrders) > 0 {
						if err := e.Orm.Create(&newOrders).Error; err != nil {
							logger.Errorf("主单减仓保存止盈、减仓失败 err:%v", err)
							return err
						}
					}
				}

				if reduceStrategy.Id > 0 {
					reduceOrderStrategys = append(reduceOrderStrategys, initOrderReduceStrategy(reduceStrategy, reduceOrder.Id))
				}
			}

			//添加后续节点
			for index, v := range preOrderExts {
				preOrderExts[index].MainOrderId = AddOrder.Id
				if index < 2 {
					// preOrderExts[index].OrderId = AddOrder.Id
					continue
				}
				var newOrder models.LinePreOrder

				if v.AddType == 1 {
					newOrder = createPreAddPosition(&AddOrder, v, tradeSet)
				} else if v.AddType == 2 {
					newOrder = createPreReduceOrder(&AddOrder, v, tradeSet)
				}

				if newOrder.OrderSn == "" {
					continue
				}

				if err := e.Orm.Create(&newOrder).Error; err != nil {
					logger.Error("保存加仓单失败")
					return err
				}

				preOrderExts[index].OrderId = newOrder.Id
				//止盈、减仓
				orders, err := makeFuturesTakeAndReduce(&newOrder, v, tradeSet)

				if err != nil {
					logger.Error("构造止盈、止损失败")
					return err
				}

				if err := e.Orm.Create(&orders).Error; err != nil {
					logger.Error("保存止盈、止损失败")
					return err
				}

				for index := range orders {
					//止盈后止盈止损
					if orders[index].OrderType == 1 && v.TakeProfitRatio.Cmp(decimal.Zero) > 0 && v.TakeProfitRatio.Cmp(decimal.NewFromInt(100)) < 0 {
						reduceChildOrders, err := makeReduceTakeAndStoploss(&(orders[index]), v, tradeSet, true)

						if err != nil {
							logger.Error("生产止盈后止盈、减仓失败")
							return err
						}

						if len(reduceChildOrders) == 0 {
							continue
						}

						if err := e.Orm.Create(&reduceChildOrders).Error; err != nil {
							logger.Error("报错止盈后止盈止损失败")
							return err
						}
					}
				}

				//减仓单绑定减仓未成交策略
				if newOrder.OrderType == 4 && reduceStrategy.Id > 0 {
					reduceOrderStrategys = append(reduceOrderStrategys, initOrderReduceStrategy(reduceStrategy, newOrder.Id))
				}
			}

			if len(reduceOrderStrategys) > 0 {
				if err := tx.Create(reduceOrderStrategys).Error; err != nil {
					logger.Error("保存减仓未成交策略失败")
					return err
				}
			}

			err = tx.Model(&models.LinePreOrderExt{}).Create(&preOrderExts).Error
			if err != nil {
				return err
			}

			//保存下单缓存
			return saveOrderCache(req, AddOrder, linestrategyTemplate)
		})
	}

	return nil
}

// 构建减仓单减仓策略
func initOrderReduceStrategy(reduceStrategy models.LineReduceStrategy, orderId int) models.LineOrderReduceStrategy {
	result := models.LineOrderReduceStrategy{}
	strategys := make([]dto.LineReduceStrategyItemResp, 0)
	result.OrderId = orderId
	result.ReduceStrategyId = reduceStrategy.Id
	result.Actived = 2

	for _, item := range reduceStrategy.Items {
		strategys = append(strategys, dto.LineReduceStrategyItemResp{
			LossPercent: item.LossPercent,
			OrderType:   item.OrderType,
			Actived:     2,
		})
	}

	str, _ := sonic.MarshalString(strategys)

	if str != "" {
		result.ItemContent = str
	}

	return result
}

// 保存下单缓存
func saveOrderCache(req *dto.LineAddPreOrderReq, AddOrder models.LinePreOrder, linestrategyTemplate models.LineStrategyTemplate) error {
	var preKey string
	var marshal []byte

	switch {
	//策略下单
	case req.StrategyTemplateType == 1 && req.StrategyTemplateId > 0:

		list := dto.StrategyOrderRedisList{
			Id:          AddOrder.Id,
			OrderSn:     AddOrder.OrderSn,
			ApiId:       AddOrder.ApiId,
			Symbol:      AddOrder.Symbol,
			Price:       AddOrder.Price,
			Site:        AddOrder.Site,
			QuoteSymbol: AddOrder.QuoteSymbol,
		}
		list.Direction = linestrategyTemplate.Direction
		list.Percentag = linestrategyTemplate.Percentag
		list.CompareType = linestrategyTemplate.CompareType
		list.TimeSlotStart = linestrategyTemplate.TimeSlotStart
		list.TimeSlotEnd = linestrategyTemplate.TimeSlotEnd

		marshal, _ = sonic.Marshal(&list)
		if AddOrder.SymbolType == global.SYMBOL_SPOT {
			preKey = fmt.Sprintf(rediskey.StrategySpotOrderList, AddOrder.ExchangeType)
		} else {
			preKey = fmt.Sprintf(rediskey.StrategyFutOrderList, AddOrder.ExchangeType)
		}

	//直接下单
	default:
		list := dto.PreOrderRedisList{
			Id:          AddOrder.Id,
			Symbol:      AddOrder.Symbol,
			Price:       AddOrder.Price,
			Site:        AddOrder.Site,
			ApiId:       AddOrder.ApiId,
			OrderSn:     AddOrder.OrderSn,
			QuoteSymbol: AddOrder.QuoteSymbol,
		}
		marshal, _ = sonic.Marshal(&list)
		if AddOrder.SymbolType == global.SYMBOL_SPOT {
			preKey = fmt.Sprintf(rediskey.PreSpotOrderList, AddOrder.ExchangeType)
		} else {
			preKey = fmt.Sprintf(rediskey.PreFutOrderList, AddOrder.ExchangeType)
		}
	}

	err := helper.DefaultRedis.LPushList(preKey, string(marshal))

	return err
}

// 生成加仓单
func createPreAddPosition(preOrder *models.LinePreOrder, v models.LinePreOrderExt, tradeSet models2.TradeSet) models.LinePreOrder {
	data := models.LinePreOrder{}
	//主单类型
	if v.AddPositionVal.Cmp(decimal.Zero) <= 0 {
		logger.Errorf("预生成加仓单失败, 主订单号：%s 加仓数值不大于0", preOrder.OrderSn)
		return data
	}

	price := utility.StrToDecimal(preOrder.Price)
	copier.Copy(&data, preOrder)

	data.Id = 0
	data.Pid = preOrder.Id
	data.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
	data.MainId = preOrder.Id

	if preOrder.MainId > 0 {
		data.MainId = preOrder.MainId
	}

	data.CreatedAt = time.Now()
	data.MainOrderType = v.OrderType
	data.Status = 0
	data.OrderCategory = 3
	data.Rate = v.PriceRatio.String()
	var percentage decimal.Decimal

	if data.Site == "BUY" {
		percentage = decimal.NewFromInt(1).Sub(v.PriceRatio.Div(decimal.NewFromInt(100)))
	} else {
		percentage = decimal.NewFromInt(1).Add(v.PriceRatio.Div(decimal.NewFromInt(100)))
	}

	//过期时间
	if v.ExpirateHour <= 0 {
		data.ExpireTime = time.Now().AddDate(10, 0, 0)
	} else {
		data.ExpireTime = time.Now().Add(time.Hour * time.Duration(v.ExpirateHour))
	}

	dataPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
	data.Price = dataPrice.String()

	if v.AddPositionType == 1 {
		buyPrice := utility.StrToDecimal(preOrder.BuyPrice).Mul(utility.SafeDiv(v.AddPositionVal, decimal.NewFromInt(100))).Truncate(2)
		data.Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
		data.BuyPrice = buyPrice.String()
	} else {
		data.BuyPrice = v.AddPositionVal.Truncate(2).String()
		data.Num = utility.SafeDiv(v.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
	}

	return data
}

// 生成减仓单
func createPreReduceOrder(preOrder *models.LinePreOrder, ext models.LinePreOrderExt, tradeSet models2.TradeSet) models.LinePreOrder {
	var stopOrder models.LinePreOrder

	//减仓单
	if ext.PriceRatio.Cmp(decimal.Zero) > 0 {
		copier.Copy(&stopOrder, preOrder)

		stopOrder.Id = 0
		stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
		stopOrder.Pid = preOrder.Id
		stopOrder.MainId = preOrder.Id

		if preOrder.MainId > 0 {
			stopOrder.MainId = preOrder.MainId
		}

		stopOrder.OrderType = 4
		stopOrder.Status = 0
		stopOrder.Rate = ext.PriceRatio.String()
		stopOrder.Num = ext.TotalAfter.Sub(ext.TotalBefore).Abs().Truncate(int32(tradeSet.AmountDigit)).String()
		stopOrder.BuyPrice = "0"
		stopOrder.Rate = ext.PriceRatio.String()

		if strings.ToUpper(preOrder.Site) == "BUY" {
			stopOrder.Site = "SELL"
		} else {
			stopOrder.Site = "BUY"
		}

		//过期时间
		if ext.ExpirateHour <= 0 {
			stopOrder.ExpireTime = time.Now().AddDate(10, 0, 0)
		} else {
			stopOrder.ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour))
		}

		binanceservice.SetPrice(&stopOrder, preOrder, tradeSet)
	}

	return stopOrder
}

// 构建止盈、止盈止损
func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreOrderExt, tradeSet models2.TradeSet) ([]models.LinePreOrder, error) {
	orders := make([]models.LinePreOrder, 0)
	mainId := preOrder.Id
	var side string

	if (preOrder.OrderType != 0 && strings.ToUpper(preOrder.Site) == "BUY") || (preOrder.OrderType == 0 && strings.ToUpper(preOrder.Site) == "SELL") {
		side = "BUY"
	} else {
		side = "SELL"
	}

	if preOrder.MainId > 0 {
		mainId = preOrder.MainId
	}

	if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 {
		// 止盈单
		profitOrder := models.LinePreOrder{}
		copier.Copy(&profitOrder, preOrder)

		profitOrder.Id = 0
		profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
		profitOrder.Pid = preOrder.Id
		profitOrder.OrderType = 1
		profitOrder.Status = 0
		profitOrder.MainId = mainId
		profitOrder.BuyPrice = "0"
		profitOrder.Site = side

		if ext.TakeProfitNumRatio.Cmp(decimal.Zero) <= 0 || ext.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) >= 0 {
			profitOrder.Num = ext.TotalAfter.Truncate(int32(tradeSet.AmountDigit)).String()
		} else {
			profitOrder.Num = ext.TotalAfter.Mul(utility.SafeDiv(ext.TakeProfitNumRatio, decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String()
		}

		// 止盈需要累加之前的亏损
		profitOrder.Rate = ext.TakeProfitRatio.Add(ext.ReTakeRatio).Truncate(2).String()

		binanceservice.SetPrice(&profitOrder, preOrder, tradeSet)
		orders = append(orders, profitOrder)
	}

	if ext.StopLossRatio.Cmp(decimal.Zero) > 0 {
		lossOrder := models.LinePreOrder{}
		copier.Copy(&lossOrder, preOrder)

		lossOrder.Id = 0
		lossOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
		lossOrder.Pid = preOrder.Id
		lossOrder.OrderType = 2
		lossOrder.Status = 0
		lossOrder.MainId = mainId
		lossOrder.BuyPrice = "0"
		lossOrder.Num = ext.TotalAfter.Truncate(int32(tradeSet.AmountDigit)).String()
		lossOrder.Rate = ext.StopLossRatio.Truncate(2).String()
		lossOrder.Site = side

		binanceservice.SetPrice(&lossOrder, preOrder, tradeSet)
		orders = append(orders, lossOrder)
	}

	return orders, nil
}

// 构建止盈后止盈止损
// parentOrder 父订单
// remainQuantity 剩余数量
// tpPriceRatio 止盈价格比例
// slPriceRatio 止损价格比例
func makeTpOrder(parentOrder *models.LinePreOrder, reminQuantity decimal.Decimal, tpPriceRatio, slPriceRatio decimal.Decimal, tradeSet *models2.TradeSet) ([]models.LinePreOrder, error) {
	result := make([]models.LinePreOrder, 0)
	tp := models.LinePreOrder{}
	sl := models.LinePreOrder{}
	copier.Copy(&tp, parentOrder)

	tp.Id = 0
	tp.Pid = parentOrder.Id
	tp.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
	tp.OrderType = 1
	tp.Status = 0
	tp.Rate = tpPriceRatio.String()
	tp.Num = reminQuantity.String()
	binanceservice.SetPrice(&tp, parentOrder, *tradeSet)
	result = append(result, tp)

	if (slPriceRatio).Cmp(decimal.Zero) > 0 {
		copier.Copy(&sl, parentOrder)
		sl.Pid = parentOrder.Id
		sl.Id = 0
		sl.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
		sl.OrderType = 2
		sl.Num = reminQuantity.Truncate(int32(tradeSet.AmountDigit)).String()
		sl.Rate = slPriceRatio.String()
		binanceservice.SetPrice(&sl, parentOrder, *tradeSet)
		result = append(result, sl)
	}

	return result, nil
}

// 构建减仓后止盈止损
// isTpTp 是否止盈后止盈止损
func makeReduceTakeAndStoploss(parentOrder *models.LinePreOrder, ext models.LinePreOrderExt, tradeSet models2.TradeSet, isTpTp bool) ([]models.LinePreOrder, error) {
	orders := make([]models.LinePreOrder, 0)
	num := ext.TotalAfter
	var takeProfitRatio, slPriceRatio decimal.Decimal
	if isTpTp {
		takeProfitRatio = ext.TpTpPriceRatio
		slPriceRatio = ext.TpSlPriceRatio
	} else {
		takeProfitRatio = ext.TakeProfitRatio
		slPriceRatio = ext.StopLossRatio
	}

	if ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && ext.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) < 0 {
		percent := decimal.NewFromInt(1).Sub(utility.SafeDiv(ext.TakeProfitNumRatio, decimal.NewFromInt(100)))
		num = ext.TotalAfter.Mul(percent).Truncate(int32(tradeSet.AmountDigit))
	}

	if takeProfitRatio.Cmp(decimal.Zero) > 0 && num.Cmp(decimal.Zero) > 0 {
		takeProfitOrder := models.LinePreOrder{}
		copier.Copy(&takeProfitOrder, parentOrder)
		takeProfitOrder.Id = 0
		takeProfitOrder.Pid = parentOrder.Id
		takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
		takeProfitOrder.Status = 0
		takeProfitOrder.OrderType = 1
		takeProfitOrder.SignPrice = parentOrder.Price
		takeProfitOrder.CreatedAt = time.Now()
		takeProfitOrder.BuyPrice = "0"
		takeProfitOrder.MainOrderType = "LIMIT"
		takeProfitOrder.Num = num.String()
		//止盈需要累加之前的亏损
		if isTpTp {
			takeProfitOrder.Rate = takeProfitRatio.Truncate(2).String()
		} else {
			takeProfitOrder.Rate = takeProfitRatio.Add(ext.ReTakeRatio).Truncate(2).String()
		}

		takeProfitOrder.BuyPrice = "0"

		binanceservice.SetPrice(&takeProfitOrder, parentOrder, tradeSet)
		orders = append(orders, takeProfitOrder)
	}
	//有止损单
	if slPriceRatio.Cmp(decimal.Zero) > 0 && num.Cmp(decimal.Zero) > 0 {
		var stoploss models.LinePreOrder

		copier.Copy(&stoploss, parentOrder)
		stoploss.Id = 0
		stoploss.Pid = parentOrder.Id
		stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
		stoploss.Status = 0
		stoploss.CreatedAt = time.Now()
		stoploss.OrderType = 2
		stoploss.SignPrice = parentOrder.Price
		stoploss.BuyPrice = "0"
		stoploss.Rate = slPriceRatio.String()
		stoploss.MainOrderType = "LIMIT"
		stoploss.Num = num.String()
		stoploss.BuyPrice = "0"

		binanceservice.SetPrice(&stoploss, parentOrder, tradeSet)
		orders = append(orders, stoploss)
	}

	return orders, nil
}

// CheckRepeatOrder 检查重复下单 检查基础货币
func (e *LinePreOrder) CheckRepeatOrder(symbolType int, apiUserId, site, baseCoin string) int64 {
	var count int64
	e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND pid=0 AND symbol like ? AND symbol_type = ? AND site = ? AND `status` IN (1,5,6)", apiUserId, baseCoin+"%", symbolType, site).Count(&count)
	return count
}

// AddBatchPreOrder 批量添加
func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p *actions.DataPermission, errs *[]error) error {
	if batchReq.SaveTemplate == "2" || batchReq.SaveTemplate == "1" { //2 = 只保存模板 1= 保存模板并下单
		var templateLog dto.LineBatchAddPreOrderReq
		copier.Copy(&templateLog, batchReq)
		templateLog.SaveTemplate = "0"
		templateLog.TemplateName = ""
		marshal, _ := sonic.Marshal(templateLog)
		saveTemplateParams := models.LineOrderTemplateLogs{
			Name:   batchReq.TemplateName,
			UserId: 0,
			Params: string(marshal),
			Type:   2,
			Switch: "0",
		}

		saveTemplateParams.CreateBy = p.UserId
		e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams)
	}

	if batchReq.SaveTemplate == "2" {
		return nil
	}

	if batchReq.SymbolGroupId != "" {
		var symbolGroupInfo models.LineSymbolGroup
		e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", utility.StringToInt(batchReq.SymbolGroupId)).Find(&symbolGroupInfo)
		if symbolGroupInfo.Id <= 0 || symbolGroupInfo.Symbol == "" {
			*errs = append(*errs, errors.New(fmt.Sprintf("选择的交易对组:%s不存在或交易对组的交易对为空", batchReq.SymbolGroupId)))
			return nil
		}
		batchReq.Symbol = symbolGroupInfo.Symbol
	}

	apiUserIds := strings.Split(batchReq.ApiUserId, ",")

	if batchReq.Script == "1" {
		//scriptLogs := make([]models.LinePreScript, 0)
		logParams := *batchReq
		for _, id := range apiUserIds {
			for j := 1; j <= batchReq.OrderNum; j++ {
				var log models.LinePreScript
				logParams.SaveTemplate = "0"
				logParams.TemplateName = ""
				logParams.Script = ""
				logParams.ApiUserId = id
				marshal, _ := sonic.Marshal(logParams)
				log.ApiId = utility.ToInt64(id)
				log.ScriptNum = int64(j)
				log.ScriptParams = string(marshal)
				log.AdminId = 0
				log.Status = "0"
				//scriptLogs = append(scriptLogs, log)
				err := e.Orm.Model(&models.LinePreScript{}).Create(&log).Error
				if err != nil {
					*errs = append(*errs, fmt.Errorf("记录脚本失败:%+v", err.Error()))
					return nil
				}
				helper.DefaultRedis.RPushList(rediskey.PreOrderScriptList, utility.IntToString(log.Id))
			}
		}

		return nil
	}
	for _, id := range apiUserIds {
		for j := 0; j < batchReq.OrderNum; j++ {
			symbols := strings.Split(batchReq.Symbol, ",")
			for _, symbol := range symbols {
				var req dto.LineAddPreOrderReq
				req.ExchangeType = batchReq.ExchangeType
				req.OrderType = batchReq.OrderType
				req.Symbol = symbol
				req.ApiUserId = id
				req.Site = batchReq.Site
				req.BuyPrice = batchReq.BuyPrice
				req.PricePattern = batchReq.PricePattern
				req.Price = batchReq.Price
				req.Profit = batchReq.Profit
				req.ProfitNumRatio = batchReq.ProfitNumRatio
				req.ProfitTpTpPriceRatio = batchReq.ProfitTpTpPriceRatio
				req.ProfitTpSlPriceRatio = batchReq.ProfitTpSlPriceRatio
				req.Ext = batchReq.Ext
				req.SymbolType = batchReq.SymbolType
				req.ReducePriceRatio = batchReq.ReducePriceRatio
				req.PriceType = batchReq.PriceType
				req.CoverType = batchReq.CoverType
				req.ExpireHour = batchReq.ExpireHour
				req.MainOrderType = batchReq.MainOrderType
				req.ReduceNumRatio = batchReq.ReduceNumRatio
				req.ReduceStopLossRatio = batchReq.ReduceStopLossRatio
				req.ReduceTakeProfitRatio = batchReq.ReduceTakeProfitRatio
				req.CreateBy = batchReq.CreateBy

				e.AddPreOrderCheck(&req, p, errs)
			}
		}
	}
	return nil
}

// QuickAddPreOrder 模板快速下单
func (e *LinePreOrder) QuickAddPreOrder(quickReq *dto.QuickAddPreOrderReq, p *actions.DataPermission, userId int, errs *[]error) error {
	templateLogs := make([]models.LineOrderTemplateLogs, 0)
	e.Orm.Model(&models.LineOrderTemplateLogs{}).Where("id in ?", strings.Split(quickReq.Ids, ",")).Find(&templateLogs)
	for _, log := range templateLogs {
		//单独添加
		if log.Type == 1 {
			var addPreOrderParams dto.LineAddPreOrderReq
			sonic.Unmarshal([]byte(log.Params), &addPreOrderParams)

			err := e.AddPreOrderCheck(&addPreOrderParams, p, errs)
			// err := e.AddPreOrder(&addPreOrderParams, p, errs, tickerSymbol)
			if err != nil {
				*errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 生成订单失败", addPreOrderParams.ApiUserId, addPreOrderParams.Symbol))
				continue
			}
		}

		//批量添加
		if log.Type == 2 {
			var batchAddPreOrder dto.LineBatchAddPreOrderReq
			sonic.Unmarshal([]byte(log.Params), &batchAddPreOrder)

			if userId > 0 {
				batchAddPreOrder.CreateBy = userId
			} else {
				batchAddPreOrder.CreateBy = log.CreateBy
			}

			e.AddBatchPreOrder(&batchAddPreOrder, p, errs)
		}
	}
	return nil
}

// Lever 设置杠杆
func (e *LinePreOrder) Lever(req *dto.LeverReq, p *actions.DataPermission, errs *[]error) {
	users := make([]models.LineApiUser, 0)
	err := e.Orm.Model(&models.LineApiUser{}).Where("id in ? AND exchange_type = ?", strings.Split(req.ApiUserIds, ","), req.ExchangeType).Find(&users).Error
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		*errs = append(*errs, errors.New(fmt.Sprintf("设置杠杆失败:%+v", err.Error())))
		return
	}
	var symbols string
	if req.Symbol != "" {
		symbols = req.Symbol
	} else {
		var symbolGroupInfo models.LineSymbolGroup
		e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ? AND exchange_type = ?", req.GroupId, req.ExchangeType).Find(&symbolGroupInfo)
		symbols = symbolGroupInfo.Symbol
	}

	if req.IsAll == 1 {
		lineSymbols := make([]models.LineSymbol, 0)
		futSymbols := make([]string, 0)
		e.Orm.Model(&models.LineSymbol{}).Where("type = '2' AND switch = '1' AND exchange_type = ?", req.ExchangeType).Find(&lineSymbols)
		for _, symbol := range lineSymbols {
			futSymbols = append(futSymbols, symbol.Symbol)
		}
		if len(futSymbols) != 0 {
			symbols = strings.Join(futSymbols, ",")
		}
	}

	if req.ExchangeType == global.EXCHANGE_BINANCE {
		e.SetBinanceLever(req, users, symbols, errs)
	}

	return
}

// SetBinanceLever 设置币安杠杆
func (e *LinePreOrder) SetBinanceLever(req *dto.LeverReq, users []models.LineApiUser, symbols string, errs *[]error) {
	for _, user := range users {
		var client *helper.BinanceClient
		if user.UserPass == "" {
			client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "", user.IpAddress)
		} else {
			client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "socks5", user.UserPass+"@"+user.IpAddress)
		}
		//client.SendFuturesRequestAuth("/")
		symbolsSlice := strings.Split(symbols, ",")
		for _, s := range symbolsSlice {
			params := map[string]string{
				"leverage": utility.IntToString(req.Leverage),
				"symbol":   s,
			}
			resp, _, err := client.SendFuturesRequestAuth("/fapi/v1/leverage", "POST", params)
			if err != nil {
				*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 交易对:%s 设置杠杆失败:%+v", user.Id, s, err.Error())))
				continue
			}
			var dataMap map[string]interface{}
			if err := sonic.Unmarshal(resp, &dataMap); err != nil {
				*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 交易对:%s 设置杠杆失败:%+v", user.Id, s, err.Error())))
				continue
			}

			if _, ok := dataMap["leverage"]; !ok {
				*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 交易对:%s 设置杠杆失败:%+v", user.Id, s, dataMap["message"])))
				continue
			}
		}
	}
}

// SetBinanceMarginType 设置币安保证金模式
func (e *LinePreOrder) SetBinanceMarginType(req *dto.MarginTypeReq, users []models.LineApiUser, symbols string, errs *[]error) {
	for _, user := range users {
		var client *helper.BinanceClient
		if user.UserPass == "" {
			client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "", user.IpAddress)
		} else {
			client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "socks5", user.UserPass+"@"+user.IpAddress)
		}
		//client.SendFuturesRequestAuth("/")
		symbolsSlice := strings.Split(symbols, ",")
		for _, s := range symbolsSlice {
			params := map[string]string{
				"marginType": req.MarginType,
				"symbol":     s,
			}
			_, _, err := client.SendFuturesRequestAuth("/fapi/v1/marginType", "POST", params)
			if err != nil {
				var dataMap map[string]interface{}
				if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
					*errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, err.Error()))
					continue
				}
				code, ok := dataMap["code"]
				if !ok {
					*errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, dataMap["message"]))
					continue
				}

				if code.(float64) != 200 {
					var msg string
					codeInt := utility.ToInt(code)

					if codeInt == -4046 {
						continue
					}

					if val, ok := messages.BinanceFutureMessage[codeInt]; ok {
						msg = val
					} else {
						msg = dataMap["message"].(string)
					}

					*errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, msg))
					continue
				}
				// *errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, err.Error()))
				continue
			}
		}
	}
}

// MarginType 设置仓位模式
func (e *LinePreOrder) MarginType(req *dto.MarginTypeReq, p *actions.DataPermission, errs *[]error) {
	users := make([]models.LineApiUser, 0)
	err := e.Orm.Model(&models.LineApiUser{}).Where("id in  ? ", strings.Split(req.ApiUserIds, ",")).Find(&users).Error
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		*errs = append(*errs, errors.New(fmt.Sprintf("设置杠杆失败:%+v", err.Error())))
		return
	}
	var symbols string
	if req.Symbol != "" {
		symbols = req.Symbol
	} else {
		var symbolGroupInfo models.LineSymbolGroup
		e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", req.GroupId).Find(&symbolGroupInfo)
		symbols = symbolGroupInfo.Symbol
	}

	if req.IsAll == 1 {
		lineSymbols := make([]models.LineSymbol, 0)
		futSymbols := make([]string, 0)
		e.Orm.Model(&models.LineSymbol{}).Where("type = '2' AND switch = '1'").Find(&lineSymbols)
		for _, symbol := range lineSymbols {
			futSymbols = append(futSymbols, symbol.Symbol)
		}
		if len(futSymbols) != 0 {
			symbols = strings.Join(futSymbols, ",")
		}
	}

	if req.ExchangeType == global.EXCHANGE_BINANCE {
		e.SetBinanceMarginType(req, users, symbols, errs)
	}
}

// CancelOpenOrder 取消委托
func (e *LinePreOrder) CancelOpenOrder(req *dto.CancelOpenOrderReq, errs *[]error) {
	newClientOrderIdList := make([]string, 0)
	var apiUserInfo models.LineApiUser
	e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo)
	futApi := binanceservice.FutRestApi{}
	spotApi := binanceservice.SpotRestApi{}

	//取消指定订单号的委托
	if req.OrderSn != "" && req.Symbol != "" {
		var err error
		var orderInfo models.LinePreOrder
		e.Orm.Model(&models.LinePreOrder{}).Where("symbol = ? AND order_sn = ? AND status = '5'", req.Symbol, req.OrderSn).Find(&orderInfo)
		if orderInfo.Id <= 0 {
			*errs = append(*errs, fmt.Errorf("取消委托失败:%+v", "未找到可以取消的委托"))
			return
		}
		if req.OrderType == 1 { //现货
			err = spotApi.CancelOpenOrderByOrderSn(apiUserInfo, req.Symbol, req.OrderSn)
		} else {
			newClientOrderIdList = append(newClientOrderIdList, req.OrderSn)
			err = futApi.CancelBatchFutOrder(apiUserInfo, req.Symbol, newClientOrderIdList)
		}
		if err != nil {
			*errs = append(*errs, fmt.Errorf("取消委托失败:%+v", err.Error()))
			return
		}
	} else {
		var orderList []models.LinePreOrder
		e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND main_id = 0 status = '5'", req.ApiId).Find(&orderList)
		if len(orderList) <= 0 {
			*errs = append(*errs, fmt.Errorf("没有可撤销的委托"))
			return
		}
		for _, order := range orderList {

			if order.SymbolType == global.SYMBOL_SPOT {
				err := spotApi.CancelOpenOrderByOrderSn(apiUserInfo, req.Symbol, req.OrderSn)
				if err != nil {
					*errs = append(*errs, fmt.Errorf("取消委托失败:%+v", err.Error()))
					continue
				}
			} else {
				err := futApi.CancelBatchFutOrder(apiUserInfo, order.Symbol, []string{order.OrderSn})
				if err != nil {
					*errs = append(*errs, fmt.Errorf("取消委托失败:%+v", err.Error()))
					continue
				}
			}
		}
	}
}

// ClearAll 一键清除数据
func (e *LinePreOrder) ClearAll() error {
	_, err := helper.DefaultRedis.BatchDeleteKeys([]string{rediskey.PreSpotOrderList, rediskey.PreFutOrderList})
	if err != nil {
		e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err)
		return err
	}
	prefixs := []string{
		"spot_stoploss_list",
		"futures_stoploss_list",
		"spot_add_position_list",
		"futures_add_position_list",
		"api_user_hold",
		"spot_trigger_lock",
		"fut_trigger_lock",
		"fut_trigger_stop_lock",
		"spot_trigger_stop_lock",
		"spot_addposition_trigger",
		"fut_addposition_trigger",
		"spot_hedge_close_position",
		"futures_hedge_close_position",
		"spot_callback",
		"fut_callback",
		"holde_a",
		"holde_b",
		"stop_loss_markt",
		"_PreSpotOrderList_",
		"_PreFutOrderList_",
		"spot_reduce_list",
		"futures_reduce_list",
		"spot_reduce_strategy_list",
		"fut_reduce_strategy_list",
		"future_position",
		"spot_position",
		"strategy_spot_order_list",
		"strategy_fut_order_list",
	}
	err = helper.DefaultRedis.DeleteKeysByPrefix(prefixs...)
	if err != nil {
		e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err)
		return err
	}

	e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order")             //订单表
	e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_status")      //订单拓展状态
	e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_ext")         //订单拓展配置
	e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_order_reduce_strategy") //订单减仓策略配置
	return err
}

// GetTargetSymbol 获取目标交易对信息
func (e *LinePreOrder) GetTargetSymbol(symbol string, symbolType int) (string, bool, models.LineSymbol, error) {
	var targetSymbol string
	var notUsdt bool
	var symbolInfo models.LineSymbol

	// 处理非 USDT 交易对
	if !strings.HasSuffix(symbol, "USDT") {
		notUsdt = true
		if err := e.Orm.Model(&models.LineSymbol{}).Where("symbol = ? AND type = ?", symbol, utility.IntToString(symbolType)).Find(&symbolInfo).Error; err != nil {
			return "", false, models.LineSymbol{}, err
		}
		if symbolInfo.Id <= 0 {
			return "", false, models.LineSymbol{}, fmt.Errorf("未找到交易对信息")
		}
		targetSymbol = symbolInfo.BaseAsset + "USDT"
	} else {
		targetSymbol = symbol
	}

	return targetSymbol, notUsdt, symbolInfo, nil
}

func (e *LinePreOrder) GetOrderInfo(req dto.ManuallyCover, symbol, orderType, site, status string) (models.LinePreOrder, error) {
	var orderInfo models.LinePreOrder
	if err := e.Orm.Model(models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND site = ? AND status = ?", req.ApiId, symbol, orderType, site, status).Find(&orderInfo).Error; err != nil {
		return models.LinePreOrder{}, err
	}
	if orderInfo.Id <= 0 {
		return models.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
	}
	return orderInfo, nil
}

func (e *LinePreOrder) GetFutOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (models.LinePreOrder, error) {
	var orderInfo models.LinePreOrder
	if err := e.Orm.Model(models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 2", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil {
		return models.LinePreOrder{}, err
	}
	if orderInfo.Id <= 0 {
		return models.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
	}
	return orderInfo, nil
}

// GetFutSpotOrderInfo 获取合约对现货的订单信息
func (e *LinePreOrder) GetFutSpotOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (models.LinePreOrder, error) {
	var orderInfo models.LinePreOrder
	if err := e.Orm.Model(models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 3", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil {
		return models.LinePreOrder{}, err
	}
	if orderInfo.Id <= 0 {
		return models.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
	}
	return orderInfo, nil
}

// CalculateAmount 计算加仓数量
func (e *LinePreOrder) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice decimal.Decimal, amountDigit int, notUsdt bool, symbolInfo models.LineSymbol) (decimal.Decimal, error) {
	var amt decimal.Decimal
	if req.CoverType == 1 {
		decimalValue := utility.SafeDiv(utility.StringToDecimal(req.Value), decimal.NewFromInt(100))
		amt = totalNum.Mul(decimalValue)
	} else {
		decimalValue := utility.StringToDecimal(req.Value)
		if notUsdt {
			tickerSymbolMaps, _ := cacheservice.GetExchangeTradeSets(global.EXCHANGE_BINANCE, 2)
			var tickerPrice decimal.Decimal
			for _, symbolMap := range tickerSymbolMaps {
				if symbolMap.Symbol == strings.ToUpper(symbolInfo.BaseAsset+"USDT") {
					tickerPrice, _ = decimal.NewFromString(symbolMap.LastPrice)
					break
				}
			}

			for _, symbolMap := range tickerSymbolMaps {
				if symbolMap.Symbol == strings.ToUpper(symbolInfo.QuoteAsset+"USDT") {
					uTickerPrice, _ := decimal.NewFromString(symbolMap.LastPrice)
					div := utility.SafeDiv(tickerPrice, decimal.NewFromInt(1).Div(uTickerPrice))
					amt = utility.SafeDiv(decimalValue, div)
					break
				}
			}
		} else {
			amt = decimalValue.Div(lastPrice)
		}
	}
	return amt.Truncate(int32(amountDigit)), nil
}

// SpotClosePosition 现货单个交易对平仓
func (e *LinePreOrder) SpotClosePosition(position *dto.ClosePosition, errs *[]error) {
	var apiUserInfo models.LineApiUser
	e.Orm.Model(&models.LineApiUser{}).Where("id = ?", position.ApiId).Find(&apiUserInfo)
	client := binanceservice.GetClient(&apiUserInfo)

	resp, _, err := client.SendSpotAuth("/api/v3/account", "GET", map[string]interface{}{
		"omitZeroBalances": true,
	})

	if err != nil {
		*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 获取账户信息失败", position.ApiId)))
		return
	}
	var balanceInfo binanceservice.SpotAccountInfo
	sonic.Unmarshal(resp, &balanceInfo)

	if len(balanceInfo.Balances) == 0 {
		*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 没有可平仓的交易对", position.ApiId)))
	}

	//查询已经开仓的现货交易对
	var spotList []models.LinePreOrder
	if position.Symbol == "" { //全平
		e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ?  AND symbol_type =1 AND status = 6 AND pid = 0 AND order_type = 0", position.ApiId).Find(&spotList)
	} else {
		e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ?  AND symbol_type =1 AND symbol = ? AND status = 6 AND pid = 0 AND order_type = 0", position.ApiId, position.Symbol).Find(&spotList)
	}
	if len(spotList) <= 0 {
		*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 没有可平仓的交易对", position.ApiId)))
	}
	api := binanceservice.SpotRestApi{}

	if len(spotList) == 0 {
		*errs = append(*errs, errors.New("无仓可平"))
		return
	}

	for _, list := range spotList {
		for _, balance := range balanceInfo.Balances {
			suffix := utility.ReplaceSuffix(list.Symbol, list.QuoteSymbol, "")
			if utility.StringToDecimal(balance.Free).GreaterThan(decimal.Zero) && balance.Asset == suffix {
				//锁仓数量大于0
				if utility.StringToDecimal(balance.Locked).GreaterThan(decimal.Zero) {
					//撤销之前的委托
					client.SendSpotAuth("/api/v3/openOrders", "DELETE", map[string]string{
						"symbol": list.Symbol,
					})
				}
				key := fmt.Sprintf(global.TICKER_SPOT, list.ExchangeType, list.Symbol)
				tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key)
				total := utility.StringToDecimal(balance.Free).Add(utility.StringToDecimal(balance.Locked)).Truncate(int32(tradeSet.AmountDigit))
				lastPrice := api.GetSpotSymbolLastPrice(list.Symbol)
				var price decimal.Decimal
				paramsMaps := make(map[string]string)
				if total.GreaterThan(decimal.NewFromFloat(tradeSet.MinQty)) {
					paramsMaps = map[string]string{
						"symbol":           list.Symbol,
						"side":             "SELL",
						"quantity":         total.String(),
						"type":             "LIMIT",
						"newClientOrderId": utility.Int64ToString(snowflakehelper.GetOrderId()),
						"timeInForce":      "GTC",
					}
					price = lastPrice.Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(utility.StringToDecimal(position.Rate), decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
					paramsMaps["price"] = price.String()
				} else {
					*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 下单数量小于最小下单数量", position.ApiId)))
					continue
				}
				order := models.LinePreOrder{
					ExchangeType:  global.EXCHANGE_BINANCE,
					Pid:           list.Id,
					MainId:        list.Id,
					ApiId:         list.ApiId,
					GroupId:       "0",
					Symbol:        list.Symbol,
					SymbolType:    position.CloseType,
					QuoteSymbol:   list.QuoteSymbol,
					SignPrice:     lastPrice.String(),
					SignPriceType: "new",
					Rate:          position.Rate,
					Price:         price.String(),
					Num:           total.String(),
					BuyPrice:      "0",
					Site:          "SELL",
					OrderSn:       paramsMaps["newClientOrderId"],
					OrderType:     3,
					Desc:          "",
					Status:        0,
					CoverType:     list.CoverType,
					ExpireTime:    time.Now().Add(time.Hour * 24 * 30),
					MainOrderType: list.MainOrderType,
					Child:         nil,
					OrderCategory: 1,
				}
				err := e.Orm.Model(&models.LinePreOrder{}).Create(&order).Error
				if err != nil {
					*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 生成订单失败:%s", position.ApiId, err.Error())))
					continue
				}

				//下订单
				_, _, err = client.SendSpotAuth("/api/v3/order", "POST", paramsMaps)
				if err != nil {
					*errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 币安下订单失败:%s", position.ApiId, err.Error())))
					continue
				}
				binanceservice.MainClosePositionClearCache(list.Id, list.SymbolType)
			}
		}
	}
}

// FutClosePosition 合约平仓
func (e *LinePreOrder) FutClosePosition(position *dto.ClosePosition, errs *[]error) {
	var apiUserInfo models.LineApiUser
	e.Orm.Model(&models.LineApiUser{}).Where("id = ?", position.ApiId).Find(&apiUserInfo)
	//client := binanceservice.GetClient(&apiUserInfo)

	//查询已经开仓的合约交易对
	var futList []models.LinePreOrder
	if position.Symbol == "" {
		query := e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND status = 6 AND symbol_type =2 AND order_type =0 AND main_id = 0", position.ApiId)

		switch strings.ToUpper(position.Side) {
		case "BUY":
			query = query.Where("site = 'BUY'")
		case "SELL":
			query = query.Where("site = 'SELL'")
		}

		query.Find(&futList)
	} else {
		query := e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND symbol_type =2 AND status = 6 AND order_type = 0 AND main_id = 0", position.ApiId, position.Symbol)

		switch strings.ToUpper(position.Side) {
		case "BUY":
			query = query.Where("side = 'BUY'")
		case "SELL":
			query = query.Where("side = 'SELL'")
		}

		query.Find(&futList)
	}
	if len(futList) <= 0 {
		*errs = append(*errs, fmt.Errorf("api_id:%d 没有可平仓的交易对", position.ApiId))
		return
	}

	api := binanceservice.FutRestApi{}

	if len(futList) == 0 {
		*errs = append(*errs, errors.New("无仓可平"))
		return
	}

	for _, list := range futList {
		risks, err := api.GetPositionV3(&apiUserInfo, list.Symbol)
		if err != nil {
			*errs = append(*errs, fmt.Errorf("api_id:%d 获取仓位信息时 没有可平仓的交易对 err: %s", position.ApiId, err))
			continue
		}

		key := fmt.Sprintf(global.TICKER_FUTURES, list.ExchangeType, list.Symbol)
		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key)
		lastPrice := api.GetFutSymbolLastPrice(list.Symbol)
		parentId := list.Id

		if list.Pid > 0 {
			parentId = list.Pid
		}

		for _, risk := range risks {
			positionAmt := utility.StringToDecimal(risk.PositionAmt)

			var riskSide string
			var orderSide string
			if positionAmt.GreaterThan(decimal.Zero) {
				riskSide = "BUY"
			} else {
				riskSide = "SELL"
			}
			if riskSide == list.Site {
				var price decimal.Decimal
				if list.Site == "BUY" && risk.PositionSide == "LONG" { //做多
					//根据仓位数量去平多
					orderSide = "SELL"
					price = lastPrice.Mul(decimal.NewFromInt(1).Add(utility.SafeDiv(utility.StringToDecimal(position.Rate), decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
				} else if list.Site == "SELL" && risk.PositionSide == "SHORT" {
					orderSide = "BUY"
					price = lastPrice.Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(utility.StringToDecimal(position.Rate), decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
				}

				if price.LessThanOrEqual(decimal.Zero) {
					continue
				}

				if positionAmt.Abs().LessThanOrEqual(decimal.NewFromFloat(tradeSet.MinQty)) {
					*errs = append(*errs, fmt.Errorf("api_id:%d 下单数量小于最小下单数量", position.ApiId))
					continue
				}

				order := models.LinePreOrder{
					Pid:           parentId,
					ExchangeType:  list.ExchangeType,
					ApiId:         list.ApiId,
					MainId:        list.Id,
					GroupId:       "0",
					Symbol:        list.Symbol,
					SymbolType:    position.CloseType,
					QuoteSymbol:   list.QuoteSymbol,
					SignPrice:     lastPrice.String(),
					SignPriceType: "new",
					Rate:          position.Rate,
					Price:         price.String(),
					Num:           positionAmt.Abs().String(),
					BuyPrice:      "0",
					Site:          orderSide,
					OrderSn:       utility.Int64ToString(snowflakehelper.GetOrderId()),
					OrderType:     3,
					Desc:          "",
					Status:        0,
					CoverType:     list.CoverType,
					ExpireTime:    time.Now().Add(24 * 30 * time.Hour),
					MainOrderType: list.MainOrderType,
					Child:         nil,
					OrderCategory: 1,
				}
				err = e.Orm.Model(&models.LinePreOrder{}).Create(&order).Error
				if err != nil {
					*errs = append(*errs, fmt.Errorf("api_id:%d 生成平仓单错误:%s", position.ApiId, err))
					continue
				}

				//撤销合约的委托(根据方向撤)
				orderSns, _ := binanceservice.GetOpenOrderSns(e.Orm, []int{list.Id})
				if len(orderSns) > 0 {
					logger.Infof("平仓 取消 订单id ：%v 订单号: %v", orderSns)
				}
				api.CancelBatchFutOrder(apiUserInfo, list.Symbol, orderSns)
				// api.CancelAllFutOrder(apiUserInfo, list.Symbol)
				//side=BUY&positionSide=LONG是开多，
				//side=SELL&positionSide=LONG是平多，
				//side=SELL&positionSide=SHORT是开空，
				//side=BUY&positionSide=SHORT是平空。
				if orderSide != "" {
					if orderSide == "BUY" { //平空
						err = api.ClosePosition(list.Symbol, order.OrderSn, utility.StringToDecimal(order.Num), "BUY", "SHORT", apiUserInfo, "LIMIT", "0", price)
					} else { // 平多
						err = api.ClosePosition(list.Symbol, order.OrderSn, utility.StringToDecimal(order.Num), "SELL", "LONG", apiUserInfo, "LIMIT", "0", price)
					}
					if err != nil {
						*errs = append(*errs, fmt.Errorf("api_id:%d 币安平仓单错误:%s", position.ApiId, err))
						continue
					}
					// 主单平仓删除缓存
					binanceservice.MainClosePositionClearCache(list.Id, list.SymbolType)
				}
			}
		}
	}

}

// ClearUnTriggered 清除待触发的交易对
func (e *LinePreOrder) ClearUnTriggered() error {
	var orderLists []models.LinePreOrder
	positions := map[string]positiondto.LinePreOrderPositioinDelReq{}
	e.Orm.Model(&models.LinePreOrder{}).Where("main_id = 0 AND pid = 0 AND status = '0'").Find(&orderLists).Unscoped().Delete(&models.LinePreOrder{})

	for _, order := range orderLists {
		redisList := dto.PreOrderRedisList{
			Id:          order.Id,
			Symbol:      order.Symbol,
			Price:       order.Price,
			Site:        order.Site,
			ApiId:       order.ApiId,
			OrderSn:     order.OrderSn,
			QuoteSymbol: order.QuoteSymbol,
		}
		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol))
		redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String()
		marshal, _ := sonic.Marshal(redisList)
		if order.SymbolType == 1 {
			key := fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType)
			helper.DefaultRedis.LRem(key, string(marshal))
		} else {
			key := fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType)
			helper.DefaultRedis.LRem(key, string(marshal))
		}

		//会影响持仓的
		removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType)

		if _, ok := positions[removeSymbolKey]; !ok {
			positions[removeSymbolKey] = positiondto.LinePreOrderPositioinDelReq{
				ApiId:        order.ApiId,
				Symbol:       order.Symbol,
				ExchangeType: order.ExchangeType,
				Side:         order.Site,
				SymbolType:   order.SymbolType,
			}
		}

		e.Orm.Model(&models.LinePreOrder{}).Where("main_id = ?", order.Id).Unscoped().Delete(&models.LinePreOrder{})
	}

	//清理仓位缓存
	for _, v := range positions {
		var count int64
		e.Orm.Model(&models.LinePreOrder{}).
			Where("api_id =? AND site=? AND symbol=? AND symbol_type =? AND exchange_type =? AND status =6",
				v.ApiId, v.Side, v.Symbol, v.SymbolType, v.ExchangeType).Count(&count)

		//没有已开仓的订单 直接清理仓位
		if count == 0 {
			var key string

			if v.SymbolType == 1 {
				key = fmt.Sprintf(rediskey.SpotPosition, v.ExchangeType, v.ApiId, v.Symbol, v.Side)
			} else {
				key = fmt.Sprintf(rediskey.FuturePosition, v.ExchangeType, v.ApiId, v.Symbol, v.Side)
			}

			helper.DefaultRedis.DeleteString(key)
		}
	}
	return nil
}

func (e *LinePreOrder) QueryOrder(req *dto.QueryOrderReq) (res interface{}, err error) {
	var apiUserInfo models.LineApiUser
	e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo)
	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": req.Symbol, "origClientOrderId": req.OrderSn}
	if req.OrderType == 1 { //现货
		var resp dto.QuickAddPreOrderReq
		auth, code, err := client.SendSpotAuth("/api/v3/order", "GET", params)
		if code != 200 {
			return nil, err
		}

		if err != nil {
			return nil, err
		}
		sonic.Unmarshal(auth, &resp)
		return resp, nil
	}

	if req.OrderType == 2 {
		var resp dto.FutQueryOrderResp
		auth, code, err := client.SendFuturesRequestAuth("/fapi/v1/order", "GET", params)
		if code != 200 {
			return nil, err
		}
		if err != nil {
			return nil, err
		}
		sonic.Unmarshal(auth, &resp)
		return resp, nil
	}
	return nil, err
}

func (e *LinePreOrder) QueryAiCoinPrice(req *dto.QueryAiCoinPriceReq) (models.LineDirection, error) {
	var info models.LineDirection
	err := e.Orm.Model(&models.LineDirection{}).Where("symbol = ?", req.Symbol).Find(&info).Error
	return info, err
}

// 根据请求参数重新生成亏损回本止盈百分比
func (e *LinePreOrder) GenerateOrder(req *dto.LineAddPreOrderReq) error {
	var tradeSet models2.TradeSet
	var tickerPrice decimal.Decimal

	if req.SymbolType == 1 {
		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, req.Symbol, 1)
	} else {
		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, req.Symbol, 2)
	}

	if tradeSet.LastPrice == "" {
		return errors.New("获取不到交易对信息")
	}

	var price decimal.Decimal
	tickerPrice = utility.StrToDecimal(tradeSet.LastPrice)
	if req.PricePattern == "percentage" {
		orderPrice, _ := decimal.NewFromString(req.Price)    //下单价百分比 10%
		priceRate := orderPrice.Div(decimal.NewFromInt(100)) //下单价除100 =0.1
		if strings.ToUpper(req.Site) == "BUY" {              //购买方向
			//实际下单价格
			price = tickerPrice.Mul(decimal.NewFromInt(1).Sub(priceRate)).Truncate(int32(tradeSet.PriceDigit))
		} else {
			price = tickerPrice.Mul(decimal.NewFromInt(1).Add(priceRate)).Truncate(int32(tradeSet.PriceDigit))
		}

	} else { //实际价格下单
		price = utility.StringToDecimal(req.Price).Truncate(int32(tradeSet.PriceDigit))
	}

	buyPrice := utility.StrToDecimal(req.BuyPrice)
	mainAmount := buyPrice.Div(price).Truncate(int32(tradeSet.AmountDigit))
	calculateResp := dto.CalculateBreakEvenRatioResp{}
	lossBeginPercent := decimal.Zero
	mainParam := dto.CalculateBreakEevenRatioReq{
		Price:            price,
		ExchangeType:     req.ExchangeType,
		Symbol:           req.Symbol,
		SymbolType:       req.SymbolType,
		BuyPrice:         buyPrice,
		LossBeginPercent: lossBeginPercent,
		LossEndPercent:   req.ReducePriceRatio,
		AddPositionType:  1,
		AddPositionVal:   req.ReduceNumRatio,
	}

	//计算减仓后
	mainParam.LossEndPercent = req.ReducePriceRatio
	mainParam.RemainingQuantity = mainAmount
	mainParam.AddType = 2
	e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet)
	mainParam.RemainingQuantity = calculateResp.RemainingQuantity
	mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU
	req.ReduceReTakeProfitRatio = calculateResp.Ratio
	lossBeginPercent = req.ReducePriceRatio

	//顺序排序
	sort.Slice(req.Ext, func(i, j int) bool {
		return req.Ext[i].PriceRatio.Cmp(req.Ext[j].PriceRatio) < 0
	})

	for index := range req.Ext {
		mainParam.AddType = req.Ext[index].AddType
		mainParam.LossBeginPercent = lossBeginPercent
		mainParam.LossEndPercent = req.Ext[index].PriceRatio
		mainParam.AddPositionType = req.Ext[index].AddPositionType
		mainParam.AddPositionVal = req.Ext[index].AddPositionVal

		e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet)

		req.Ext[index].ReTakeProfitRatio = calculateResp.Ratio
		lossBeginPercent = req.Ext[index].PriceRatio
		mainParam.RemainingQuantity = calculateResp.RemainingQuantity
		mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU
	}

	return nil
}

// 计算亏损百分比
func (e *LinePreOrder) CalculateBreakEvenRatio(req *dto.CalculateBreakEevenRatioReq, data *dto.CalculateBreakEvenRatioResp, tradeSet models2.TradeSet) error {

	if req.LossEndPercent.Cmp(req.LossBeginPercent) < 0 {
		return errors.New("截至亏损百分比必须大于开始亏损百分比")
	}

	var addPositionBuyPrice decimal.Decimal

	if req.AddType == 1 && req.AddPositionType == 1 {
		addPositionBuyPrice = req.BuyPrice.Mul(req.AddPositionVal.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(2)
	} else if req.AddType == 1 {
		addPositionBuyPrice = req.AddPositionVal.Truncate(2)
	}

	var percentDiff decimal.Decimal
	var reduceAmount decimal.Decimal
	nowPrice := req.Price.Mul(decimal.NewFromInt(1).Sub(req.LossEndPercent.Div(decimal.NewFromInt(100)).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
	addPositionAmount := addPositionBuyPrice.Div(nowPrice).Truncate(int32(tradeSet.AmountDigit))

	//计算价格下跌价差
	if req.LossEndPercent.Cmp(req.LossBeginPercent) > 0 {
		percentDiff = req.LossEndPercent.Sub(req.LossBeginPercent).Abs()
	}

	totalAmount := req.RemainingQuantity.Add(addPositionAmount)
	lossAmountU := req.Price.Mul(percentDiff.Div(decimal.NewFromInt(100).Truncate(4))).Mul(req.RemainingQuantity).Truncate(int32(tradeSet.PriceDigit))

	//计算减仓数量
	if req.AddType == 2 && req.AddPositionType == 1 && req.AddPositionVal.Cmp(decimal.NewFromInt(0)) > 0 {
		reduceAmount = totalAmount.Mul(req.AddPositionVal.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.AmountDigit))
	}

	data.RemainingQuantity = totalAmount.Sub(reduceAmount)
	data.TotalLossAmountU = lossAmountU.Add(req.TotalLossAmountU)

	//计算百分比
	if data.RemainingQuantity.Cmp(decimal.Zero) > 0 {
		data.Ratio = data.TotalLossAmountU.Div(data.RemainingQuantity).Div(nowPrice).Mul(decimal.NewFromInt(100)).Truncate(2)
	} else {
		data.Ratio = decimal.Zero
	}

	return nil
}
