package binanceservice import ( "database/sql" "errors" "fmt" DbModels "go-admin/app/admin/models" "go-admin/common/global" "go-admin/common/helper" "go-admin/models" "go-admin/pkg/maphelper" "go-admin/pkg/utility/snowflakehelper" "go-admin/services/cacheservice" "strconv" "time" "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 ReverseService struct { service.Service } // SaveMainOrder 保存订单信息 // mapData: 下单数据 // status: 订单状态 // orderSn: 自定义订单号 func (e *ReverseService) handleSimpleStatusChange(status string, orderSn string, mapData map[string]interface{}) (bool, error) { statusMap := map[string]int{ "NEW": 2, "CANCELED": 6, "EXPIRED": 7, "EXPIRED_IN_MATCH": 7, } if newStatus, ok := statusMap[status]; ok { _ = e.changeOrderStatus(newStatus, orderSn, mapData) return false, nil } return false, fmt.Errorf("不支持的订单状态 %s", status) } // ReverseOrder 反向下单 // apiKey: api key // mapData: 下单数据 // return apiInfo, bool // bool: true=需要反单, false=不需要反单 func (e *ReverseService) ReverseOrder(apiKey string, mapData map[string]interface{}) (bool, error) { apiInfo := GetApiInfoByKey(apiKey) if apiInfo.Id == 0 { e.Log.Errorf("获取apiInfo失败 %s", apiKey) return false, nil } status, _ := maphelper.GetString(mapData, "X") orderSn, err := maphelper.GetString(mapData, "c") if err != nil { return true, err } ot, err := maphelper.GetString(mapData, "ot") if err != nil { return true, err } switch status { case "NEW", "CANCELED", "EXPIRED", "EXPIRED_IN_MATCH": result, err := e.handleSimpleStatusChange(status, orderSn, mapData) //如果是 新开止盈止损 需要取消反单的止盈止损之后重下反单止盈止损 if status == "NEW" && (ot == "TAKE_PROFIT_MARKET" || ot == "STOP_MARKET" || ot == "TAKE_PROFIT" || ot == "STOP") && apiInfo.ReverseStatus == 1 && apiInfo.OpenStatus == 1 && apiInfo.ReverseApiId > 0 { symbolCode, err := maphelper.GetString(mapData, "s") if err != nil { e.Log.Errorf("获取symbolCode失败 symbol:%s err:%v", symbolCode, err) return true, err } symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, symbolCode, 1) if err != nil { e.Log.Errorf("获取symbol失败 symbol:%s err:%v", symbolCode, err) return true, err } if err := e.ReTakeOrStopOrder(&mapData, orderSn, &apiInfo, &symbol); err != nil { return true, err } } return result, err case "FILLED": if apiInfo.ReverseStatus == 1 && apiInfo.OpenStatus == 1 && apiInfo.ReverseApiId > 0 { reverseApiInfo, err := GetApiInfo(apiInfo.ReverseApiId) if err != nil { e.Log.Errorf("获取反向api信息失败 reverseApiId:%d, err:%v", apiInfo.ReverseApiId, err) return true, err } mainOrder, err := e.SaveMainOrder(mapData, apiInfo) if err != nil { return false, err } switch { case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": if mainOrder.Category == 0 { needReverseOrder, closePosition, err1 := e.savePosition(&mainOrder, &apiInfo, true, false) if err1 != nil { return true, err1 } if needReverseOrder { e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, false, closePosition) } } case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": if mainOrder.Category == 0 { needReverseOrder, closePosition, err1 := e.savePosition(&mainOrder, &apiInfo, true, true) if err1 != nil { e.Log.Errorf("保存主订单失败: %v", err1) return true, err1 } if needReverseOrder { e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, true, closePosition) } } default: return true, errors.New("不支持的订单类型") } return true, nil } else if apiInfo.Subordinate == "2" { e.changeOrderStatus(3, orderSn, mapData) symbol, err := maphelper.GetString(mapData, "s") if err != nil { return true, err } positionSide, err := maphelper.GetString(mapData, "ps") if err != nil { return true, err } side, err := maphelper.GetString(mapData, "S") if err != nil { return true, err } price := maphelper.GetDecimal(mapData, "ap") totalNum := maphelper.GetDecimal(mapData, "z") mainOrder := DbModels.LineReverseOrder{ ApiId: apiInfo.Id, Symbol: symbol, PositionSide: positionSide, TotalNum: totalNum, Side: side, Price: price, FinalPrice: price, } switch { case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": if mainOrder.Category == 0 { if _, _, err1 := e.savePositionWithRetry(&mainOrder, &apiInfo, false, false); err1 != nil { e.Log.Errorf("反向订单持仓保存失败: %v", err1) return true, err1 } } case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": if mainOrder.Category == 0 { if _, _, err1 := e.savePositionWithRetry(&mainOrder, &apiInfo, false, true); err1 != nil { e.Log.Errorf("反向订单持仓保存失败: %v", err1) return true, err1 } } default: return true, errors.New("不支持的订单类型") } } default: return false, fmt.Errorf("不支持的订单状态 %s", status) } return false, nil } // 修改订单状态 // status: 订单状态 1-待下单 2-已下单 3-已成交 6-已取消 7-已过期 func (e *ReverseService) changeOrderStatus(status int, orderSn string, mapData map[string]interface{}) error { data := map[string]interface{}{"status": status, "updated_at": time.Now()} if status == 3 { now := time.Now() if orderId, ok := mapData["i"].(float64); ok { data["order_id"] = orderId } if ap, ok := mapData["ap"].(string); ok { data["final_price"], _ = decimal.NewFromString(ap) } if num, ok := mapData["z"].(string); ok { data["total_num"], _ = decimal.NewFromString(num) } data["trigger_time"] = &now } else if status == 2 { if orderId, ok := mapData["i"].(float64); ok { data["order_id"] = orderId } } db := e.Orm.Model(&DbModels.LineReverseOrder{}). Where("order_sn =? and status != 3", orderSn). Updates(data) if db.Error != nil { e.Log.Errorf("修改订单状态失败 orderSn:%s, err:%v", orderSn, db.Error) } if db.RowsAffected == 0 { e.Log.Errorf("修改订单状态失败 orderSn:%s, 未找到订单", orderSn) } return db.Error } // 先保存主单,持仓信息 必须用双向持仓! func (e *ReverseService) SaveMainOrder(mapData map[string]interface{}, apiInfo DbModels.LineApiUser) (DbModels.LineReverseOrder, error) { now := time.Now() symbol, err := maphelper.GetString(mapData, "s") var reverseOrder DbModels.LineReverseOrder if err != nil { return reverseOrder, err } orderSn, err := maphelper.GetString(mapData, "c") if err != nil { return reverseOrder, err } side, err := maphelper.GetString(mapData, "S") if err != nil { return reverseOrder, err } positionSide, err := maphelper.GetString(mapData, "ps") if err != nil { return reverseOrder, err } mainType, _ := maphelper.GetString(mapData, "ot") reverseOrder = DbModels.LineReverseOrder{ ApiId: apiInfo.Id, Category: 0, OrderSn: orderSn, TriggerTime: &now, Status: 3, Symbol: symbol, FollowOrderSn: "", Type: mainType, Price: maphelper.GetDecimal(mapData, "p"), FinalPrice: maphelper.GetDecimal(mapData, "ap"), TotalNum: maphelper.GetDecimal(mapData, "z"), Side: side, PositionSide: positionSide, } reverseOrder.PriceU = reverseOrder.Price if !reverseOrder.Price.IsZero() && !reverseOrder.TotalNum.IsZero() { reverseOrder.BuyPrice = reverseOrder.Price.Mul(reverseOrder.TotalNum) } if id, err := maphelper.GetFloat64(mapData, "i"); err == nil { reverseOrder.OrderId = strconv.FormatFloat(id, 'f', -1, 64) } orderType, _ := maphelper.GetString(mapData, "ot") switch orderType { case "LIMIT", "MARKET": reverseOrder.OrderType = 0 case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": reverseOrder.OrderType = 1 case "STOP_MARKET", "STOP", "TRAILING_STOP_MARKET": reverseOrder.OrderType = 2 default: return reverseOrder, fmt.Errorf("不支持的订单类型: %s", orderType) } if reverseOrder.PositionSide == "BOTH" { return reverseOrder, errors.New("不支持的持仓类型,必须为双向持仓") } if err := e.Orm.Create(&reverseOrder).Error; err != nil { e.Log.Errorf("保存主单失败:%v", err) return reverseOrder, err } return reverseOrder, nil } // savePositionWithRetry 带重试机制的持仓保存 func (e *ReverseService) savePositionWithRetry(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) { const maxRetries = 3 for i := 0; i < maxRetries; i++ { needReverseOrder, closePosition, err := e.savePosition(order, apiInfo, isMain, reducePosition) if err == nil { return needReverseOrder, closePosition, nil } e.Log.Warnf("持仓保存失败,重试 %d/%d: %v", i+1, maxRetries, err) time.Sleep(time.Millisecond * 100 * time.Duration(i+1)) } return false, false, fmt.Errorf("持仓保存失败,已重试 %d 次", maxRetries) } // 更新仓位信息 // apiInfo: 当前下单api信息 // return // neeReverseOrder: 是否需要反单 // closePosition: true=平仓, false=减仓 // err error 错误信息 func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) { position := DbModels.LineReversePosition{} positionSide := order.PositionSide side := order.Side totalNum := order.TotalNum closePosition := false needReverseOrder := false symbol, err1 := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) if err1 != nil { e.Log.Errorf("获取交易对失败 symbol:%s err:%v", order.Symbol, err1) } var querySql string sqlStr := "" //如果是主单,存储仓位则是反单的持仓方向 if isMain { if order.PositionSide == "LONG" { positionSide = "SHORT" } else { positionSide = "LONG" } //减仓 判断是否为平仓 closePosition = e.getClosePosition(reducePosition, apiInfo, order, order.PositionSide, isMain) if !reducePosition { //反单止盈止损方向相反 if side == "SELL" { side = "BUY" } else { side = "SELL" } position.ReverseApiId = apiInfo.ReverseApiId position.Side = side position.ApiId = order.ApiId position.Symbol = order.Symbol position.Status = 1 position.ReverseStatus = 0 position.PositionSide = positionSide position.AveragePrice = order.FinalPrice position.PositionNo = snowflakehelper.GetOrderNo() } querySql = "api_id =? and position_side =? and symbol =? and status =1" //平仓 if closePosition { totalNum = decimal.Zero sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2,version=version+1 where id =@id and status!=2 and version=@version" } else if reducePosition { //只减仓 sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version" } else { sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version" } } else { querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)" //减仓 判断是否为平仓 closePosition = e.getClosePosition(reducePosition, apiInfo, order, order.PositionSide, isMain) // 关键修复:确保反向持仓状态正确更新 if closePosition { totalNum = decimal.Zero // 增强的平仓更新逻辑,确保状态正确设置为2 sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2,version=version+1 where id =@id and reverse_status !=2 and version=@version" } else if reducePosition { sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version" } else { sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version" } } var averagePrice decimal.Decimal var remainQuantity decimal.Decimal err := e.Orm.Transaction(func(tx *gorm.DB) error { err1 := tx.Model(&position).Where(querySql, order.ApiId, positionSide, order.Symbol).First(&position).Error if err1 != nil { //主单仓位不存在,创建新仓位 if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition { // 初始化版本号 position.Version = 0 if err2 := tx.Create(&position).Error; err2 != nil { return err2 } averagePrice = position.AveragePrice } else { return err1 } } else { var totalAmount decimal.Decimal var totalPrice decimal.Decimal if !position.Amount.IsZero() && !position.AveragePrice.IsZero() { if isMain { totalPrice = position.Amount.Mul(position.AveragePrice) totalAmount = position.Amount } else { totalPrice = position.ReverseAmount.Mul(position.ReverseAveragePrice) totalAmount = position.ReverseAmount } //加仓 if !reducePosition { totalPrice = totalPrice.Add(order.Price.Mul(order.TotalNum)) totalAmount = totalAmount.Add(order.TotalNum) } else if reducePosition && !closePosition { //只减仓 totalPrice = totalPrice.Sub(order.Price.Mul(order.TotalNum)) totalAmount = totalAmount.Sub(order.TotalNum) } } if totalAmount.IsZero() || totalPrice.IsZero() { if isMain { averagePrice = position.AveragePrice } else { if position.ReverseAveragePrice.IsZero() { averagePrice = order.Price } else { averagePrice = position.ReverseAveragePrice } } } else { //平仓 if closePosition { if isMain { averagePrice = position.AveragePrice } else { averagePrice = position.ReverseAveragePrice } } else { averagePrice = totalPrice.Div(totalAmount).Truncate(int32(symbol.PriceDigit)) } } } //关联订单的仓位id if err2 := tx.Exec("UPDATE line_reverse_order set position_id=@positionId where id=@orderId and position_id = 0", sql.Named("positionId", position.Id), sql.Named("orderId", order.Id)).Error; err2 != nil { return err2 } // 使用乐观锁执行更新 dbResult := tx.Exec(sqlStr, sql.Named("totalNum", totalNum), sql.Named("id", position.Id), sql.Named("averagePrice", averagePrice), sql.Named("version", position.Version)) if dbResult.Error != nil { return dbResult.Error } order.PositionId = position.Id if dbResult.RowsAffected == 0 { e.Log.Errorf("减仓数据 是否平仓单:%v :%v, 可能存在并发冲突", closePosition, order) return fmt.Errorf("没有找到对应的持仓信息或版本冲突,position_id:%d, version:%d", position.Id, position.Version) } if reducePosition && !isMain { remainQuantity = position.ReverseAmount.Sub(totalNum) } else if !isMain { remainQuantity = position.ReverseAmount.Add(totalNum) } //主单且对手单没有平仓 if isMain && position.ReverseStatus != 2 { needReverseOrder = true } return nil }) if !isMain && !closePosition { e.doDefaultTakeStop(order, apiInfo, remainQuantity) } else if !isMain && closePosition { //取消剩余的委托 e.DoCancelTakeAndStop(order.Symbol, position.PositionSide, apiInfo) } return needReverseOrder, closePosition, err } // 获取是否为平仓状态 func (e *ReverseService) getClosePosition(reducePosition bool, apiInfo *DbModels.LineApiUser, order *DbModels.LineReverseOrder, positionSide string, isMain bool) bool { closePosition := false if reducePosition { futApi := FutRestApi{} holdData := HoldeData{} err := futApi.GetPositionData(apiInfo, order.Symbol, positionSide, &holdData) if err != nil { e.Log.Errorf("获取剩余持仓信息失败 symbol:%s err:%v", order.Symbol, err) lastPosition := DbModels.LineReversePosition{} if err2 := e.Orm.Model(&DbModels.LineReversePosition{}).Where("position_side =? and symbol =? and status =1", positionSide, order.Symbol).First(&lastPosition).Error; err2 != nil { e.Log.Errorf("获取上一次持仓信息失败 symbol:%s err:%v", order.Symbol, err2) } else if isMain && lastPosition.Amount.Cmp(order.TotalNum) <= 0 { //如果剩余仓位小于等于 closePosition = true } else if !isMain && lastPosition.ReverseAmount.Cmp(order.TotalNum) <= 0 { closePosition = true } } else if holdData.TotalQuantity.IsZero() { closePosition = true } } return closePosition } // 反向下单 // mainOrder: 主单信息 // reverseApiId: 反向apiId // orderProportion: 反向下单比例 // reducePosition: 是否减仓 // closePosition: 是否平仓 func (e *ReverseService) DoAddReverseOrder(mainOrder *DbModels.LineReverseOrder, reverseApiInfo *DbModels.LineApiUser, orderProportion decimal.Decimal, reducePosition, closePosition bool) error { order := DbModels.LineReverseOrder{} order.ApiId = reverseApiInfo.Id order.Category = 1 order.OrderSn = helper.GetOrderNo() order.FollowOrderSn = mainOrder.OrderSn order.Symbol = mainOrder.Symbol order.OrderType = 0 switch mainOrder.PositionSide { case "LONG": order.PositionSide = "SHORT" case "SHORT": order.PositionSide = "LONG" } switch mainOrder.Side { case "SELL": order.Side = "BUY" case "BUY": order.Side = "SELL" default: return fmt.Errorf("不支持的订单类型 side:%s", mainOrder.Side) } if reducePosition && closePosition { order.OrderType = 4 } else if reducePosition { order.OrderType = 3 } symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, mainOrder.Symbol, 1) if err != nil { e.Log.Errorf("获取交易对信息失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) return err } setting, err := cacheservice.GetReverseSetting(e.Orm) if err != nil { e.Log.Errorf("获取反单设置失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) return err } signPrice, _ := decimal.NewFromString(symbol.LastPrice) price := signPrice.Truncate(int32(symbol.PriceDigit)) if price.Cmp(decimal.Zero) <= 0 { e.Log.Errorf("获取最新价格失败 symbol:%s custom:%s 单价小于0", mainOrder.Symbol, mainOrder.OrderSn) return errors.New("获取最新价格失败") } var percent decimal.Decimal switch { case order.PositionSide == "LONG" && order.Side == "BUY", order.PositionSide == "SHORT" && order.Side == "BUY": percent = decimal.NewFromInt(100).Add(setting.ReversePremiumRatio) case order.PositionSide == "SHORT" && order.Side == "SELL", order.PositionSide == "LONG" && order.Side == "SELL": percent = decimal.NewFromInt(100).Sub(setting.ReversePremiumRatio) default: return fmt.Errorf("不支持的订单类型 ps:%s, side:%s", order.PositionSide, order.Side) } percent = percent.Div(decimal.NewFromInt(100)).Truncate(4) //计算溢价单价 price = price.Mul(percent).Truncate(int32(symbol.PriceDigit)) var amount decimal.Decimal //平仓单直接卖出全部数量 if closePosition { var position DbModels.LineReversePosition if err1 := e.Orm.Model(position). Where("position_side =? and reverse_api_id =? and reverse_status =1", order.PositionSide, order.ApiId). Select("reverse_amount"). Find(&position).Error; err1 != nil { e.Log.Errorf("获取剩余仓位失败 symbol:%s custom:%s :%v", order.Symbol, order.OrderSn, err1) if len(err1.Error()) < 255 { order.Remark = err1.Error() } else { order.Remark = err1.Error()[:254] } } amount = position.ReverseAmount if amount.IsZero() { order.Remark = "没有持仓数量" } } else { proportion := decimal.NewFromInt(100) if orderProportion.Cmp(decimal.Zero) > 0 { proportion = orderProportion } //反向下单百分比 proportion = proportion.Div(decimal.NewFromInt(100)).Truncate(4) amount = mainOrder.TotalNum.Mul(proportion).Truncate(int32(symbol.AmountDigit)) logger.Info("反向下单比例 %d ,原始数量:%d,反向下单数量:%d", proportion, mainOrder.TotalNum, amount) if amount.Cmp(decimal.Zero) <= 0 { e.Log.Errorf("计算数量失败 symbol:%s custom:%s 数量小于0", mainOrder.Symbol, mainOrder.OrderSn) return errors.New("计算数量失败") } } order.TotalNum = amount order.Price = price order.PriceU = price order.BuyPrice = amount.Mul(price).Truncate(int32(symbol.PriceDigit)) order.Status = 1 order.Type = setting.ReverseOrderType order.SignPrice = signPrice order.PositionId = mainOrder.PositionId if order.Remark != "" { order.Status = 8 } if err := e.Orm.Model(&order).Create(&order).Error; err != nil { e.Log.Errorf("保存反单失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err) return err } if order.Status == 1 { err = e.DoBianceOrder(&order, reverseApiInfo, &setting, reducePosition, closePosition) //Biance下单成共 且为平仓单时 取消止盈止损 if err == nil && closePosition { e.DoCancelTakeProfitBatch(symbol.GetSymbol(), order.PositionSide, order.Side, -1, reverseApiInfo) } } return nil } // 取消止盈止损订单 // symbol: 交易对 // positionSide: 持仓方向 // side: 订单方向 // orderType: 订单类型 -1-全部 1-止盈 2-止损 // apiInfo: 下单api func (e *ReverseService) DoCancelTakeProfitBatch(symbol, positionSide, side string, orderType int, apiInfo *DbModels.LineApiUser) error { orderSns := e.GetTakeProfitBatch(symbol, positionSide, side, apiInfo.Id, orderType) if len(orderSns) == 0 { return nil } if len(orderSns) > 0 { futApi := FutRestApi{} err := futApi.CancelBatchFutOrderLoop(*apiInfo, symbol, orderSns) if err != nil { e.Log.Errorf("币安撤单失败 symbol:%s custom:%v :%v", symbol, orderSns, err) return err } } return nil } // 取消止盈和止损单 func (e *ReverseService) DoCancelTakeAndStop(symbol, positionSide string, apiInfo *DbModels.LineApiUser) error { var orderSns []string e.Orm.Model(&DbModels.LineReverseOrder{}).Where("symbol =? and position_side =? and status =2", symbol, positionSide).Pluck("order_sn", &orderSns) if len(orderSns) > 0 { futApi := FutRestApi{} if err := futApi.CancelBatchFutOrderLoop(*apiInfo, symbol, orderSns); err != nil { e.Log.Errorf("币安撤单失败 symbol:%s custom:%v :%v", symbol, orderSns, err) return err } } return nil } // 获取止盈止损订单 // symbol: 交易对 // positionSide: 持仓方向 // side: 订单方向 // apiId: 币安apiId // orderType: 订单类型 -1-全部 1-止盈 2-止损 func (e *ReverseService) GetTakeProfitBatch(symbol, positionSide, side string, apiId int, orderType int) []string { var orderSns []string orderTypes := []int{1, 2} if orderType == 1 { orderTypes = []int{1} } else if orderType == 2 { orderTypes = []int{2} } e.Orm.Model(&DbModels.LineReverseOrder{}).Where("symbol =? and position_side =? and side = ? and status =2 and order_type in ?", symbol, positionSide, side, orderTypes).Pluck("order_sn", &orderSns) return orderSns } // 处理币安订单 // order: 反单信息 // apiInfo: 币安api信息 func (e *ReverseService) DoBianceOrder(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, setting *DbModels.LineReverseSetting, reducePosition, closePosition bool) error { futApiV2 := FuturesResetV2{Service: e.Service} orderType := setting.ReverseOrderType if orderType == "" { orderType = "LIMIT" } params := FutOrderPlace{ ApiId: apiInfo.Id, Symbol: order.Symbol, PositionSide: order.PositionSide, Side: order.Side, OrderType: orderType, Quantity: order.TotalNum, Price: order.Price, NewClientOrderId: order.OrderSn, } err := futApiV2.OrderPlaceLoop(apiInfo, params) if err != nil { e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", order.Symbol, order.OrderSn, err) remark := err.Error() if len(remark) > 255 { remark = remark[:254] } if err1 := e.Orm.Model(&order).Where("id =? and status !=3", order.Id).Updates(map[string]interface{}{"status": 8, "remark": remark, "updated_at": time.Now()}).Error; err1 != nil { e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", err1) } } return nil } // 处理默认止盈止损 // order: 订单信息 // apiInfo: api信息 // Optimized Reverse Order Handling // File: reverse_order_handler.go func (e *ReverseService) doDefaultTakeStop(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, totalNum decimal.Decimal) error { if totalNum.LessThanOrEqual(decimal.Zero) { return nil } orders, err := e.getActiveReverseOrders(order.PositionId) if err != nil { return err } if len(orders) == 2 { return nil } setting, err := GetReverseSetting(e.Orm) if err != nil { e.Log.Errorf("获取反单设置失败:%v", err) return err } symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) if err != nil { e.Log.Errorf("获取交易对信息失败:%v", err) return err } side := e.getOppositeSide(order.Side) lastPrice, _ := decimal.NewFromString(symbol.LastPrice) now := time.Now() types := []struct { Enabled bool CheckTypes []string OrderType string Ratio decimal.Decimal IsTakeProfit bool }{ {!setting.TakeProfitRatio.IsZero(), []string{"TAKE_PROFIT_MARKET", "TAKE_PROFIT"}, "TAKE_PROFIT_MARKET", setting.TakeProfitRatio, true}, {!setting.StopLossRatio.IsZero(), []string{"STOP_MARKET", "STOP"}, "STOP_MARKET", setting.StopLossRatio, false}, } for _, t := range types { if t.Enabled && !e.hasOrderType(orders, t.CheckTypes...) { price := e.calculatePrice(order.PositionSide, order.FinalPrice, t.Ratio, t.IsTakeProfit) err := e.createReverseOrder(CreateOrderParams{ Order: order, ApiInfo: apiInfo, Symbol: &symbol, Side: side, OrderType: t.OrderType, Price: price, TotalNum: totalNum, Now: now, LastPrice: lastPrice, Close: true, PositionId: order.PositionId, }) if err != nil { e.Log.Errorf("止盈止损下单失败:%v", err) } } } return nil } // 重下止盈止损 // mapData: func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error { side, err := maphelper.GetString(*mapData, "S") if err != nil { return err } ot, err := maphelper.GetString(*mapData, "ot") if err != nil { return err } positionSide, err := maphelper.GetString(*mapData, "ps") if err != nil { return err } close := maphelper.GetBool(*mapData, "cp") stopPrice := maphelper.GetDecimal(*mapData, "sp") if stopPrice.IsZero() { e.Log.Errorf("获取止盈止损单触发价失败 symbol:%s custom:%s :%v", symbol, orderSn, err) return err } apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId) if err != nil { e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err) return err } side = e.getOppositeSide(side) if positionSide == "LONG" { positionSide = "SHORT" } else { positionSide = "LONG" } var orderType int switch ot { case "STOP_MARKET", "STOP": orderType = 2 case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": orderType = 1 default: return fmt.Errorf("不支持的订单类型 ot:%s", ot) } var reversePosition DbModels.LineReversePosition e.Orm.Model(&reversePosition). Where("symbol =? and reverse_api_id =? and position_side =? and reverse_status =1", symbol.GetSymbol(), apiInfo.Id, positionSide). First(&reversePosition) if reversePosition.Id == 0 { e.Log.Errorf("获取反单持仓失败 symbol:%s custom:%s :%v", symbol, orderSn, err) return err } mainPercent := stopPrice.Div(reversePosition.AveragePrice) mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4) var percent decimal.Decimal switch { case orderType == 2 && positionSide == "LONG", orderType == 1 && positionSide == "SHORT": percent = decimal.NewFromInt(1).Sub(mainPercent) case orderType == 2 && positionSide == "SHORT", orderType == 1 && positionSide == "LONG": percent = decimal.NewFromInt(1).Add(mainPercent) default: return fmt.Errorf("不支持的订单类型 ot:%s, ps:%s", ot, positionSide) } now := time.Now() price := reversePosition.AveragePrice.Mul(percent).Truncate(int32(symbol.PriceDigit)) lastPrice, _ := decimal.NewFromString(symbol.LastPrice) return e.createReverseOrder(CreateOrderParams{ ApiInfo: &apiInfo, Symbol: symbol, Side: side, OrderType: ot, Price: price, TotalNum: reversePosition.TotalReverseAmount, Now: now, LastPrice: lastPrice, Close: close, PositionId: reversePosition.Id, OrderSn: orderSn, PositionSide: positionSide, }) } func (e *ReverseService) getActiveReverseOrders(positionId int) ([]DbModels.LineReverseOrder, error) { var orders []DbModels.LineReverseOrder err := e.Orm.Model(&DbModels.LineReverseOrder{}). Where("position_id =? and status =2", positionId). Select("id,position_side,side,type").Find(&orders).Error if err != nil { e.Log.Errorf("获取订单信息失败:%v", err) } return orders, err } func (e *ReverseService) getOppositeSide(side string) string { if side == "SELL" { return "BUY" } return "SELL" } func (e *ReverseService) hasOrderType(orders []DbModels.LineReverseOrder, types ...string) bool { typeSet := make(map[string]bool) for _, t := range types { typeSet[t] = true } for _, o := range orders { if typeSet[o.Type] { return true } } return false } func (e *ReverseService) calculatePrice(positionSide string, base decimal.Decimal, ratio decimal.Decimal, isTakeProfit bool) decimal.Decimal { adjust := decimal.NewFromInt(100) if positionSide == "LONG" { if isTakeProfit { return base.Mul(adjust.Add(ratio).Div(adjust)).Truncate(4) } return base.Mul(adjust.Sub(ratio).Div(adjust)).Truncate(4) } if isTakeProfit { return base.Mul(adjust.Sub(ratio).Div(adjust)).Truncate(4) } return base.Mul(adjust.Add(ratio).Div(adjust)).Truncate(4) } type CreateOrderParams struct { ApiInfo *DbModels.LineApiUser Order *DbModels.LineReverseOrder Symbol *models.TradeSet Side string OrderType string Price decimal.Decimal TotalNum decimal.Decimal Now time.Time LastPrice decimal.Decimal Close bool PositionId int OrderSn string PositionSide string } func (e *ReverseService) createReverseOrder(params CreateOrderParams) error { orderSn := params.OrderSn if orderSn == "" { orderSn = helper.GetOrderNo() } if params.PositionSide == "" && params.Order != nil { params.PositionSide = params.Order.PositionSide } newOrder := DbModels.LineReverseOrder{ PositionId: params.PositionId, OrderSn: orderSn, OrderType: getOrderType(params.OrderType), Status: 1, Price: params.Price, TotalNum: params.TotalNum, Symbol: params.Symbol.GetSymbol(), Side: params.Side, PositionSide: params.PositionSide, FollowOrderSn: params.OrderSn, Type: params.OrderType, SignPrice: params.LastPrice, Category: 1, ApiId: params.ApiInfo.Id, IsAddPosition: 2, TriggerTime: ¶ms.Now, BuyPrice: params.TotalNum.Mul(params.Price).Truncate(int32(params.Symbol.PriceDigit)), } if err := e.Orm.Create(&newOrder).Error; err != nil { e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", params.Symbol.GetSymbol(), newOrder.OrderSn, err) return err } futApiV2 := FuturesResetV2{Service: e.Service} params2 := FutOrderPlace{ ApiId: params.ApiInfo.Id, Symbol: params.Symbol.GetSymbol(), PositionSide: newOrder.PositionSide, Side: newOrder.Side, OrderType: newOrder.Type, Quantity: newOrder.TotalNum, Price: newOrder.Price, StopPrice: newOrder.Price, Profit: newOrder.Price, NewClientOrderId: newOrder.OrderSn, ClosePosition: params.Close, } err := futApiV2.OrderPlaceLoop(params.ApiInfo, params2) if err != nil { e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", params.Symbol.GetSymbol(), newOrder.OrderSn, err) e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}) return err } e.DoCancelTakeProfitBatch(params.Symbol.GetSymbol(), newOrder.PositionSide, newOrder.Side, newOrder.OrderType, params.ApiInfo) return nil } func getOrderType(t string) int { if t == "TAKE_PROFIT_MARKET" || t == "TAKE_PROFIT" { return 1 } return 2 }