This commit is contained in:
2025-02-08 14:05:57 +08:00
parent c0b8749eef
commit 979ef507fe
30 changed files with 660 additions and 431 deletions

View File

@ -93,7 +93,7 @@ func (e SpotRestApi) GetSpotTicker24h(tradeSet *map[string]models.TradeSet) (del
}
for _, item := range tickers {
key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, item.Symbol)
key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, item.Symbol)
symbol, exits := (*tradeSet)[item.Symbol]
if !exits {
@ -138,7 +138,7 @@ func (e SpotRestApi) GetSpotTicker24h(tradeSet *map[string]models.TradeSet) (del
- @data 结果
*/
func (e SpotRestApi) GetSpotTicker24(symbol string, data *models.Ticker24, tradeSet *models.TradeSet) error {
key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol)
key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)
val, err := helper.DefaultRedis.GetString(key)
if err != nil {
@ -304,7 +304,7 @@ func (e SpotRestApi) CancelOpenOrderByOrderSn(apiUserInfo DbModels.LineApiUser,
} else {
client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress)
}
_, code, err := client.SendSpotAuth("/api/v3/order ", "DELETE", params)
_, code, err := client.SendSpotAuth("/api/v3/order", "DELETE", params)
if err != nil || code != 200 {
log.Error("取消现货委托失败 参数:", params)
log.Error("取消现货委托失败 code:", code)
@ -352,7 +352,7 @@ func (e SpotRestApi) ClosePosition(symbol string, orderSn string, quantity decim
}
if orderType == "LIMIT" {
key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol)
key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)
tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
rateFloat, _ := decimal.NewFromString(rate)
if rateFloat.GreaterThan(decimal.Zero) {
@ -515,6 +515,117 @@ func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi S
}
}
// 判断是否触发止损
func JudgeSpotStopLoss(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE)
stopLossVal, _ := helper.DefaultRedis.GetAllList(key)
if len(stopLossVal) == 0 {
return
}
db := GetDBConnection()
spotApi := SpotRestApi{}
setting, err := GetSystemSetting(db)
if err != nil {
log.Error("获取系统设置失败")
return
}
tradeSet, err := GetTradeSet(trade.Coin+trade.Currency, 0)
if err != nil {
log.Error("获取交易设置失败")
return
}
for _, item := range stopLossVal {
stopOrder := dto.StopLossRedisList{}
if err := sonic.Unmarshal([]byte(item), &stopOrder); err != nil {
log.Error("反序列化失败")
continue
}
if stopOrder.Symbol == trade.Coin+trade.Currency {
orderPrice := stopOrder.Price
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
//买入
if strings.ToUpper(stopOrder.Site) == "SELL" &&
orderPrice.Cmp(tradePrice) >= 0 &&
orderPrice.Cmp(decimal.Zero) > 0 &&
tradePrice.Cmp(decimal.Zero) > 0 {
SpotStopLossTrigger(db, stopOrder, spotApi, setting, tradeSet, key, item)
}
}
}
}
// 触发现货止损
func SpotStopLossTrigger(db *gorm.DB, stopOrder dto.StopLossRedisList, spotApi SpotRestApi, setting DbModels.LineSystemSetting, tradeSet models.TradeSet, key string, item string) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, stopOrder.ApiId, stopOrder.Symbol), 20, 5, 100*time.Millisecond)
if ok, err := lock.AcquireWait(context.Background()); err != nil {
log.Error("获取锁失败", err)
return
} else if ok {
defer lock.Release()
takeOrder := DbModels.LinePreOrder{}
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type =1", stopOrder.PId).Find(&takeOrder).Error; err != nil {
log.Error("查询止盈单失败")
return
}
apiInfo, _ := GetApiInfo(takeOrder.ApiId)
if apiInfo.Id == 0 {
log.Error("现货止损 查询api用户不存在")
return
}
var err error
for x := 1; x <= 4; x++ {
err = spotApi.CancelOpenOrderByOrderSn(apiInfo, takeOrder.Symbol, takeOrder.OrderSn)
if err == nil {
break
}
}
if err != nil {
log.Error("现货止损撤单失败", err)
return
}
stopPreOrder, _ := GetOrderById(db, stopOrder.Id)
price := stopOrder.Price.Mul(decimal.NewFromInt(1).Sub(setting.StopLossPremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
num := utility.StrToDecimal(takeOrder.Num).Truncate(int32(tradeSet.AmountDigit))
params := OrderPlacementService{
ApiId: takeOrder.ApiId,
Side: takeOrder.Site,
Type: "LIMIT",
TimeInForce: "GTC",
Symbol: takeOrder.Symbol,
Price: price,
Quantity: num,
NewClientOrderId: stopPreOrder.OrderSn,
}
if err := spotApi.OrderPlace(db, params); err != nil {
log.Errorf("现货止损挂单失败 id%s err:%v", stopOrder.Id, err)
}
if _, err := helper.DefaultRedis.LRem(key, item); err != nil {
log.Errorf("现货止损 删除缓存失败 id:%v err:%v", stopOrder.Id, err)
}
} else {
log.Error("获取锁失败")
}
}
/*
获取api用户信息
*/

View File

@ -319,7 +319,7 @@ func InitSymbolsTicker24h(maps *map[string]models.TradeSet) (deletes []string, e
symbol.QuoteVolume = item.QuoteVolume
symbol.LastPrice = item.LastPrice
key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, item.Symbol)
key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, item.Symbol)
if !strings.HasSuffix(item.Symbol, symbol.Currency) || item.Count <= 0 || utility.StringToFloat64(item.QuoteVolume) <= 0 {
helper.DefaultRedis.DeleteString(key)
deleteSymbol = append(deleteSymbol, item.Symbol)
@ -332,7 +332,7 @@ func InitSymbolsTicker24h(maps *map[string]models.TradeSet) (deletes []string, e
log.Error("设置行情序列化报错", err)
}
tcKey := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, item.Symbol)
tcKey := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, item.Symbol)
caches[tcKey] = string(val)
priceChange = append(priceChange, &redis.Z{
Score: symbol.PriceChange,

View File

@ -2,11 +2,9 @@ package binanceservice
import (
"context"
"errors"
"fmt"
"go-admin/app/admin/models"
DbModels "go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
@ -28,11 +26,17 @@ import (
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 {
@ -134,42 +138,47 @@ func handleStopLoss(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
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
return true
}
// 检查对冲单购买金额
if order.HedgeBuyTotal.Cmp(decimal.Zero) <= 0 {
logger.Errorf("止损单成交 对冲单购买金额为0 订单号:%s", order.OrderSn)
return
return true
}
// 获取系统设置
setting, err := GetSystemSetting(db)
if err != nil {
logger.Errorf("止损单成交 获取系统设置失败 订单号:%s err:%v", order.OrderSn, err)
return
return true
}
// 获取API信息
apiInfo, err := GetChildApiInfo(order.ApiId)
if err != nil {
logger.Errorf("止损单成交 获取api信息失败 订单号:%s err:%v", order.OrderSn, err)
return
return true
}
// 计算价格
price := utility.StrToDecimal(preOrder.Price).Truncate(int32(tradeSet.PriceDigit))
if order.CoverType == 2 {
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
return true
}
// 创建对冲订单
@ -179,17 +188,25 @@ func handleStopLoss(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
// 批量插入订单
if err := db.Model(&DbModels.LinePreOrder{}).CreateInBatches(orders, 1).Error; err != nil {
logger.Errorf("主单止损 重下对冲失败止损单id%s err:%v", order.OrderSn, err)
return
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,
@ -199,7 +216,7 @@ func createHedgeOrders(order DbModels.LinePreOrder, price decimal.Decimal, trade
SignPrice: order.Price,
SignPriceType: "new",
Rate: "0",
BuyPrice: order.HedgeBuyTotal.String(),
BuyPrice: buyPrice,
OrderCategory: 2,
OrderSn: utility.Int64ToString(snowflakehelper.GetOrderId()),
OrderType: 0,
@ -302,7 +319,9 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder) {
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)
@ -312,7 +331,7 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder) {
case 1: // 止盈
processFutTakeProfitOrder(db, futApi, order, num)
case 2: // 止损
processFutStopLossOrder(order)
processFutStopLossOrder(db, order, price, num)
}
}
}
@ -345,6 +364,8 @@ func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.Line
if err == nil {
break
}
time.Sleep(time.Millisecond * 250)
}
}
@ -364,29 +385,39 @@ func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.Line
// 处理止损订单
// order 止损单
func processFutStopLossOrder(order models.LinePreOrder) error {
price := utility.StrToDecimal(order.Price)
stopLoss := dto.StopLossRedisList{
Id: order.Id,
PId: order.Pid,
ApiId: order.ApiId,
OrderTye: order.OrderType,
OrderCategory: order.OrderCategory,
Price: price,
SymbolType: order.SymbolType,
Symbol: order.Symbol,
Site: order.Site,
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)
}
stoplossVal, _ := sonic.MarshalString(&stopLoss)
stoplossKey := fmt.Sprintf(rediskey.FuturesStopLossList, global.EXCHANGE_BINANCE)
if stoplossVal == "" {
return errors.New("stoplossVal is empty")
}
if err := helper.DefaultRedis.RPushList(stoplossKey, stoplossVal); err != nil {
return err
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

View File

@ -27,11 +27,17 @@ import (
func ChangeSpotOrder(mapData map[string]interface{}) {
// 检查订单号是否存在
orderSn, ok := mapData["c"]
originOrderSn := mapData["C"] //取消操作 代表原始订单号
if !ok {
logger.Error("订单回调失败, 没有订单号", mapData)
return
}
if originOrderSn != "" {
orderSn = originOrderSn
}
// 获取数据库连接
db := GetDBConnection()
if db == nil {
@ -123,6 +129,13 @@ func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =?", preOrder.Pid).Update("status", 9).Error; err != nil {
logger.Errorf("主单止损回调 订单号:%s 修改主单状态失败:%v", preOrder.OrderSn, err)
}
} else {
order, err := GetOrderById(db, preOrder.Pid)
if err != nil {
logger.Errorf("主单止损回调 获取主单失败 订单号:%s err:%v", preOrder.OrderSn, err)
return
}
triggerHedgeOrder(order, db, preOrder)
}
}
}
@ -178,7 +191,7 @@ func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
for _, v := range stoplossVal {
sonic.Unmarshal([]byte(v), &stoploss)
if stoploss.Pid == preOrder.Id {
if stoploss.Pid == preOrder.Pid {
_, err := helper.DefaultRedis.LRem(stoplossKey, v)
if err != nil {
@ -235,7 +248,7 @@ func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, r
params["num"] = num
params["price"] = total.Div(totalAmount)
preOrder.Num = num.String()
preOrder.Price = params["price"].(string)
preOrder.Price = total.Div(totalAmount).String()
}
case false:
status, _ := mapData["X"].(string)
@ -243,10 +256,11 @@ func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, r
if status == "FILLED" {
num, _ := decimal.NewFromString(mapData["z"].(string))
params["num"] = num.Mul(decimal.NewFromFloat(0.998)).String()
params["price"], _ = mapData["ap"].(string)
price, _ := mapData["ap"].(string)
params["price"] = price
preOrder.Num = num.String()
preOrder.Price = params["price"].(string)
preOrder.Price = price
}
}