1197 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1197 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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.savePosition(&mainOrder, &apiInfo, false, false); err1 != nil {
 | ||
| 						return true, err1
 | ||
| 					}
 | ||
| 				}
 | ||
| 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SrgetELL":
 | ||
| 				if mainOrder.Category == 0 {
 | ||
| 					if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, true); err1 != nil {
 | ||
| 						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
 | ||
| }
 | ||
| 
 | ||
| // 更新仓位信息
 | ||
| // 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 where id =@id and status!=2 "
 | ||
| 		} else if reducePosition {
 | ||
| 			//只减仓
 | ||
| 			sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice where id =@id and status!=2 "
 | ||
| 		} else {
 | ||
| 			sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice where id =@id and status!=2"
 | ||
| 		}
 | ||
| 	} 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
 | ||
| 			sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2 where id =@id and reverse_status !=2"
 | ||
| 		} else if reducePosition {
 | ||
| 			sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice where id =@id and reverse_status !=2"
 | ||
| 		} 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 where id =@id and reverse_status !=2"
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	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 {
 | ||
| 
 | ||
| 				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))
 | ||
| 		if dbResult.Error != nil {
 | ||
| 			return dbResult.Error
 | ||
| 		}
 | ||
| 
 | ||
| 		order.PositionId = position.Id
 | ||
| 		if dbResult.RowsAffected == 0 {
 | ||
| 			e.Log.Errorf("减仓数据 是否平仓单:%v :%v", closePosition, order)
 | ||
| 			return errors.New("没有找到对应的持仓信息")
 | ||
| 		}
 | ||
| 
 | ||
| 		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
 | ||
| }
 | ||
| 
 | ||
| // // 重下止盈止损
 | ||
| // // mapData: 主单止盈止损回调
 | ||
| // func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error {
 | ||
| // 	orderType := 0 //订单类型 1-止盈 2-止损
 | ||
| // 	side, err := maphelper.GetString(*mapData, "S")
 | ||
| 
 | ||
| // 	if err != nil {
 | ||
| // 		return err
 | ||
| // 	}
 | ||
| 
 | ||
| // 	ot, err := maphelper.GetString(*mapData, "ot")
 | ||
| 
 | ||
| // 	if err != nil {
 | ||
| // 		return err
 | ||
| // 	}
 | ||
| 
 | ||
| // 	//反单止盈止损方向相反
 | ||
| // 	if side == "SELL" {
 | ||
| // 		side = "BUY"
 | ||
| // 	} else {
 | ||
| // 		side = "SELL"
 | ||
| // 	}
 | ||
| 
 | ||
| // 	positionSide, err := maphelper.GetString(*mapData, "ps")
 | ||
| 
 | ||
| // 	if err != nil {
 | ||
| // 		return err
 | ||
| // 	}
 | ||
| 
 | ||
| // 	if positionSide == "LONG" {
 | ||
| // 		positionSide = "SHORT"
 | ||
| // 	} else {
 | ||
| // 		positionSide = "LONG"
 | ||
| // 	}
 | ||
| 
 | ||
| // 	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
 | ||
| // 	}
 | ||
| 
 | ||
| // 	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 := decimal.NewFromInt(1)
 | ||
| 
 | ||
| // 	if !stopPrice.IsZero() && !reversePosition.AveragePrice.IsZero() {
 | ||
| // 		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)
 | ||
| // 	newOrder := DbModels.LineReverseOrder{
 | ||
| // 		PositionId:    reversePosition.Id,
 | ||
| // 		OrderSn:       helper.GetOrderNo(),
 | ||
| // 		OrderType:     orderType,
 | ||
| // 		Status:        1,
 | ||
| // 		Price:         price,
 | ||
| // 		TotalNum:      reversePosition.TotalReverseAmount,
 | ||
| // 		Symbol:        symbol.GetSymbol(),
 | ||
| // 		Side:          side,
 | ||
| // 		PositionSide:  positionSide,
 | ||
| // 		FollowOrderSn: orderSn,
 | ||
| // 		Type:          ot,
 | ||
| // 		SignPrice:     lastPrice,
 | ||
| // 		Category:      1,
 | ||
| // 		ApiId:         apiInfo.Id,
 | ||
| // 		IsAddPosition: 2,
 | ||
| // 		TriggerTime:   &now,
 | ||
| // 		BuyPrice:      reversePosition.TotalReverseAmount.Mul(price).Truncate(int32(symbol.PriceDigit)),
 | ||
| // 	}
 | ||
| 
 | ||
| // 	if err1 := e.Orm.Create(&newOrder).Error; err1 != nil {
 | ||
| // 		e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", symbol, orderSn, err1)
 | ||
| // 		return err1
 | ||
| // 	}
 | ||
| // 	params := FutOrderPlace{
 | ||
| // 		ApiId:            apiInfo.Id,
 | ||
| // 		Symbol:           symbol.GetSymbol(),
 | ||
| // 		PositionSide:     newOrder.PositionSide,
 | ||
| // 		Side:             newOrder.Side,
 | ||
| // 		OrderType:        ot,
 | ||
| // 		Quantity:         newOrder.TotalNum,
 | ||
| // 		Price:            price,
 | ||
| // 		StopPrice:        price,
 | ||
| // 		Profit:           price,
 | ||
| // 		NewClientOrderId: newOrder.OrderSn,
 | ||
| // 		ClosePosition:    close,
 | ||
| // 	}
 | ||
| // 	futApiV2 := FuturesResetV2{Service: e.Service}
 | ||
| // 	err = futApiV2.OrderPlaceLoop(&apiInfo, params)
 | ||
| 
 | ||
| // 	if err != nil {
 | ||
| // 		e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", symbol.GetSymbol(), orderSn, err)
 | ||
| 
 | ||
| // 		if err1 := e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil {
 | ||
| // 			e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", newOrder.Symbol, newOrder.OrderSn, err1)
 | ||
| // 		}
 | ||
| // 		return err
 | ||
| // 	}
 | ||
| 
 | ||
| // 	e.DoCancelTakeProfitBatch(symbol.GetSymbol(), newOrder.PositionSide, newOrder.Side, newOrder.OrderType, &apiInfo)
 | ||
| // 	return nil
 | ||
| // }
 |