diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index 7f689e4..0676804 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -7,6 +7,7 @@ import ( "go-admin/common/global" "go-admin/common/helper" models2 "go-admin/models" + "go-admin/models/positiondto" "go-admin/pkg/utility" "go-admin/pkg/utility/snowflakehelper" "go-admin/services/binanceservice" @@ -225,11 +226,7 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi var data models.LinePreOrder var list []models.LinePreOrder e.Orm.Model(&models.LinePreOrder{}).Where("id in ?", d.GetId()).Find(&list) - //for _, order := range list { - //if order.Status != "0" { - // return errors.New(fmt.Sprintf("订单id %d 已被触发 无法被删除", order.Id)) - //} - //} + db := e.Orm.Model(&data). Scopes( actions.Permission(data.TableName(), p), @@ -245,6 +242,8 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi return errors.New("无权删除该数据") } + positions := map[string]positiondto.LinePreOrderPositioinDelReq{} + //删除的缓存 for _, order := range list { redisList := dto.PreOrderRedisList{ @@ -272,6 +271,19 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi helper.DefaultRedis.LRem(listKey, string(marshal)) } + //会影响持仓的 + removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType) + + if _, ok := positions[removeSymbolKey]; !ok { + positions[removeSymbolKey] = positiondto.LinePreOrderPositioinDelReq{ + ApiId: order.ApiId, + Symbol: order.Symbol, + ExchangeType: order.ExchangeType, + Side: order.Site, + SymbolType: order.SymbolType, + } + } + binanceservice.MainClosePositionClearCache(order.Id, order.SymbolType) ints = append(ints, order.Id) @@ -280,6 +292,27 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi if len(ints) > 0 { e.Orm.Model(&models.LinePreOrder{}).Where("main_id >0 AND main_id in ?", ints).Unscoped().Delete(&models.LinePreOrder{}) } + + //清理仓位缓存 + for _, v := range positions { + var count int64 + e.Orm.Model(&models.LinePreOrder{}). + Where("api_id =? AND site=? AND symbol=? AND symbol_type =? AND exchange_type =? AND status =6", + v.ApiId, v.Side, v.Symbol, v.SymbolType, v.ExchangeType).Count(&count) + + //没有已开仓的订单 直接清理仓位 + if count == 0 { + var key string + + if v.SymbolType == 1 { + key = fmt.Sprintf(rediskey.SpotPosition, v.ExchangeType, v.ApiId, v.Symbol, v.Side) + } else { + key = fmt.Sprintf(rediskey.FuturePosition, v.ExchangeType, v.ApiId, v.Symbol, v.Side) + } + + helper.DefaultRedis.DeleteString(key) + } + } return nil } @@ -323,11 +356,11 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP //获取交易对 tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key) - orderCount := e.CheckRepeatOrder(req.SymbolType, id, req.Site, tradeSet.Coin) - if orderCount > 0 { - *errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 该交易对已存在,请勿重复下单", id, req.Symbol)) - continue - } + // orderCount := e.CheckRepeatOrder(req.SymbolType, id, req.Site, tradeSet.Coin) + // if orderCount > 0 { + // *errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 该交易对已存在,请勿重复下单", id, req.Symbol)) + // continue + // } tickerPrice := utility.StrToDecimal(tradeSet.LastPrice) if tickerPrice.Equal(decimal.Zero) { //redis 没有这个值 *errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 交易行情出错", id, req.Symbol)) diff --git a/common/const/rediskey/redis_key.go b/common/const/rediskey/redis_key.go index 16306a4..820f41b 100644 --- a/common/const/rediskey/redis_key.go +++ b/common/const/rediskey/redis_key.go @@ -1,5 +1,6 @@ package rediskey +// 量化 const ( IPPositionCache = "_IPPositionCache" // IP 归属地缓存 AppLoginUserToken = "_AppLoginUserToken_%d" // App登录用户的Token {uid} @@ -52,10 +53,18 @@ const ( SpotAddPositionList = "spot_add_position_list:%s" //现货加仓待触发 {交易所code} FuturesAddPositionList = "futures_add_position_list:%s" //合约加仓待触发 {交易所code} - + //现货持仓 {exchangeType,apiuserid,symbol,side} + SpotPosition = "spot_position:%s:%v:%s_%s" + //合约持仓 {exchangeType,apiuserid,symbol,side} + FuturePosition = "future_position:%s:%v:%s_%s" //需要清理键值---------END----------------- - JobReOrderTrigger = "job_re_order_trigger" //定时取消限价并下市价锁 + //定时取消限价并下市价锁 + JobReOrderTrigger = "job_re_order_trigger" + //现货持仓修改锁{apiId,symbol,side} + SpotPositionLock = "spot_position_lock:%v:%s:%s" + //合约持仓修改锁{apiId,symbol,side} + FuturePositionLock = "future_position_lock:%v:%s:%s" ListenAveLastSymbol = "listen_ave_last_symbol" // 监听最新交易对 AveRequestToken = "ave_request_token" // AVE请求token diff --git a/models/positiondto/cache.go b/models/positiondto/cache.go new file mode 100644 index 0000000..364fb0e --- /dev/null +++ b/models/positiondto/cache.go @@ -0,0 +1,33 @@ +package positiondto + +import "github.com/shopspring/decimal" + +//持仓信息 +type PositionDto struct { + SymbolType int `json:"symbolType"` //交易对类型 1-现货 2-合约 + Side string `json:"side"` //买卖方向 BUY SELL + PositionSide string `json:"positionSide"` //持仓方向 LONG SHORT + Quantity decimal.Decimal `json:"quantity"` //总数量 + TotalLoss decimal.Decimal `json:"totalLoss"` //总亏损 + Symbol string `json:"symbol"` //交易对 + ApiId int `json:"apiId"` //apiid + LastPrice decimal.Decimal `json:"lastPrice"` //上一次成交价 +} + +type PositionAddReq struct { + SymbolType int `json:"symbolType"` //交易对类型 1-现货 2-合约 + Side string `json:"side"` //方向 + Quantity decimal.Decimal `json:"quantity"` //总数量 + Symbol string `json:"symbol"` //交易对 + ApiId int `json:"apiId"` //apiid + Price decimal.Decimal `json:"price"` //本次成交价 + PositionSide string `json:"positionSide"` //持仓方向 +} + +type LinePreOrderPositioinDelReq struct { + SymbolType int `json:"symbolType"` //交易对类型 1-现货 2-合约 + Side string `json:"side"` //方向 + Symbol string `json:"symbol"` //交易对 + ApiId int `json:"apiId"` //apiid + ExchangeType string `json:"exchangeType"` //交易所类型 +} diff --git a/services/binanceservice/binancerest.go b/services/binanceservice/binancerest.go index 40096ee..d833ae9 100644 --- a/services/binanceservice/binancerest.go +++ b/services/binanceservice/binancerest.go @@ -230,10 +230,8 @@ func (e SpotRestApi) OrderPlace(orm *gorm.DB, params OrderPlacementService) erro paramsMaps["stopPrice"] = params.StopPrice.String() } } - var apiUserInfo DbModels.LineApiUser - - err := orm.Model(&DbModels.LineApiUser{}).Where("id = ?", params.ApiId).Find(&apiUserInfo).Error - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + apiUserInfo, err := GetApiInfo(params.ApiId) + if apiUserInfo.Id == 0 { log.Errorf("api用户出错 err: %+v", err) return err } @@ -273,6 +271,24 @@ func (e SpotRestApi) OrderPlace(orm *gorm.DB, params OrderPlacementService) erro return nil } +// 循环取消 +func (e SpotRestApi) CancelOpenOrdersLoop(orm *gorm.DB, req CancelOpenOrdersReq, retryCount int) error { + err := e.CancelOpenOrders(orm, req) + + if err != nil { + for x := 1; x < retryCount; x++ { + err = e.CancelOpenOrders(orm, req) + if err == nil { + break + } + + time.Sleep(time.Duration(x) * 200 * time.Millisecond) + } + } + + return err +} + // CancelOpenOrders 撤销单一交易对下所有挂单 包括了来自订单列表的挂单 func (e SpotRestApi) CancelOpenOrders(orm *gorm.DB, req CancelOpenOrdersReq) error { if orm == nil { @@ -285,19 +301,12 @@ func (e SpotRestApi) CancelOpenOrders(orm *gorm.DB, req CancelOpenOrdersReq) err params := map[string]string{ "symbol": req.Symbol, } - var apiUserInfo DbModels.LineApiUser + apiUserInfo, err := GetApiInfo(req.ApiId) - err = orm.Model(&DbModels.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo).Error - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + if apiUserInfo.Id == 0 { return fmt.Errorf("api_id:%d 交易对:%s api用户出错:%+v", apiUserInfo.Id, req.Symbol, err) } - var client *helper.BinanceClient - - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) _, _, err = client.SendSpotAuth("/api/v3/openOrders", "DELETE", params) if err != nil { dataMap := make(map[string]interface{}) @@ -327,13 +336,7 @@ func (e SpotRestApi) CancelOpenOrderByOrderSn(apiUserInfo DbModels.LineApiUser, "origClientOrderId": newClientOrderId, "recvWindow": "10000", } - var client *helper.BinanceClient - - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) _, code, err := client.SendSpotAuth("/api/v3/order", "DELETE", params) if err != nil || code != 200 { log.Error("取消现货委托失败 参数:", params) diff --git a/services/binanceservice/commonservice.go b/services/binanceservice/commonservice.go index c1e2cfc..d554c8c 100644 --- a/services/binanceservice/commonservice.go +++ b/services/binanceservice/commonservice.go @@ -9,7 +9,9 @@ import ( "go-admin/common/global" "go-admin/common/helper" "go-admin/models" + "go-admin/models/positiondto" "go-admin/pkg/utility" + "go-admin/services/positionservice" "strings" "time" @@ -410,3 +412,64 @@ func cancelSymbolTakeAndStop(db *gorm.DB, mainId int, symbolType int) error { return nil } + +// 保存仓位信息 +func savePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) positiondto.PositionDto { + positionManage := positionservice.BinancePositionManagement{} + positionManage.Orm = db + positionReq := positiondto.PositionAddReq{ + ApiId: preOrder.ApiId, + SymbolType: preOrder.SymbolType, + Symbol: preOrder.Symbol, + Price: utility.StrToDecimal(preOrder.Price), + Side: preOrder.Site, + Quantity: utility.StrToDecimal(preOrder.Num), + } + + switch { + case preOrder.OrderType == 0 && preOrder.Site == "BUY", + preOrder.OrderType != 0 && preOrder.Site == "SELL": + positionReq.PositionSide = "LONG" + case preOrder.OrderType == 0 && preOrder.Site == "SELL", + preOrder.OrderType != 0 && preOrder.Site == "BUY": + positionReq.PositionSide = "SHORT" + } + + //减仓单 数量为负 + if preOrder.OrderType == 4 { + positionReq.Quantity = positionReq.Quantity.Mul(decimal.NewFromInt(-1)) + } + + positionData, err := positionManage.SavePosition(&positionReq, global.EXCHANGE_BINANCE) + + if err != nil { + logger.Error("保存持仓信息失败, 主单号:%s, 错误信息:%v", preOrder.OrderSn, err) + } + return positionData +} + +// 获取已开仓的主单id +func getOpenPositionMainOrderId(db *gorm.DB, newId, apiId, symbolType int, exchangeType, symbol, side string) ([]DbModels.LinePreOrder, error) { + mainOrders := make([]DbModels.LinePreOrder, 0) + + if err := db.Model(&DbModels.LinePreOrder{}). + Where("api_id =? AND status>4 AND status<7 AND symbol=? AND symbol_type =? AND side= ? AND exchange_type=? AND id!=?", + apiId, symbol, symbolType, side, exchangeType, newId). + Select("id", "order_sn").Find(&mainOrders).Error; err != nil { + return nil, err + } + + return mainOrders, nil +} + +// 获取需要取消的订单号 +func getOpenOrderSns(db *gorm.DB, mainIds []int) ([]string, error) { + result := []string{} + + //委托中的订单 + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id IN ? AND status=5", mainIds).Select("order_sn").Find(&result).Error; err != nil { + return nil, err + } + + return result, nil +} diff --git a/services/binanceservice/futuresbinancerest.go b/services/binanceservice/futuresbinancerest.go index 70bfa53..cd1ac54 100644 --- a/services/binanceservice/futuresbinancerest.go +++ b/services/binanceservice/futuresbinancerest.go @@ -467,8 +467,7 @@ func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { if err2 != nil { return err2 } - var paramsMaps map[string]string - paramsMaps = map[string]string{ + paramsMaps := map[string]string{ "symbol": params.Symbol, "side": params.Side, "quantity": params.Quantity.String(), @@ -519,18 +518,11 @@ func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { paramsMaps["positionSide"] = "LONG" } } - var apiUserInfo DbModels.LineApiUser - err := orm.Model(&DbModels.LineApiUser{}).Where("id = ?", params.ApiId).Find(&apiUserInfo).Error + apiUserInfo, err := GetApiInfo(params.ApiId) if err != nil { return err } - var client *helper.BinanceClient - - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) _, statusCode, err := client.SendFuturesRequestAuth("/fapi/v1/order", "POST", paramsMaps) if err != nil { var dataMap map[string]interface{} @@ -662,12 +654,7 @@ func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, } func (e FutRestApi) GetPositionV3(apiUserInfo *DbModels.LineApiUser, symbol string) ([]PositionRisk, error) { - var client *helper.BinanceClient - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(apiUserInfo) params := map[string]string{ "symbol": symbol, "recvWindow": "5000", @@ -736,12 +723,7 @@ func (e FutRestApi) ClosePosition(symbol string, orderSn string, quantity decima params["timeInForce"] = "GTC" } - var client *helper.BinanceClient - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params) if err != nil { @@ -781,12 +763,7 @@ func (e FutRestApi) CancelFutOrder(apiUserInfo DbModels.LineApiUser, symbol stri "symbol": symbol, //交易对 "origClientOrderId": newClientOrderId, //用户自定义订单号 } - var client *helper.BinanceClient - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) _, _, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params) if err != nil { var dataMap map[string]interface{} @@ -815,12 +792,7 @@ func (e FutRestApi) CancelAllFutOrder(apiUserInfo DbModels.LineApiUser, symbol s "symbol": symbol, //交易对 "recvWindow": "5000", } - var client *helper.BinanceClient - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) _, _, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params) if err != nil { var dataMap map[string]interface{} @@ -854,12 +826,7 @@ func (e FutRestApi) CancelBatchFutOrder(apiUserInfo DbModels.LineApiUser, symbol "symbol": symbol, //交易对 "origClientOrderIdList": string(marshal), } - var client *helper.BinanceClient - if apiUserInfo.UserPass == "" { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) - } else { - client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) - } + client := GetClient(&apiUserInfo) _, code, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params) if err != nil { log.Error("取消合约委托失败 参数:", params) @@ -893,8 +860,8 @@ func (e FutRestApi) CalcSymbolExchangeAmt(symbol string, quoteSymbol string, tot tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() tickerSymbolMaps := make([]dto.Ticker, 0) sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) - var targetSymbol string - targetSymbol = strings.Replace(symbol, quoteSymbol, "USDT", 1) //ETHBTC -》 ETHUSDT + + targetSymbol := strings.Replace(symbol, quoteSymbol, "USDT", 1) //ETHBTC -》 ETHUSDT key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, targetSymbol) tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) var targetPrice decimal.Decimal @@ -908,11 +875,6 @@ func (e FutRestApi) CalcSymbolExchangeAmt(symbol string, quoteSymbol string, tot return quantity } -// 加仓主账号 -func (e FutRestApi) CoverAccountA(apiUserInfo DbModels.LineApiUser, symbol string) { - -} - // GetFutSymbolLastPrice 获取现货交易对最新价格 func (e FutRestApi) GetFutSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) { key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, targetSymbol) @@ -979,7 +941,7 @@ func (e FutRestApi) GetOrderByOrderSn(symbol, orderSn string, apiUserInfo DbMode } /* -查询现货委托 +查询合约委托 */ // 根据订单号获取订单信息,如果获取失败,则进行重试 func (e FutRestApi) GetOrderByOrderSnLoop(symbol, ordersn string, apiUserInfo DbModels.LineApiUser, retryCount int) (order binancedto.BinanceFutureOrder, err error) { diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index 5f2472f..719dc7e 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -127,45 +127,51 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { logger.Errorf("handleReduceFilled 获取交易对设置失败,订单号:%s", preOrder.OrderSn) return } + rate := utility.StringAsFloat(preOrder.Rate) - price := utility.StrToDecimal(preOrder.Price) - parentOrder, err := GetOrderById(db, preOrder.Pid) + // 100%减仓 终止流程 + if rate >= 100 { + //缓存 + removeFutLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, preOrder) - if err != nil { - logger.Errorf("handleReduceFilled 获取主单失败,订单号:%s", preOrder.OrderSn) - return - } - parentPrice := utility.StrToDecimal(parentOrder.Price) - num := utility.StrToDecimal(preOrder.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) - - if err != nil { - logger.Errorf("handleMainReduceFilled 获取币本位对应U交易对设置失败,订单号:%s", preOrder.OrderSn) - return + 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%减仓完毕,终结流程") } - - lossAmount = lossAmount.Mul(utility.StrToDecimal(tradeSetU.LastPrice)).Truncate(int32(tradeSetU.PriceDigit)) - } - - if err := db.Model(&parentOrder).Where("loss_amount = ?", 0).Update("loss_amount", lossAmount).Error; err != nil { - logger.Errorf("handleMainReduceFilled 更新亏损金额失败,订单号:%s", preOrder.OrderSn) return } + positionData := savePosition(db, preOrder) orders := make([]models.LinePreOrder, 0) if err := db.Model(&models.LinePreOrder{}).Where("pid =? AND order_type IN (1,2) AND status = 0", preOrder.Id).Find(&orders).Error; err != nil { logger.Errorf("handleMainReduceFilled 获取待触发订单失败,订单号:%s", preOrder.OrderSn) return } + orderExt := models.LinePreOrderExt{} totalNum := getFuturesPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) totalNum = totalNum.Truncate(int32(tradeSet.AmountDigit)) + price := utility.StrToDecimal(preOrder.Price).Truncate(int32(tradeSet.PriceDigit)) futApi := FutRestApi{} + db.Model(&orderExt).Where("order_id =?", preOrder.Pid).First(&orderExt) + for _, v := range orders { if v.OrderType == 1 { + //亏损大于0 重新计算比例 + if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { + percentag := positionData.TotalLoss.Div(totalNum).Div(price) + percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) + v.Rate = percentag.String() + percentag = percentag.Div(decimal.NewFromInt(100)) + + if positionData.PositionSide == "LONG" { + v.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() + } else { + v.Price = price.Mul(decimal.NewFromInt(1).Sub(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() + } + } + processFutTakeProfitOrder(db, futApi, v, totalNum) } else if v.OrderType == 2 { processFutStopLossOrder(db, v, utility.StrToDecimal(v.Price), totalNum) @@ -275,7 +281,8 @@ func getFuturesPositionNum(apiUserInfo DbModels.LineApiUser, preOrder *DbModels. // 平仓单成交 func handleClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { - removeFutLossAndAddPosition(preOrder) + removeFutLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, preOrder) futApi := FutRestApi{} apiUserInfo, _ := GetApiInfo(preOrder.ApiId) @@ -295,7 +302,8 @@ func handleClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { // 止损单成交 func handleStopLoss(db *gorm.DB, preOrder *DbModels.LinePreOrder) { - removeFutLossAndAddPosition(preOrder) + removeFutLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, preOrder) spotApi := SpotRestApi{} apiUserInfo, _ := GetApiInfo(preOrder.ApiId) @@ -319,7 +327,8 @@ func handleStopLoss(db *gorm.DB, preOrder *DbModels.LinePreOrder) { // 止盈单成交 func handleTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder) { - removeFutLossAndAddPosition(preOrder) + removeFutLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, preOrder) futApi := FutRestApi{} apiUserInfo, _ := GetApiInfo(preOrder.ApiId) @@ -338,7 +347,7 @@ func handleTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } // 清除合约缓存 -func removeFutLossAndAddPosition(preOrder *DbModels.LinePreOrder) { +func removeFutLossAndAddPosition(mainId int, orderSn string) { stoplossKey := fmt.Sprintf(rediskey.FuturesStopLossList, global.EXCHANGE_BINANCE) stoplossVal, _ := helper.DefaultRedis.GetAllList(stoplossKey) addPositionKey := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE) @@ -352,11 +361,11 @@ func removeFutLossAndAddPosition(preOrder *DbModels.LinePreOrder) { //止损缓存 for _, v := range stoplossVal { sonic.Unmarshal([]byte(v), &stoploss) - if stoploss.MainId == preOrder.MainId { + if stoploss.MainId == mainId { _, err := helper.DefaultRedis.LRem(stoplossKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除止损缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除止损缓存失败:%v", orderSn, err) } } } @@ -364,11 +373,11 @@ func removeFutLossAndAddPosition(preOrder *DbModels.LinePreOrder) { //加仓缓存 for _, v := range addPositionVal { sonic.Unmarshal([]byte(v), &addPosition) - if addPosition.MainId == preOrder.MainId { + if addPosition.MainId == mainId { _, err := helper.DefaultRedis.LRem(addPositionKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除加仓缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除加仓缓存失败:%v", orderSn, err) } } } @@ -376,87 +385,172 @@ func removeFutLossAndAddPosition(preOrder *DbModels.LinePreOrder) { //减仓缓存 for _, v := range reduceVal { sonic.Unmarshal([]byte(v), &reduce) - if reduce.MainId == preOrder.MainId { + if reduce.MainId == mainId { _, err := helper.DefaultRedis.LRem(reduceKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除减仓缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除减仓缓存失败:%v", orderSn, err) } } } } -// 主单成交 处理止盈止损订单 -// preOrder 主单 +// 处理主单成交,处理止盈、止损、减仓订单 func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder) { - orders := []models.LinePreOrder{} - tradeSet, _ := GetTradeSet(preOrder.Symbol, 1) - - if tradeSet.Coin == "" { - logger.Error("获取交易对失败") + // 获取交易对配置和API信息 + tradeSet, err := GetTradeSet(preOrder.Symbol, 1) + if err != nil || tradeSet.Coin == "" { + logger.Errorf("获取交易对配置失败, 回调订单号:%s, 错误信息: %v", preOrder.OrderSn, err) return } - if tradeSet.Coin == "" { - logger.Errorf("获取交易对配置失败, 回调订单号:%s", preOrder.OrderSn) - return - } - - if preOrder.OrderCategory == 3 { - if err := db.Model(&DbModels.LinePreOrderStatus{}).Where("order_id = ?", preOrder.MainId).Update("add_position_status", 1).Error; err != nil { - logger.Errorf("更新主单加仓状态失败, 主单号:%s, 错误信息:%v", preOrder.MainId, err) - } - - if err := cancelSymbolTakeAndStop(db, preOrder.MainId, preOrder.SymbolType); err != nil { - logger.Errorf("取消止盈止损订单失败 orderSn:%s err:%v", preOrder.OrderSn, err) - } - } apiInfo, err := GetApiInfo(preOrder.ApiId) - if apiInfo.Id == 0 { - logger.Error("订单回调查询apiuserinfo失败 err:", err) - return - } - 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) { - logger.Error("订单回调查询止盈止损单失败:", err) + if err != nil || apiInfo.Id == 0 { + logger.Errorf("订单回调查询apiuserinfo失败, 订单号:%s, 错误信息: %v", preOrder.OrderSn, err) return } - futApi := FutRestApi{} - num := getFuturesPositionAvailableQuantity(db, apiInfo, preOrder, tradeSet) - num = num.Truncate(int32(tradeSet.AmountDigit)) + // 处理主单加仓 + if preOrder.OrderCategory == 3 { + if err := handleMainOrderAddPosition(db, preOrder); err != nil { + logger.Errorf("处理主单加仓失败, 主单号:%s, 错误信息: %v", preOrder.MainId, err) + return + } + } else { + // 处理其他主单逻辑 + if err := cancelPositionOtherOrders(apiInfo, db, preOrder); err != nil { + logger.Errorf("取消主单相关订单失败, 订单号:%s, 错误信息: %v", preOrder.OrderSn, err) + return + } + } + // 获取止盈止损订单 + orders, err := getStopOrders(db, preOrder) + if err != nil { + logger.Errorf("查询止盈止损订单失败, 订单号:%s, 错误信息: %v", preOrder.OrderSn, err) + return + } + + // 获取和保存持仓数据 + positionData := savePosition(db, preOrder) + orderExt := models.LinePreOrderExt{} + num := getFuturesPositionAvailableQuantity(db, apiInfo, preOrder, tradeSet).Truncate(int32(tradeSet.AmountDigit)) + + // 更新订单数量并处理止盈、止损、减仓 for _, order := range orders { price := utility.StrToDecimal(order.Price).Truncate(int32(tradeSet.PriceDigit)) order.Price = price.String() - if order.OrderType == 4 { - ext := DbModels.LinePreOrderExt{} - db.Model(&ext).Where("order_id=?", preOrder.Id).Find(&ext) + // 更新止盈止损订单数量 + num = updateOrderQuantity(db, order, preOrder, num, tradeSet) - if ext.ReduceNumRatio.Cmp(decimal.Zero) > 0 { - num = num.Mul(ext.ReduceNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) - order.Num = num.String() - } - } - - if err := db.Model(&order).Update("num", num).Error; err != nil { - logger.Errorf("修改止盈止损数量失败 订单号:%s err:%v", order.OrderSn, err) - } - - logger.Errorf("止盈止损 下单数量:%v", num) + // 根据订单类型处理 switch order.OrderType { case 1: // 止盈 - processFutTakeProfitOrder(db, futApi, order, num) + //亏损大于0 重新计算比例 + if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { + percentag := positionData.TotalLoss.Div(num).Div(price) + percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) + order.Rate = percentag.String() + percentag = percentag.Div(decimal.NewFromInt(100)) + + if positionData.PositionSide == "LONG" { + order.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() + } else { + order.Price = price.Mul(decimal.NewFromInt(1).Sub(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() + } + } + + processFutTakeProfitOrder(db, FutRestApi{}, order, num) case 2: // 止损 processFutStopLossOrder(db, order, price, num) - case 4: //减仓 + case 4: // 减仓 processFutReduceOrder(order, price, num) } } } +// 处理主单加仓 +func handleMainOrderAddPosition(db *gorm.DB, preOrder *models.LinePreOrder) error { + // 更新加仓状态 + if err := db.Model(&DbModels.LinePreOrderStatus{}). + Where("order_id = ?", preOrder.MainId). + Update("add_position_status", 1).Error; err != nil { + return err + } + + // 取消止盈止损订单 + return cancelSymbolTakeAndStop(db, preOrder.MainId, preOrder.SymbolType) +} + +// 取消主单相关订单 +func cancelPositionOtherOrders(apiUserInfo DbModels.LineApiUser, db *gorm.DB, preOrder *models.LinePreOrder) error { + mainOrders, err := getOpenPositionMainOrderId(db, preOrder.Id, preOrder.ApiId, preOrder.SymbolType, preOrder.ExchangeType, preOrder.Symbol, preOrder.Site) + if err != nil { + return err + } + + mainIds := []int{} + for _, mainOrder := range mainOrders { + removeFutLossAndAddPosition(mainOrder.Id, mainOrder.OrderSn) + mainIds = append(mainIds, mainOrder.Id) + } + + if len(mainIds) > 0 { + orderSns, err := getOpenOrderSns(db, mainIds) + if err != nil { + return err + } + + // 批量取消订单 + orderArray := utility.SplitSlice(orderSns, 10) + futApi := FutRestApi{} + for _, item := range orderArray { + err := futApi.CancelBatchFutOrder(apiUserInfo, preOrder.Symbol, item) + + if err != nil { + logger.Errorf("批量取消订单失败 orderSns:%v", item) + } + } + } + return nil +} + +// 获取止盈止损订单 +func getStopOrders(db *gorm.DB, preOrder *models.LinePreOrder) ([]models.LinePreOrder, error) { + var orders []models.LinePreOrder + 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) { + return nil, err + } + return orders, nil +} + +// 更新订单数量 +func updateOrderQuantity(db *gorm.DB, order models.LinePreOrder, preOrder *models.LinePreOrder, num decimal.Decimal, tradeSet models2.TradeSet) decimal.Decimal { + // 处理减仓比例 + if order.OrderType == 4 { + ext := DbModels.LinePreOrderExt{} + if err := db.Model(&ext).Where("order_id=?", preOrder.Id).Find(&ext).Error; err != nil { + logger.Errorf("查询减仓比例失败, 订单号:%s, 错误信息: %v", order.OrderSn, err) + } + + // 计算减仓数量 + if ext.ReduceNumRatio.Cmp(decimal.Zero) > 0 { + num = num.Mul(ext.ReduceNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) + order.Num = num.String() + } + } + + // 更新订单数量 + if err := db.Model(&order).Update("num", num).Error; err != nil { + logger.Errorf("修改止盈止损数量失败 订单号:%s err:%v", order.OrderSn, err) + } + + return num +} + // 减仓单 func processFutReduceOrder(order DbModels.LinePreOrder, price, num decimal.Decimal) { key := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE) @@ -510,7 +604,8 @@ func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.Line logger.Error("合约止盈下单失败,更新状态失败:", order.OrderSn, " err:", err) } } else { - if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", order.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil { + if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", order.Id). + Updates(map[string]interface{}{"trigger_time": time.Now(), "rate": order.Rate}).Error; err != nil { logger.Error("更新合约止盈单触发事件 ordersn:", order.OrderSn) } diff --git a/services/binanceservice/orderservice.go b/services/binanceservice/orderservice.go index 1d242a3..9fba597 100644 --- a/services/binanceservice/orderservice.go +++ b/services/binanceservice/orderservice.go @@ -154,3 +154,8 @@ func GetSymbolTriggerCount(db *gorm.DB, symbol string, apiId, symbolType int) (i return count, nil } + +//获取已开仓的 +// func GetOpenedOrders(db *gorm.DB, apiId int, exchange, symbol, symbolType, side string) ([]models.LinePreOrder, error) { + +// } diff --git a/services/binanceservice/spotjudgeservice.go b/services/binanceservice/spotjudgeservice.go index da383e4..047e5cd 100644 --- a/services/binanceservice/spotjudgeservice.go +++ b/services/binanceservice/spotjudgeservice.go @@ -83,13 +83,6 @@ func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi S return } - //判断是否有已触发交易对 - count, _ := GetSymbolTriggerCount(db, v.Symbol, v.ApiId, 1) - - if count > 0 { - return - } - price, _ := decimal.NewFromString(v.Price) num, _ := decimal.NewFromString(preOrder.Num) params := OrderPlacementService{ diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index 40ce946..f2c2a38 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -11,7 +11,9 @@ import ( "go-admin/common/global" "go-admin/common/helper" models2 "go-admin/models" + "go-admin/models/positiondto" "go-admin/pkg/utility" + "go-admin/services/positionservice" "strconv" "strings" "time" @@ -127,7 +129,8 @@ func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus //主单止损回调 case preOrder.OrderType == 2 && orderStatus == 6: - removeSpotLossAndAddPosition(preOrder) + removeSpotLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, preOrder) if err := db.Model(&DbModels.LinePreOrder{}).Where("id =?", preOrder.MainId).Update("status", 9).Error; err != nil { logger.Errorf("主单止损回调 订单号:%s 修改主单状态失败:%v", preOrder.OrderSn, err) @@ -168,6 +171,8 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { return } + orderExt := models.LinePreOrderExt{} + positionData := savePosition(db, preOrder) orders := make([]models.LinePreOrder, 0) rate := utility.StringAsFloat(preOrder.Rate) @@ -177,7 +182,10 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { // 100%减仓 终止流程 if rate >= 100 { - removeSpotLossAndAddPosition(preOrder) + //缓存 + removeSpotLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, 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%减仓完毕,终结流程") @@ -185,7 +193,10 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { return } + db.Model(&orderExt).Where("order_id =?", preOrder.Pid).Find(&orderExt) totalNum := getSpotPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet) + totalNum = totalNum.Truncate(int32(tradeSet.AmountDigit)) + price := utility.StrToDecimal(preOrder.Price) if err := db.Model(&models.LinePreOrder{}).Where("pid =? AND order_type IN (1,2) AND status=0", preOrder.Id).Find(&orders).Error; err != nil { logger.Errorf("获取减仓单止盈止损失败 err:%v", err) @@ -195,26 +206,24 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { spotApi := SpotRestApi{} for index := range orders { - orders[index].Num = totalNum.Truncate(int32(tradeSet.AmountDigit)).String() + orders[index].Num = totalNum.String() if orders[index].OrderType == 1 { + //亏损大于0 重新计算比例 + if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { + percentag := positionData.TotalLoss.Div(totalNum).Div(price) + percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) + orders[index].Rate = percentag.String() + percentag = percentag.Div(decimal.NewFromInt(100)) + orders[index].Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() + } + processTakeProfitOrder(db, spotApi, orders[index]) } else if orders[index].OrderType == 2 { processStopLossOrder(orders[index]) } } - //计算实际亏损 - // if parentOrder.Price != "" { - // parentPrice := utility.StrToDecimal(parentOrder.Price) - // reduceNum := utility.StrToDecimal(preOrder.Num) - // 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) - // } - // } - //加仓待触发 addPositionOrder := DbModels.LinePreOrder{} @@ -376,7 +385,8 @@ func handleMainOrderClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) }) } - removeSpotLossAndAddPosition(preOrder) + removeSpotLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) + removePosition(db, preOrder) spotApi := SpotRestApi{} apiUserInfo, _ := GetApiInfo(preOrder.ApiId) @@ -387,14 +397,14 @@ func handleMainOrderClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) ApiId: preOrder.ApiId, } if err := spotApi.CancelOpenOrders(db, req); err != nil { - logger.Errorf("止盈单成功 取消其它订单失败 订单号:%s:", err) + logger.Errorf("平仓单成功 取消其它订单失败 订单号:%s:", err) } } } // 止盈成交 func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { - removeSpotLossAndAddPosition(preOrder) + removeSpotLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) spotApi := SpotRestApi{} apiUserInfo, _ := GetApiInfo(preOrder.ApiId) @@ -409,6 +419,8 @@ func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } } + removePosition(db, preOrder) + db.Transaction(func(tx *gorm.DB) error { ids := []int{preOrder.Pid, preOrder.MainId} if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status =6 AND order_type=0", ids).Update("status", 9).Error; err != nil { @@ -428,7 +440,28 @@ func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { } -func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) { +// 移除仓位信息 +func removePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + positionMangement := positionservice.BinancePositionManagement{} + positionMangement.Orm = db + positionDelReq := positiondto.LinePreOrderPositioinDelReq{ + ApiId: preOrder.ApiId, + SymbolType: preOrder.SymbolType, + Symbol: preOrder.Symbol, + ExchangeType: preOrder.ExchangeType, + } + + if preOrder.Site == "BUY" { + positionDelReq.Side = "SELL" + } else { + positionDelReq.Side = "BUY" + } + + positionMangement.RemovePosition(&positionDelReq) +} + +// 清理待加仓、待止损、待减仓缓存 +func removeSpotLossAndAddPosition(mainId int, orderSn string) { stoplossKey := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) stoplossVal, _ := helper.DefaultRedis.GetAllList(stoplossKey) addPositionKey := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) @@ -442,11 +475,11 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) { //止损缓存 for _, v := range stoplossVal { sonic.Unmarshal([]byte(v), &stoploss) - if stoploss.MainId == preOrder.MainId { + if stoploss.MainId == mainId { _, err := helper.DefaultRedis.LRem(stoplossKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除止损缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除止损缓存失败:%v", orderSn, err) } } } @@ -454,11 +487,11 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) { //加仓缓存 for _, v := range addPositionVal { sonic.Unmarshal([]byte(v), &addPosition) - if addPosition.MainId == preOrder.MainId { + if addPosition.MainId == mainId { _, err := helper.DefaultRedis.LRem(addPositionKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除加仓缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除加仓缓存失败:%v", orderSn, err) } } } @@ -466,11 +499,11 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) { //减仓缓存 for _, v := range reduceVal { sonic.Unmarshal([]byte(v), &reduce) - if reduce.MainId == preOrder.MainId { + if reduce.MainId == mainId { _, err := helper.DefaultRedis.LRem(reduceKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除减仓缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除减仓缓存失败:%v", orderSn, err) } } } @@ -478,13 +511,31 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) { // 主单成交 func handleMainOrderFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + //修改持仓信息 + positionData := savePosition(db, preOrder) + if preOrder.OrderCategory == 3 { if err := db.Model(&DbModels.LinePreOrderStatus{}).Where("order_id = ?", preOrder.MainId).Update("add_position_status", 1).Error; err != nil { logger.Errorf("更新主单加仓状态失败, 主单号:%s, 错误信息:%v", preOrder.MainId, err) } + } else { + mainOrders, _ := getOpenPositionMainOrderId(db, preOrder.Id, preOrder.ApiId, preOrder.SymbolType, preOrder.ExchangeType, preOrder.Symbol, preOrder.Site) + + if len(mainOrders) > 0 { + for _, mainOrder := range mainOrders { + removeSpotLossAndAddPosition(mainOrder.Id, mainOrder.OrderSn) + } + + spotApi := SpotRestApi{} + err := spotApi.CancelOpenOrdersLoop(db, CancelOpenOrdersReq{ApiId: preOrder.ApiId, Symbol: preOrder.Symbol}, 4) + + if err != nil { + logger.Errorf("取消未成交订单失败, 交易对:%s 主单号:%s, 错误信息:%v", preOrder.Symbol, preOrder.MainId, err) + } + } } - processTakeProfitAndStopLossOrders(db, preOrder) + processTakeProfitAndStopLossOrders(db, preOrder, &positionData) } // 解析订单状态 @@ -551,7 +602,8 @@ func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, r // 主单成交 处理止盈止损订单 // preOrder 主单 -func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrder) { +// positionData 持仓缓存信息 +func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrder, positionData *positiondto.PositionDto) { orders := []models.LinePreOrder{} tradeSet, _ := GetTradeSet(preOrder.Symbol, 0) @@ -581,7 +633,10 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd return } + price := utility.StrToDecimal(preOrder.Price) spotApi := SpotRestApi{} + orderExt := models.LinePreOrderExt{} + db.Model(&orderExt).Where("order_id =?", preOrder.Id).First(&orderExt) for _, order := range orders { order.Num = num.Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit)).String() @@ -601,6 +656,15 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd switch order.OrderType { case 1: // 止盈 + //亏损大于0 重新计算比例 + if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { + percentag := positionData.TotalLoss.Div(num).Div(price) + percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) + order.Rate = percentag.String() + percentag = percentag.Div(decimal.NewFromInt(100)) + order.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() + } + processTakeProfitOrder(db, spotApi, order) case 2: // 止损 processStopLossOrder(order) @@ -686,7 +750,8 @@ func processTakeProfitOrder(db *gorm.DB, spotApi SpotRestApi, order models.LineP logger.Error("现货止盈下单失败,更新状态失败:", order.OrderSn, " err:", err) } } else { - if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", order.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil { + if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", order.Id). + Updates(map[string]interface{}{"trigger_time": time.Now(), "rate": order.Rate}).Error; err != nil { logger.Error("更新现货止盈单触发事件 ordersn:", order.OrderSn) } if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ? and status ='0'", order.Id). diff --git a/services/positionservice/position_management.go b/services/positionservice/position_management.go new file mode 100644 index 0000000..dd74aa4 --- /dev/null +++ b/services/positionservice/position_management.go @@ -0,0 +1,120 @@ +package positionservice + +import ( + "context" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "go-admin/common/service" + "go-admin/models/positiondto" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" +) + +type BinancePositionManagement struct { + service.Service +} + +// 保存仓位信息 +// return 仓位信息 +// return 错误信息 +func (e *BinancePositionManagement) SavePosition(data *positiondto.PositionAddReq, exchangeType string) (positiondto.PositionDto, error) { + var key string + result := positiondto.PositionDto{} + + switch data.SymbolType { + case 1: + key = fmt.Sprintf(rediskey.SpotPosition, exchangeType, data.ApiId, data.Symbol, data.Side) + case 2: + key = fmt.Sprintf(rediskey.FuturePosition, exchangeType, data.ApiId, data.Symbol, data.Side) + default: + return result, fmt.Errorf("symbol type error") + } + + lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotPositionLock, data.ApiId, data.Symbol, data.Side), 200, 5, 200*time.Millisecond) + + if ok, err := lock.AcquireWait(context.Background()); err != nil { + logger.Debug("获取锁失败", err) + return result, err + } else if ok { + defer lock.Release() + val, _ := helper.DefaultRedis.GetString(key) + + if val != "" { + sonic.Unmarshal([]byte(val), &result) + } + + if result.Symbol == "" { + result.Symbol = data.Symbol + result.Side = data.Side + result.ApiId = data.ApiId + result.SymbolType = data.SymbolType + result.PositionSide = data.PositionSide + } + + var totalLoss decimal.Decimal + + if result.LastPrice.Cmp(decimal.Zero) > 0 { + switch { + //多 买入 + case data.PositionSide == "LONG": + totalLoss = result.LastPrice.Sub(data.Price).Abs().Mul(result.Quantity) + + case data.PositionSide == "SHORT": + totalLoss = data.Price.Sub(result.LastPrice).Abs().Mul(result.Quantity) + } + } + + result.LastPrice = data.Price + result.TotalLoss = result.TotalLoss.Add(totalLoss) + result.Quantity = data.Quantity.Add(result.Quantity) + + dataVal, _ := sonic.MarshalString(result) + if err := helper.DefaultRedis.SetString(key, dataVal); err != nil { + logger.Errorf("保存仓位信息失败,val:%s err:%v", dataVal, err) + } + } + + return result, nil +} + +// 获取系统内仓位信息 +func (e *BinancePositionManagement) GetPosition(apiId, symbolType int, exchangeTyp, symbol, side string) (positiondto.PositionDto, error) { + result := positiondto.PositionDto{} + var key string + + switch symbolType { + case 1: + key = fmt.Sprintf(rediskey.SpotPosition, exchangeTyp, apiId, symbol, side) + case 2: + key = fmt.Sprintf(rediskey.FuturePosition, exchangeTyp, apiId, symbol, side) + default: + return result, fmt.Errorf("symbol type error") + } + + val, _ := helper.DefaultRedis.GetString(key) + if val != "" { + sonic.Unmarshal([]byte(val), &result) + } + + return result, nil +} + +// 移除仓位信息 +func (e *BinancePositionManagement) RemovePosition(req *positiondto.LinePreOrderPositioinDelReq) error { + var key string + + switch req.SymbolType { + case 1: + key = fmt.Sprintf(rediskey.SpotPosition, req.ExchangeType, req.ApiId, req.Symbol, req.Side) + case 2: + key = fmt.Sprintf(rediskey.FuturePosition, req.ExchangeType, req.ApiId, req.Symbol, req.Side) + default: + return fmt.Errorf("symbol type error") + } + + return helper.DefaultRedis.DeleteString(key) +}