2025-04-03 18:32:23 +08:00
|
|
|
package binanceservice
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"go-admin/app/admin/models"
|
|
|
|
|
"go-admin/app/admin/service/dto"
|
|
|
|
|
"go-admin/common/const/rediskey"
|
|
|
|
|
"go-admin/common/global"
|
|
|
|
|
"go-admin/common/helper"
|
|
|
|
|
models2 "go-admin/models"
|
|
|
|
|
"go-admin/pkg/utility"
|
|
|
|
|
"go-admin/services/cacheservice"
|
2025-04-11 09:06:09 +08:00
|
|
|
"strings"
|
2025-04-03 18:32:23 +08:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/bytedance/sonic"
|
2025-04-11 09:06:09 +08:00
|
|
|
"github.com/go-admin-team/go-admin-core/logger"
|
2025-04-03 18:32:23 +08:00
|
|
|
"github.com/go-admin-team/go-admin-core/sdk/service"
|
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type BinanceStrategyOrderService struct {
|
|
|
|
|
service.Service
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 判断是否触发波段订单
|
|
|
|
|
func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) {
|
|
|
|
|
//现货
|
|
|
|
|
orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType))
|
|
|
|
|
e.DoJudge(orderStrs, 1, exchangeType)
|
|
|
|
|
|
|
|
|
|
//合约
|
|
|
|
|
futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType))
|
|
|
|
|
e.DoJudge(futOrdedrStrs, 2, exchangeType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理订单
|
|
|
|
|
func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) {
|
|
|
|
|
for _, orderStr := range orderStrs {
|
|
|
|
|
var lockKey string
|
|
|
|
|
orderItem := dto.StrategyOrderRedisList{}
|
|
|
|
|
err := sonic.Unmarshal([]byte(orderStr), &orderItem)
|
|
|
|
|
|
|
|
|
|
if err != nil || orderItem.Symbol == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if symbolType == 1 {
|
|
|
|
|
lockKey = rediskey.StrategySpotTriggerLock
|
|
|
|
|
} else {
|
|
|
|
|
lockKey = rediskey.StrategyFutTriggerLock
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock := helper.NewRedisLock(fmt.Sprintf(lockKey, orderItem.ApiId, orderItem.Symbol), 200, 50, 100*time.Millisecond)
|
|
|
|
|
|
|
|
|
|
if ok, err := lock.AcquireWait(context.Background()); err != nil {
|
|
|
|
|
e.Log.Debug("获取锁失败", err)
|
|
|
|
|
return
|
|
|
|
|
} else if ok {
|
|
|
|
|
defer lock.Release()
|
|
|
|
|
|
|
|
|
|
//判断是否符合条件
|
|
|
|
|
success, err := e.JudgeStrategy(orderItem, 1, exchangeType)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if success {
|
|
|
|
|
e.TriggerOrder(orderItem, symbolType)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 判断是否符合触发条件
|
|
|
|
|
func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedisList, symbolType int, exchangeType string) (bool, error) {
|
|
|
|
|
var symbolKey string
|
|
|
|
|
result := false
|
|
|
|
|
nowUtc := time.Now().UnixMilli()
|
|
|
|
|
|
|
|
|
|
switch symbolType {
|
|
|
|
|
case 1:
|
|
|
|
|
symbolKey = fmt.Sprintf(rediskey.SpotTickerLastPrice, exchangeType, order.Symbol)
|
|
|
|
|
case 2:
|
|
|
|
|
symbolKey = fmt.Sprintf(rediskey.FutureTickerLastPrice, exchangeType, order.Symbol)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastUtc := nowUtc - (int64(order.TimeSlotStart) * 60 * 1000)
|
|
|
|
|
beforePrice, _ := helper.DefaultRedis.GetNextAfterScore(symbolKey, float64(lastUtc))
|
|
|
|
|
lastPrices, _ := helper.DefaultRedis.GetLastSortSet(symbolKey)
|
|
|
|
|
|
|
|
|
|
if beforePrice == "" || len(lastPrices) == 0 {
|
|
|
|
|
return result, errors.New("获取交易对起止价格失败")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
score := lastPrices[0].Score
|
2025-04-11 09:06:09 +08:00
|
|
|
var startPrice decimal.Decimal
|
|
|
|
|
var lastPrice decimal.Decimal
|
|
|
|
|
startPriceArgs := strings.Split(beforePrice, ":")
|
|
|
|
|
endPricecArgs := strings.Split(lastPrices[0].Member.(string), ":")
|
|
|
|
|
|
|
|
|
|
if len(startPriceArgs) == 0 || len(endPricecArgs) == 0 {
|
|
|
|
|
return result, errors.New("获取交易对起止价格失败")
|
|
|
|
|
}
|
|
|
|
|
startPrice = utility.StrToDecimal(startPriceArgs[len(startPriceArgs)-1])
|
|
|
|
|
lastPrice = utility.StrToDecimal(endPricecArgs[len(endPricecArgs)-1])
|
2025-04-03 18:32:23 +08:00
|
|
|
|
|
|
|
|
//时间差超过10s
|
|
|
|
|
if (nowUtc-int64(score))/1000 > 10 {
|
|
|
|
|
return result, fmt.Errorf("时间差超过 %ss", "10")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if startPrice.Cmp(decimal.Zero) == 0 || lastPrice.Cmp(decimal.Zero) == 0 {
|
|
|
|
|
return result, errors.New("获取交易对起止价格有一个为0")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
percentag := lastPrice.Div(startPrice).Sub(decimal.NewFromInt(1)).Truncate(6)
|
|
|
|
|
|
|
|
|
|
//价格没有变动
|
|
|
|
|
if percentag.Cmp(decimal.Zero) == 0 {
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//满足条件
|
|
|
|
|
switch order.CompareType {
|
|
|
|
|
case 1:
|
|
|
|
|
result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0
|
|
|
|
|
case 2:
|
|
|
|
|
result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0
|
|
|
|
|
case 5:
|
|
|
|
|
result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0
|
|
|
|
|
default:
|
|
|
|
|
return result, errors.New("没有对应的类型")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 触发委托单
|
|
|
|
|
func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisList, symbolType int) error {
|
|
|
|
|
orders := make([]models.LinePreOrder, 0)
|
|
|
|
|
if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =?", order.Id).Find(&orders).Error; err != nil {
|
|
|
|
|
e.Log.Errorf("order_id:%d 获取委托单失败:%s", order.Id, err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setting, _ := cacheservice.GetSystemSetting(e.Orm)
|
|
|
|
|
|
|
|
|
|
if setting.Id == 0 {
|
|
|
|
|
return errors.New("获取系统设置失败")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, symbolType)
|
|
|
|
|
|
|
|
|
|
if tradeSet.Coin == "" {
|
|
|
|
|
return errors.New("获取交易对行情失败")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastPrice := utility.StrToDecimal(tradeSet.LastPrice)
|
|
|
|
|
|
|
|
|
|
if lastPrice.Cmp(decimal.Zero) <= 0 {
|
|
|
|
|
return errors.New("最新成交价小于等于0")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mainOrder models.LinePreOrder
|
|
|
|
|
|
|
|
|
|
for _, v := range orders {
|
|
|
|
|
if v.MainId == 0 {
|
|
|
|
|
mainOrder = v
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GetOrderByPid(&mainOrder, orders, mainOrder.Id)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重新计算订单单价、数量
|
|
|
|
|
// tradeSet 交易对行情
|
|
|
|
|
// mainOrder 主订单
|
|
|
|
|
// setting 系统设置
|
|
|
|
|
func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet, mainOrder *models.LinePreOrder, setting models.LineSystemSetting) error {
|
|
|
|
|
exts := make([]models.LinePreOrderExt, 0)
|
|
|
|
|
if err := e.Orm.Model(models.LinePreOrderExt{}).Where("main_id =?", mainOrder.Id).Find(&exts).Error; err != nil {
|
|
|
|
|
return errors.New("获取拓展信息失败")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastPrice := utility.StrToDecimal(tradeSet.LastPrice)
|
|
|
|
|
rate := utility.StrToDecimal(mainOrder.Rate)
|
|
|
|
|
newPrice := lastPrice.Mul(decimal.NewFromInt(1).Add(rate.Div(decimal.NewFromInt(100)).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
|
|
|
|
|
buyPrice := utility.StrToDecimal(mainOrder.BuyPrice)
|
|
|
|
|
totalNum := buyPrice.Div(newPrice).Truncate(int32(tradeSet.AmountDigit))
|
|
|
|
|
|
|
|
|
|
mainOrder.SignPrice = lastPrice.String()
|
|
|
|
|
mainOrder.Price = newPrice.String()
|
|
|
|
|
mainOrder.Num = totalNum.String()
|
2025-04-11 09:06:09 +08:00
|
|
|
remainQuantity := totalNum
|
2025-04-03 18:32:23 +08:00
|
|
|
|
|
|
|
|
for index := range mainOrder.Childs {
|
2025-04-11 09:06:09 +08:00
|
|
|
var ext models.LinePreOrderExt
|
|
|
|
|
for _, v := range exts {
|
|
|
|
|
if v.OrderId == mainOrder.Child[index].Id {
|
|
|
|
|
ext = v
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ext.Id <= 0 {
|
|
|
|
|
logger.Errorf("子订单ext不存在 id:%d", mainOrder.Child[index].Id)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//主单止盈、止损
|
|
|
|
|
if mainOrder.Child[index].Pid == mainOrder.Child[index].MainId && (mainOrder.Child[index].OrderType == 1 || mainOrder.Child[index].OrderType == 2) {
|
|
|
|
|
var percent decimal.Decimal
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
// 加价
|
|
|
|
|
case mainOrder.Child[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Child[index].OrderType == 2 && mainOrder.Site == "SELL":
|
|
|
|
|
percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio)
|
|
|
|
|
//减价
|
|
|
|
|
case mainOrder.Child[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Child[index].OrderType == 1 && mainOrder.Site == "SELL":
|
|
|
|
|
percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio)
|
2025-04-03 18:32:23 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-11 09:06:09 +08:00
|
|
|
childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
|
|
|
|
|
mainOrder.Child[index].Price = childPrice.String()
|
|
|
|
|
mainOrder.Child[index].Num = totalNum.String()
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
//todo 重新计算
|
|
|
|
|
lastNum := remainQuantity
|
|
|
|
|
|
|
|
|
|
//过期时间
|
|
|
|
|
if ext.ExpirateHour <= 0 {
|
|
|
|
|
mainOrder.Child[index].ExpireTime = time.Now().AddDate(10, 0, 0)
|
|
|
|
|
} else {
|
|
|
|
|
mainOrder.Child[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
//加仓单
|
|
|
|
|
case mainOrder.Child[index].OrderType == 1 && mainOrder.Child[index].OrderCategory == 3:
|
|
|
|
|
var percentage decimal.Decimal
|
|
|
|
|
|
2025-04-03 18:32:23 +08:00
|
|
|
if mainOrder.Site == "BUY" {
|
2025-04-11 09:06:09 +08:00
|
|
|
percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100)))
|
2025-04-03 18:32:23 +08:00
|
|
|
} else {
|
2025-04-11 09:06:09 +08:00
|
|
|
percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100)))
|
2025-04-03 18:32:23 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-11 09:06:09 +08:00
|
|
|
dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
|
|
|
|
|
mainOrder.Child[index].Price = dataPrice.String()
|
|
|
|
|
|
|
|
|
|
if ext.AddPositionType == 1 {
|
|
|
|
|
buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2)
|
|
|
|
|
mainOrder.Child[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
mainOrder.Child[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//加库存
|
|
|
|
|
lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Child[index].Num))
|
|
|
|
|
//todo 计算子订单
|
|
|
|
|
//减仓单
|
|
|
|
|
case mainOrder.Child[index].OrderType == 4:
|
|
|
|
|
// todo 计算
|
|
|
|
|
//减库存
|
|
|
|
|
lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Child[index].Num))
|
|
|
|
|
//todo 计算子订单
|
2025-04-03 18:32:23 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 递归订单树
|
|
|
|
|
func GetOrderByPid(order *models.LinePreOrder, orders []models.LinePreOrder, pid int) {
|
|
|
|
|
for _, v := range orders {
|
|
|
|
|
if v.Pid == pid {
|
|
|
|
|
GetOrderByPid(&v, orders, v.Id)
|
|
|
|
|
|
|
|
|
|
order.Childs = append(order.Childs, v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|