diff --git a/app/admin/apis/line_pre_order.go b/app/admin/apis/line_pre_order.go index 8b68bd1..3b7d15a 100644 --- a/app/admin/apis/line_pre_order.go +++ b/app/admin/apis/line_pre_order.go @@ -602,3 +602,29 @@ func (e LinePreOrder) QueryAiCoinPrice(c *gin.Context) { } e.OK(res, "操作成功") } + +// 计算回本盈利比例 +func (e LinePreOrder) CalculateBreakEevenRatio(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.CalculateBreakEevenRatioReq{} + + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + data := dto.CalculateBreakEvenRatioResp{} + err = s.CalculateBreakEvenRatio(&req, &data) + if err != nil { + e.Error(500, err, err.Error()) + return + } + + e.OK(data, "操作成功") +} diff --git a/app/admin/models/line_symbol.go b/app/admin/models/line_symbol.go index 1bd68bf..bbad8c2 100644 --- a/app/admin/models/line_symbol.go +++ b/app/admin/models/line_symbol.go @@ -2,19 +2,22 @@ package models import ( "go-admin/common/models" + + "github.com/shopspring/decimal" ) type LineSymbol struct { models.Model - ExchangeType string `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型 字典 exchange_type"` - ApiId string `json:"apiId" gorm:"type:int;comment:api账户id"` - Symbol string `json:"symbol" gorm:"type:varchar(32);comment:交易对"` - BaseAsset string `json:"baseAsset" gorm:"type:varchar(255);comment:基础货币"` - QuoteAsset string `json:"quoteAsset" gorm:"type:varchar(255);comment:计价货币"` - Switch string `json:"switch" gorm:"type:enum('0','1');comment:状态"` - Type string `json:"type" gorm:"type:enum('1','2');comment:交易对类型"` - Number int `json:"number" gorm:"->"` + ExchangeType string `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型 字典 exchange_type"` + ApiId string `json:"apiId" gorm:"type:int;comment:api账户id"` + Symbol string `json:"symbol" gorm:"type:varchar(32);comment:交易对"` + BaseAsset string `json:"baseAsset" gorm:"type:varchar(255);comment:基础货币"` + QuoteAsset string `json:"quoteAsset" gorm:"type:varchar(255);comment:计价货币"` + Switch string `json:"switch" gorm:"type:enum('0','1');comment:状态"` + Type string `json:"type" gorm:"type:enum('1','2');comment:交易对类型"` + Number int `json:"number" gorm:"->"` + LastPrice decimal.Decimal `json:"lastPrice"gorm:"->"` models.ModelTime models.ControlBy } diff --git a/app/admin/router/line_pre_order.go b/app/admin/router/line_pre_order.go index adc6a95..589f877 100644 --- a/app/admin/router/line_pre_order.go +++ b/app/admin/router/line_pre_order.go @@ -37,6 +37,8 @@ func registerLinePreOrderRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTM r.GET("getOrderPage", actions.PermissionAction(), api.GetOrderPage) //订单列表 r.POST("clearUnTriggered", actions.PermissionAction(), api.ClearUnTriggered) // 清除待触发的交易对 r.POST("aiCoinPrice", actions.PermissionAction(), api.QueryAiCoinPrice) //获取aiCoin买入点 + + r.GET("/calculate", api.CalculateBreakEevenRatio) //计算亏损后止盈百分比 } } diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go index 6264e34..31a97bb 100644 --- a/app/admin/service/dto/line_pre_order.go +++ b/app/admin/service/dto/line_pre_order.go @@ -446,6 +446,29 @@ type QueryAiCoinPriceReq struct { Symbol string `json:"symbol"` //交易对 } +// 计算亏损止盈百分比 +type CalculateBreakEevenRatioReq struct { + Price decimal.Decimal `form:"price"` + Symbol string `form:"symbol"` //交易对 + ExchangeType string `form:"exchangeType"` //交易所类型 字典exchange_type + SymbolType int `form:"symbolType"` + BuyPrice decimal.Decimal `form:"buyPrice"` //主单购买总金额 + LossBeginPercent decimal.Decimal `form:"lossBeginPercent"` //亏损开始百分比 + LossEndPercent decimal.Decimal `form:"lossEndPercent"` //亏损截至百分比 + AddPositionType int `form:"addPositionType"` //加仓类型 1-百分比 2-实际金额 + AddPositionVal decimal.Decimal `form:"addPositionVal"` //加仓金额 + ReducePercent decimal.Decimal `form:"reducePercent"` //减仓百分比 + RemainingQuantity decimal.Decimal `form:"remainingQuantity"` //剩余数量 + TotalLossAmountU decimal.Decimal `form:"totalLossAmountU"` //累计亏损金额 +} + +// 计算亏损返回值 +type CalculateBreakEvenRatioResp struct { + RemainingQuantity decimal.Decimal `json:"remainingQuantity"` //剩余数量 + Ratio decimal.Decimal `json:"ratio"` //亏损止盈百分比 + TotalLossAmountU decimal.Decimal `json:"totalLossAmountU"` //总亏损金额 +} + type SpotQueryOrderResp struct { Symbol string `json:"symbol"` OrderId int `json:"orderId"` diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index 6072b3f..88e517d 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -1316,3 +1316,65 @@ func (e *LinePreOrder) QueryAiCoinPrice(req *dto.QueryAiCoinPriceReq) (models.Li err := e.Orm.Model(&models.LineDirection{}).Where("symbol = ?", req.Symbol).Find(&info).Error return info, err } + +// 计算亏损百分比 +func (e *LinePreOrder) CalculateBreakEvenRatio(req *dto.CalculateBreakEevenRatioReq, data *dto.CalculateBreakEvenRatioResp) error { + var tradeSet models2.TradeSet + + if req.LossEndPercent.Cmp(req.LossBeginPercent) < 0 { + return errors.New("截至亏损百分比必须大于开始亏损百分比") + } + + if req.SymbolType == 1 { + val, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_SPOT, req.ExchangeType, req.Symbol)) + + sonic.Unmarshal([]byte(val), &tradeSet) + } else if req.SymbolType == 2 { + val, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_FUTURES, req.ExchangeType, req.Symbol)) + + sonic.Unmarshal([]byte(val), &tradeSet) + } else { + return errors.New("symbolType error") + } + + if tradeSet.LastPrice == "" { + return errors.New("没有找到交易对行情") + } + + var addPositionBuyPrice decimal.Decimal + + if req.AddPositionType == 1 { + addPositionBuyPrice = req.BuyPrice.Mul(req.AddPositionVal.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(2) + } else { + addPositionBuyPrice = req.BuyPrice.Add(req.AddPositionVal).Truncate(2) + } + + var percentDiff decimal.Decimal + var reduceAmount decimal.Decimal + nowPrice := req.Price.Mul(decimal.NewFromInt(1).Sub(req.LossEndPercent.Div(decimal.NewFromInt(100).Truncate(4)))) + addPositionAmount := addPositionBuyPrice.Div(nowPrice).Truncate(int32(tradeSet.AmountDigit)) + + //计算价格下跌价差 + if req.LossEndPercent.Cmp(req.LossBeginPercent) > 0 { + percentDiff = req.LossEndPercent.Sub(req.LossBeginPercent).Abs() + } + + totalAmount := req.RemainingQuantity.Add(addPositionAmount) + lossAmountU := req.Price.Mul(percentDiff.Div(decimal.NewFromInt(100)).Truncate(4)).Mul(req.RemainingQuantity).Truncate(int32(tradeSet.AmountDigit)) + + //计算减仓数量 + if req.ReducePercent.Cmp(decimal.NewFromInt(0)) > 0 { + reduceAmount = totalAmount.Mul(req.ReducePercent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.AmountDigit)) + } + + data.RemainingQuantity = totalAmount.Sub(reduceAmount) + data.TotalLossAmountU = lossAmountU.Add(req.TotalLossAmountU) + + //计算百分比 + if data.RemainingQuantity.Cmp(decimal.Zero) > 0 { + data.Ratio = data.TotalLossAmountU.Div(data.RemainingQuantity).Div(nowPrice).Mul(decimal.NewFromInt(100)).Truncate(2) + } + + return nil + +} diff --git a/app/admin/service/line_symbol.go b/app/admin/service/line_symbol.go index fc4bd93..c4ece3e 100644 --- a/app/admin/service/line_symbol.go +++ b/app/admin/service/line_symbol.go @@ -44,6 +44,35 @@ func (e *LineSymbol) GetPage(c *dto.LineSymbolGetPageReq, p *actions.DataPermiss e.Log.Errorf("LineSymbolService GetPage error:%s \r\n", err) return err } + + var key string + + if c.Type == "1" { + key = fmt.Sprintf(global.TICKER_SPOT, c.ExchangeType, "*") + } else { + key = fmt.Sprintf(global.TICKER_FUTURES, c.ExchangeType, "*") + } + + tickers, _ := helper.DefaultRedis.GetAllKeysAndValues(key) + allTicker := make(map[string]commonModels.TradeSet) + tradeSet := commonModels.TradeSet{} + + for _, v := range tickers { + sonic.Unmarshal([]byte(v), &tradeSet) + + if tradeSet.Coin != "" && tradeSet.Currency != "" { + allTicker[tradeSet.Coin+tradeSet.Currency] = tradeSet + } + } + + for index := range *list { + symbol := (*list)[index].Symbol + + if v, ok := allTicker[symbol]; ok { + (*list)[index].LastPrice = utility.StrToDecimal(v.LastPrice) + } + } + return nil } diff --git a/services/binanceservice/futuresjudgeservice.go b/services/binanceservice/futuresjudgeservice.go index 79873b8..407bcbb 100644 --- a/services/binanceservice/futuresjudgeservice.go +++ b/services/binanceservice/futuresjudgeservice.go @@ -10,7 +10,6 @@ import ( "go-admin/common/global" "go-admin/common/helper" "go-admin/models" - "go-admin/pkg/utility" "strings" "time" @@ -233,7 +232,7 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes } price := reduceOrder.Price.Mul(decimal.NewFromInt(1).Sub(setting.ReducePremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)) - num := utility.StrToDecimal(takeOrder.Num).Truncate(int32(tradeSet.AmountDigit)) + num := reduceOrder.Num.Truncate(int32(tradeSet.AmountDigit)) var positionSide string if reduceOrder.Side == "BUY" { @@ -338,7 +337,7 @@ func FutAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, futApi return } - price := v.Price + price := v.Price.Truncate(int32(tradeSet.PriceDigit)) num, _ := decimal.NewFromString(preOrder.Num) if setting.AddPositionPremium.Cmp(decimal.Zero) > 0 { @@ -351,7 +350,7 @@ func FutAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, futApi Side: v.Side, OrderType: "LIMIT", Price: price, - Quantity: num, + Quantity: num.Truncate(int32(tradeSet.AmountDigit)), NewClientOrderId: v.OrderSn, } preOrderVal, _ := sonic.MarshalString(&v) diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index b332590..2ce3df8 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -196,7 +196,7 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { takeProfitOrder.Rate = ext.ReduceTakeProfitRatio.String() //止盈需要累加之前的亏损 if totalLossAmountU.Cmp(decimal.Zero) > 0 { - percent := totalLossAmountU.Div(totalNum).Div(price).Sub(decimal.NewFromInt(1)).Abs() + percent := totalLossAmountU.Div(totalNum).Div(price).Abs() takeProfitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.ReduceTakeProfitRatio).Truncate(2).String() } @@ -241,7 +241,7 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { //加仓待触发 addPositionOrder := DbModels.LinePreOrder{} - if err := db.Model(&addPositionOrder).Where("main_id =? AND order_category=3 AND status=0", preOrder.MainId).First(addPositionOrder).Error; err != nil { + if err := db.Model(&addPositionOrder).Where("main_id =? AND order_category=3 AND status=0", preOrder.MainId).First(&addPositionOrder).Error; err != nil { logger.Errorf("handleMainReduceFilled 获取加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) return } @@ -301,7 +301,8 @@ func getFuturesPositionAvailableQuantity(db *gorm.DB, apiUserInfo DbModels.LineA if totalNum.Cmp(decimal.Zero) <= 0 { totalNum = utility.StrToDecimal(preOrder.Num) } - return totalNum + + return totalNum.Truncate(int32(tradeSet.AmountDigit)) } // 获取币安合约持仓 @@ -524,16 +525,16 @@ func createFutPreAddPosition(preOrder *DbModels.LinePreOrder, db *gorm.DB, trade var percentage decimal.Decimal if data.Site == "BUY" { - percentage = decimal.NewFromInt(1).Add(v.AddPositionPriceRatio.Div(decimal.NewFromInt(100))) - } else { percentage = decimal.NewFromInt(1).Sub(v.AddPositionPriceRatio.Div(decimal.NewFromInt(100))) + } else { + percentage = decimal.NewFromInt(1).Add(v.AddPositionPriceRatio.Div(decimal.NewFromInt(100))) } dataPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) data.Price = dataPrice.String() if v.AddPositionType == 1 { - data.Num = preOrder.Num + data.Num = utility.StrToDecimal(preOrder.Num).Truncate(int32(tradeSet.AmountDigit)).String() data.BuyPrice = "0" } else { data.BuyPrice = v.AddPositionVal.String() @@ -588,6 +589,7 @@ func makeFuturesTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, trad profitOrder := models.LinePreOrder{} copier.Copy(&profitOrder, preOrder) + profitOrder.Id = 0 profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) profitOrder.Pid = preOrder.Id profitOrder.OrderType = 1 @@ -598,7 +600,7 @@ func makeFuturesTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, trad //止盈需要累加之前的亏损 if totalLossAmountU.Cmp(decimal.Zero) > 0 { - percent := totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs() + percent := totalLossAmountU.Div(num).Div(price).Abs() profitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.TakeProfitRatio).Truncate(2).String() } @@ -617,6 +619,7 @@ func makeFuturesTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, trad stopOrder := models.LinePreOrder{} copier.Copy(&stopOrder, preOrder) + stopOrder.Id = 0 stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) stopOrder.Pid = preOrder.Id stopOrder.MainId = ext.MainOrderId diff --git a/services/binanceservice/spotjudgeservice.go b/services/binanceservice/spotjudgeservice.go index f367bac..8846797 100644 --- a/services/binanceservice/spotjudgeservice.go +++ b/services/binanceservice/spotjudgeservice.go @@ -83,6 +83,13 @@ func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi S return } + //判断是否有已触发交易对 + count, _ := GetSymbolTriggerCount(db, v.Symbol, 2) + + if count > 0 { + return + } + price, _ := decimal.NewFromString(v.Price) num, _ := decimal.NewFromString(preOrder.Num) params := OrderPlacementService{ @@ -419,13 +426,6 @@ func SpotAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, spotAp return } - //判断是否有已触发交易对 - count, _ := GetSymbolTriggerCount(db, v.Symbol, 2) - - if count > 0 { - return - } - price := v.Price num, _ := decimal.NewFromString(preOrder.Num) diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index 31e82b2..529ba88 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -199,7 +199,7 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { takeProfitOrder.Rate = ext.ReduceTakeProfitRatio.String() //止盈需要累加之前的亏损 if totalLossAmountU.Cmp(decimal.Zero) > 0 { - percent := totalLossAmountU.Div(totalNum).Div(price).Sub(decimal.NewFromInt(1)).Abs() + percent := totalLossAmountU.Div(totalNum).Div(price).Abs() takeProfitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.ReduceTakeProfitRatio).Truncate(2).String() } @@ -252,6 +252,12 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } } + for _, item := range orders { + if item.OrderType == 2 { + processStopLossOrder(item) + } + } + //计算实际亏损 if parentOrder.Price != "" { parentPrice := utility.StrToDecimal(parentOrder.Price) @@ -734,7 +740,7 @@ func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSe //止盈需要累加之前的亏损 if totalLossAmountU.Cmp(decimal.Zero) > 0 { - percent := totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs() + percent := totalLossAmountU.Div(num).Div(price).Abs() profitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.TakeProfitRatio).Truncate(2).String() } @@ -874,6 +880,7 @@ func processStopLossOrder(order models.LinePreOrder) error { price := utility.StrToDecimal(order.Price) stopLoss := dto.StopLossRedisList{ Id: order.Id, + MainId: order.MainId, PId: order.Pid, ApiId: order.ApiId, OrderTye: order.OrderType,