diff --git a/app/admin/apis/line_direction.go b/app/admin/apis/line_direction.go index 0d50b35..a45eb71 100644 --- a/app/admin/apis/line_direction.go +++ b/app/admin/apis/line_direction.go @@ -91,6 +91,45 @@ func (e LineDirection) Get(c *gin.Context) { e.OK(object, "查询成功") } +// Get 获取预估方向管理 +// @Summary 获取预估方向管理 +// @Description 获取预估方向管理 +// @Tags 预估方向管理 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineDirection} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-direction/{id} [get] +// @Security Bearer +func (e LineDirection) GetBySymbol(c *gin.Context) { + req := dto.LineDirectionGetReq{} + s := service.LineDirection{} + 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 + } + + if req.Symbol == "" || req.SymbolType == 0 { + e.Error(500, nil, "参数缺失") + return + } + + var object models.LineDirection + + p := actions.GetPermissionFromContext(c) + err = s.GetBySymbol(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取预估方向管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + // Insert 创建预估方向管理 // @Summary 创建预估方向管理 // @Description 创建预估方向管理 diff --git a/app/admin/models/line_pre_order.go b/app/admin/models/line_pre_order.go index 313ec9d..f790c17 100644 --- a/app/admin/models/line_pre_order.go +++ b/app/admin/models/line_pre_order.go @@ -33,7 +33,7 @@ type LinePreOrder struct { CoverType int `json:"coverType" gorm:"type:int unsigned;omitempty;comment:对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货"` ExpireTime time.Time `json:"expireTime" gorm:"comment:过期时间"` MainOrderType string `json:"mainOrderType" gorm:"type:enum;comment:第一笔(主单类型) 限价(LIMIT)市价(MARKET)"` - LossAmount decimal.Decimal `json:"lossAmount" gorm:"type:decimal(15,2);comment:亏损金额(U)"` + LossAmount decimal.Decimal `json:"lossAmount" gorm:"type:decimal(18,8);comment:亏损金额(U)"` Child []LinePreOrder `json:"child" gorm:"-"` ApiName string `json:"api_name" gorm:"->"` ChildNum int64 `json:"child_num" gorm:"->"` diff --git a/app/admin/router/line_direction.go b/app/admin/router/line_direction.go index ce84a42..eb0f35c 100644 --- a/app/admin/router/line_direction.go +++ b/app/admin/router/line_direction.go @@ -30,6 +30,7 @@ func registerLineDirectionRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWT { r.GET("", actions.PermissionAction(), api.GetPage) r.GET("/:id", actions.PermissionAction(), api.Get) + r.GET("/symbol", actions.PermissionAction(), api.GetBySymbol) r.POST("", api.Insert) r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) diff --git a/app/admin/service/dto/line_direction.go b/app/admin/service/dto/line_direction.go index 310446b..047adcb 100644 --- a/app/admin/service/dto/line_direction.go +++ b/app/admin/service/dto/line_direction.go @@ -114,7 +114,9 @@ func (s *LineDirectionUpdateReq) GetId() interface{} { // LineDirectionGetReq 功能获取请求参数 type LineDirectionGetReq struct { - Id int `uri:"id"` + Id int `uri:"id"` + Symbol string `json:"symbol" form:"symbol"` + SymbolType int `json:"symbolType" form:"symbolType"` } func (s *LineDirectionGetReq) GetId() interface{} { diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go index 6b4e24a..93cf3b6 100644 --- a/app/admin/service/dto/line_pre_order.go +++ b/app/admin/service/dto/line_pre_order.go @@ -234,31 +234,31 @@ func (req LineAddPreOrderReq) CheckParams() error { // LineBatchAddPreOrderReq 批量添加订单请求参数 type LineBatchAddPreOrderReq struct { - ExchangeType string `json:"exchange_type"` //交易所类型 字典exchange_type - SymbolType int `json:"symbolType"` //主单交易对类型 0-现货 1-合约 - OrderType int `json:"order_type"` //订单类型 - SymbolGroupId string `json:"symbol_group_id"` //交易对组id - Symbol string `json:"symbol"` //交易对 - ApiUserId string `json:"api_id"` //下单用户 - Site string `json:"site"` //购买方向 - BuyPrice string `json:"buy_price"` //购买金额 U - PricePattern string `json:"price_pattern"` //价格模式 - Price string `json:"price"` //下单价百分比 - Profit string `json:"profit"` //止盈价 - StopPrice string `json:"stop_price"` //止损价 - PriceType string `json:"price_type"` //价格类型 - SaveTemplate string `json:"save_template"` //是否保存模板 - TemplateName string `json:"template_name"` //模板名字 - OrderNum int `json:"order_num"` //脚本运行次数 - Script string `json:"script"` //是否是脚本运行 1 = 是 0= 否 - CoverType int `json:"cover_type"` //对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货 - ExpireHour int `json:"expire_hour"` // 过期时间 单位小时 - MainOrderType string `json:"main_order_type"` //主单类型:限价(LIMIT)或市价(MARKET) - ReducePriceRatio decimal.Decimal `json:"reduce_price"` //主单减仓价格百分比 - ReduceNumRatio decimal.Decimal `json:"reduce_num"` //主单减仓数量百分比 - ReduceTakeProfitRatio decimal.Decimal `json:"reduce_take_profit"` //主单减仓后止盈价百分比 - ReduceStopLossRatio decimal.Decimal `json:"reduce_stop_price"` //主单减仓后止损价百分比 - Ext LineAddPreOrderExtReq `json:"ext"` //拓展字段 + ExchangeType string `json:"exchange_type"` //交易所类型 字典exchange_type + SymbolType int `json:"symbol_type"` //主单交易对类型 0-现货 1-合约 + OrderType int `json:"order_type"` //订单类型 + SymbolGroupId string `json:"symbol_group_id"` //交易对组id + Symbol string `json:"symbol"` //交易对 + ApiUserId string `json:"api_id"` //下单用户 + Site string `json:"site"` //购买方向 + BuyPrice string `json:"buy_price"` //购买金额 U + PricePattern string `json:"price_pattern"` //价格模式 + Price string `json:"price"` //下单价百分比 + Profit string `json:"profit"` //止盈价 + StopPrice string `json:"stop_price"` //止损价 + PriceType string `json:"price_type"` //价格类型 + SaveTemplate string `json:"save_template"` //是否保存模板 + TemplateName string `json:"template_name"` //模板名字 + OrderNum int `json:"order_num"` //脚本运行次数 + Script string `json:"script"` //是否是脚本运行 1 = 是 0= 否 + CoverType int `json:"cover_type"` //对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货 + ExpireHour int `json:"expire_hour"` // 过期时间 单位小时 + MainOrderType string `json:"main_order_type"` //主单类型:限价(LIMIT)或市价(MARKET) + ReducePriceRatio decimal.Decimal `json:"reduce_price"` //主单减仓价格百分比 + ReduceNumRatio decimal.Decimal `json:"reduce_num"` //主单减仓数量百分比 + ReduceTakeProfitRatio decimal.Decimal `json:"reduce_take_profit"` //主单减仓后止盈价百分比 + ReduceStopLossRatio decimal.Decimal `json:"reduce_stop_price"` //主单减仓后止损价百分比 + Ext []LineAddPreOrderExtReq `json:"ext"` //拓展字段 } func (req LineBatchAddPreOrderReq) CheckParams() error { @@ -337,6 +337,11 @@ type GetChildOrderReq struct { Id int `json:"id"` //id } +type LinePreOrderChildCount struct { + Id int `json:"id"` + Count int `json:"count"` +} + func (c MarginTypeReq) CheckParams() error { if c.Symbol == "" && c.GroupId == 0 { return errors.New("请选择交易对或交易对组") diff --git a/app/admin/service/dto/line_pre_order_ext.go b/app/admin/service/dto/line_pre_order_ext.go index 8892a6b..793a9c3 100644 --- a/app/admin/service/dto/line_pre_order_ext.go +++ b/app/admin/service/dto/line_pre_order_ext.go @@ -43,6 +43,7 @@ type LineAddPreOrderExtReq struct { ReduceTakeProfitRatio decimal.Decimal `json:"reduceTakeProfitRatio" comment:"减仓后止盈百分比"` ReduceStopLossRatio decimal.Decimal `json:"reduceStopLossRatio" comment:"减仓后止损百分比"` AddPositionPriceRatio decimal.Decimal `json:"addPositionPriceRatio" comment:"加仓价格百分比"` + AddPositionOrderType string `json:"addPositionOrderType" comment:"加仓订单类型 LIMIT-限价 MARKET-市价"` AddPositionType int `json:"addPositionType" comment:"加仓类型 1-百分比 2-实际金额"` AddPositionVal decimal.Decimal `json:"addPositionVal" comment:"加仓值"` } diff --git a/app/admin/service/dto/line_system_setting.go b/app/admin/service/dto/line_system_setting.go index 65fc4ec..2d0bea2 100644 --- a/app/admin/service/dto/line_system_setting.go +++ b/app/admin/service/dto/line_system_setting.go @@ -77,6 +77,8 @@ func (s *LineSystemSettingUpdateReq) Generate(model *models.LineSystemSetting) { model.ProfitRate = s.ProfitRate model.CoverOrderTypeBRate = s.CoverOrderTypeBRate model.StopLossPremium = s.StopLossPremium + model.AddPositionPremium = s.AddPositionPremium + model.ReducePremium = s.ReducePremium model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 } diff --git a/app/admin/service/line_direction.go b/app/admin/service/line_direction.go index 7e73397..f88354b 100644 --- a/app/admin/service/line_direction.go +++ b/app/admin/service/line_direction.go @@ -61,6 +61,25 @@ func (e *LineDirection) Get(d *dto.LineDirectionGetReq, p *actions.DataPermissio return nil } +// Get 获取LineDirection对象 +func (e *LineDirection) GetBySymbol(d *dto.LineDirectionGetReq, p *actions.DataPermission, model *models.LineDirection) error { + var data models.LineDirection + + err := e.Orm.Model(&data). + Where("symbol =? AND type =?", d.Symbol, d.SymbolType). + First(model).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在") + e.Log.Errorf("Service GetLineDirection error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + // Insert 创建LineDirection对象 func (e *LineDirection) Insert(c *dto.LineDirectionInsertReq) error { var err error diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index 3b8a6f1..52e702a 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -139,6 +139,29 @@ func (e *LinePreOrder) GetChildList(req *dto.GetChildOrderReq, p *actions.DataPe e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err) return err } + ids := make([]int, 0) + + for _, v := range *order { + ids = append(ids, v.Id) + } + + if len(ids) > 0 { + var counts []dto.LinePreOrderChildCount + + if err := e.Orm.Model(&models.LinePreOrder{}).Where("pid in ?", ids).Group("pid").Select("pid as id,count(*) as count").Find(&counts).Error; err != nil { + logger.Errorf("获取子订单数量失败 %v", err) + return nil + } + + for index := range *order { + for _, v := range counts { + if v.Id == (*order)[index].Id { + (*order)[index].ChildNum = int64(v.Count) + } + } + } + } + return nil } @@ -249,7 +272,7 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi helper.DefaultRedis.LRem(listKey, string(marshal)) } - binanceservice.MainClosePositionClearCache(order.Id, order.CoverType) + binanceservice.MainClosePositionClearCache(order.Id, order.SymbolType) ints = append(ints, order.Id) } @@ -341,7 +364,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP } else { //实际价格下单 AddOrder.Price = utility.StringToDecimal(req.Price).Truncate(int32(tradeSet.PriceDigit)).String() AddOrder.SignPrice = req.Price - AddOrder.SignPriceType = "mixture" + AddOrder.SignPriceType = req.PricePattern AddOrder.Rate = "0" } buyPrice, _ := decimal.NewFromString(req.BuyPrice) //购买多少U @@ -413,6 +436,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP ReduceTakeProfitRatio: addPosition.ReduceTakeProfitRatio, ReduceStopLossRatio: addPosition.ReduceStopLossRatio, AddPositionPriceRatio: addPosition.AddPositionPriceRatio, + AddPositionOrderType: addPosition.AddPositionOrderType, AddPositionType: addPosition.AddPositionType, AddPositionVal: addPosition.AddPositionVal, } @@ -510,7 +534,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP // CheckRepeatOrder 检查重复下单 检查基础货币 func (e *LinePreOrder) CheckRepeatOrder(orderType int, apiUserId, site, baseCoin string) int64 { var count int64 - e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND symbol like ? AND order_type = ? AND site = ? AND `status` IN (1,5,6)", apiUserId, baseCoin+"%", orderType, site).Count(&count) + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND pid=0 AND symbol like ? AND order_type = ? AND site = ? AND `status` IN (1,5,6)", apiUserId, baseCoin+"%", orderType, site).Count(&count) return count } @@ -597,6 +621,7 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p req.PricePattern = batchReq.PricePattern req.Price = batchReq.Price req.Profit = batchReq.Profit + req.Ext = batchReq.Ext // req.StopPrice = batchReq.StopPrice req.ReducePriceRatio = batchReq.ReducePriceRatio req.PriceType = batchReq.PriceType @@ -1056,6 +1081,7 @@ func (e *LinePreOrder) SpotClosePosition(position *dto.ClosePosition, errs *[]er ApiId: list.ApiId, GroupId: "0", Symbol: list.Symbol, + SymbolType: position.CloseType, QuoteSymbol: list.QuoteSymbol, SignPrice: lastPrice.String(), SignPriceType: "new", @@ -1086,7 +1112,7 @@ func (e *LinePreOrder) SpotClosePosition(position *dto.ClosePosition, errs *[]er *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 币安下订单失败:%s", position.ApiId, err.Error()))) continue } - binanceservice.MainClosePositionClearCache(list.Id, list.CoverType) + binanceservice.MainClosePositionClearCache(list.Id, list.SymbolType) } } } @@ -1160,10 +1186,12 @@ func (e *LinePreOrder) FutClosePosition(position *dto.ClosePosition, errs *[]err order := models.LinePreOrder{ Pid: parentId, + ExchangeType: list.ExchangeType, ApiId: list.ApiId, MainId: list.Id, GroupId: "0", Symbol: list.Symbol, + SymbolType: position.CloseType, QuoteSymbol: list.QuoteSymbol, SignPrice: lastPrice.String(), SignPriceType: "new", @@ -1205,7 +1233,7 @@ func (e *LinePreOrder) FutClosePosition(position *dto.ClosePosition, errs *[]err continue } // 主单平仓删除缓存 - binanceservice.MainClosePositionClearCache(list.Id, list.CoverType) + binanceservice.MainClosePositionClearCache(list.Id, list.SymbolType) } } } diff --git a/services/binanceservice/binancerest.go b/services/binanceservice/binancerest.go index a048b69..a7e50db 100644 --- a/services/binanceservice/binancerest.go +++ b/services/binanceservice/binancerest.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" DbModels "go-admin/app/admin/models" - "go-admin/app/admin/service/dto" "go-admin/common/const/rediskey" commondto "go-admin/common/dto" "go-admin/common/global" @@ -14,6 +13,7 @@ import ( "go-admin/pkg/httputils" "go-admin/pkg/utility" "strings" + "time" "github.com/shopspring/decimal" "gorm.io/gorm" @@ -34,7 +34,7 @@ var ErrorMaps = map[float64]string{ -1111: "金额设置错误。精度错误", -1021: "请求的时间戳在recvWindow之外", -2011: "该交易对没有订单", - -2010: "账户余额不足", + // -2010: "账户余额不足", } type SpotRestApi struct { @@ -174,6 +174,36 @@ func (e SpotRestApi) Ticker() { helper.DefaultRedis.SetString(rediskey.SpotSymbolTicker, string(mapData)) } +// 循环下单 +func (e SpotRestApi) OrderPlaceLoop(db *gorm.DB, params OrderPlacementService, retryCount int) error { + var err error + err = e.OrderPlace(db, params) + + if err != nil { + //数量不正确 + if strings.Contains(err.Error(), "LOT_SIZE") { + return err + } + + for x := 1; x <= retryCount; x++ { + err = e.OrderPlace(db, params) + if err == nil || strings.Contains(err.Error(), "LOT_SIZE") { + break + } + + // if strings.Contains(err.Error(), "余额不足") { + // apiUserInfo, _ := GetApiInfo(params.ApiId) + // tradeset, _ := GetTradeSet(params.Symbol, 0) + // num, _ := getSpotPositionNum(apiUserInfo, &DbModels.LinePreOrder{Symbol: params.Symbol, QuoteSymbol: "USDT", OrderSn: params.NewClientOrderId}, tradeset) + // log.Info(" 循环下单 余额:%v", num) + // } + time.Sleep(500 * time.Millisecond) + } + } + + return err +} + // OrderPlace 现货下单 func (e SpotRestApi) OrderPlace(orm *gorm.DB, params OrderPlacementService) error { if orm == nil { @@ -476,15 +506,26 @@ func GetApiGroups() []commondto.ApiGroupDto { // GetSpotSymbolLastPrice 获取现货交易对最新价格 func (e SpotRestApi) GetSpotSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) { - tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() - tickerSymbolMaps := make([]dto.Ticker, 0) - sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) - //key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, targetSymbol) - //tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) - for _, symbolMap := range tickerSymbolMaps { - if symbolMap.Symbol == strings.ToUpper(targetSymbol) { - lastPrice = utility.StringToDecimal(symbolMap.Price) + key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, targetSymbol) + ticker, _ := helper.DefaultRedis.GetString(key) + + if ticker != "" { + tradeSet := models.TradeSet{} + sonic.Unmarshal([]byte(ticker), &tradeSet) + + if tradeSet.LastPrice != "" { + lastPrice = utility.StrToDecimal(tradeSet.LastPrice).Truncate(int32(tradeSet.PriceDigit)) } } + // tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + // tickerSymbolMaps := make([]dto.Ticker, 0) + // sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + // //key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, targetSymbol) + // //tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) + // for _, symbolMap := range tickerSymbolMaps { + // if symbolMap.Symbol == strings.ToUpper(targetSymbol) { + // lastPrice = utility.StringToDecimal(symbolMap.Price) + // } + // } return lastPrice } diff --git a/services/binanceservice/commonservice.go b/services/binanceservice/commonservice.go index fea6845..c1e2cfc 100644 --- a/services/binanceservice/commonservice.go +++ b/services/binanceservice/commonservice.go @@ -275,9 +275,9 @@ func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice // 主单平仓删除缓存 // mainOrderId 主单id -// coverType 1现货->合约 2->合约->合约 3合约->现货 -func MainClosePositionClearCache(mainOrderId int, coverType int) { - if coverType == 1 { +// symbolType 1现货 2合约 +func MainClosePositionClearCache(mainId int, symbolType int) { + if symbolType == 1 { keySpotStop := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) keySpotAddposition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) spotStopArray, _ := helper.DefaultRedis.GetAllList(keySpotStop) @@ -290,7 +290,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { log.Error("MainClosePositionClearCache Unmarshal err:", err) } - if position.Pid == mainOrderId { + if position.MainId == mainId { helper.DefaultRedis.LRem(keySpotAddposition, item) } } @@ -300,7 +300,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { log.Error("MainClosePositionClearCache Unmarshal err:", err) } - if stop.PId == mainOrderId { + if stop.MainId == mainId { helper.DefaultRedis.LRem(keySpotStop, item) } } @@ -318,7 +318,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { log.Error("MainClosePositionClearCache Unmarshal err:", err) } - if position.Pid == mainOrderId { + if position.MainId == mainId { helper.DefaultRedis.LRem(keyFutAddposition, item) } } @@ -328,7 +328,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { log.Error("MainClosePositionClearCache Unmarshal err:", err) } - if stop.PId == mainOrderId { + if stop.MainId == mainId { helper.DefaultRedis.LRem(keyFutStop, item) } } @@ -352,3 +352,61 @@ func getPreOrder(db *gorm.DB, orderSn interface{}) (*DbModels.LinePreOrder, erro } return preOrder, nil } + +// 取消主单下的止盈止损 +// mainId 主单id +// symbolType 1现货 2合约 +func cancelSymbolTakeAndStop(db *gorm.DB, mainId int, symbolType int) error { + orders, err := GetSymbolTakeAndStop(db, mainId, symbolType) + errStr := "" + + if err != nil { + return err + } + + for _, order := range orders { + apiUserInfo, _ := GetApiInfo(order.ApiId) + if apiUserInfo.Id == 0 { + logger.Errorf("cancelSymbolTakeAndStop 查询api用户失败 apiid:%v ordersn:%s", order.ApiId, order.OrderSn) + } + + switch { + case order.OrderType == 1 && symbolType == 1: + err = CancelOpenOrderByOrderSnLoop(apiUserInfo, order.Symbol, order.OrderSn) + + if err != nil { + errStr += fmt.Sprintf("取消止盈失败,订单号:%s,错误:%v ", order.OrderSn, err) + } + case order.OrderType == 2 && symbolType == 1: + key := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) + stoplosss, _ := helper.DefaultRedis.GetAllList(key) + stop := dto.StopLossRedisList{} + + for _, item := range stoplosss { + sonic.Unmarshal([]byte(item), &stop) + + if stop.MainId == order.MainId { + _, err2 := helper.DefaultRedis.LRem(key, item) + + if err2 != nil { + logger.Errorf("移除止损缓存失败 err:%v", err2) + } + } + } + case order.OrderType == 1 && symbolType == 2: + err = CancelFutOrderByOrderSnLoop(apiUserInfo, order.Symbol, order.OrderSn) + + if err != nil { + errStr += fmt.Sprintf("取消止盈失败,订单号:%s,错误:%v ", order.OrderSn, err) + } + case order.OrderType == 2 && symbolType == 2: + err = CancelFutOrderByOrderSnLoop(apiUserInfo, order.Symbol, order.OrderSn) + + if err != nil { + errStr += fmt.Sprintf("取消止损失败,订单号:%s,错误:%v ", order.OrderSn, err) + } + } + } + + return nil +} diff --git a/services/binanceservice/futuresbinancerest.go b/services/binanceservice/futuresbinancerest.go index c1ae766..3b9bc95 100644 --- a/services/binanceservice/futuresbinancerest.go +++ b/services/binanceservice/futuresbinancerest.go @@ -415,6 +415,48 @@ func CheckKlineType(kLineType string) bool { return false } +func (e FutRestApi) OrderPlaceLoop(db *gorm.DB, params FutOrderPlace, retryCount int) error { + var err error + err = e.OrderPlace(db, params) + if err != nil { + //数量不正确 + if strings.Contains(err.Error(), "LOT_SIZE") { + return err + } + + for x := 1; x <= retryCount; x++ { + err = e.OrderPlace(db, params) + if err == nil || strings.Contains(err.Error(), "LOT_SIZE") { + break + } + time.Sleep(200 * time.Millisecond) + } + } + return err +} + +// 循环平仓 +func (e FutRestApi) ClosePositionLoop(symbol string, orderSn string, quantity decimal.Decimal, side string, positionSide string, + apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal, retryCount int) error { + var err error + err = e.ClosePosition(symbol, orderSn, quantity, side, positionSide, apiUserInfo, orderType, rate, price) + if err != nil { + //数量不正确 + if strings.Contains(err.Error(), "LOT_SIZE") { + return err + } + + for x := 1; x <= retryCount; x++ { + err = e.ClosePosition(symbol, orderSn, quantity, side, positionSide, apiUserInfo, orderType, rate, price) + if err == nil || strings.Contains(err.Error(), "LOT_SIZE") { + break + } + time.Sleep(200 * time.Millisecond) + } + } + return err +} + // OrderPlace 合约下单 func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { if orm == nil { @@ -447,7 +489,7 @@ func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { paramsMaps["timeInForce"] = "GTC" paramsMaps["price"] = params.Price.String() paramsMaps["stopprice"] = params.Profit.String() - paramsMaps["workingType"] = "MARK_PRICE" + // paramsMaps["workingType"] = "MARK_PRICE" } if strings.ToUpper(params.OrderType) == "STOP_MARKET" { @@ -872,13 +914,25 @@ func (e FutRestApi) CoverAccountA(apiUserInfo DbModels.LineApiUser, symbol strin // GetFutSymbolLastPrice 获取现货交易对最新价格 func (e FutRestApi) GetFutSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) { - tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() - tickerSymbolMaps := make([]dto.Ticker, 0) - sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) - for _, symbolMap := range tickerSymbolMaps { - if symbolMap.Symbol == strings.ToUpper(targetSymbol) { - lastPrice = utility.StringToDecimal(symbolMap.Price) + key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, targetSymbol) + tickerSymbol, _ := helper.DefaultRedis.GetString(key) + + if tickerSymbol != "" { + tradeSet := models.TradeSet{} + + sonic.Unmarshal([]byte(tickerSymbol), &tradeSet) + + if tradeSet.LastPrice != "" { + lastPrice = utility.StrToDecimal(tradeSet.LastPrice).Truncate(int32(tradeSet.PriceDigit)) } } + // tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + // tickerSymbolMaps := make([]dto.Ticker, 0) + // sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + // for _, symbolMap := range tickerSymbolMaps { + // if symbolMap.Symbol == strings.ToUpper(targetSymbol) { + // lastPrice = utility.StringToDecimal(symbolMap.Price) + // } + // } return lastPrice } diff --git a/services/binanceservice/futuresjudgeservice.go b/services/binanceservice/futuresjudgeservice.go index 63085c7..79873b8 100644 --- a/services/binanceservice/futuresjudgeservice.go +++ b/services/binanceservice/futuresjudgeservice.go @@ -88,6 +88,13 @@ func futTriggerOrder(db *gorm.DB, v *dto.PreOrderRedisList, item string, futApi return } + //判断是否有已触发交易对 + count, _ := GetSymbolTriggerCount(db, v.Symbol, 2) + + if count > 0 { + return + } + price, _ := decimal.NewFromString(v.Price) num, _ := decimal.NewFromString(preOrder.Num) @@ -108,7 +115,7 @@ func futTriggerOrder(db *gorm.DB, v *dto.PreOrderRedisList, item string, futApi } preOrderVal, _ := sonic.MarshalString(&v) - if err := futApi.OrderPlace(db, params); err != nil { + if err := futApi.OrderPlaceLoop(db, params, 3); err != nil { log.Error("下单失败", v.Symbol, " err:", err) err := db.Model(&DbModels.LinePreOrder{}).Where("id =? and status='0'", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error @@ -218,35 +225,35 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes return } - var err error - for x := 1; x <= 4; x++ { - err = futApi.CancelFutOrder(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) - - if err == nil || strings.Contains(err.Error(), "取消订单被拒绝") { - err = nil - break - } - - } + err := CancelFutOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) if err != nil { log.Error("合约止盈撤单失败", err) return } + 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)) + var positionSide string - params := FutOrderPlace{ - ApiId: reduceOrder.ApiId, - Side: reduceOrder.Side, - OrderType: "LIMIT", - Symbol: reduceOrder.Symbol, - Price: price, - Quantity: num, - NewClientOrderId: reduceOrder.OrderSn, + if reduceOrder.Side == "BUY" { + positionSide = "SHORT" + } else { + positionSide = "LONG" } - if err := futApi.OrderPlace(db, params); err != nil { + // params := FutOrderPlace{ + // ApiId: reduceOrder.ApiId, + // Side: reduceOrder.Side, + // OrderType: "STOP", + // Symbol: reduceOrder.Symbol, + // Price: price, + // StopPrice: price, + // Quantity: num, + // NewClientOrderId: reduceOrder.OrderSn, + // } + + if err := futApi.ClosePositionLoop(reduceOrder.Symbol, reduceOrder.OrderSn, num, reduceOrder.Side, positionSide, apiInfo, "LIMIT", "0", price, 3); err != nil { log.Errorf("合约减仓挂单失败 id:%s err:%v", reduceOrder.Id, err) } @@ -349,7 +356,7 @@ func FutAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, futApi } preOrderVal, _ := sonic.MarshalString(&v) - if err := futApi.OrderPlace(db, params); err != nil { + if err := futApi.OrderPlaceLoop(db, params, 3); err != nil { log.Error("下单失败", v.Symbol, " err:", err) err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status =0", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index efde2eb..ccb5a84 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -115,6 +115,11 @@ func handleFutOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderSta // 减仓回调 func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { apiUserInfo, _ := GetApiInfo(preOrder.ApiId) + mainId := preOrder.Id + + if preOrder.MainId > 0 { + mainId = preOrder.MainId + } if apiUserInfo.Id == 0 { logger.Errorf("handleMainReduceFilled 获取api信息失败,订单号:%s", preOrder.OrderSn) @@ -137,7 +142,7 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } parentPrice := utility.StrToDecimal(parentOrder.Price) num := utility.StrToDecimal(preOrder.Num) - lossAmount := price.Sub(parentPrice).Abs().Mul(num) + lossAmount := price.Sub(parentPrice).Abs().Mul(num).Truncate(int32(tradeSet.PriceDigit)) if !strings.HasSuffix(preOrder.Symbol, "USDT") { tradeSetU, err := GetTradeSet(utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, ""), 1) @@ -147,10 +152,10 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { return } - lossAmount = lossAmount.Mul(utility.StrToDecimal(tradeSetU.LastPrice)).Truncate(2) + lossAmount = lossAmount.Mul(utility.StrToDecimal(tradeSetU.LastPrice)).Truncate(int32(tradeSetU.PriceDigit)) } - if err := db.Model(&parentOrder).Where("loss_amount=0", preOrder.Pid).Update("loss_amount", lossAmount).Error; err != nil { + if err := db.Model(&parentOrder).Where("loss_amount = ?", 0).Update("loss_amount", lossAmount).Error; err != nil { logger.Errorf("handleMainReduceFilled 更新亏损金额失败,订单号:%s", preOrder.OrderSn) return } @@ -162,59 +167,74 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { db.Model(&ext).Where("order_id =?", preOrder.Pid).First(&ext) // 不是100%减仓 就需要挂止盈止损 - if rate < 100 { - totalNum, err := getFuturesPositionNum(apiUserInfo, preOrder, tradeSet) - - if err != nil { - return + if rate >= 100 { + removeFutLossAndAddPosition(preOrder) + ids := []int{preOrder.MainId, preOrder.Pid} + if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status =6", ids).Update("status", 9).Error; err != nil { + logger.Info("100%减仓完毕,终结流程") } + return + } - takeProfitOrder := models.LinePreOrder{} - copier.Copy(&takeProfitOrder, &preOrder) - takeProfitOrder.Id = 0 - takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) - takeProfitOrder.Status = 0 - takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio)).Truncate(int32(tradeSet.PriceDigit)).String() - takeProfitOrder.OrderType = 1 - takeProfitOrder.Rate = "100" - takeProfitOrder.SignPrice = preOrder.Price - takeProfitOrder.CreatedAt = time.Now() - takeProfitOrder.BuyPrice = "0" - takeProfitOrder.MainOrderType = "LIMIT" - takeProfitOrder.Num = totalNum.String() - orders = append(orders, takeProfitOrder) + totalLossAmountU, _ := GetTotalLossAmount(db, mainId) + totalNum := getFuturesPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) - //有止损单 - if ext.ReduceStopLossRatio.Cmp(decimal.Zero) > 0 { - var stoploss models.LinePreOrder + takeProfitOrder := models.LinePreOrder{} + copier.Copy(&takeProfitOrder, &preOrder) + takeProfitOrder.Id = 0 + takeProfitOrder.Pid = preOrder.Id + takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + takeProfitOrder.Status = 0 + takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio)).Truncate(int32(tradeSet.PriceDigit)).String() + takeProfitOrder.OrderType = 1 + takeProfitOrder.Rate = ext.ReduceTakeProfitRatio.String() + takeProfitOrder.SignPrice = preOrder.Price + takeProfitOrder.CreatedAt = time.Now() + takeProfitOrder.BuyPrice = "0" + takeProfitOrder.MainOrderType = "LIMIT" + takeProfitOrder.Num = totalNum.String() + takeProfitOrder.Rate = ext.ReduceTakeProfitRatio.String() + //止盈需要累加之前的亏损 + if totalLossAmountU.Cmp(decimal.Zero) > 0 { + percent := totalLossAmountU.Div(totalNum).Div(price).Sub(decimal.NewFromInt(1)).Abs() + takeProfitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.ReduceTakeProfitRatio).Truncate(2).String() + } - copier.Copy(&stoploss, &preOrder) - stoploss.Id = 0 - stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) - stoploss.Status = 0 - stoploss.CreatedAt = time.Now() - stoploss.OrderType = 2 - stoploss.SignPrice = preOrder.Price - stoploss.BuyPrice = "0" - stoploss.Rate = "100" - stoploss.MainOrderType = "LIMIT" - stoploss.Num = totalNum.String() + setPrice(&takeProfitOrder, preOrder, tradeSet) + orders = append(orders, takeProfitOrder) - orders = append(orders, stoploss) - } + //有止损单 + if ext.ReduceStopLossRatio.Cmp(decimal.Zero) > 0 { + var stoploss models.LinePreOrder - if err := db.Create(&orders).Error; err != nil { - logger.Errorf("handleMainReduceFilled 创建止盈止损单失败:%v", err) - return - } + copier.Copy(&stoploss, &preOrder) + stoploss.Id = 0 + stoploss.Pid = preOrder.Id + stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + stoploss.Status = 0 + stoploss.CreatedAt = time.Now() + stoploss.OrderType = 2 + stoploss.SignPrice = preOrder.Price + stoploss.BuyPrice = "0" + stoploss.Rate = ext.ReduceStopLossRatio.String() + stoploss.MainOrderType = "LIMIT" + stoploss.Num = totalNum.String() - futApi := FutRestApi{} - for _, v := range orders { - if v.OrderType == 1 { - processFutTakeProfitOrder(db, futApi, v, totalNum) - } else if v.OrderType == 2 { - processFutStopLossOrder(db, v, utility.StrToDecimal(v.Price), totalNum) - } + setPrice(&stoploss, preOrder, tradeSet) + orders = append(orders, stoploss) + } + + if err := db.Create(&orders).Error; err != nil { + logger.Errorf("handleMainReduceFilled 创建止盈止损单失败:%v", err) + return + } + + futApi := FutRestApi{} + for _, v := range orders { + if v.OrderType == 1 { + processFutTakeProfitOrder(db, futApi, v, totalNum) + } else if v.OrderType == 2 { + processFutStopLossOrder(db, v, utility.StrToDecimal(v.Price), totalNum) } } @@ -229,6 +249,8 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { keyFutAddpositionKey := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE) addPositionData := AddPositionList{ + Id: addPositionOrder.Id, + OrderSn: addPositionOrder.OrderSn, MainId: addPositionOrder.MainId, Pid: addPositionOrder.Pid, Price: utility.StrToDecimal(addPositionOrder.Price), @@ -250,6 +272,38 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } } +// 获取合约可用数量 +func getFuturesPositionAvailableQuantity(db *gorm.DB, apiUserInfo DbModels.LineApiUser, preOrder *DbModels.LinePreOrder, tradeSet models2.TradeSet) decimal.Decimal { + var totalNum decimal.Decimal + var err error + for x := 1; x < 4; x++ { + totalNum, err = getFuturesPositionNum(apiUserInfo, preOrder, tradeSet) + + if err == nil { + break + } + + time.Sleep(time.Millisecond * 150) + } + + if totalNum.Cmp(decimal.Zero) <= 0 { + var mainId int + + if preOrder.MainId > 0 { + mainId = preOrder.MainId + } else { + mainId = preOrder.Id + } + + totalNum = getInternalNum(db, mainId) + } + + if totalNum.Cmp(decimal.Zero) <= 0 { + totalNum = utility.StrToDecimal(preOrder.Num) + } + return totalNum +} + // 获取币安合约持仓 func getFuturesPositionNum(apiUserInfo DbModels.LineApiUser, preOrder *DbModels.LinePreOrder, tradeSet models2.TradeSet) (decimal.Decimal, error) { futApi := FutRestApi{} @@ -386,6 +440,12 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder) { return } + if preOrder.OrderCategory == 3 { + if err := cancelSymbolTakeAndStop(db, preOrder.MainId, preOrder.SymbolType); err != nil { + logger.Errorf("取消止盈止损订单失败 orderSn:%s err:%v", preOrder.OrderSn, err) + } + } + if err := db.Model(&DbModels.LinePreOrder{}). Where("pid = ? AND order_type > 0 AND status = '0' ", preOrder.Id). Find(&orders).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { @@ -440,7 +500,7 @@ func createFutPreAddPosition(preOrder *DbModels.LinePreOrder, db *gorm.DB, trade if v.OrderId == 0 { var data DbModels.LinePreOrder - copier.Copy(&data, &v) + copier.Copy(&data, &preOrder) data.Id = 0 data.Pid = preOrder.Id @@ -450,6 +510,7 @@ func createFutPreAddPosition(preOrder *DbModels.LinePreOrder, db *gorm.DB, trade data.MainOrderType = v.AddPositionOrderType data.Status = 0 data.OrderCategory = 3 + data.Rate = v.AddPositionPriceRatio.String() var percentage decimal.Decimal if data.Site == "BUY" { @@ -457,7 +518,17 @@ func createFutPreAddPosition(preOrder *DbModels.LinePreOrder, db *gorm.DB, trade } else { percentage = decimal.NewFromInt(1).Sub(v.AddPositionPriceRatio.Div(decimal.NewFromInt(100))) } - data.Price = price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)).String() + + dataPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) + data.Price = dataPrice.String() + + if v.AddPositionType == 1 { + data.Num = preOrder.Num + data.BuyPrice = "0" + } else { + data.BuyPrice = v.AddPositionVal.String() + data.Num = v.AddPositionVal.Div(dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() + } err := db.Transaction(func(tx *gorm.DB) error { if err2 := tx.Create(&data).Error; err2 != nil { @@ -500,18 +571,12 @@ func makeFuturesTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, trad } totalLossAmountU, _ := GetTotalLossAmount(db, preOrder.MainId) - num, err := getFuturesPositionNum(apiInfo, preOrder, tradeSet) - - if err != nil { - logger.Error("订单回调查询持仓数量失败:", err) - return nil, errors.New("订单回调查询持仓数量失败") - } + num := getFuturesPositionAvailableQuantity(db, apiInfo, preOrder, tradeSet) //止盈单 if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 { profitOrder := models.LinePreOrder{} copier.Copy(&profitOrder, preOrder) - var rate decimal.Decimal profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) profitOrder.Pid = preOrder.Id @@ -519,16 +584,21 @@ func makeFuturesTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, trad profitOrder.Status = 0 profitOrder.MainId = ext.MainOrderId profitOrder.Num = num.String() + profitOrder.Rate = ext.TakeProfitRatio.String() //止盈需要累加之前的亏损 if totalLossAmountU.Cmp(decimal.Zero) > 0 { - rate = totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs().Add(ext.TakeProfitRatio).Truncate(2) - } else { - rate = ext.TakeProfitRatio + percent := totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs() + profitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.TakeProfitRatio).Truncate(2).String() } - profitOrder.Rate = rate.String() + if strings.ToUpper(preOrder.Site) == "BUY" { + profitOrder.Site = "SELL" + } else { + profitOrder.Site = "BUY" + } + setPrice(&profitOrder, preOrder, tradeSet) orders = append(orders, profitOrder) } @@ -551,17 +621,33 @@ func makeFuturesTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, trad stopOrder.Num = num.String() } + if strings.ToUpper(preOrder.Site) == "BUY" { + stopOrder.Site = "SELL" + } else { + stopOrder.Site = "BUY" + } + + setPrice(&stopOrder, preOrder, tradeSet) orders = append(orders, stopOrder) } for index := range orders { orderRate := utility.StrToDecimal(orders[index].Rate) + orderType := orders[index].OrderType + if strings.ToUpper(preOrder.Site) == "BUY" { orders[index].Site = "SELL" - orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Sub(orderRate.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() } else { orders[index].Site = "BUY" + } + + switch { + //做多止盈、做空止损或减仓 + case (orderType == 1 && preOrder.Site == "BUY"), ((orderType == 2 || orderType == 4) && preOrder.Site == "SELL"): orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Add(orderRate.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + //做多止损或减仓、做空止盈 + case ((orderType == 2 || orderType == 4) && preOrder.Site == "BUY"), (orderType == 1 && preOrder.Site == "SELL"): + orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Sub(orderRate.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() } } @@ -613,27 +699,11 @@ func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.Line Price: price, Quantity: num, OrderType: "TAKE_PROFIT", - StopPrice: price, + Profit: 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) - } - } + err := futApi.OrderPlaceLoop(db, params, 3) if err != nil { logger.Error("合约止盈下单失败:", order.OrderSn, " err:", err) @@ -663,17 +733,7 @@ func processFutStopLossOrder(db *gorm.DB, order models.LinePreOrder, price, num 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) - } + err := futApi.OrderPlaceLoop(db, params, 3) if err != nil { if err2 := db.Model(&order).Updates(map[string]interface{}{"status": 2, "desc": err.Error()}).Error; err2 != nil { @@ -688,3 +748,18 @@ func processFutStopLossOrder(db *gorm.DB, order models.LinePreOrder, price, num return nil } + +// 循环取消合约订单 +func CancelFutOrderByOrderSnLoop(apiInfo DbModels.LineApiUser, symbol, orderSn string) error { + futApi := FutRestApi{} + var err error + for x := 1; x <= 4; x++ { + err = futApi.CancelFutOrder(apiInfo, symbol, orderSn) + + if err == nil || strings.Contains(err.Error(), "取消订单被拒绝") { + err = nil + break + } + } + return err +} diff --git a/services/binanceservice/orderservice.go b/services/binanceservice/orderservice.go index c103f5f..8454c8f 100644 --- a/services/binanceservice/orderservice.go +++ b/services/binanceservice/orderservice.go @@ -128,3 +128,29 @@ func GetTotalLossAmount(db *gorm.DB, mainId int) (decimal.Decimal, error) { return totalLossAmountU, nil } + +// 获取交易对的 委托中的止盈止损 +// mainId 主单id +// symbolType 交易对类型 +func GetSymbolTakeAndStop(db *gorm.DB, mainId int, symbolType int) ([]models.LinePreOrder, error) { + result := make([]models.LinePreOrder, 0) + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN (1,2,4) AND symbol_type =? AND status !=0 AND status !=4", mainId, symbolType).Find(&result).Error; err != nil { + return result, err + } + + return result, nil +} + +// 获取交易对触发数量 +// symbol 交易对 +// symbolType 交易对类型 1-现货 2-合约 +func GetSymbolTriggerCount(db *gorm.DB, symbol string, symbolType int) (int64, error) { + var count int64 + + if err := db.Model(&models.LinePreOrder{}).Where("symbol =? AND symbol_type =? AND order_type =0 AND pid=0 AND status IN (1,5,6)", symbol, symbolType).Count(&count).Error; err != nil { + logger.Error("查询交易对触发数量失败:", err) + return count, err + } + + return count, nil +} diff --git a/services/binanceservice/spotjudgeservice.go b/services/binanceservice/spotjudgeservice.go index a417ab0..f367bac 100644 --- a/services/binanceservice/spotjudgeservice.go +++ b/services/binanceservice/spotjudgeservice.go @@ -97,7 +97,7 @@ func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi S } preOrderVal, _ := sonic.MarshalString(&v) - if err := spotApi.OrderPlace(db, params); err != nil { + if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { log.Error("下单失败", v.Symbol, " err:", err) err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status =0", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error @@ -235,7 +235,7 @@ func SpotStopLossTrigger(db *gorm.DB, stopOrder dto.StopLossRedisList, spotApi S NewClientOrderId: stopPreOrder.OrderSn, } - if err := spotApi.OrderPlace(db, params); err != nil { + if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { log.Errorf("现货止损挂单失败 id:%s err:%v", stopOrder.Id, err) } @@ -322,15 +322,7 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest return } - var err error - for x := 1; x <= 4; x++ { - err = spotApi.CancelOpenOrderByOrderSn(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) - - if err == nil { - break - } - - } + err := CancelOpenOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) if err != nil { log.Error("现货止盈撤单失败", err) @@ -349,7 +341,7 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest NewClientOrderId: reduceOrder.OrderSn, } - if err := spotApi.OrderPlace(db, params); err != nil { + if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { log.Errorf("现货减仓挂单失败 id:%s err:%v", reduceOrder.Id, err) } @@ -427,6 +419,13 @@ 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) @@ -441,12 +440,12 @@ func SpotAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, spotAp Type: preOrder.MainOrderType, TimeInForce: "GTC", Price: price, - Quantity: num, + Quantity: num.Truncate(int32(tradeSet.AmountDigit)), NewClientOrderId: v.OrderSn, } preOrderVal, _ := sonic.MarshalString(&v) - if err := spotApi.OrderPlace(db, params); err != nil { + if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { log.Error("下单失败", v.Symbol, " err:", err) err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status =0", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index 323f0f1..2695fcf 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -115,7 +115,7 @@ func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus handleMainOrderFilled(db, preOrder) //主单减仓完毕 - case preOrder.OrderCategory == 1 && preOrder.OrderType == 4 && orderStatus == 6: + case preOrder.OrderType == 4 && orderStatus == 6: handleMainReduceFilled(db, preOrder) //主单取消 case preOrder.OrderType == 0 && preOrder.Pid == 0 && orderStatus == 4: @@ -144,6 +144,11 @@ func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus // 主单减仓完毕 func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { apiUserInfo, _ := GetApiInfo(preOrder.ApiId) + mainId := preOrder.Id + + if preOrder.MainId > 0 { + mainId = preOrder.MainId + } if apiUserInfo.Id == 0 { logger.Errorf("handleMainReduceFilled 获取api信息失败,订单号:%s", preOrder.OrderSn) @@ -165,70 +170,85 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { //获取订单配置 db.Model(&ext).Where("order_id =?", preOrder.Pid).First(&ext) - // 不是100%减仓 就需要挂止盈止损 - if rate < 100 { - num, err := getSpotPositionNum(apiUserInfo, preOrder, tradeSet) - if err != nil { - return + // 100%减仓 终止流程 + if rate >= 100 { + removeSpotLossAndAddPosition(preOrder) + ids := []int{preOrder.MainId, preOrder.Pid} + if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status =6", ids).Update("status", 9).Error; err != nil { + logger.Info("100%减仓完毕,终结流程") } + return + } + totalLossAmountU, _ := GetTotalLossAmount(db, mainId) + totalNum := getSpotPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet) - takeProfitOrder := models.LinePreOrder{} - copier.Copy(&takeProfitOrder, &preOrder) - takeProfitOrder.Id = 0 - takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) - takeProfitOrder.Status = 0 - takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() - takeProfitOrder.OrderType = 1 - takeProfitOrder.Rate = "100" - takeProfitOrder.SignPrice = preOrder.Price - takeProfitOrder.CreatedAt = time.Now() - takeProfitOrder.BuyPrice = "0" - takeProfitOrder.MainOrderType = "LIMIT" - takeProfitOrder.Num = num.String() - orders = append(orders, takeProfitOrder) + takeProfitOrder := models.LinePreOrder{} + copier.Copy(&takeProfitOrder, &preOrder) + takeProfitOrder.Id = 0 + takeProfitOrder.Pid = preOrder.Id + takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + takeProfitOrder.Status = 0 + // takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + takeProfitOrder.OrderType = 1 + takeProfitOrder.Rate = "100" + takeProfitOrder.SignPrice = preOrder.Price + takeProfitOrder.CreatedAt = time.Now() + takeProfitOrder.BuyPrice = "0" + takeProfitOrder.MainOrderType = "LIMIT" + takeProfitOrder.Num = totalNum.String() + takeProfitOrder.Rate = ext.ReduceTakeProfitRatio.String() + //止盈需要累加之前的亏损 + if totalLossAmountU.Cmp(decimal.Zero) > 0 { + percent := totalLossAmountU.Div(totalNum).Div(price).Sub(decimal.NewFromInt(1)).Abs() + takeProfitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.ReduceTakeProfitRatio).Truncate(2).String() + } - //有止损单 - if ext.ReduceStopLossRatio.Cmp(decimal.Zero) > 0 { - var stoploss models.LinePreOrder + setPrice(&takeProfitOrder, preOrder, tradeSet) + orders = append(orders, takeProfitOrder) - copier.Copy(&stoploss, &preOrder) - stoploss.Id = 0 - stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) - stoploss.Status = 0 - stoploss.CreatedAt = time.Now() - stoploss.OrderType = 2 - stoploss.SignPrice = preOrder.Price - stoploss.BuyPrice = "0" - stoploss.Rate = "100" - stoploss.MainOrderType = "LIMIT" - stoploss.Num = num.String() + //有止损单 + if ext.ReduceStopLossRatio.Cmp(decimal.Zero) > 0 { + var stoploss models.LinePreOrder - orders = append(orders, stoploss) - } + copier.Copy(&stoploss, &preOrder) + stoploss.Id = 0 + stoploss.Pid = preOrder.Id + stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + stoploss.Status = 0 + stoploss.CreatedAt = time.Now() + stoploss.OrderType = 2 + stoploss.SignPrice = preOrder.Price + stoploss.BuyPrice = "0" + stoploss.Rate = ext.ReduceStopLossRatio.String() + stoploss.MainOrderType = "LIMIT" + stoploss.Num = totalNum.String() + // stoploss.Price = price.Mul(decimal.NewFromInt(1).Sub(ext.ReduceStopLossRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + setPrice(&stoploss, preOrder, tradeSet) + orders = append(orders, stoploss) + } - if err := db.Create(&orders).Error; err != nil { - logger.Errorf("handleMainReduceFilled 创建止盈止损单失败:%v", err) - return - } + if err := db.Create(&orders).Error; err != nil { + logger.Errorf("handleMainReduceFilled 创建止盈止损单失败:%v", err) + return + } - spotApi := SpotRestApi{} - paramsMap := OrderPlacementService{ - ApiId: takeProfitOrder.ApiId, - Symbol: takeProfitOrder.Symbol, - Side: takeProfitOrder.Site, - Type: "LIMIT", - TimeInForce: "GTC", - Price: utility.StrToDecimal(takeProfitOrder.Price), - Quantity: num, - NewClientOrderId: takeProfitOrder.OrderSn, - StopPrice: utility.StrToDecimal(takeProfitOrder.Price), - } - if err := spotApi.OrderPlace(db, paramsMap); err != nil { - logger.Errorf("减仓后重下止盈失败 减仓order_sn:%s err:%v", preOrder.OrderSn, err) + spotApi := SpotRestApi{} + paramsMap := OrderPlacementService{ + ApiId: takeProfitOrder.ApiId, + Symbol: takeProfitOrder.Symbol, + Side: takeProfitOrder.Site, + Type: "LIMIT", + TimeInForce: "GTC", + Price: utility.StrToDecimal(takeProfitOrder.Price), + Quantity: totalNum, + NewClientOrderId: takeProfitOrder.OrderSn, + StopPrice: utility.StrToDecimal(takeProfitOrder.Price), + } + if err := spotApi.OrderPlaceLoop(db, paramsMap, 3); err != nil { + logger.Errorf("减仓后重下止盈失败 减仓order_sn:%s err:%v", preOrder.OrderSn, err) - if err2 := db.Model(&takeProfitOrder).Updates(map[string]interface{}{"status": 2, "": err.Error()}).Error; err2 != nil { - logger.Errorf("handleMainReduceFilled 更新止盈单失败:%v", err2) - } + if err2 := db.Model(&takeProfitOrder).Updates(map[string]interface{}{"status": 2, "": err.Error()}).Error; err2 != nil { + logger.Errorf("handleMainReduceFilled 更新止盈单失败:%v", err2) } } @@ -236,7 +256,7 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { if parentOrder.Price != "" { parentPrice := utility.StrToDecimal(parentOrder.Price) reduceNum := utility.StrToDecimal(preOrder.Num) - lossAmountU := price.Sub(parentPrice).Abs().Mul(reduceNum).Truncate(2) + lossAmountU := price.Sub(parentPrice).Abs().Mul(reduceNum) //.Truncate(int32(tradeSet.PriceDigit)) if err := db.Model(&parentOrder).Update("loss_amount", lossAmountU).Error; err != nil { logger.Errorf("修改主单实际亏损失败 订单号:%s err:%v", parentOrder.OrderSn, err) @@ -246,7 +266,7 @@ func handleMainReduceFilled(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 order_type=0 AND status=0", preOrder.MainId).First(&addPositionOrder).Error; err != nil { logger.Errorf("handleMainReduceFilled 获取加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) return } @@ -254,6 +274,8 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { keySpotAddPosition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) addPositionData := AddPositionList{ + Id: addPositionOrder.Id, + OrderSn: addPositionOrder.OrderSn, MainId: addPositionOrder.MainId, Pid: addPositionOrder.Pid, Price: utility.StrToDecimal(addPositionOrder.Price), @@ -275,6 +297,59 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } } +// 获取主单可用数量 +func getSpotPositionAvailableQuantity(db *gorm.DB, apiUserInfo DbModels.LineApiUser, preOrder *DbModels.LinePreOrder, tradeSet models2.TradeSet) decimal.Decimal { + totalNum := getSpotTotalNum(apiUserInfo, preOrder, tradeSet) + + //biance查询失败 查系统内数量 + if totalNum.Cmp(decimal.Zero) <= 0 { + var mainId int + + if preOrder.MainId > 0 { + mainId = preOrder.MainId + } else { + mainId = preOrder.Id + } + + totalNum = getInternalNum(db, mainId) + } + return totalNum +} + +// 获取现货剩余数量 +func getSpotTotalNum(apiUserInfo DbModels.LineApiUser, preOrder *DbModels.LinePreOrder, tradeSet models2.TradeSet) decimal.Decimal { + var totalNum decimal.Decimal + var err error + for x := 1; x < 4; x++ { + totalNum, err = getSpotPositionNum(apiUserInfo, preOrder, tradeSet) + + if err == nil { + break + } + + time.Sleep(time.Millisecond * 150) + } + if err != nil { + totalNum = utility.StrToDecimal(preOrder.Num) + } + return totalNum +} + +// 获取系统内剩余数量 +func getInternalNum(db *gorm.DB, mainId int) decimal.Decimal { + var totalNum, totalSellNum decimal.Decimal + var data DbModels.LinePreOrder + if err := db.Model(&data).Where("(main_id =? OR id =?) AND order_type =0 AND status =6", mainId, mainId).Select("COALESCE(sum(num), 0)").Find(&totalNum).Error; err != nil { + logger.Errorf("getSpotInternalNum 查询所有数量失败,mainId:%d err:%v", mainId, err) + } + + if err := db.Model(&data).Where("main_id =? AND order_type =4 AND status =6", mainId).Select("COALESCE(sum(num), 0)").Find(&totalSellNum).Error; err != nil { + logger.Errorf("getSpotInternalNum 查询所有减仓数量失败,mainId:%d err:%v", mainId, err) + } + + return totalNum.Sub(totalSellNum).Abs() +} + // 获取现货持仓数量 func getSpotPositionNum(apiUserInfo DbModels.LineApiUser, preOrder *DbModels.LinePreOrder, tradeSet models2.TradeSet) (decimal.Decimal, error) { client := GetClient(&apiUserInfo) @@ -449,7 +524,7 @@ func handleMainOrderFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { if v.OrderId == 0 { var data DbModels.LinePreOrder - copier.Copy(&data, &v) + copier.Copy(&data, &preOrder) data.Id = 0 data.Pid = preOrder.Id @@ -459,14 +534,25 @@ func handleMainOrderFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { data.MainOrderType = v.AddPositionOrderType data.Status = 0 data.OrderCategory = 3 + data.Rate = v.AddPositionPriceRatio.String() 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.BuyPrice = "0" + } else { + data.BuyPrice = v.AddPositionVal.String() + data.Num = v.AddPositionVal.Div(dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() } - data.Price = price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)).String() err := db.Transaction(func(tx *gorm.DB) error { if err2 := tx.Create(&data).Error; err2 != nil { @@ -528,7 +614,7 @@ func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, r //主单 修改单价 和成交数量 if total.Cmp(decimal.Zero) > 0 && totalAmount.Cmp(decimal.Zero) > 0 { - num := totalAmount.Div(decimal.NewFromFloat(100)).Mul(decimal.NewFromFloat(99.8)) + num := totalAmount.Mul(decimal.NewFromFloat(0.998)) params["num"] = num params["price"] = total.Div(totalAmount) preOrder.Num = num.String() @@ -569,20 +655,21 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd return } - num, err := getSpotPositionNum(apiInfo, preOrder, tradeSet) - - if err != nil { - logger.Error("订单回调查询持仓数量失败:", err) - num = utility.StrToDecimal(preOrder.Num) + if preOrder.OrderCategory == 3 { + if err := cancelSymbolTakeAndStop(db, preOrder.MainId, preOrder.SymbolType); err != nil { + logger.Errorf("取消止盈止损订单失败 orderSn:%s err:%v", preOrder.OrderSn, err) + } } + num := getSpotPositionAvailableQuantity(db, apiInfo, preOrder, tradeSet) + 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 && errors.Is(err, gorm.ErrRecordNotFound) { logger.Error("订单回调查询止盈止损单失败:", err) return } else if len(orders) == 0 && preOrder.OrderCategory == 3 { - orders, err = makeSpotTakeAndReduce(preOrder, db, tradeSet, orders, num) + orders, err = makeSpotTakeAndReduce(preOrder, db, tradeSet, num) if err != nil { return @@ -591,11 +678,7 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd spotApi := SpotRestApi{} - for i, order := range orders { - if i >= 2 { // 最多处理 2 个订单 - break - } - + for _, order := range orders { order.Num = num.Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit)).String() if order.OrderType == 4 { @@ -623,9 +706,10 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd } // 构建现货止盈、减仓单 -func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSet models2.TradeSet, orders []DbModels.LinePreOrder, num decimal.Decimal) ([]DbModels.LinePreOrder, error) { +func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSet models2.TradeSet, num decimal.Decimal) ([]DbModels.LinePreOrder, error) { ext := models.LinePreOrderExt{} price := utility.StrToDecimal(preOrder.Price) + orders := make([]DbModels.LinePreOrder, 0) if err := db.Model(&ext).Where("order_id = ?", preOrder.Id).First(&ext).Error; err != nil { logger.Error("订单回调查询止盈止损单扩展表失败:", err) @@ -639,6 +723,7 @@ func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSe profitOrder := models.LinePreOrder{} copier.Copy(&profitOrder, preOrder) + profitOrder.Id = 0 profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) profitOrder.Pid = preOrder.Id profitOrder.OrderType = 1 @@ -649,11 +734,17 @@ func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSe //止盈需要累加之前的亏损 if totalLossAmountU.Cmp(decimal.Zero) > 0 { - profitOrder.Rate = totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs().Add(ext.TakeProfitRatio).Truncate(2).String() - } else { - profitOrder.Rate = ext.TakeProfitRatio.String() + percent := totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs() + profitOrder.Rate = percent.Mul(decimal.NewFromInt(100)).Add(ext.TakeProfitRatio).Truncate(2).String() } + if strings.ToUpper(preOrder.Site) == "BUY" { + profitOrder.Site = "SELL" + } else { + profitOrder.Site = "BUY" + } + + setPrice(&profitOrder, preOrder, tradeSet) orders = append(orders, profitOrder) } @@ -662,6 +753,7 @@ func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSe 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 @@ -674,18 +766,14 @@ func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSe } else { stopOrder.Num = num.String() } - - orders = append(orders, stopOrder) - } - - for index := range orders { if strings.ToUpper(preOrder.Site) == "BUY" { - orders[index].Site = "SELL" - orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Sub(ext.ReducePriceRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + stopOrder.Site = "SELL" } else { - orders[index].Site = "BUY" - orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Add(ext.ReducePriceRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + stopOrder.Site = "BUY" } + + setPrice(&stopOrder, preOrder, tradeSet) + orders = append(orders, stopOrder) } if len(orders) > 0 { @@ -697,6 +785,22 @@ func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSe return orders, nil } +// 根据下单百分比计算价格 +func setPrice(order *models.LinePreOrder, preOrder *models.LinePreOrder, tradeSet models2.TradeSet) { + orderType := order.OrderType + itemSide := order.Site + rate := utility.StrToDecimal(order.Rate) + + switch { + //做多止盈、做空止损或减仓 + case (orderType == 1 && itemSide == "SELL"), ((orderType == 2 || orderType == 4) && itemSide == "BUY"): + order.Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Add(rate.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + //做多止损或减仓、做空止盈 + case ((orderType == 2 || orderType == 4) && itemSide == "SELL"), (orderType == 1 && itemSide == "BUY"): + order.Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Sub(rate.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + } +} + // 现货减仓 func processSpotReduceOrder(preOrder DbModels.LinePreOrder) { key := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE) @@ -748,21 +852,7 @@ func processTakeProfitOrder(db *gorm.DB, spotApi SpotRestApi, order models.LineP NewClientOrderId: order.OrderSn, } - err := spotApi.OrderPlace(db, params) - - if err != nil { - for x := 0; x < 5; x++ { - if strings.Contains(err.Error(), "LOT_SIZE") { - break - } - - err = spotApi.OrderPlace(db, params) - - if err == nil { - break - } - } - } + err := spotApi.OrderPlaceLoop(db, params, 3) if err != nil { logger.Error("现货止盈下单失败:", order.OrderSn, " err:", err) @@ -846,6 +936,21 @@ func ResetSystemSetting(db *gorm.DB) (DbModels.LineSystemSetting, error) { return DbModels.LineSystemSetting{}, nil } +// 循环取消订单 +func CancelOpenOrderByOrderSnLoop(apiInfo DbModels.LineApiUser, symbol string, orderSn string) error { + spotApi := SpotRestApi{} + var err error + for x := 1; x <= 4; x++ { + err = spotApi.CancelOpenOrderByOrderSn(apiInfo, symbol, orderSn) + + if err == nil || strings.Contains(err.Error(), "该交易对没有订单") { + err = nil + break + } + } + return err +} + // NEW // PENDING_NEW // PARTIALLY_FILLED diff --git a/services/excservice/binancesocketmanager.go b/services/excservice/binancesocketmanager.go index b7c8c9d..45b428a 100644 --- a/services/excservice/binancesocketmanager.go +++ b/services/excservice/binancesocketmanager.go @@ -84,7 +84,12 @@ func (wm *BinanceWebSocketManager) Restart(apiKey, apiSecret, proxyType, proxyAd wm.proxyType = proxyType wm.proxyAddress = proxyAddress - wm.reconnect <- struct{}{} + if wm.isStopped { + wm.run() + } else { + wm.reconnect <- struct{}{} + } + return wm } diff --git a/services/spotservice/binancemarket.go b/services/spotservice/binancemarket.go index 093790a..2b5c9c6 100644 --- a/services/spotservice/binancemarket.go +++ b/services/spotservice/binancemarket.go @@ -251,7 +251,7 @@ func handleTickerMessage(msg []byte) { utility.SafeGoParam(binanceservice.JudgeSpotReduce, trades[index]) //加仓 - utility.SafeGoParam(binanceservice.JudgeFutAddPosition, trades[index]) + utility.SafeGoParam(binanceservice.JudgeSpotAddPosition, trades[index]) } } }