Files
exchange_go/services/binanceservice/futuresrest.go
2025-02-08 18:32:41 +08:00

425 lines
13 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 (
"context"
"fmt"
"go-admin/app/admin/models"
DbModels "go-admin/app/admin/models"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
models2 "go-admin/models"
"go-admin/pkg/utility"
"go-admin/pkg/utility/snowflakehelper"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
/*
修改订单信息
*/
func ChangeFutureOrder(mapData map[string]interface{}) {
// 检查订单号是否存在
orderSn, ok := mapData["c"]
originOrderSn := mapData["C"] //取消操作 代表原始订单号
if !ok {
logger.Error("合约订单回调失败,没有订单号")
return
}
if originOrderSn != "" {
orderSn = originOrderSn
}
// 获取数据库连接
db := GetDBConnection()
if db == nil {
logger.Error("合约订单回调失败,无法获取数据库连接")
return
}
// 获取 Redis 锁
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.FutCallBack, orderSn), 10, 5, 500*time.Millisecond)
acquired, err := lock.AcquireWait(context.Background())
if err != nil {
logger.Error("合约订单回调失败,获取锁失败:", orderSn, " err:", err)
return
}
if !acquired {
logger.Error("合约订单回调失败,获取锁失败:", orderSn)
return
}
defer lock.Release()
// 查询订单
preOrder, err := getPreOrder(db, orderSn)
if err != nil {
logger.Error("合约订单回调失败,查询订单失败:", orderSn, " err:", err)
return
}
// 解析订单状态
status, ok := mapData["X"].(string)
if !ok {
mapStr, _ := sonic.Marshal(&mapData)
logger.Error("订单回调失败,没有状态:", string(mapStr))
return
}
// 更新订单状态
orderStatus, reason := parseOrderStatus(status, mapData)
if orderStatus == 0 {
logger.Error("订单回调失败,状态错误:", orderSn, " status:", status, " reason:", reason)
return
}
if err := updateOrderStatus(db, preOrder, orderStatus, reason, true, mapData); err != nil {
logger.Error("修改订单状态失败:", orderSn, " err:", err)
return
}
handleFutOrderByType(db, preOrder, orderStatus)
}
// 合约回调
func handleFutOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus int) {
switch {
//主单成交
case preOrder.OrderType == 0 && orderStatus == 6:
handleFutMainOrderFilled(db, preOrder)
//止盈成交
case preOrder.OrderType == 1 && orderStatus == 6:
handleTakeProfit(db, preOrder)
//止损成交
case preOrder.OrderType == 2 && orderStatus == 6:
handleStopLoss(db, preOrder)
//平仓成交
case preOrder.OrderType == 3 && orderStatus == 6:
handleClosePosition(db, preOrder)
}
}
// 平仓单成交
func handleClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
panic("unimplemented")
}
// 止损单成交
func handleStopLoss(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
// 获取主订单
order, err := GetOrderById(db, preOrder.Pid)
if err != nil {
logger.Errorf("止损单成交 获取主单失败 订单号:%s err:%v", preOrder.OrderSn, err)
return
}
// 检查订单类型
if order.OrderCategory != 1 {
if err := db.Model(&order).Where("pid =? AND order_status <9 AND order_type =0", preOrder.Pid).Error; err != nil {
logger.Errorf("对冲止损单成交 修改对冲主单状态失败 订单号:%s err:%v", order.OrderSn, err)
}
return
}
// 修改主单状态
if err := db.Model(&order).Where("id =? AND order_status < 9", preOrder.Pid).Error; err != nil {
logger.Errorf("止损单成交 修改主单状态失败 订单号:%s err:%v", order.OrderSn, err)
return
}
// 检查是否有对冲
if order.CoverType <= 0 {
return
}
triggerHedgeOrder(order, db, preOrder)
}
// 下合约对冲单
func triggerHedgeOrder(order DbModels.LinePreOrder, db *gorm.DB, preOrder *DbModels.LinePreOrder) bool {
symbolName := utility.ReplaceSuffix(order.Symbol, order.QuoteSymbol, "USDT")
// 获取交易对配置
tradeSet, err := GetTradeSet(symbolName, 1)
if tradeSet.Coin == "" || err != nil {
logger.Errorf("止损单成交 获取交易对配置失败 交易对:%s 订单号:%s err:%v", symbolName, order.OrderSn, err)
return true
}
// 检查对冲单购买金额
// if order.HedgeBuyTotal.Cmp(decimal.Zero) <= 0 {
// logger.Errorf("止损单成交 对冲单购买金额为0 订单号:%s", order.OrderSn)
// return true
// }
// 获取系统设置
setting, err := GetSystemSetting(db)
if err != nil {
logger.Errorf("止损单成交 获取系统设置失败 订单号:%s err:%v", order.OrderSn, err)
return true
}
// 获取API信息
apiInfo, err := GetChildApiInfo(order.ApiId)
if err != nil {
logger.Errorf("止损单成交 获取api信息失败 订单号:%s err:%v", order.OrderSn, err)
return true
}
// 计算价格
price := utility.StrToDecimal(preOrder.Price).Truncate(int32(tradeSet.PriceDigit))
if order.CoverType == 1 {
price = utility.StrToDecimal(tradeSet.LastPrice).Truncate(int32(tradeSet.PriceDigit))
}
if price.Cmp(decimal.Zero) <= 0 {
logger.Errorf("单价异常 price:%v 止损单编号:%s 行情:%v", price, order.OrderSn, tradeSet)
return true
}
// 创建对冲订单
hedgeOrder, hedgeStoplossOrder, hedgeTakeProfitOrder := createHedgeOrders(order, price, &tradeSet, &apiInfo, &setting)
orders := []DbModels.LinePreOrder{hedgeOrder, hedgeStoplossOrder, hedgeTakeProfitOrder}
// 批量插入订单
if err := db.Model(&DbModels.LinePreOrder{}).CreateInBatches(orders, 1).Error; err != nil {
logger.Errorf("主单止损 重下对冲失败止损单id%s err:%v", order.OrderSn, err)
return true
}
// 调用API下单
if err := placeFutOrder(db, hedgeOrder, price, &apiInfo); err != nil {
logger.Errorf("主单止损 重下对冲失败,止损单号:%s err:%v", order.OrderSn, err)
}
return false
}
// 创建对冲订单
func createHedgeOrders(order DbModels.LinePreOrder, price decimal.Decimal, tradeSet *models2.TradeSet, apiInfo *DbModels.LineApiUser, setting *DbModels.LineSystemSetting) (DbModels.LinePreOrder, DbModels.LinePreOrder, DbModels.LinePreOrder) {
// buyPrice := order.HedgeBuyTotal.String()
// if order.HedgeBuyType == 1 {
// mainTotal := utility.StrToDecimal(order.BuyPrice)
// buyPrice = mainTotal.Mul(order.HedgeBuyTotal).Truncate(int32(tradeSet.PriceDigit)).String()
// }
hedgeOrder := DbModels.LinePreOrder{
ApiId: apiInfo.Id,
ExchangeType: order.ExchangeType,
Pid: order.Pid,
Symbol: order.Symbol,
SymbolType: global.SYMBOL_FUTURES,
SignPrice: order.Price,
SignPriceType: "new",
Rate: "0",
// BuyPrice: buyPrice,
OrderCategory: 2,
OrderSn: utility.Int64ToString(snowflakehelper.GetOrderId()),
OrderType: 0,
CoverType: 0,
// MainOrderType: order.HedgeOrderType,
}
hedgeStoplossOrder := order
hedgeTakeProfitOrder := order
if order.Site == "BUY" {
hedgeOrder.Site = "SELL"
hedgeStoplossOrder.Site = "BUY"
hedgeTakeProfitOrder.Site = "BUY"
} else {
hedgeOrder.Site = "BUY"
hedgeStoplossOrder.Site = "SELL"
hedgeTakeProfitOrder.Site = "SELL"
}
// 计算止盈止损价格
// if hedgeOrder.Site == "BUY" {
// hedgeTakeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(order.HedgeTakeProfit.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
// hedgeStoplossOrder.Price = price.Mul(decimal.NewFromInt(1).Sub(order.HedgeStopLoss.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
// } else {
// hedgeTakeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Sub(order.HedgeTakeProfit.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
// hedgeStoplossOrder.Price = price.Mul(decimal.NewFromInt(1).Add(order.HedgeStopLoss.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
// }
if hedgeOrder.MainOrderType == "LIMIT" {
coverRate := utility.StrToDecimal(setting.CoverOrderTypeBRate)
price = price.Mul(decimal.NewFromInt(1).Add(coverRate)).Truncate(int32(tradeSet.PriceDigit))
}
// num := order.HedgeBuyTotal.Div(price).Truncate(int32(tradeSet.AmountDigit))
hedgeOrder.Price = price.String()
// hedgeOrder.Num = num.String()
hedgeStoplossOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
// hedgeStoplossOrder.Rate = order.HedgeStopLoss.String()
// hedgeStoplossOrder.Num = num.String()
hedgeTakeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId())
// hedgeTakeProfitOrder.Rate = order.HedgeTakeProfit.String()
// hedgeTakeProfitOrder.Num = num.String()
return hedgeOrder, hedgeStoplossOrder, hedgeTakeProfitOrder
}
// 调用合约API下单
func placeFutOrder(db *gorm.DB, hedgeOrder DbModels.LinePreOrder, price decimal.Decimal, apiInfo *DbModels.LineApiUser) error {
futApi := FutRestApi{}
params := FutOrderPlace{
ApiId: apiInfo.Id,
Symbol: hedgeOrder.Symbol,
Side: hedgeOrder.Site,
Quantity: utility.StrToDecimal(hedgeOrder.Num),
Price: price,
NewClientOrderId: hedgeOrder.OrderSn,
SideType: hedgeOrder.MainOrderType,
OrderType: hedgeOrder.MainOrderType,
}
return futApi.OrderPlace(db, params)
}
// 止盈单成交
func handleTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
if preOrder.OrderCategory == 1 {
//主单止盈成交
if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ?", preOrder.Pid).Update("order_type", 3).Error; err != nil {
logger.Errorf("主单止盈成功修改主单状态失败 订单号:%s:", err)
}
} else {
//对冲单止盈成交
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_category = 2 AND order_type =0 AND status < 9", preOrder.Pid).Update("status", 9).Error; err != nil {
logger.Errorf("对冲止盈成功修改主单状态失败 订单号:%s:", err)
}
}
}
// 主单成交 处理止盈止损订单
// preOrder 主单
func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder) {
orders := []models.LinePreOrder{}
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_category = 1 AND order_type > 0 AND status = '0' ", preOrder.Id).Find(&orders).Error; err != nil {
logger.Error("订单回调查询止盈止损单失败:", err)
return
}
futApi := FutRestApi{}
num, _ := decimal.NewFromString(preOrder.Num)
tradeSet, _ := GetTradeSet(preOrder.Symbol, 0)
if tradeSet.Coin == "" {
logger.Error("获取交易对失败")
return
}
for i, order := range orders {
if i >= 2 { // 最多处理 2 个订单
break
}
price := utility.StrToDecimal(order.Price).Truncate(int32(tradeSet.PriceDigit))
num = num.Truncate(int32(tradeSet.AmountDigit))
order.Price = price.String()
if err := db.Model(&order).Update("num", num).Error; err != nil {
logger.Errorf("修改止盈止损数量失败 订单号:%s err:%v", order.OrderSn, err)
}
switch order.OrderType {
case 1: // 止盈
processFutTakeProfitOrder(db, futApi, order, num)
case 2: // 止损
processFutStopLossOrder(db, order, price, num)
}
}
}
// 处理止盈订单
func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.LinePreOrder, num decimal.Decimal) {
price, _ := decimal.NewFromString(order.Price)
params := FutOrderPlace{
ApiId: order.ApiId,
Symbol: order.Symbol,
Side: order.Site,
Price: price,
Quantity: num,
OrderType: "TAKE_PROFIT_LIMIT",
StopPrice: price,
NewClientOrderId: order.OrderSn,
}
err := futApi.OrderPlace(db, params)
if err != nil {
for x := 0; x < 5; x++ {
if strings.Contains(err.Error(), "LOT_SIZE") {
break
}
err = futApi.OrderPlace(db, params)
if err == nil {
break
}
time.Sleep(time.Millisecond * 250)
}
}
if err != nil {
logger.Error("合约止盈下单失败:", order.OrderSn, " err:", err)
if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ?", order.Id).
Updates(map[string]interface{}{"status": "2", "desc": err.Error(), "num": params.Quantity}).Error; err != nil {
logger.Error("合约止盈下单失败,更新状态失败:", order.OrderSn, " err:", err)
}
} else {
if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ? and status =0", order.Id).
Updates(map[string]interface{}{"status": "1", "num": num.String()}).Error; err != nil {
logger.Error("合约止盈下单成功,更新状态失败:", order.OrderSn, " err:", err)
}
}
}
// 处理止损订单
// order 止损单
func processFutStopLossOrder(db *gorm.DB, order models.LinePreOrder, price, num decimal.Decimal) error {
params := FutOrderPlace{
ApiId: order.ApiId,
Symbol: order.Symbol,
Side: order.Site,
Price: price,
Quantity: num,
OrderType: "STOP_MARKET",
StopPrice: price,
NewClientOrderId: order.OrderSn,
}
futApi := FutRestApi{}
var err error
for x := 1; x < 4; x++ {
err = futApi.OrderPlace(db, params)
if err == nil {
break
}
time.Sleep(time.Millisecond * 200)
}
if err != nil {
if err2 := db.Model(&order).Updates(map[string]interface{}{"status": 2, "desc": err.Error()}).Error; err2 != nil {
logger.Error("合约止损下单失败,更新状态失败:", order.OrderSn, " err:", err2)
}
} else {
if err := db.Model(&order).Where("status =0").
Updates(map[string]interface{}{"status": "1"}).Error; err != nil {
logger.Error("合约止损下单成功,更新状态失败:", order.OrderSn, " err:", err)
}
}
return nil
}