Files
exchange_go/services/binanceservice/reverse_service.go
2025-08-11 09:27:32 +08:00

1197 lines
37 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: &params.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
// }