1075 lines
34 KiB
Go
1075 lines
34 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.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
|
||
}
|