diff --git a/app/admin/apis/line_reduce_strategy.go b/app/admin/apis/line_reduce_strategy.go new file mode 100644 index 0000000..5cd68db --- /dev/null +++ b/app/admin/apis/line_reduce_strategy.go @@ -0,0 +1,205 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineReduceStrategy struct { + api.Api +} + +// GetPage 获取减仓策略列表 +// @Summary 获取减仓策略列表 +// @Description 获取减仓策略列表 +// @Tags 减仓策略 +// @Param name query string false "减仓策略名称" +// @Param status query string false "状态 1-启用 2-禁用" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReduceStrategy}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-reduce-strategy [get] +// @Security Bearer +func (e LineReduceStrategy) GetPage(c *gin.Context) { + req := dto.LineReduceStrategyGetPageReq{} + s := service.LineReduceStrategy{} + 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 + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineReduceStrategy, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取减仓策略失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取减仓策略 +// @Summary 获取减仓策略 +// @Description 获取减仓策略 +// @Tags 减仓策略 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineReduceStrategy} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-reduce-strategy/{id} [get] +// @Security Bearer +func (e LineReduceStrategy) Get(c *gin.Context) { + req := dto.LineReduceStrategyGetReq{} + s := service.LineReduceStrategy{} + 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 + } + var object models.LineReduceStrategy + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取减仓策略失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建减仓策略 +// @Summary 创建减仓策略 +// @Description 创建减仓策略 +// @Tags 减仓策略 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineReduceStrategyInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-reduce-strategy [post] +// @Security Bearer +func (e LineReduceStrategy) Insert(c *gin.Context) { + req := dto.LineReduceStrategyInsertReq{} + s := service.LineReduceStrategy{} + 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 err := req.Valid(); err != nil { + e.Error(500, err, "") + return + } + + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建减仓策略失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改减仓策略 +// @Summary 修改减仓策略 +// @Description 修改减仓策略 +// @Tags 减仓策略 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineReduceStrategyUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-reduce-strategy/{id} [put] +// @Security Bearer +func (e LineReduceStrategy) Update(c *gin.Context) { + req := dto.LineReduceStrategyUpdateReq{} + s := service.LineReduceStrategy{} + 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 err := req.Valid(); err != nil { + e.Error(500, err, "") + return + } + + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改减仓策略失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除减仓策略 +// @Summary 删除减仓策略 +// @Description 删除减仓策略 +// @Tags 减仓策略 +// @Param data body dto.LineReduceStrategyDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-reduce-strategy [delete] +// @Security Bearer +func (e LineReduceStrategy) Delete(c *gin.Context) { + s := service.LineReduceStrategy{} + req := dto.LineReduceStrategyDeleteReq{} + 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 + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除减仓策略失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_reduce_strategy_item.go b/app/admin/apis/line_reduce_strategy_item.go new file mode 100644 index 0000000..e9b823f --- /dev/null +++ b/app/admin/apis/line_reduce_strategy_item.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineReduceStrategyItem struct { + api.Api +} + +// GetPage 获取减仓策略明细列表 +// @Summary 获取减仓策略明细列表 +// @Description 获取减仓策略明细列表 +// @Tags 减仓策略明细 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineReduceStrategyItem}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-reduce-strategy-item [get] +// @Security Bearer +func (e LineReduceStrategyItem) GetPage(c *gin.Context) { + req := dto.LineReduceStrategyItemGetPageReq{} + s := service.LineReduceStrategyItem{} + 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 + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineReduceStrategyItem, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取减仓策略明细失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取减仓策略明细 +// @Summary 获取减仓策略明细 +// @Description 获取减仓策略明细 +// @Tags 减仓策略明细 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineReduceStrategyItem} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-reduce-strategy-item/{id} [get] +// @Security Bearer +func (e LineReduceStrategyItem) Get(c *gin.Context) { + req := dto.LineReduceStrategyItemGetReq{} + s := service.LineReduceStrategyItem{} + 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 + } + var object models.LineReduceStrategyItem + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取减仓策略明细失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建减仓策略明细 +// @Summary 创建减仓策略明细 +// @Description 创建减仓策略明细 +// @Tags 减仓策略明细 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineReduceStrategyItemInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-reduce-strategy-item [post] +// @Security Bearer +func (e LineReduceStrategyItem) Insert(c *gin.Context) { + req := dto.LineReduceStrategyItemInsertReq{} + s := service.LineReduceStrategyItem{} + 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 + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建减仓策略明细失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改减仓策略明细 +// @Summary 修改减仓策略明细 +// @Description 修改减仓策略明细 +// @Tags 减仓策略明细 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineReduceStrategyItemUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-reduce-strategy-item/{id} [put] +// @Security Bearer +func (e LineReduceStrategyItem) Update(c *gin.Context) { + req := dto.LineReduceStrategyItemUpdateReq{} + s := service.LineReduceStrategyItem{} + 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 + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改减仓策略明细失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除减仓策略明细 +// @Summary 删除减仓策略明细 +// @Description 删除减仓策略明细 +// @Tags 减仓策略明细 +// @Param data body dto.LineReduceStrategyItemDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-reduce-strategy-item [delete] +// @Security Bearer +func (e LineReduceStrategyItem) Delete(c *gin.Context) { + s := service.LineReduceStrategyItem{} + req := dto.LineReduceStrategyItemDeleteReq{} + 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 + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除减仓策略明细失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_strategy_template.go b/app/admin/apis/line_strategy_template.go new file mode 100644 index 0000000..c6a3918 --- /dev/null +++ b/app/admin/apis/line_strategy_template.go @@ -0,0 +1,206 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineStrategyTemplate struct { + api.Api +} + +// GetPage 获取波段策略列表 +// @Summary 获取波段策略列表 +// @Description 获取波段策略列表 +// @Tags 波段策略 +// @Param direction query int false "涨跌方向 1-涨 2-跌" +// @Param percentag query decimal.Decimal false "涨跌点数" +// @Param compareType query int false "比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 " +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineStrategyTemplate}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-strategy-template [get] +// @Security Bearer +func (e LineStrategyTemplate) GetPage(c *gin.Context) { + req := dto.LineStrategyTemplateGetPageReq{} + s := service.LineStrategyTemplate{} + 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 + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineStrategyTemplate, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取波段策略失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取波段策略 +// @Summary 获取波段策略 +// @Description 获取波段策略 +// @Tags 波段策略 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineStrategyTemplate} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-strategy-template/{id} [get] +// @Security Bearer +func (e LineStrategyTemplate) Get(c *gin.Context) { + req := dto.LineStrategyTemplateGetReq{} + s := service.LineStrategyTemplate{} + 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 + } + var object models.LineStrategyTemplate + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取波段策略失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建波段策略 +// @Summary 创建波段策略 +// @Description 创建波段策略 +// @Tags 波段策略 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineStrategyTemplateInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-strategy-template [post] +// @Security Bearer +func (e LineStrategyTemplate) Insert(c *gin.Context) { + req := dto.LineStrategyTemplateInsertReq{} + s := service.LineStrategyTemplate{} + 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 err := req.Valid(); err != nil { + e.Error(500, err, "") + return + } + + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建波段策略失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改波段策略 +// @Summary 修改波段策略 +// @Description 修改波段策略 +// @Tags 波段策略 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineStrategyTemplateUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-strategy-template/{id} [put] +// @Security Bearer +func (e LineStrategyTemplate) Update(c *gin.Context) { + req := dto.LineStrategyTemplateUpdateReq{} + s := service.LineStrategyTemplate{} + 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 err := req.Valid(); err != nil { + e.Error(500, err, "") + return + } + + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改波段策略失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除波段策略 +// @Summary 删除波段策略 +// @Description 删除波段策略 +// @Tags 波段策略 +// @Param data body dto.LineStrategyTemplateDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-strategy-template [delete] +// @Security Bearer +func (e LineStrategyTemplate) Delete(c *gin.Context) { + s := service.LineStrategyTemplate{} + req := dto.LineStrategyTemplateDeleteReq{} + 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 + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除波段策略失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_symbol_price.go b/app/admin/apis/line_symbol_price.go new file mode 100644 index 0000000..f612ded --- /dev/null +++ b/app/admin/apis/line_symbol_price.go @@ -0,0 +1,193 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineSymbolPrice struct { + api.Api +} + +// GetPage 获取缓存价格交易对列表 +// @Summary 获取缓存价格交易对列表 +// @Description 获取缓存价格交易对列表 +// @Tags 缓存价格交易对 +// @Param symbol query string false "交易对" +// @Param status query int false "状态 1-启用 2-禁用" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineSymbolPrice}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-price [get] +// @Security Bearer +func (e LineSymbolPrice) GetPage(c *gin.Context) { + req := dto.LineSymbolPriceGetPageReq{} + s := service.LineSymbolPrice{} + 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 + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineSymbolPrice, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取缓存价格交易对失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取缓存价格交易对 +// @Summary 获取缓存价格交易对 +// @Description 获取缓存价格交易对 +// @Tags 缓存价格交易对 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineSymbolPrice} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-price/{id} [get] +// @Security Bearer +func (e LineSymbolPrice) Get(c *gin.Context) { + req := dto.LineSymbolPriceGetReq{} + s := service.LineSymbolPrice{} + 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 + } + var object models.LineSymbolPrice + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取缓存价格交易对失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建缓存价格交易对 +// @Summary 创建缓存价格交易对 +// @Description 创建缓存价格交易对 +// @Tags 缓存价格交易对 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineSymbolPriceInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-symbol-price [post] +// @Security Bearer +func (e LineSymbolPrice) Insert(c *gin.Context) { + req := dto.LineSymbolPriceInsertReq{} + s := service.LineSymbolPrice{} + 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 + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建缓存价格交易对失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改缓存价格交易对 +// @Summary 修改缓存价格交易对 +// @Description 修改缓存价格交易对 +// @Tags 缓存价格交易对 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineSymbolPriceUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-symbol-price/{id} [put] +// @Security Bearer +func (e LineSymbolPrice) Update(c *gin.Context) { + req := dto.LineSymbolPriceUpdateReq{} + s := service.LineSymbolPrice{} + 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 + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改缓存价格交易对失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除缓存价格交易对 +// @Summary 删除缓存价格交易对 +// @Description 删除缓存价格交易对 +// @Tags 缓存价格交易对 +// @Param data body dto.LineSymbolPriceDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-symbol-price [delete] +// @Security Bearer +func (e LineSymbolPrice) Delete(c *gin.Context) { + s := service.LineSymbolPrice{} + req := dto.LineSymbolPriceDeleteReq{} + 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 + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除缓存价格交易对失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/models/line_order_reduce_strategy.go b/app/admin/models/line_order_reduce_strategy.go new file mode 100644 index 0000000..2e79945 --- /dev/null +++ b/app/admin/models/line_order_reduce_strategy.go @@ -0,0 +1,27 @@ +package models + +import "go-admin/common/models" + +type LineOrderReduceStrategy struct { + models.Model + + OrderId int `json:"orderId" gorm:"type:bigint;not null;comment:订单ID"` + ReduceStrategyId int `json:"reduceStrategyId" gorm:"type:bigint;not null;comment:减仓策略ID"` + ItemContent string `json:"itemContent" gorm:"type:text;not null;comment:策略明细json"` + Actived int `json:"actived" gorm:"type:tinyint;comment:"是否已减仓 1=已减仓 2=未减仓""` + models.ModelTime + models.ControlBy +} + +func (LineOrderReduceStrategy) TableName() string { + return "line_order_reduce_strategy" +} + +func (e *LineOrderReduceStrategy) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineOrderReduceStrategy) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_pre_order.go b/app/admin/models/line_pre_order.go index 48f8caf..95f5e6d 100644 --- a/app/admin/models/line_pre_order.go +++ b/app/admin/models/line_pre_order.go @@ -11,7 +11,6 @@ type LinePreOrder struct { models.Model ExchangeType string `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型 (字典 exchange_type)"` StrategyTemplateType int `json:"strategyTemplateType" gorm:"type:tinyint;comment:策略类型 0-无 1-波段涨跌幅"` - StrategyTemplateId int `json:"strategyTemplateId" gorm:"type:bigint;comment:策略模板id"` Pid int `json:"pid" gorm:"type:int unsigned;omitempty;comment:pid"` MainId int `json:"mainId" gorm:"type:int;comment:主单id"` ApiId int `json:"apiId" gorm:"type:varchar(255);omitempty;comment:api用户"` @@ -43,6 +42,8 @@ type LinePreOrder struct { AddPositionStatus int `json:"add_position_status" gorm:"->"` ReduceStatus int `json:"reduce_status" gorm:"->"` Childs []LinePreOrder `json:"childs" gorm:"foreignKey:pid;references:id"` + ReduceOrderId int `json:"reduceOrderId" gorm:"type:bigint;comment:主减仓单id"` + // OrderReduceStrategy LineOrderReduceStrategy `json:"-" gorm:"foreignKey:order_id;references:id"` // LinePreOrder 线上预埋单\ models.ModelTime models.ControlBy diff --git a/app/admin/models/line_reduce_strategy.go b/app/admin/models/line_reduce_strategy.go new file mode 100644 index 0000000..52129c9 --- /dev/null +++ b/app/admin/models/line_reduce_strategy.go @@ -0,0 +1,28 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineReduceStrategy struct { + models.Model + + Name string `json:"name" gorm:"type:varchar(50);comment:减仓策略名称"` + Status int `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` + Items []LineReduceStrategyItem `json:"items" gorm:"foreignKey:ReduceStrategyId;references:Id"` + models.ModelTime + models.ControlBy +} + +func (LineReduceStrategy) TableName() string { + return "line_reduce_strategy" +} + +func (e *LineReduceStrategy) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineReduceStrategy) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_reduce_strategy_item.go b/app/admin/models/line_reduce_strategy_item.go new file mode 100644 index 0000000..64d70c1 --- /dev/null +++ b/app/admin/models/line_reduce_strategy_item.go @@ -0,0 +1,31 @@ +package models + +import ( + "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type LineReduceStrategyItem struct { + models.Model + + ReduceStrategyId int `json:"reduceStrategyId" gorm:"type:bigint;comment:减仓策略id"` + LossPercent decimal.Decimal `json:"lossPercent" gorm:"type:decimal(10,2);comment:亏损百分比"` + OrderType string `json:"orderType" gorm:"type:varchar(20);comment:订单类型 LIMIT-限价 MARKET-市价"` + ReduceStrategy LineReduceStrategy `json:"reduceStrategy" gorm:"foreignKey:ReduceStrategyId;"` + models.ModelTime + models.ControlBy +} + +func (LineReduceStrategyItem) TableName() string { + return "line_reduce_strategy_item" +} + +func (e *LineReduceStrategyItem) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineReduceStrategyItem) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_strategy_template.go b/app/admin/models/line_strategy_template.go new file mode 100644 index 0000000..3b83c12 --- /dev/null +++ b/app/admin/models/line_strategy_template.go @@ -0,0 +1,33 @@ +package models + +import ( + "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type LineStrategyTemplate struct { + models.Model + + Name string `json:"name" gorm:"type:varchar(50);comment:策略名称"` + Direction int `json:"direction" gorm:"type:tinyint;comment:涨跌方向 1-涨 2-跌"` + Percentag decimal.Decimal `json:"percentag" gorm:"type:decimal(10,2);comment:涨跌点数"` + CompareType int `json:"compareType" gorm:"type:tinyint;comment:比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` + TimeSlotStart int `json:"timeSlotStart" gorm:"type:int;comment:时间段开始(分)"` + TimeSlotEnd int `json:"timeSlotEnd" gorm:"type:int;comment:时间断截至(分)"` + models.ModelTime + models.ControlBy +} + +func (LineStrategyTemplate) TableName() string { + return "line_strategy_template" +} + +func (e *LineStrategyTemplate) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineStrategyTemplate) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_symbol_price.go b/app/admin/models/line_symbol_price.go new file mode 100644 index 0000000..68d23cd --- /dev/null +++ b/app/admin/models/line_symbol_price.go @@ -0,0 +1,29 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LineSymbolPrice struct { + models.Model + + Symbol string `json:"symbol" gorm:"type:varchar(15);comment:交易对"` + Status int `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` + models.ModelTime + models.ControlBy +} + +func (LineSymbolPrice) TableName() string { + return "line_symbol_price" +} + +func (e *LineSymbolPrice) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineSymbolPrice) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_system_setting.go b/app/admin/models/line_system_setting.go index 97cec09..a6097c9 100644 --- a/app/admin/models/line_system_setting.go +++ b/app/admin/models/line_system_setting.go @@ -9,13 +9,14 @@ import ( type LineSystemSetting struct { models.Model - Time int64 `json:"time" gorm:"type:int;comment:导入:挂单时长达到时间后失效"` - BatchTime int64 `json:"batchTime" gorm:"type:int;comment:批量:挂单时长达到时间后失效"` - ProfitRate string `json:"profitRate" gorm:"type:decimal(10,2);comment:平仓盈利比例"` - CoverOrderTypeBRate string `json:"coverOrderTypeBRate" gorm:"type:decimal(10,2);comment:b账户限价补单的买入百分比"` - StopLossPremium decimal.Decimal `json:"stopLossPremium" gorm:"type:decimal(10,2);comment:限价止损溢价百分比"` - AddPositionPremium decimal.Decimal `json:"addPositionPremium" gorm:"type:decimal(10,2);comment:限价加仓溢价百分比` - ReducePremium decimal.Decimal `json:"reducePremium" gorm:"type:decimal(10,2);comment:限价减仓溢价百分比"` + Time int64 `json:"time" gorm:"type:int;comment:导入:挂单时长达到时间后失效"` + BatchTime int64 `json:"batchTime" gorm:"type:int;comment:批量:挂单时长达到时间后失效"` + ProfitRate string `json:"profitRate" gorm:"type:decimal(10,2);comment:平仓盈利比例"` + CoverOrderTypeBRate string `json:"coverOrderTypeBRate" gorm:"type:decimal(10,2);comment:b账户限价补单的买入百分比"` + StopLossPremium decimal.Decimal `json:"stopLossPremium" gorm:"type:decimal(10,2);comment:限价止损溢价百分比"` + AddPositionPremium decimal.Decimal `json:"addPositionPremium" gorm:"type:decimal(10,2);comment:限价加仓溢价百分比` + ReducePremium decimal.Decimal `json:"reducePremium" gorm:"type:decimal(10,2);comment:限价减仓溢价百分比"` + ReduceEarlyTriggerPercent decimal.Decimal `json:"reduceEarlyTriggerPercent" gorm:"type:decimal(10,2);comment:减仓提前触发百分比"` models.ModelTime models.ControlBy } diff --git a/app/admin/router/line_reduce_strategy.go b/app/admin/router/line_reduce_strategy.go new file mode 100644 index 0000000..80c44e6 --- /dev/null +++ b/app/admin/router/line_reduce_strategy.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineReduceStrategyRouter) +} + +// registerLineReduceStrategyRouter +func registerLineReduceStrategyRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineReduceStrategy{} + r := v1.Group("/line-reduce-strategy").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_reduce_strategy_item.go b/app/admin/router/line_reduce_strategy_item.go new file mode 100644 index 0000000..4d199b9 --- /dev/null +++ b/app/admin/router/line_reduce_strategy_item.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineReduceStrategyItemRouter) +} + +// registerLineReduceStrategyItemRouter +func registerLineReduceStrategyItemRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineReduceStrategyItem{} + r := v1.Group("/line-reduce-strategy-item").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_strategy_template.go b/app/admin/router/line_strategy_template.go new file mode 100644 index 0000000..d138ec0 --- /dev/null +++ b/app/admin/router/line_strategy_template.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineStrategyTemplateRouter) +} + +// registerLineStrategyTemplateRouter +func registerLineStrategyTemplateRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineStrategyTemplate{} + r := v1.Group("/line-strategy-template").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_symbol_price.go b/app/admin/router/line_symbol_price.go new file mode 100644 index 0000000..e78366d --- /dev/null +++ b/app/admin/router/line_symbol_price.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineSymbolPriceRouter) +} + +// registerLineSymbolPriceRouter +func registerLineSymbolPriceRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineSymbolPrice{} + r := v1.Group("/line-symbol-price").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/service/dto/line_order_reduce_strategy.go b/app/admin/service/dto/line_order_reduce_strategy.go new file mode 100644 index 0000000..23f3af9 --- /dev/null +++ b/app/admin/service/dto/line_order_reduce_strategy.go @@ -0,0 +1,19 @@ +package dto + +import "github.com/shopspring/decimal" + +type LineOrderReduceStrategyResp struct { + OrderId int `json:"orderId"` + Symbol string `json:"symbol"` + Side string `json:"side" comment:"BUY SELL"` + Items []LineOrderReduceStrategyRespItem `json:"items"` +} + +// 减仓节点 +type LineOrderReduceStrategyRespItem struct { + Price decimal.Decimal `json:"p" comment:"下单价"` + TriggerPrice decimal.Decimal `json:"t" comment:"触发价"` + LossPercent decimal.Decimal `json:"l" comment:"亏损百分比"` + OrderType string `json:"o" comment:"订单类型 LIMIT-限价 MARKET-市价"` + Actived bool `json:"a" comment:"是否触发 "` +} diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go index ae716cc..1868464 100644 --- a/app/admin/service/dto/line_pre_order.go +++ b/app/admin/service/dto/line_pre_order.go @@ -188,6 +188,7 @@ type LineAddPreOrderReq struct { ExchangeType string `json:"exchange_type" vd:"len($)>0"` //交易所类型 StrategyTemplateType int `json:"strategy_template_type"` //策略类型 0-无 1-波段涨跌幅 StrategyTemplateId int `json:"strategy_template_id"` //策略id + ReduceStrategyId int `json:"reduce_strategy_id"` //减仓策略id OrderType int `json:"order_type"` //订单类型 Symbol string `json:"symbol"` //交易对 ApiUserId string `json:"api_id" ` //下单用户 @@ -201,6 +202,7 @@ type LineAddPreOrderReq struct { ProfitNumRatio decimal.Decimal `json:"profit_num_ratio"` //止盈数量百分比 ProfitTpTpPriceRatio decimal.Decimal `json:"profit_tp_tp_price_ratio"` //止盈后止盈价百分比 ProfitTpSlPriceRatio decimal.Decimal `json:"profit_tp_sl_price_ratio"` //止盈后止损价百分比 + StopLoss decimal.Decimal `json:"stop_loss"` //止损价 PriceType string `json:"price_type"` //价格类型 SaveTemplate string `json:"save_template"` //是否保存模板 TemplateName string `json:"template_name"` //模板名字 @@ -324,7 +326,9 @@ func (req LineAddPreOrderReq) Valid() error { return fmt.Errorf("%s单下跌价格不能为空", name) } - if v.TakeProfitRatio.IsZero() || v.TakeProfitRatio.Cmp(decimal.NewFromInt(100)) > 0 { + if (v.AddType == 2 && v.AddPositionVal.Cmp(decimal.NewFromInt(100)) < 0 && v.TakeProfitRatio.IsZero()) || + (v.AddType == 1 && v.TakeProfitRatio.IsZero()) || + v.TakeProfitRatio.Cmp(decimal.NewFromInt(100)) > 0 { return errors.New("止盈价格不正确") } @@ -474,7 +478,9 @@ func (req LineBatchAddPreOrderReq) CheckParams() error { return fmt.Errorf("%s单下跌价格不能为空", name) } - if v.TakeProfitRatio.IsZero() || v.TakeProfitRatio.Cmp(decimal.NewFromInt(100)) > 0 { + if (v.AddType == 2 && v.AddPositionVal.Cmp(decimal.NewFromInt(100)) < 0 && v.TakeProfitRatio.IsZero()) || + (v.AddType == 1 && v.TakeProfitRatio.IsZero()) || + v.TakeProfitRatio.Cmp(decimal.NewFromInt(100)) > 0 { return errors.New("止盈价格不正确") } @@ -569,6 +575,17 @@ type PreOrderRedisList struct { QuoteSymbol string `json:"quote_symbol"` } +type StrategyOrderRedisList struct { + Id int `json:"id"` + Symbol string `json:"symbol"` + Price string `json:"price"` + Site string `json:"site"` + ApiId int `json:"api_id"` + OrderSn string `json:"order_sn"` + QuoteSymbol string `json:"quote_symbol"` + LineStrategyTemplateRedis +} + type StopLossRedisList struct { Id int `json:"id"` PId int `json:"pid"` //父级id diff --git a/app/admin/service/dto/line_reduce_strategy.go b/app/admin/service/dto/line_reduce_strategy.go new file mode 100644 index 0000000..bba58d8 --- /dev/null +++ b/app/admin/service/dto/line_reduce_strategy.go @@ -0,0 +1,157 @@ +package dto + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineReduceStrategyGetPageReq struct { + dto.Pagination `search:"-"` + Name string `form:"name" search:"type:contains;column:name;table:line_reduce_strategy" comment:"减仓策略名称"` + Status string `form:"status" search:"type:exact;column:status;table:line_reduce_strategy" comment:"状态 1-启用 2-禁用"` + LineReduceStrategyOrder +} + +type LineReduceStrategyOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_reduce_strategy"` + Name string `form:"nameOrder" search:"type:order;column:name;table:line_reduce_strategy"` + Status string `form:"statusOrder" search:"type:order;column:status;table:line_reduce_strategy"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_reduce_strategy"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_reduce_strategy"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_reduce_strategy"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_reduce_strategy"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_reduce_strategy"` +} + +func (m *LineReduceStrategyGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineReduceStrategyInsertReq struct { + Id int `json:"-" comment:"主键id"` // 主键id + Name string `json:"name" comment:"减仓策略名称"` + Status int `json:"status" comment:"状态 1-启用 2-禁用"` + Items []LineReduceStrategyItem `json:"items" comment:"减仓单节点"` + common.ControlBy +} + +// 参数校验 +func (s *LineReduceStrategyInsertReq) Valid() error { + if s.Name == "" { + return errors.New("减仓策略名称不能为空") + } + + if s.Status < 1 || s.Status > 2 { + return errors.New("状态值不合法") + } + + if len(s.Items) == 0 { + return errors.New("减仓策略节点不能为空") + } + + for index, item := range s.Items { + if err := item.Valid(); err != nil { + return err + } + + if index > 0 && item.LossPercent.Cmp(s.Items[index].LossPercent) <= 0 { + return errors.New("亏损比例必须递增") + } + } + + return nil +} + +func (s *LineReduceStrategyInsertReq) Generate(model *models.LineReduceStrategy) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Name = s.Name + model.Status = s.Status + + for _, item := range s.Items { + strategyItem := models.LineReduceStrategyItem{} + strategyItem.OrderType = item.OrderType + strategyItem.LossPercent = item.LossPercent + + model.Items = append(model.Items, strategyItem) + } + + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineReduceStrategyInsertReq) GetId() interface{} { + return s.Id +} + +type LineReduceStrategyUpdateReq struct { + Id int `uri:"id" comment:"主键id"` // 主键id + Name string `json:"name" comment:"减仓策略名称"` + Status int `json:"status" comment:"状态 1-启用 2-禁用"` + Items []LineReduceStrategyItem `json:"items" comment:"减仓单节点"` + common.ControlBy +} + +func (s *LineReduceStrategyUpdateReq) Generate(model *models.LineReduceStrategy) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Name = s.Name + model.Status = s.Status + + for _, item := range s.Items { + strategyItem := models.LineReduceStrategyItem{} + strategyItem.OrderType = item.OrderType + strategyItem.LossPercent = item.LossPercent + + model.Items = append(model.Items, strategyItem) + } + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +// 参数校验 +func (s *LineReduceStrategyUpdateReq) Valid() error { + if s.Name == "" { + return errors.New("减仓策略名称不能为空") + } + + if s.Status < 1 || s.Status > 2 { + return errors.New("状态值不合法") + } + + if len(s.Items) == 0 { + return errors.New("减仓策略节点不能为空") + } + + for _, item := range s.Items { + if err := item.Valid(); err != nil { + return err + } + } + + return nil +} + +func (s *LineReduceStrategyUpdateReq) GetId() interface{} { + return s.Id +} + +// LineReduceStrategyGetReq 功能获取请求参数 +type LineReduceStrategyGetReq struct { + Id int `uri:"id"` +} + +func (s *LineReduceStrategyGetReq) GetId() interface{} { + return s.Id +} + +// LineReduceStrategyDeleteReq 功能删除请求参数 +type LineReduceStrategyDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineReduceStrategyDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_reduce_strategy_item.go b/app/admin/service/dto/line_reduce_strategy_item.go new file mode 100644 index 0000000..cda988f --- /dev/null +++ b/app/admin/service/dto/line_reduce_strategy_item.go @@ -0,0 +1,125 @@ +package dto + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + "go-admin/pkg/utility" + + "github.com/shopspring/decimal" +) + +type LineReduceStrategyItemGetPageReq struct { + dto.Pagination `search:"-"` + LineReduceStrategyItemOrder +} + +type LineReduceStrategyItemOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_reduce_strategy_item"` + ReduceStrategyId string `form:"reduceStrategyIdOrder" search:"type:order;column:reduce_strategy_id;table:line_reduce_strategy_item"` + LossPercent string `form:"lossPercentOrder" search:"type:order;column:loss_percent;table:line_reduce_strategy_item"` + OrderType string `form:"orderTypeOrder" search:"type:order;column:order_type;table:line_reduce_strategy_item"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_reduce_strategy_item"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_reduce_strategy_item"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_reduce_strategy_item"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_reduce_strategy_item"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_reduce_strategy_item"` +} + +func (m *LineReduceStrategyItemGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineReduceStrategyItem struct { + LossPercent decimal.Decimal `json:"lossPercent" comment:"止损百分比"` + OrderType string `json:"orderType" comment:"订单类型 LIMIT-限价 MARKET-市价"` +} + +func (s *LineReduceStrategyItem) Valid() error { + + if s.LossPercent.Cmp(decimal.Zero) <= 0 { + return errors.New("百分比不能小于等于0") + } + + if s.LossPercent.Cmp(decimal.NewFromFloat(100)) >= 0 { + return errors.New("百分比不能大于等于100") + } + + keys := []string{"LIMIT", "MARKET"} + + if !utility.ContainsStr(keys, s.OrderType) { + return errors.New("订单类型不正确") + } + + return nil +} + +type LineReduceStrategyItemResp struct { + ReduceStrategyId int `json:"reduceStrategyId" comment:"减仓策略id"` + LossPercent decimal.Decimal `json:"lossPercent" comment:"亏损百分比"` + OrderType string `json:"orderType" comment:"订单类型 LIMIT-限价 MARKET-市价"` + Actived int `json:"actived" comment:"是否已减仓 1=未减仓 2=已减仓"` +} + +type LineReduceStrategyItemInsertReq struct { + Id int `json:"-" comment:"主键id"` // 主键id + ReduceStrategyId int `json:"reduceStrategyId" comment:"减仓策略id"` + LossPercent decimal.Decimal `json:"lossPercent" comment:"亏损百分比"` + OrderType string `json:"orderType" comment:"订单类型 LIMIT-限价 MARKET-市价"` + common.ControlBy +} + +func (s *LineReduceStrategyItemInsertReq) Generate(model *models.LineReduceStrategyItem) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.ReduceStrategyId = s.ReduceStrategyId + model.LossPercent = s.LossPercent + model.OrderType = s.OrderType + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineReduceStrategyItemInsertReq) GetId() interface{} { + return s.Id +} + +type LineReduceStrategyItemUpdateReq struct { + Id int `uri:"id" comment:"主键id"` // 主键id + ReduceStrategyId int `json:"reduceStrategyId" comment:"减仓策略id"` + LossPercent decimal.Decimal `json:"lossPercent" comment:"亏损百分比"` + OrderType string `json:"orderType" comment:"订单类型 LIMIT-限价 MARKET-市价"` + common.ControlBy +} + +func (s *LineReduceStrategyItemUpdateReq) Generate(model *models.LineReduceStrategyItem) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.ReduceStrategyId = s.ReduceStrategyId + model.LossPercent = s.LossPercent + model.OrderType = s.OrderType + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineReduceStrategyItemUpdateReq) GetId() interface{} { + return s.Id +} + +// LineReduceStrategyItemGetReq 功能获取请求参数 +type LineReduceStrategyItemGetReq struct { + Id int `uri:"id"` +} + +func (s *LineReduceStrategyItemGetReq) GetId() interface{} { + return s.Id +} + +// LineReduceStrategyItemDeleteReq 功能删除请求参数 +type LineReduceStrategyItemDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineReduceStrategyItemDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_strategy_template.go b/app/admin/service/dto/line_strategy_template.go new file mode 100644 index 0000000..6b0cb4f --- /dev/null +++ b/app/admin/service/dto/line_strategy_template.go @@ -0,0 +1,174 @@ +package dto + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type LineStrategyTemplateGetPageReq struct { + dto.Pagination `search:"-"` + Direction int `form:"direction" search:"type:exact;column:direction;table:line_strategy_template" comment:"涨跌方向 1-涨 2-跌"` + Percentag decimal.Decimal `form:"percentag" search:"type:exact;column:percentag;table:line_strategy_template" comment:"涨跌点数"` + CompareType int `form:"compareType" search:"type:exact;column:compare_type;table:line_strategy_template" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` + LineStrategyTemplateOrder +} + +type LineStrategyTemplateOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_strategy_template"` + Direction string `form:"directionOrder" search:"type:order;column:direction;table:line_strategy_template"` + Percentag string `form:"percentagOrder" search:"type:order;column:percentag;table:line_strategy_template"` + CompareType string `form:"compareTypeOrder" search:"type:order;column:compare_type;table:line_strategy_template"` + TimeSlotStart string `form:"timeSlotStartOrder" search:"type:order;column:time_slot_start;table:line_strategy_template"` + TimeSlotEnd string `form:"timeSlotEndOrder" search:"type:order;column:time_slot_end;table:line_strategy_template"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_strategy_template"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_strategy_template"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_strategy_template"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_strategy_template"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_strategy_template"` +} + +func (m *LineStrategyTemplateGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineStrategyTemplateInsertReq struct { + Id int `json:"-" comment:"主键id"` // 主键id + Name string `json:"name" comment:"策略名称"` + Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"` + Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"` + CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` + TimeSlotStart int `json:"timeSlotStart" comment:"时间段开始(分)"` + TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"` + common.ControlBy +} + +// 交易参数 +func (s *LineStrategyTemplateInsertReq) Valid() error { + if s.Name == "" { + return errors.New("策略名称不能为空") + } + if len(s.Name) > 50 { + return errors.New("策略名称长度不能超过50") + } + + if s.Percentag.Cmp(decimal.Zero) <= 0 { + return errors.New("涨跌点数不能为空") + } + + if s.CompareType < 1 || s.CompareType > 5 { + return errors.New("比较类型不合法") + } + + if s.TimeSlotStart < 0 || s.TimeSlotEnd > 59 { + return errors.New("时间段不合法") + } + + if s.TimeSlotEnd < s.TimeSlotStart { + return errors.New("时间段不合法") + } + + return nil +} + +func (s *LineStrategyTemplateInsertReq) Generate(model *models.LineStrategyTemplate) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Name = s.Name + model.Direction = s.Direction + model.Percentag = s.Percentag + model.CompareType = s.CompareType + model.TimeSlotStart = s.TimeSlotStart + model.TimeSlotEnd = s.TimeSlotEnd + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineStrategyTemplateInsertReq) GetId() interface{} { + return s.Id +} + +type LineStrategyTemplateUpdateReq struct { + Id int `uri:"id" comment:"主键id"` // 主键id + Name string `json:"name" comment:"策略名称"` + Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"` + Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"` + CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` + TimeSlotStart int `json:"timeSlotStart" comment:"时间段开始(分)"` + TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"` + common.ControlBy +} + +// 交易参数 +func (s *LineStrategyTemplateUpdateReq) Valid() error { + if s.Name == "" { + return errors.New("策略名称不能为空") + } + + if len(s.Name) > 50 { + return errors.New("策略名称长度不能超过50") + } + + if s.Percentag.Cmp(decimal.Zero) <= 0 { + return errors.New("涨跌点数不能为空") + } + + if s.CompareType < 1 || s.CompareType > 5 { + return errors.New("比较类型不合法") + } + + if s.TimeSlotStart < 0 || s.TimeSlotEnd > 59 { + return errors.New("时间段不合法") + } + + if s.TimeSlotEnd < s.TimeSlotStart { + return errors.New("时间段不合法") + } + + return nil +} +func (s *LineStrategyTemplateUpdateReq) Generate(model *models.LineStrategyTemplate) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Name = s.Name + model.Direction = s.Direction + model.Percentag = s.Percentag + model.CompareType = s.CompareType + model.TimeSlotStart = s.TimeSlotStart + model.TimeSlotEnd = s.TimeSlotEnd + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineStrategyTemplateUpdateReq) GetId() interface{} { + return s.Id +} + +// LineStrategyTemplateGetReq 功能获取请求参数 +type LineStrategyTemplateGetReq struct { + Id int `uri:"id"` +} + +func (s *LineStrategyTemplateGetReq) GetId() interface{} { + return s.Id +} + +// LineStrategyTemplateDeleteReq 功能删除请求参数 +type LineStrategyTemplateDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineStrategyTemplateDeleteReq) GetId() interface{} { + return s.Ids +} + +type LineStrategyTemplateRedis struct { + Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"` + Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"` + CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` + TimeSlotStart int `json:"timeSlotStart" comment:"时间段开始(分)"` + TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"` +} diff --git a/app/admin/service/dto/line_symbol.go b/app/admin/service/dto/line_symbol.go index 872cd7b..a4f1bf1 100644 --- a/app/admin/service/dto/line_symbol.go +++ b/app/admin/service/dto/line_symbol.go @@ -14,6 +14,7 @@ type LineSymbolGetPageReq struct { BaseAsset string `form:"baseAsset" search:"type:contains;column:base_asset;table:line_symbol" comment:"基础货币"` QuoteAsset string `form:"quoteAsset" search:"type:exact;column:quote_asset;table:line_symbol" comment:"计价货币"` Type string `form:"type" search:"type:exact;column:type;table:line_symbol" comment:"交易对类型"` + // StrategyTemplateId int `form:"strategyTemplateId" search:"-" comment:"策略id"` LineSymbolOrder } diff --git a/app/admin/service/dto/line_symbol_price.go b/app/admin/service/dto/line_symbol_price.go new file mode 100644 index 0000000..2f83aa4 --- /dev/null +++ b/app/admin/service/dto/line_symbol_price.go @@ -0,0 +1,88 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + "strings" +) + +type LineSymbolPriceGetPageReq struct { + dto.Pagination `search:"-"` + Symbol string `form:"symbol" search:"type:exact;column:symbol;table:line_symbol_price" comment:"交易对"` + Status int `form:"status" search:"type:exact;column:status;table:line_symbol_price" comment:"状态 1-启用 2-禁用"` + LineSymbolPriceOrder +} + +type LineSymbolPriceOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_symbol_price"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_symbol_price"` + Status string `form:"statusOrder" search:"type:order;column:status;table:line_symbol_price"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_symbol_price"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_symbol_price"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_symbol_price"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_symbol_price"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_symbol_price"` +} + +func (m *LineSymbolPriceGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineSymbolPriceInsertReq struct { + Id int `json:"-" comment:"主键id"` // 主键id + Symbol string `json:"symbol" comment:"交易对"` + Status int `json:"status" comment:"状态 1-启用 2-禁用"` + common.ControlBy +} + +func (s *LineSymbolPriceInsertReq) Generate(model *models.LineSymbolPrice) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Symbol = strings.ToUpper(s.Symbol) + model.Status = s.Status + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineSymbolPriceInsertReq) GetId() interface{} { + return s.Id +} + +type LineSymbolPriceUpdateReq struct { + Id int `uri:"id" comment:"主键id"` // 主键id + Symbol string `json:"symbol" comment:"交易对"` + Status int `json:"status" comment:"状态 1-启用 2-禁用"` + common.ControlBy +} + +func (s *LineSymbolPriceUpdateReq) Generate(model *models.LineSymbolPrice) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Symbol = strings.ToUpper(s.Symbol) + model.Status = s.Status + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineSymbolPriceUpdateReq) GetId() interface{} { + return s.Id +} + +// LineSymbolPriceGetReq 功能获取请求参数 +type LineSymbolPriceGetReq struct { + Id int `uri:"id"` +} + +func (s *LineSymbolPriceGetReq) GetId() interface{} { + return s.Id +} + +// LineSymbolPriceDeleteReq 功能删除请求参数 +type LineSymbolPriceDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineSymbolPriceDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index ca44ef0..ee291dc 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -11,6 +11,7 @@ import ( "go-admin/pkg/utility" "go-admin/pkg/utility/snowflakehelper" "go-admin/services/binanceservice" + "go-admin/services/cacheservice" "sort" "strconv" "strings" @@ -403,6 +404,15 @@ func (e *LinePreOrder) AddPreOrderCheck(req *dto.LineAddPreOrderReq, p *actions. apiIds = append(apiIds, apiId) } + if req.StrategyTemplateType == 1 && req.StrategyTemplateId > 0 { + cachePriceSymbols, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice) + + if !utility.ContainsStr(cachePriceSymbols, req.Symbol) { + *errs = append(*errs, errors.New("交易对涨跌幅缓存不存在")) + return nil + } + } + activeApiIds, _ := apiUserService.GetActiveApis(apiIds) if len(activeApiIds) == 0 { @@ -438,6 +448,17 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int saveTemplateParams.CreateBy = req.CreateBy e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams) } + + linestrategyTemplate := models.LineStrategyTemplate{} + + if req.StrategyTemplateId > 0 { + e.Orm.Where("id =?", req.StrategyTemplateId).First(&linestrategyTemplate) + + if linestrategyTemplate.Id == 0 { + *errs = append(*errs, fmt.Errorf("策略不存在:%v", req.StrategyTemplateId)) + return nil + } + } if req.SaveTemplate == "2" { return nil } @@ -461,13 +482,13 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int } var AddOrder models.LinePreOrder var profitOrder models.LinePreOrder - var stopOrder models.LinePreOrder + var reduceOrder models.LinePreOrder //获取交易对 tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key) tickerPrice := utility.StrToDecimal(tradeSet.LastPrice) if tickerPrice.Equal(decimal.Zero) { //redis 没有这个值 - *errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 交易行情出错", id, req.Symbol)) + *errs = append(*errs, fmt.Errorf("api_id:%d 获取交易对:%s 交易行情出错", id, req.Symbol)) continue } @@ -540,7 +561,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int AddOrder.Num = utility.SafeDiv(buyPrice, fromString).Truncate(int32(tradeSet.AmountDigit)).String() } if utility.StringToFloat64(AddOrder.Num) < tradeSet.MinQty { - *errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 小于最小下单数量", id, req.Symbol)) + *errs = append(*errs, fmt.Errorf("api_id:%d 获取交易对:%s 小于最小下单数量", id, req.Symbol)) continue } AddOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) @@ -551,7 +572,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int AddOrder.GroupId = "0" AddOrder.Status = 0 copier.Copy(&profitOrder, &AddOrder) - copier.Copy(&stopOrder, &AddOrder) + copier.Copy(&reduceOrder, &AddOrder) preOrderStatus := models.LinePreOrderStatus{} preOrderStatus.OrderSn = AddOrder.OrderSn @@ -565,6 +586,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int OrderType: req.PriceType, TakeProfitRatio: utility.StringToDecimal(req.Profit), TakeProfitNumRatio: req.ProfitNumRatio, + StopLossRatio: req.StopLoss, TpTpPriceRatio: req.ProfitTpTpPriceRatio, TpSlPriceRatio: req.ProfitTpSlPriceRatio, } @@ -584,7 +606,6 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int defultExt.TotalAfter = utility.StrToDecimal(AddOrder.Num).Truncate(int32(tradeSet.AmountDigit)) defultExt2.TotalBefore = defultExt.TotalAfter - // if decimal.NewFromInt(100).Sub(req.ReduceNumRatio).Cmp(decimal.Zero) > 0 { default2NumPercent := utility.SafeDiv(decimal.NewFromInt(100).Sub(req.ReduceNumRatio), decimal.NewFromInt(100)) defultExt2.TotalAfter = mainAmount.Mul(default2NumPercent).Truncate(int32(tradeSet.AmountDigit)) defultExt2.ReTakeRatio = utility.SafeDiv(req.ReducePriceRatio, default2NumPercent).Truncate(2) @@ -614,7 +635,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU //buyPrice.Mul(req.ReducePriceRatio.Div(decimal.NewFromInt(100)).Truncate(4)).Truncate(int32(tradeSet.PriceDigit)) req.ReduceReTakeProfitRatio = calculateResp.Ratio mainParam.LossBeginPercent = req.ReducePriceRatio - defultExt.ReTakeRatio = calculateResp.Ratio + // defultExt.ReTakeRatio = calculateResp.Ratio for index, addPosition := range req.Ext { ext := models.LinePreOrderExt{ @@ -651,11 +672,20 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int preOrderExts = append(preOrderExts, ext) } + //获取减仓策略 + var reduceStrategy models.LineReduceStrategy + + if req.ReduceStrategyId > 0 { + reduceStrategyService := LineReduceStrategy{Service: e.Service} + reduceStrategy, _ = reduceStrategyService.GetById(req.ReduceStrategyId) + } + //事务添加 e.Orm.Transaction(func(tx *gorm.DB) error { + reduceOrderStrategys := make([]models.LineOrderReduceStrategy, 0) err := tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&AddOrder).Error if err != nil { - *errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 生成订单失败", id, req.Symbol)) + *errs = append(*errs, fmt.Errorf("api_id:%d 获取交易对:%s 生成订单失败", id, req.Symbol)) return err } @@ -664,117 +694,72 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int //加仓、减仓状态 tx.Model(&models.LinePreOrderStatus{}).Create(&preOrderStatus) preOrderExts[0].OrderId = AddOrder.Id - list := dto.PreOrderRedisList{ - Id: AddOrder.Id, - Symbol: AddOrder.Symbol, - Price: AddOrder.Price, - Site: AddOrder.Site, - ApiId: AddOrder.ApiId, - OrderSn: AddOrder.OrderSn, - QuoteSymbol: AddOrder.QuoteSymbol, - } - marshal, _ := sonic.Marshal(&list) - var preKey string - if AddOrder.SymbolType == global.SYMBOL_SPOT { - preKey = fmt.Sprintf(rediskey.PreSpotOrderList, AddOrder.ExchangeType) - } else { - preKey = fmt.Sprintf(rediskey.PreFutOrderList, AddOrder.ExchangeType) - } - helper.DefaultRedis.LPushList(preKey, string(marshal)) - //是否有止盈止损订单 - if req.Profit != "" { - if req.PricePattern == "mixture" { - mixturePrice := utility.StrToDecimal(req.Profit) + childOrders, err := makeFuturesTakeAndReduce(&AddOrder, defultExt, tradeSet) - if mixturePrice.Cmp(decimal.Zero) <= 0 { - return fmt.Errorf("止盈价不能小于等于0") - } - - profitOrder.Price = mixturePrice.Truncate(int32(tradeSet.PriceDigit)).String() - profitOrder.Rate = "0" - } else { - if strings.ToUpper(req.Site) == "BUY" { - // profitOrder.Site = "SELL" - profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 + utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String() - } else { - // profitOrder.Site = "BUY" - profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 - utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String() - } - profitOrder.Rate = req.Profit - } - - if strings.ToUpper(req.Site) == "BUY" { - profitOrder.Site = "SELL" - } else { - profitOrder.Site = "BUY" - } - profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) - profitOrder.Pid = AddOrder.Id - profitOrder.OrderType = 1 - profitOrder.Status = 0 - profitOrder.MainId = AddOrder.Id - - if req.ProfitNumRatio.Cmp(decimal.Zero) > 0 { - numPercent := utility.SafeDiv(req.ProfitNumRatio, decimal.NewFromInt(100)) - profitOrder.Num = utility.StrToDecimal(profitOrder.Num).Mul(numPercent).Truncate(int32(tradeSet.AmountDigit)).String() - } - tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&profitOrder) - - //不全部止盈的时候 - if req.ProfitNumRatio.Cmp(decimal.Zero) > 0 && req.ProfitNumRatio.Cmp(decimal.NewFromInt(100)) < 0 { - reminQuantity := utility.StrToDecimal(AddOrder.Num).Sub(utility.StrToDecimal(profitOrder.Num)) - - childrens, err := makeTpOrder(&profitOrder, reminQuantity, req.ProfitTpTpPriceRatio, req.ProfitTpSlPriceRatio, &tradeSet) - - if err != nil { - logger.Error("生成止盈后子订单失败") - return err - } - - tx.Create(&childrens) - } + if err != nil { + logger.Errorf("构建主单止盈止损失败,err:%v", err) } + if len(childOrders) > 0 { + tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&childOrders) + + for _, childOrder := range childOrders { + //不全部止盈的时候 + if childOrder.OrderType == 1 && req.ProfitNumRatio.Cmp(decimal.Zero) > 0 && req.ProfitNumRatio.Cmp(decimal.NewFromInt(100)) < 0 { + reminQuantity := utility.StrToDecimal(AddOrder.Num).Sub(utility.StrToDecimal(childOrder.Num)) + + childrens, err := makeTpOrder(&childOrder, reminQuantity, req.ProfitTpTpPriceRatio, req.ProfitTpSlPriceRatio, &tradeSet) + + if err != nil { + logger.Error("生成止盈后子订单失败") + return err + } + + tx.Create(&childrens) + } + } + } + //减仓单 if req.ReducePriceRatio.Cmp(decimal.Zero) > 0 { if req.PricePattern == "mixture" { if req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 { return errors.New("检查价格不能小于等于0") } - stopOrder.Price = req.ReducePriceRatio.Truncate(int32(tradeSet.PriceDigit)).String() - stopOrder.Rate = "0" + reduceOrder.Price = req.ReducePriceRatio.Truncate(int32(tradeSet.PriceDigit)).String() + reduceOrder.Rate = "0" } else { if strings.ToUpper(req.Site) == "BUY" { // stopOrder.Site = "SELL" - stopOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + reduceOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Sub(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() } else { // stopOrder.Site = "BUY" - stopOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Add(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() + reduceOrder.Price = utility.StrToDecimal(AddOrder.Price).Mul(decimal.NewFromInt(1).Add(utility.SafeDiv(req.ReducePriceRatio, decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String() } - stopOrder.Rate = req.ReducePriceRatio.String() + reduceOrder.Rate = req.ReducePriceRatio.String() } if strings.ToUpper(req.Site) == "BUY" { - stopOrder.Site = "SELL" + reduceOrder.Site = "SELL" } else { - stopOrder.Site = "BUY" + reduceOrder.Site = "BUY" } - stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) - stopOrder.Pid = AddOrder.Id - stopOrder.MainId = AddOrder.Id - stopOrder.OrderType = 4 - stopOrder.Status = 0 - stopOrder.BuyPrice = "0" + reduceOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) + reduceOrder.Pid = AddOrder.Id + reduceOrder.MainId = AddOrder.Id + reduceOrder.OrderType = 4 + reduceOrder.Status = 0 + reduceOrder.BuyPrice = "0" stopNum := utility.StrToDecimal(AddOrder.Num).Mul(req.ReduceNumRatio.Div(decimal.NewFromInt(100)).Truncate(4)) - stopOrder.Num = stopNum.Truncate(int32(tradeSet.AmountDigit)).String() - stopOrder.ExpireTime = time.Now().AddDate(10, 0, 0) + reduceOrder.Num = stopNum.Truncate(int32(tradeSet.AmountDigit)).String() + reduceOrder.ExpireTime = time.Now().AddDate(10, 0, 0) - tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&stopOrder) - preOrderExts[1].OrderId = stopOrder.Id + tx.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&reduceOrder) + preOrderExts[1].OrderId = reduceOrder.Id if req.ReduceNumRatio.Cmp(decimal.Zero) > 0 && req.ReduceNumRatio.Cmp(decimal.NewFromInt(100)) < 0 { - if newOrders, err := makeReduceTakeAndStoploss(&stopOrder, defultExt2, tradeSet, false); err != nil { + if newOrders, err := makeReduceTakeAndStoploss(&reduceOrder, defultExt2, tradeSet, false); err != nil { logger.Errorf("主单减仓生成止盈、减仓失败 err:%v", err) return err } else if len(newOrders) > 0 { @@ -784,9 +769,13 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int } } } + + if reduceStrategy.Id > 0 { + reduceOrderStrategys = append(reduceOrderStrategys, initOrderReduceStrategy(reduceStrategy, reduceOrder.Id)) + } } - //添加止盈单 + //添加后续节点 for index, v := range preOrderExts { preOrderExts[index].MainOrderId = AddOrder.Id if index < 2 { @@ -825,7 +814,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int } for index := range orders { - //减仓单且 减仓比例大于0 小于100 就冲下止盈止损 + //止盈后止盈止损 if orders[index].OrderType == 1 && v.TakeProfitRatio.Cmp(decimal.Zero) > 0 && v.TakeProfitRatio.Cmp(decimal.NewFromInt(100)) < 0 { reduceChildOrders, err := makeReduceTakeAndStoploss(&(orders[index]), v, tradeSet, true) @@ -844,19 +833,113 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int } } } + + //减仓单绑定减仓未成交策略 + if newOrder.OrderType == 4 && reduceStrategy.Id > 0 { + reduceOrderStrategys = append(reduceOrderStrategys, initOrderReduceStrategy(reduceStrategy, newOrder.Id)) + } + } + + if len(reduceOrderStrategys) > 0 { + if err := tx.Create(reduceOrderStrategys).Error; err != nil { + logger.Error("保存减仓未成交策略失败") + return err + } } err = tx.Model(&models.LinePreOrderExt{}).Create(&preOrderExts).Error if err != nil { return err } - return nil + + //保存下单缓存 + return saveOrderCache(req, AddOrder, linestrategyTemplate) }) } return nil } +// 构建减仓单减仓策略 +func initOrderReduceStrategy(reduceStrategy models.LineReduceStrategy, orderId int) models.LineOrderReduceStrategy { + result := models.LineOrderReduceStrategy{} + strategys := make([]dto.LineReduceStrategyItemResp, 0) + result.OrderId = orderId + result.ReduceStrategyId = reduceStrategy.Id + result.Actived = 2 + + for _, item := range reduceStrategy.Items { + strategys = append(strategys, dto.LineReduceStrategyItemResp{ + LossPercent: item.LossPercent, + OrderType: item.OrderType, + Actived: 2, + }) + } + + str, _ := sonic.MarshalString(strategys) + + if str != "" { + result.ItemContent = str + } + + return result +} + +// 保存下单缓存 +func saveOrderCache(req *dto.LineAddPreOrderReq, AddOrder models.LinePreOrder, linestrategyTemplate models.LineStrategyTemplate) error { + var preKey string + var marshal []byte + + switch { + //策略下单 + case req.StrategyTemplateType == 1 && req.StrategyTemplateId > 0: + + list := dto.StrategyOrderRedisList{ + Id: AddOrder.Id, + OrderSn: AddOrder.OrderSn, + ApiId: AddOrder.ApiId, + Symbol: AddOrder.Symbol, + Price: AddOrder.Price, + Site: AddOrder.Site, + QuoteSymbol: AddOrder.QuoteSymbol, + } + list.Direction = linestrategyTemplate.Direction + list.Percentag = linestrategyTemplate.Percentag + list.CompareType = linestrategyTemplate.CompareType + list.TimeSlotStart = linestrategyTemplate.TimeSlotStart + list.TimeSlotEnd = linestrategyTemplate.TimeSlotEnd + + marshal, _ = sonic.Marshal(&list) + if AddOrder.SymbolType == global.SYMBOL_SPOT { + preKey = fmt.Sprintf(rediskey.StrategySpotOrderList, AddOrder.ExchangeType) + } else { + preKey = fmt.Sprintf(rediskey.StrategyFutOrderList, AddOrder.ExchangeType) + } + + //直接下单 + default: + list := dto.PreOrderRedisList{ + Id: AddOrder.Id, + Symbol: AddOrder.Symbol, + Price: AddOrder.Price, + Site: AddOrder.Site, + ApiId: AddOrder.ApiId, + OrderSn: AddOrder.OrderSn, + QuoteSymbol: AddOrder.QuoteSymbol, + } + marshal, _ = sonic.Marshal(&list) + if AddOrder.SymbolType == global.SYMBOL_SPOT { + preKey = fmt.Sprintf(rediskey.PreSpotOrderList, AddOrder.ExchangeType) + } else { + preKey = fmt.Sprintf(rediskey.PreFutOrderList, AddOrder.ExchangeType) + } + } + + err := helper.DefaultRedis.LPushList(preKey, string(marshal)) + + return err +} + // 生成加仓单 func createPreAddPosition(preOrder *models.LinePreOrder, v models.LinePreOrderExt, tradeSet models2.TradeSet) models.LinePreOrder { data := models.LinePreOrder{} @@ -1134,31 +1217,6 @@ func (e *LinePreOrder) CheckRepeatOrder(symbolType int, apiUserId, site, baseCoi // AddBatchPreOrder 批量添加 func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p *actions.DataPermission, errs *[]error) error { - // apiIds := []int{} - // apiUserIds := strings.Split(batchReq.ApiUserId, ",") - // apiUserService := LineApiUser{Service: e.Service} - - // for _, v := range apiUserIds { - // apiId, _ := strconv.Atoi(v) - // apiIds = append(apiIds, apiId) - // } - - // activeIds, err := apiUserService.GetActiveApis(apiIds) - - // if err != nil { - // return err - // } - - // if len(activeIds) == 0 { - // return errors.New("没有可用的api") - // } - - // for _, v := range apiIds { - // if !utility.ContainsInt(activeIds, v) { - // *errs = append(*errs, errors.New("api_id:"+strconv.Itoa(v)+"不可用")) - // } - // } - if batchReq.SaveTemplate == "2" || batchReq.SaveTemplate == "1" { //2 = 只保存模板 1= 保存模板并下单 var templateLog dto.LineBatchAddPreOrderReq copier.Copy(&templateLog, batchReq) @@ -1551,6 +1609,8 @@ func (e *LinePreOrder) ClearAll() error { "_PreFutOrderList_", "spot_reduce_list", "futures_reduce_list", + "spot_reduce_strategy_list", + "fut_reduce_strategy_list", } err = helper.DefaultRedis.DeleteKeysByPrefix(prefixs...) if err != nil { @@ -1558,9 +1618,10 @@ func (e *LinePreOrder) ClearAll() error { return err } - e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order") //订单表 - e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_status") //订单拓展状态 - e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_ext") //订单拓展配置 + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order") //订单表 + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_status") //订单拓展状态 + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_ext") //订单拓展配置 + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_order_reduce_strategy") //订单减仓策略配置 return err } @@ -2042,9 +2103,9 @@ func (e *LinePreOrder) GenerateOrder(req *dto.LineAddPreOrderReq) error { var tickerPrice decimal.Decimal if req.SymbolType == 1 { - tradeSet, _ = binanceservice.GetTradeSet(req.Symbol, 0) + tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, req.Symbol, 0) } else { - tradeSet, _ = binanceservice.GetTradeSet(req.Symbol, 1) + tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, req.Symbol, 1) } if tradeSet.LastPrice == "" { @@ -2161,164 +2222,3 @@ func (e *LinePreOrder) CalculateBreakEvenRatio(req *dto.CalculateBreakEevenRatio return nil } - -// // 手动加仓 -// func (e *LinePreOrder) AddPosition(req *dto.LinePreOrderAddPositionReq) error { -// lastPositionOrder := models.LinePreOrder{} -// var tradeSet models2.TradeSet - -// if req.OrderType == 1 { -// tradeSet, _ = binanceservice.GetTradeSet(req.Symbol, 0) -// } else if req.OrderType == 2 { -// tradeSet, _ = binanceservice.GetTradeSet(req.Symbol, 1) -// } else { -// return fmt.Errorf("交易对:%s 订单类型错误", req.Symbol) -// } - -// if tradeSet.LastPrice == "" { -// return fmt.Errorf("交易对:%s 交易对配置错误", req.Symbol) -// } - -// if err := e.Orm.Model(&lastPositionOrder).Where("symbol =? AND status = 6 AND site =? AND api_id =? AND symbol_type =? AND exchange_type=?", -// req.Symbol, req.Site, req.ApiUserId, req.OrderType, req.ExchangeType).Error; err != nil { -// logger.Errorf("交易对:%s查询已开仓订单失败", req.Symbol) -// return fmt.Errorf("交易对:%s 没有已开仓的订单", req.Symbol) -// } - -// ext := models.LinePreOrderExt{ -// MainOrderId: lastPositionOrder.Id, -// TakeProfitRatio: req.Profit, -// ReducePriceRatio: req.ReducePriceRatio, -// ReduceNumRatio: req.ReduceNumRatio, -// ReduceTakeProfitRatio: req.ReduceTakeProfitRatio, -// ReduceStopLossRatio: req.ReduceStopLossRatio, -// AddPositionOrderType: req.AddPositionOrderType, -// AddPositionType: 2, -// AddPositionVal: req.BuyPrice, -// } - -// addPosition := models.LinePreOrder{ -// SignPrice: tradeSet.LastPrice, -// Pid: lastPositionOrder.Id, -// MainId: lastPositionOrder.Id, -// Symbol: req.Symbol, -// QuoteSymbol: tradeSet.Currency, -// SignPriceU: utility.StrToDecimal(tradeSet.LastPrice), -// ApiId: req.ApiUserId, -// Site: req.Site, -// ExchangeType: req.ExchangeType, -// OrderType: 0, -// OrderCategory: 3, -// BuyPrice: req.BuyPrice.String(), -// Status: 0, -// SymbolType: req.OrderType, -// MainOrderType: req.AddPositionOrderType, -// ExpireTime: time.Now().Add(4), -// OrderSn: utility.Int64ToString(snowflakehelper.GetOrderId()), -// } - -// tickerPrice := utility.StrToDecimal(tradeSet.LastPrice) -// if req.PricePattern == "percentage" { -// addPosition.Rate = req.Price.String() -// priceRate := req.Price.Div(decimal.NewFromInt(100)) //下单价除100 =0.1 - -// if strings.ToUpper(req.Site) == "BUY" { //购买方向 -// //实际下单价格 -// truncate := tickerPrice.Mul(decimal.NewFromInt(1).Sub(priceRate)).Truncate(int32(tradeSet.PriceDigit)) -// addPosition.Price = truncate.String() -// } else { -// truncate := tickerPrice.Mul(decimal.NewFromInt(1).Add(priceRate)).Truncate(int32(tradeSet.PriceDigit)) -// addPosition.Price = truncate.String() -// } - -// } else { //实际价格下单 -// addPosition.Price = req.Price.Truncate(int32(tradeSet.PriceDigit)).String() -// addPosition.SignPriceType = req.PricePattern -// addPosition.Rate = "0" -// } -// if tradeSet.Currency != "USDT" { //不是U本位 -// //获取币本位兑换u的价格 -// ticker2 := models2.TradeSet{} -// tickerVal, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_SPOT, req.ExchangeType, strings.ToUpper(tradeSet.Coin+"USDT"))) - -// if tickerVal == "" { -// logger.Error("查询行情失败") -// return fmt.Errorf("交易对:%s 获取u本位行情失败", req.Symbol) -// } - -// err := sonic.Unmarshal([]byte(tickerVal), &ticker2) - -// if ticker2.LastPrice == "" { -// logger.Errorf("查询行情失败 %s err:%v", strings.ToUpper(tradeSet.Coin+"USDT"), err) -// return fmt.Errorf("交易对:%s 获取u本位行情 反序列化失败", req.Symbol) -// } -// //LTCBTC --> LTCUSDT -// uTickerPrice := utility.StrToDecimal(ticker2.LastPrice) //94069 -// //换算成U -// //div := decimal.NewFromInt(1).Div(uTickerPrice) //0.0000106365 -// //在换算成对应交易对对应的价值 -// //LTCBTC --> LTCUSDT == LTCUSDT -- 100.502 -// div := tickerPrice.Div(decimal.NewFromInt(1).Div(uTickerPrice)) -// //计算下单数量 -// addPosition.Num = req.BuyPrice.Div(div).Truncate(int32(tradeSet.AmountDigit)).String() -// } else { -// fromString, _ := decimal.NewFromString(addPosition.Price) -// addPosition.Num = req.BuyPrice.Div(fromString).Truncate(int32(tradeSet.AmountDigit)).String() -// } -// //事务保存 -// err := e.Orm.Transaction(func(tx *gorm.DB) error { -// //添加加仓单 -// if err := tx.Create(&addPosition).Error; err != nil { -// return err -// } - -// //止盈、减仓 -// orders, err := makeFuturesTakeAndReduce(&addPosition, ext, tradeSet) - -// if err != nil { -// logger.Error("构造加仓单止盈、减仓失败") -// return err -// } - -// if err := e.Orm.Create(&orders).Error; err != nil { -// logger.Error("保存加仓单止盈、减仓失败") -// return err -// } - -// //处理减仓单 -// for index := range orders { -// //减仓单且 减仓比例大于0 小于100 就冲下止盈止损 -// if orders[index].OrderType == 4 && ext.ReduceNumRatio.Cmp(decimal.Zero) > 0 && ext.ReduceNumRatio.Cmp(decimal.NewFromInt(100)) < 0 { -// reduceChildOrders, err := makeReduceTakeAndStoploss(&(orders[index]), ext, tradeSet) - -// if err != nil { -// logger.Error("生产加仓单止盈、减仓失败") -// return err -// } - -// if len(reduceChildOrders) == 0 { -// continue -// } - -// if err := e.Orm.Create(&reduceChildOrders).Error; err != nil { -// logger.Error("报错减仓后止盈止损失败") -// return err -// } -// } -// } - -// ext.OrderId = addPosition.Id -// if err := tx.Create(&ext).Error; err != nil { -// return err -// } - -// return nil -// }) - -// if err != nil { -// logger.Errorf("交易对:%s 添加加仓订单失败", req.Symbol) -// return fmt.Errorf("交易对:%s 添加加仓订单失败", req.Symbol) -// } - -// return nil -// } diff --git a/app/admin/service/line_pre_order_strategy.go b/app/admin/service/line_pre_order_strategy.go new file mode 100644 index 0000000..bb27ba5 --- /dev/null +++ b/app/admin/service/line_pre_order_strategy.go @@ -0,0 +1 @@ +package service diff --git a/app/admin/service/line_reduce_strategy.go b/app/admin/service/line_reduce_strategy.go new file mode 100644 index 0000000..ae3321b --- /dev/null +++ b/app/admin/service/line_reduce_strategy.go @@ -0,0 +1,178 @@ +package service + +import ( + "errors" + "fmt" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + "go-admin/common/const/rediskey" + cDto "go-admin/common/dto" + "go-admin/common/helper" +) + +type LineReduceStrategy struct { + service.Service +} + +// GetPage 获取LineReduceStrategy列表 +func (e *LineReduceStrategy) GetPage(c *dto.LineReduceStrategyGetPageReq, p *actions.DataPermission, list *[]models.LineReduceStrategy, count *int64) error { + var err error + var data models.LineReduceStrategy + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineReduceStrategyService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineReduceStrategy对象 +func (e *LineReduceStrategy) Get(d *dto.LineReduceStrategyGetReq, p *actions.DataPermission, model *models.LineReduceStrategy) error { + var data models.LineReduceStrategy + + err := e.Orm.Model(&data). + Preload("Items"). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineReduceStrategy error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// 根据id获取对象 +func (e *LineReduceStrategy) GetById(id int) (models.LineReduceStrategy, error) { + result := models.LineReduceStrategy{} + key := fmt.Sprintf(rediskey.ReduceStrategy, id) + str, _ := helper.DefaultRedis.GetString(key) + + if str != "" { + sonic.Unmarshal([]byte(str), &result) + } + + if result.Id == 0 { + if err := e.Orm.Model(&models.LineReduceStrategy{}).First(&result, id).Error; err != nil { + return result, err + } + } + + return result, nil +} + +// Insert 创建LineReduceStrategy对象 +func (e *LineReduceStrategy) Insert(c *dto.LineReduceStrategyInsertReq) error { + var err error + var data models.LineReduceStrategy + var count int64 + + e.Orm.Model(&models.LineReduceStrategy{}).Where("name = ?", c.Name).Count(&count) + if count > 0 { + err = errors.New("策略名称重复") + return err + } + + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineReduceStrategyService Insert error:%s \r\n", err) + return err + } + + e.saveCache(data) + + return nil +} + +// Update 修改LineReduceStrategy对象 +func (e *LineReduceStrategy) Update(c *dto.LineReduceStrategyUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineReduceStrategy{} + var count int64 + + e.Orm.Model(&models.LineReduceStrategy{}).Where("name = ? AND id !=?", c.Name, c.GetId()).Count(&count) + if count > 0 { + err = errors.New("策略名称重复") + return err + } + + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + err = e.Orm.Transaction(func(tx *gorm.DB) error { + if errr := tx.Delete(&models.LineReduceStrategyItem{}, "reduce_strategy_id =?", c.GetId()).Error; errr != nil { + return errr + } + + db := tx.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineReduceStrategyService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + return nil + }) + + e.saveCache(data) + + return err +} + +func (e *LineReduceStrategy) saveCache(data models.LineReduceStrategy) { + str, _ := sonic.MarshalString(data) + + if str != "" { + key := fmt.Sprintf(rediskey.ReduceStrategy, data.Id) + + if errr := helper.DefaultRedis.SetString(key, str); errr != nil { + e.Log.Errorf("LineReduceStrategyService SetString error:%s \r\n", errr) + } + } +} + +// Remove 删除LineReduceStrategy +func (e *LineReduceStrategy) Remove(d *dto.LineReduceStrategyDeleteReq, p *actions.DataPermission) error { + var data models.LineReduceStrategy + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineReduceStrategy error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + + key := fmt.Sprintf(rediskey.ReduceStrategy, data.Id) + helper.DefaultRedis.DeleteString(key) + return nil +} diff --git a/app/admin/service/line_reduce_strategy_item.go b/app/admin/service/line_reduce_strategy_item.go new file mode 100644 index 0000000..e6c3fad --- /dev/null +++ b/app/admin/service/line_reduce_strategy_item.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineReduceStrategyItem struct { + service.Service +} + +// GetPage 获取LineReduceStrategyItem列表 +func (e *LineReduceStrategyItem) GetPage(c *dto.LineReduceStrategyItemGetPageReq, p *actions.DataPermission, list *[]models.LineReduceStrategyItem, count *int64) error { + var err error + var data models.LineReduceStrategyItem + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineReduceStrategyItemService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineReduceStrategyItem对象 +func (e *LineReduceStrategyItem) Get(d *dto.LineReduceStrategyItemGetReq, p *actions.DataPermission, model *models.LineReduceStrategyItem) error { + var data models.LineReduceStrategyItem + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineReduceStrategyItem error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineReduceStrategyItem对象 +func (e *LineReduceStrategyItem) Insert(c *dto.LineReduceStrategyItemInsertReq) error { + var err error + var data models.LineReduceStrategyItem + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineReduceStrategyItemService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineReduceStrategyItem对象 +func (e *LineReduceStrategyItem) Update(c *dto.LineReduceStrategyItemUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineReduceStrategyItem{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineReduceStrategyItemService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineReduceStrategyItem +func (e *LineReduceStrategyItem) Remove(d *dto.LineReduceStrategyItemDeleteReq, p *actions.DataPermission) error { + var data models.LineReduceStrategyItem + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineReduceStrategyItem error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_strategy_template.go b/app/admin/service/line_strategy_template.go new file mode 100644 index 0000000..c9175fc --- /dev/null +++ b/app/admin/service/line_strategy_template.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineStrategyTemplate struct { + service.Service +} + +// GetPage 获取LineStrategyTemplate列表 +func (e *LineStrategyTemplate) GetPage(c *dto.LineStrategyTemplateGetPageReq, p *actions.DataPermission, list *[]models.LineStrategyTemplate, count *int64) error { + var err error + var data models.LineStrategyTemplate + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineStrategyTemplateService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineStrategyTemplate对象 +func (e *LineStrategyTemplate) Get(d *dto.LineStrategyTemplateGetReq, p *actions.DataPermission, model *models.LineStrategyTemplate) error { + var data models.LineStrategyTemplate + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineStrategyTemplate error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineStrategyTemplate对象 +func (e *LineStrategyTemplate) Insert(c *dto.LineStrategyTemplateInsertReq) error { + var err error + var data models.LineStrategyTemplate + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineStrategyTemplateService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineStrategyTemplate对象 +func (e *LineStrategyTemplate) Update(c *dto.LineStrategyTemplateUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineStrategyTemplate{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineStrategyTemplateService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineStrategyTemplate +func (e *LineStrategyTemplate) Remove(d *dto.LineStrategyTemplateDeleteReq, p *actions.DataPermission) error { + var data models.LineStrategyTemplate + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineStrategyTemplate error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_symbol_price.go b/app/admin/service/line_symbol_price.go new file mode 100644 index 0000000..5ea8d47 --- /dev/null +++ b/app/admin/service/line_symbol_price.go @@ -0,0 +1,147 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + "go-admin/common/const/rediskey" + cDto "go-admin/common/dto" + "go-admin/common/helper" +) + +type LineSymbolPrice struct { + service.Service +} + +// GetPage 获取LineSymbolPrice列表 +func (e *LineSymbolPrice) GetPage(c *dto.LineSymbolPriceGetPageReq, p *actions.DataPermission, list *[]models.LineSymbolPrice, count *int64) error { + var err error + var data models.LineSymbolPrice + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineSymbolPriceService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineSymbolPrice对象 +func (e *LineSymbolPrice) Get(d *dto.LineSymbolPriceGetReq, p *actions.DataPermission, model *models.LineSymbolPrice) error { + var data models.LineSymbolPrice + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineSymbolPrice error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineSymbolPrice对象 +func (e *LineSymbolPrice) Insert(c *dto.LineSymbolPriceInsertReq) error { + var err error + var data models.LineSymbolPrice + c.Generate(&data) + var count int64 + + e.Orm.Model(&models.LineSymbolPrice{}).Where("symbol = ?", c.Symbol).Count(&count) + + if count > 0 { + return errors.New("交易对已存在,请不要重复添加") + } + + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineSymbolPriceService Insert error:%s \r\n", err) + return err + } + + e.cacheList() + + return nil +} + +func (e *LineSymbolPrice) cacheList() { + symbols := make([]string, 0) + if err := e.Orm.Model(&models.LineSymbolPrice{}).Where("status =1").Pluck("symbol", &symbols).Error; err != nil { + e.Log.Errorf("获取可用交易对失败,err:", err) + } + + if len(symbols) > 0 { + if err := helper.DefaultRedis.SetListCache(rediskey.CacheSymbolLastPrice, 0, symbols...); err != nil { + e.Log.Errorf("设置可缓存价格交易对失败") + } + } +} + +// Update 修改LineSymbolPrice对象 +func (e *LineSymbolPrice) Update(c *dto.LineSymbolPriceUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineSymbolPrice{} + var count int64 + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + e.Orm.Model(&models.LineSymbolPrice{}).Where("symbol = ? and id !=?", c.Symbol, c.GetId()).Count(&count) + if count > 0 { + return errors.New("交易对已存在,请不要重复添加") + } + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineSymbolPriceService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + e.cacheList() + return nil +} + +// Remove 删除LineSymbolPrice +func (e *LineSymbolPrice) Remove(d *dto.LineSymbolPriceDeleteReq, p *actions.DataPermission) error { + var data models.LineSymbolPrice + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineSymbolPrice error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + e.cacheList() + return nil +} + +func (e *LineSymbolPrice) InitCache() { + e.cacheList() +} diff --git a/app/jobs/jobs.go b/app/jobs/jobs.go index 685eb24..9b2c257 100644 --- a/app/jobs/jobs.go +++ b/app/jobs/jobs.go @@ -15,6 +15,7 @@ import ( "go-admin/pkg/utility" "go-admin/pkg/utility/snowflakehelper" "go-admin/services/binanceservice" + "go-admin/services/cacheservice" "go-admin/services/fileservice" "io" "net/http" @@ -241,7 +242,7 @@ func (t LimitOrderTimeoutDuration) ReSpotOrderPlace(db *gorm.DB, order models.Li } else { var remainingQuantity decimal.Decimal spotOrder, err := spotApi.GetOrderByOrderSnLoop(order.Symbol, order.OrderSn, apiUserinfo, 4) - tradeSet, _ := binanceservice.GetTradeSet(order.Symbol, 0) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 0) if err == nil { origQty := utility.StrToDecimal(spotOrder.OrigQty) @@ -359,7 +360,7 @@ func (t LimitOrderTimeoutDuration) ReFutOrderPlace(db *gorm.DB, order models.Lin } else { var remainingQuantity decimal.Decimal spotOrder, err := futApi.GetOrderByOrderSnLoop(order.Symbol, order.OrderSn, apiUserinfo, 4) - tradeSet, _ := binanceservice.GetTradeSet(order.Symbol, 1) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) if err == nil { origQty := utility.StrToDecimal(spotOrder.OrigQty) diff --git a/app/jobs/order_job.go b/app/jobs/order_job.go new file mode 100644 index 0000000..674b3ab --- /dev/null +++ b/app/jobs/order_job.go @@ -0,0 +1,12 @@ +package jobs + +//波段交易策略任务 +type StrategyOrderJob struct { +} + +func (t StrategyOrderJob) Exec(arg interface{}) error { + // strategyOrderService:= + //todo 策略订单任务 + + return nil +} diff --git a/common/const/rediskey/redis_key.go b/common/const/rediskey/redis_key.go index 53f2ac0..70dbc31 100644 --- a/common/const/rediskey/redis_key.go +++ b/common/const/rediskey/redis_key.go @@ -25,6 +25,11 @@ const ( PreSpotOrderList = "_PreSpotOrderList_:%s" // 待触发的现货订单集合{交易所类型 exchange_type} PreFutOrderList = "_PreFutOrderList_:%s" // 待触发的订单集合 {交易所类型 exchange_type} + //策略现货订单集合 {交易所类型 exchange_type} + StrategySpotOrderList = "strategy_spot_order_list_%s" + //策略合约订单集合 {交易所类型 exchange_type} + StrategyFutOrderList = "strategy_fut_order_list_%s" + API_USER = "api_user:%v" // api用户 SystemSetting = "system_setting" //系统设置 ApiGroup = "api_group:%v" //api用户组 {id} @@ -41,6 +46,11 @@ const ( SpotTrigger = "spot_trigger_lock:%v_%s" //现货触发 {apiuserid|symbol} FutTrigger = "fut_trigger_lock:%v_%s" //合约触发 {apiuserid|symbol} + //波段现货触发{apiuserid|symbol} + StrategySpotTriggerLock = "strategy_spot_trigger_l:%v_%s" + //波段合约触发{apiuserid|symbol} + StrategyFutTriggerLock = "strategy_fut_trigger_l:%v_%s" + SpotCallBack = "spot_callback:%s" //现货回调 {ordersn} FutCallBack = "fut_callback:%s" //合约回调 {ordersn} @@ -61,6 +71,11 @@ const ( SpotPosition = "spot_position:%s:%v:%s_%s" //合约持仓 {exchangeType,apiuserid,symbol,side} FuturePosition = "future_position:%s:%v:%s_%s" + + //现货减仓单减仓策略 {exchangeType} + SpotOrderReduceStrategyList = "spot_reduce_strategy_list:%s" + //合约减仓单减仓策略 {exchangeType} + FutOrderReduceStrategyList = "fut_reduce_strategy_list:%s" //需要清理键值---------END----------------- //定时取消限价并下市价锁 @@ -79,6 +94,12 @@ const ( SpotTickerLastPrice = "spot_ticker_last_price:%s:%s" //合约最后成交价 sort set {exchangeType,symbol} FutureTickerLastPrice = "fut_ticker_last_price:%s:%s" + + //允许缓存交易对价格的交易对 list + CacheSymbolLastPrice = "cache_symbol_price" + + //减仓策略缓存 {id} + ReduceStrategy = "reduce_stragy:%d" ) // 用户下单 diff --git a/common/helper/redis_helper.go b/common/helper/redis_helper.go index 368cbfa..9e551a9 100644 --- a/common/helper/redis_helper.go +++ b/common/helper/redis_helper.go @@ -697,6 +697,22 @@ func (e *RedisHelper) GetRevRangeScoresSortSet(key string) ([]redis.Z, error) { return e.client.ZRevRangeWithScores(e.ctx, key, 0, -1).Result() } +// 获取最后一条数据 +func (e *RedisHelper) GetLastSortSet(key string) ([]redis.Z, error) { + // 获取最后一个元素及其分数 + results, err := e.client.ZRevRangeWithScores(e.ctx, key, 0, 0).Result() + if err != nil { + return nil, fmt.Errorf("failed to get last member: %w", err) + } + + // 如果没有数据,返回空 + if len(results) == 0 { + return []redis.Z{}, nil + } + + return results, nil +} + // 获取指定区间数据 func (e *RedisHelper) GetSortSetMembers(key string, start, stop int64) ([]string, error) { return e.client.ZRange(e.ctx, key, start, stop).Result() diff --git a/config/serverinit/business_init.go b/config/serverinit/business_init.go index 61e0608..c48c5fe 100644 --- a/config/serverinit/business_init.go +++ b/config/serverinit/business_init.go @@ -31,9 +31,14 @@ func BusinessInit(db *gorm.DB) { //初始化参数配置 cacheservice.InitConfigCache(db) + //初始化可缓存价格交易对 + symbolPriceService := service.LineSymbolPrice{} + symbolPriceService.Orm = db + symbolPriceService.Log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{}) + symbolPriceService.InitCache() //初始化订单配置 - binanceservice.ResetSystemSetting(db) + cacheservice.ResetSystemSetting(db) lineApiUser := service.LineApiUser{} lineApiUser.Orm = db if err := lineApiUser.InitCache(); err != nil { diff --git a/services/binanceservice/commonservice.go b/services/binanceservice/commonservice.go index 8a47ac0..fa6fca1 100644 --- a/services/binanceservice/commonservice.go +++ b/services/binanceservice/commonservice.go @@ -1,14 +1,12 @@ package binanceservice import ( - "errors" "fmt" DbModels "go-admin/app/admin/models" "go-admin/app/admin/service/dto" "go-admin/common/const/rediskey" "go-admin/common/global" "go-admin/common/helper" - "go-admin/models" "go-admin/models/positiondto" "go-admin/pkg/utility" "go-admin/services/positionservice" @@ -27,32 +25,6 @@ type AddPosition struct { Db *gorm.DB } -// 获取缓存交易对 -// symbolType 0-现货 1-合约 -func GetTradeSet(symbol string, symbolType int) (models.TradeSet, error) { - result := models.TradeSet{} - val := "" - - switch symbolType { - case 0: - key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol) - val, _ = helper.DefaultRedis.GetString(key) - case 1: - key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol) - val, _ = helper.DefaultRedis.GetString(key) - } - - if val != "" { - if err := sonic.Unmarshal([]byte(val), &result); err != nil { - return result, err - } - } else { - return result, errors.New("未找到交易对信息") - } - - return result, nil -} - func GetRisk(futApi *FutRestApi, apiInfo *DbModels.LineApiUser, symbol string) []PositionRisk { for x := 0; x < 5; x++ { risks, _ := futApi.GetPositionV3(apiInfo, symbol) diff --git a/services/binanceservice/futuresjudgeservice.go b/services/binanceservice/futuresjudgeservice.go index 1d53614..9437d11 100644 --- a/services/binanceservice/futuresjudgeservice.go +++ b/services/binanceservice/futuresjudgeservice.go @@ -10,6 +10,7 @@ import ( "go-admin/common/global" "go-admin/common/helper" "go-admin/models" + "go-admin/services/cacheservice" "strings" "time" @@ -151,19 +152,37 @@ func JudgeFuturesReduce(trade models.TradeSet) { key := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE) reduceVal, _ := helper.DefaultRedis.GetAllList(key) - if len(reduceVal) == 0 { - return - } - db := GetDBConnection() futApi := FutRestApi{} - setting, err := GetSystemSetting(db) + setting, err := cacheservice.GetSystemSetting(db) if err != nil { log.Error("获取系统设置失败") return } + tradePrice, _ := decimal.NewFromString(trade.LastPrice) + //减仓单减仓策略 + orderReduceVal, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE)) + reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} + + for _, item := range orderReduceVal { + sonic.Unmarshal([]byte(item), &reduceOrderStrategy) + + for _, item2 := range reduceOrderStrategy.Items { + if reduceOrderStrategy.Symbol == trade.Coin+trade.Currency { + //买入 + if item2.TriggerPrice.Cmp(decimal.Zero) > 0 && + tradePrice.Cmp(decimal.Zero) > 0 && + ((strings.ToUpper(reduceOrderStrategy.Side) == "SELL" && item2.TriggerPrice.Cmp(tradePrice) >= 0) || + (strings.ToUpper(reduceOrderStrategy.Side) == "BUY" && item2.TriggerPrice.Cmp(tradePrice) <= 0)) { + //todo 生成订单并触发 + // SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item) + } + } + } + } + for _, item := range reduceVal { reduceOrder := ReduceListItem{} if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { @@ -173,7 +192,6 @@ func JudgeFuturesReduce(trade models.TradeSet) { if reduceOrder.Symbol == trade.Coin+trade.Currency { orderPrice := reduceOrder.Price - tradePrice, _ := decimal.NewFromString(trade.LastPrice) //买入 if orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 && @@ -188,7 +206,7 @@ func JudgeFuturesReduce(trade models.TradeSet) { // 触发合约减仓 func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string) { - tradeSet, _ := GetTradeSet(reduceOrder.Symbol, 1) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 1) if tradeSet.LastPrice == "" { return @@ -239,17 +257,6 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes positionSide = "LONG" } - // 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) @@ -267,6 +274,9 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes Where("id = ? AND status =0", reduceOrder.Id).Updates(map[string]interface{}{"status": 1}).Error; err != nil { log.Errorf("合约减仓更新状态失败 id:%s err:%v", reduceOrder.Id, err) } + + //处理减仓单减仓策略 + CacheOrderStrategyAndReCreate(db, reduceOrder, 2, tradeSet, setting) } if _, err := helper.DefaultRedis.LRem(key, item); err != nil { @@ -322,8 +332,8 @@ func FutAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, futApi } else if ok { defer lock.Release() - setting, _ := GetSystemSetting(db) - tradeSet, _ := GetTradeSet(v.Symbol, 1) + setting, _ := cacheservice.GetSystemSetting(db) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, v.Symbol, 1) if tradeSet.LastPrice == "" { log.Errorf("合约加仓触发 查询交易对失败 交易对:%s ordersn:%s", v.Symbol, v.OrderSn) diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index 81c7d45..bd91528 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -13,6 +13,7 @@ import ( models2 "go-admin/models" "go-admin/models/positiondto" "go-admin/pkg/utility" + "go-admin/services/cacheservice" "strings" "time" @@ -126,13 +127,15 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { return } - tradeSet, err := GetTradeSet(preOrder.Symbol, 1) + tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, preOrder.Symbol, 1) if err != nil { logger.Errorf("handleReduceFilled 获取交易对设置失败,订单号:%s", preOrder.OrderSn) return } + //修改减仓单减仓策略状态 + ReduceCallBack(db, preOrder) orderExt := models.LinePreOrderExt{} db.Model(&orderExt).Where("order_id =?", preOrder.Id).First(&orderExt) // rate := utility.StringAsFloat(orderExt.AddPositionVal) @@ -473,7 +476,7 @@ func removeFutLossAndAddPosition(mainId int, orderSn string) { // 处理主单成交,处理止盈、止损、减仓订单 func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder, extOrderId int, first bool) { // 获取交易对配置和API信息 - tradeSet, err := GetTradeSet(preOrder.Symbol, 1) + tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, preOrder.Symbol, 1) mainId := preOrder.Id if err != nil || tradeSet.Coin == "" { logger.Errorf("获取交易对配置失败, 回调订单号:%s, 错误信息: %v", preOrder.OrderSn, err) @@ -742,7 +745,7 @@ func processFutReduceOrder(order DbModels.LinePreOrder, price, num decimal.Decim // 处理止盈订单 func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.LinePreOrder, num decimal.Decimal) { price, _ := decimal.NewFromString(order.Price) - tradeSet, _ := GetTradeSet(order.Symbol, 1) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) params := FutOrderPlace{ ApiId: order.ApiId, @@ -779,7 +782,7 @@ func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.Line // 处理止损订单 // order 止损单 func processFutStopLossOrder(db *gorm.DB, order models.LinePreOrder, price, num decimal.Decimal) error { - tradeSet, _ := GetTradeSet(order.Symbol, 1) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) params := FutOrderPlace{ ApiId: order.ApiId, diff --git a/services/binanceservice/reduce_order_strategy_service.go b/services/binanceservice/reduce_order_strategy_service.go new file mode 100644 index 0000000..516a8ea --- /dev/null +++ b/services/binanceservice/reduce_order_strategy_service.go @@ -0,0 +1,134 @@ +package binanceservice + +import ( + "fmt" + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + models2 "go-admin/models" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +// 缓存减仓节点和重新生成 +// reduceOrder:原始减仓单 +// reduceOrderStrategy:减仓策略 +func CacheOrderStrategyAndReCreate(db *gorm.DB, reduceOrder ReduceListItem, symbolType int, tradeSet models2.TradeSet, setting models.LineSystemSetting) error { + reduceOrderStrategy := models.LineReduceStrategy{} + var key string + + if symbolType == 1 { + key = fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE) + } else { + key = fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE) + } + + if err := db.Model(&reduceOrderStrategy).Where("order_id =?", reduceOrder.Id).Find(reduceOrderStrategy).Error; err != nil { + logger.Errorf("获取减仓策略失败,err:%v", err) + return err + } + + if reduceOrderStrategy.Id > 0 { + strategyCache := dto.LineOrderReduceStrategyResp{ + OrderId: reduceOrder.Id, + } + + for _, item := range reduceOrderStrategy.Items { + var rate decimal.Decimal + var triggerRate decimal.Decimal + + if reduceOrder.Side == "BUY" { + rate = (decimal.NewFromInt(100).Sub(item.LossPercent)).Div(decimal.NewFromInt(100)) + + if setting.ReduceEarlyTriggerPercent.Cmp(decimal.Zero) > 0 { + triggerRate = rate.Add(setting.ReduceEarlyTriggerPercent.Div(decimal.NewFromInt(100))) + } else { + triggerRate = rate + } + } else { + rate = (decimal.NewFromInt(100).Add(item.LossPercent)).Div(decimal.NewFromInt(100)) + + if setting.ReduceEarlyTriggerPercent.Cmp(decimal.Zero) > 0 { + triggerRate = rate.Sub(setting.ReduceEarlyTriggerPercent.Div(decimal.NewFromInt(100))) + } else { + triggerRate = rate + } + } + price := reduceOrder.Price.Mul(rate).Truncate(int32(tradeSet.PriceDigit)) + triggerPrice := reduceOrder.Price.Mul(triggerRate).Truncate(int32(tradeSet.PriceDigit)) + + strategyCache.Items = append(strategyCache.Items, dto.LineOrderReduceStrategyRespItem{ + LossPercent: item.LossPercent, + OrderType: item.OrderType, + Price: price, + TriggerPrice: triggerPrice, + }) + } + + str, _ := sonic.MarshalString(reduceOrderStrategy) + + if str != "" { + if err := helper.DefaultRedis.SetString(key, str); err != nil { + logger.Errorf("减仓单缓存减仓策略,err:%v", err) + } + } + } + + return nil +} + +// 根据订单id获取订单减仓策略 +func GetReduceStrategyById(db *gorm.DB, orderId int) (models.LineOrderReduceStrategy, error) { + result := models.LineOrderReduceStrategy{} + + if err := db.Model(result).Where("order_id =?", orderId).Select("id", "order_id").First(&result).Error; err != nil { + return result, err + } + + return result, nil +} + +// 减仓单成功回调 +func ReduceCallBack(db *gorm.DB, preOrder *models.LinePreOrder) error { + reduceOrderId := preOrder.Id + var key string + if preOrder.ReduceOrderId > 0 { + reduceOrderId = preOrder.ReduceOrderId + } + + switch preOrder.SymbolType { + case 1: + key = fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE) + case 2: + key = fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE) + default: + return fmt.Errorf("交易对类型错误") + } + + arrays, _ := helper.DefaultRedis.GetAllList(key) + cache := dto.LineOrderReduceStrategyResp{} + + for _, v := range arrays { + sonic.Unmarshal([]byte(v), &cache) + + if cache.OrderId == reduceOrderId { + if _, err := helper.DefaultRedis.LRem(key, v); err != nil { + logger.Errorf("移除减仓单减仓策略失败redis err:%v", err) + } + } + } + + orderReduceStrategy, _ := GetReduceStrategyById(db, reduceOrderId) + if orderReduceStrategy.Id > 0 { + if err := db.Model(&orderReduceStrategy).Update("actived", 1).Error; err != nil { + logger.Errorf("更新减仓单减仓策略状态失败 order_id:%d err:%v", orderReduceStrategy.OrderId, err) + } + } + + return nil +} diff --git a/services/binanceservice/spotjudgeservice.go b/services/binanceservice/spotjudgeservice.go index ff85199..9d6ef31 100644 --- a/services/binanceservice/spotjudgeservice.go +++ b/services/binanceservice/spotjudgeservice.go @@ -11,6 +11,7 @@ import ( "go-admin/common/helper" "go-admin/models" "go-admin/pkg/utility" + "go-admin/services/cacheservice" "strings" "time" @@ -141,14 +142,14 @@ func JudgeSpotStopLoss(trade models.TradeSet) { db := GetDBConnection() spotApi := SpotRestApi{} - setting, err := GetSystemSetting(db) + setting, err := cacheservice.GetSystemSetting(db) if err != nil { log.Error("获取系统设置失败") return } - tradeSet, err := GetTradeSet(trade.Coin+trade.Currency, 0) + tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, trade.Coin+trade.Currency, 0) if err != nil { log.Error("获取交易设置失败") @@ -256,21 +257,40 @@ func SpotStopLossTrigger(db *gorm.DB, stopOrder dto.StopLossRedisList, spotApi S // 判断是否触发现货减仓 func JudgeSpotReduce(trade models.TradeSet) { key := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE) - reduceVal, _ := helper.DefaultRedis.GetAllList(key) - - if len(reduceVal) == 0 { - return - } - db := GetDBConnection() spotApi := SpotRestApi{} - setting, err := GetSystemSetting(db) + setting, err := cacheservice.GetSystemSetting(db) if err != nil { log.Error("获取系统设置失败") return } + tradePrice, _ := decimal.NewFromString(trade.LastPrice) + //减仓单减仓策略 + orderReduceVal, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE)) + reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} + + for _, item := range orderReduceVal { + sonic.Unmarshal([]byte(item), &reduceOrderStrategy) + + for _, item2 := range reduceOrderStrategy.Items { + if reduceOrderStrategy.Symbol == trade.Coin+trade.Currency { + //买入 + if strings.ToUpper(reduceOrderStrategy.Side) == "SELL" && + item2.TriggerPrice.Cmp(tradePrice) >= 0 && + item2.TriggerPrice.Cmp(decimal.Zero) > 0 && + tradePrice.Cmp(decimal.Zero) > 0 { + //todo 生成订单并触发 + // SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item) + } + } + } + } + + //减仓单 + reduceVal, _ := helper.DefaultRedis.GetAllList(key) + for _, item := range reduceVal { reduceOrder := ReduceListItem{} if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { @@ -280,7 +300,6 @@ func JudgeSpotReduce(trade models.TradeSet) { if reduceOrder.Symbol == trade.Coin+trade.Currency { orderPrice := reduceOrder.Price - tradePrice, _ := decimal.NewFromString(trade.LastPrice) //买入 if strings.ToUpper(reduceOrder.Side) == "SELL" && orderPrice.Cmp(tradePrice) >= 0 && @@ -295,7 +314,7 @@ func JudgeSpotReduce(trade models.TradeSet) { // 触发现货减仓 func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string) { - tradeSet, err := GetTradeSet(reduceOrder.Symbol, 0) + tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 0) if err != nil { log.Error("获取交易设置失败") @@ -366,6 +385,9 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest Updates(map[string]interface{}{"status": 1}).Error; err != nil { log.Errorf("修改现货减仓状态失败 id:%s err:%v", reduceOrder.Id, err) } + + //处理减仓单减仓策略 + CacheOrderStrategyAndReCreate(db, reduceOrder, 1, tradeSet, setting) } if _, err := helper.DefaultRedis.LRem(key, item); err != nil { @@ -414,8 +436,8 @@ func SpotAddPositionTrigger(db *gorm.DB, v *AddPositionList, item string, spotAp } else if ok { defer lock.Release() - setting, _ := GetSystemSetting(db) - tradeSet, _ := GetTradeSet(v.Symbol, 0) + setting, _ := cacheservice.GetSystemSetting(db) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, v.Symbol, 0) if tradeSet.LastPrice == "" { log.Errorf("现货加仓触发 查询交易对失败 交易对:%s ordersn:%s", v.Symbol, v.OrderSn) diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index e921d9a..bba58e4 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -13,6 +13,7 @@ import ( models2 "go-admin/models" "go-admin/models/positiondto" "go-admin/pkg/utility" + "go-admin/services/cacheservice" "go-admin/services/positionservice" "strconv" "strings" @@ -165,13 +166,16 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { return } - tradeSet, err := GetTradeSet(preOrder.Symbol, 0) + tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, preOrder.Symbol, 0) if err != nil { logger.Errorf("handleMainReduceFilled 获取交易对设置失败,订单号:%s", preOrder.OrderSn) return } + //修改减仓单减仓策略状态 + ReduceCallBack(db, preOrder) + orderExt := models.LinePreOrderExt{} positionData := savePosition(db, preOrder) @@ -731,7 +735,7 @@ func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, r // fist 首次止盈止损 func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrder, positionData *positiondto.PositionDto, extOrderId int, fist bool) { orders := []models.LinePreOrder{} - tradeSet, _ := GetTradeSet(preOrder.Symbol, 0) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, preOrder.Symbol, 0) mainId := preOrder.Id if preOrder.MainId > 0 { @@ -869,7 +873,7 @@ func processSpotReduceOrder(preOrder DbModels.LinePreOrder) { // 处理止盈订单 func processTakeProfitOrder(db *gorm.DB, spotApi SpotRestApi, order models.LinePreOrder) { - tradeSet, _ := GetTradeSet(order.Symbol, 0) + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 0) if tradeSet.Coin == "" { logger.Error("获取交易对失败") @@ -941,44 +945,6 @@ func processStopLossOrder(order models.LinePreOrder) error { return nil } -func GetSystemSetting(db *gorm.DB) (models.LineSystemSetting, error) { - key := fmt.Sprintf(rediskey.SystemSetting) - val, _ := helper.DefaultRedis.GetString(key) - setting := models.LineSystemSetting{} - - if val != "" { - sonic.UnmarshalString(val, &setting) - } - - if setting.Id > 0 { - return setting, nil - } - - var err error - setting, err = ResetSystemSetting(db) - if err != nil { - return setting, err - } - - return setting, nil -} - -func ResetSystemSetting(db *gorm.DB) (DbModels.LineSystemSetting, error) { - setting := DbModels.LineSystemSetting{} - if err := db.Model(&setting).First(&setting).Error; err != nil { - return setting, err - } - - settVal, _ := sonic.MarshalString(&setting) - - if settVal != "" { - if err := helper.DefaultRedis.SetString(rediskey.SystemSetting, settVal); err != nil { - logger.Error("redis添加系统设置失败", err) - } - } - return DbModels.LineSystemSetting{}, nil -} - // 循环取消订单 func CancelOpenOrderByOrderSnLoop(apiInfo DbModels.LineApiUser, symbol string, orderSn string) error { spotApi := SpotRestApi{} diff --git a/services/binanceservice/strategy_order_service.go b/services/binanceservice/strategy_order_service.go new file mode 100644 index 0000000..0b9e504 --- /dev/null +++ b/services/binanceservice/strategy_order_service.go @@ -0,0 +1,232 @@ +package binanceservice + +import ( + "context" + "errors" + "fmt" + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + models2 "go-admin/models" + "go-admin/pkg/utility" + "go-admin/services/cacheservice" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" +) + +type BinanceStrategyOrderService struct { + service.Service +} + +// 判断是否触发波段订单 +func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) { + //现货 + orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType)) + e.DoJudge(orderStrs, 1, exchangeType) + + //合约 + futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType)) + e.DoJudge(futOrdedrStrs, 2, exchangeType) +} + +// 处理订单 +func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) { + for _, orderStr := range orderStrs { + var lockKey string + orderItem := dto.StrategyOrderRedisList{} + err := sonic.Unmarshal([]byte(orderStr), &orderItem) + + if err != nil || orderItem.Symbol == "" { + continue + } + + if symbolType == 1 { + lockKey = rediskey.StrategySpotTriggerLock + } else { + lockKey = rediskey.StrategyFutTriggerLock + } + + lock := helper.NewRedisLock(fmt.Sprintf(lockKey, orderItem.ApiId, orderItem.Symbol), 200, 50, 100*time.Millisecond) + + if ok, err := lock.AcquireWait(context.Background()); err != nil { + e.Log.Debug("获取锁失败", err) + return + } else if ok { + defer lock.Release() + + //判断是否符合条件 + success, err := e.JudgeStrategy(orderItem, 1, exchangeType) + + if err != nil { + e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err) + } + + if success { + e.TriggerOrder(orderItem, symbolType) + } + } + } +} + +// 判断是否符合触发条件 +func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedisList, symbolType int, exchangeType string) (bool, error) { + var symbolKey string + result := false + nowUtc := time.Now().UnixMilli() + + switch symbolType { + case 1: + symbolKey = fmt.Sprintf(rediskey.SpotTickerLastPrice, exchangeType, order.Symbol) + case 2: + symbolKey = fmt.Sprintf(rediskey.FutureTickerLastPrice, exchangeType, order.Symbol) + } + + lastUtc := nowUtc - (int64(order.TimeSlotStart) * 60 * 1000) + beforePrice, _ := helper.DefaultRedis.GetNextAfterScore(symbolKey, float64(lastUtc)) + lastPrices, _ := helper.DefaultRedis.GetLastSortSet(symbolKey) + + if beforePrice == "" || len(lastPrices) == 0 { + return result, errors.New("获取交易对起止价格失败") + } + + score := lastPrices[0].Score + startPrice := utility.StrToDecimal(beforePrice) + lastPrice := utility.StrToDecimal(lastPrices[0].Member.(string)) + + //时间差超过10s + if (nowUtc-int64(score))/1000 > 10 { + return result, fmt.Errorf("时间差超过 %ss", "10") + } + + if startPrice.Cmp(decimal.Zero) == 0 || lastPrice.Cmp(decimal.Zero) == 0 { + return result, errors.New("获取交易对起止价格有一个为0") + } + + percentag := lastPrice.Div(startPrice).Sub(decimal.NewFromInt(1)).Truncate(6) + + //价格没有变动 + if percentag.Cmp(decimal.Zero) == 0 { + return result, nil + } + + //满足条件 + switch order.CompareType { + case 1: + result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0 + case 2: + result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0 + case 5: + result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0 + default: + return result, errors.New("没有对应的类型") + } + + return result, nil +} + +// 触发委托单 +func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisList, symbolType int) error { + orders := make([]models.LinePreOrder, 0) + if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =?", order.Id).Find(&orders).Error; err != nil { + e.Log.Errorf("order_id:%d 获取委托单失败:%s", order.Id, err.Error()) + return err + } + + setting, _ := cacheservice.GetSystemSetting(e.Orm) + + if setting.Id == 0 { + return errors.New("获取系统设置失败") + } + + tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, symbolType) + + if tradeSet.Coin == "" { + return errors.New("获取交易对行情失败") + } + + lastPrice := utility.StrToDecimal(tradeSet.LastPrice) + + if lastPrice.Cmp(decimal.Zero) <= 0 { + return errors.New("最新成交价小于等于0") + } + + var mainOrder models.LinePreOrder + + for _, v := range orders { + if v.MainId == 0 { + mainOrder = v + break + } + } + + GetOrderByPid(&mainOrder, orders, mainOrder.Id) + + return nil +} + +// 重新计算订单单价、数量 +// tradeSet 交易对行情 +// mainOrder 主订单 +// setting 系统设置 +func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet, mainOrder *models.LinePreOrder, setting models.LineSystemSetting) error { + exts := make([]models.LinePreOrderExt, 0) + if err := e.Orm.Model(models.LinePreOrderExt{}).Where("main_id =?", mainOrder.Id).Find(&exts).Error; err != nil { + return errors.New("获取拓展信息失败") + } + + lastPrice := utility.StrToDecimal(tradeSet.LastPrice) + rate := utility.StrToDecimal(mainOrder.Rate) + newPrice := lastPrice.Mul(decimal.NewFromInt(1).Add(rate.Div(decimal.NewFromInt(100)).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) + buyPrice := utility.StrToDecimal(mainOrder.BuyPrice) + totalNum := buyPrice.Div(newPrice).Truncate(int32(tradeSet.AmountDigit)) + + mainOrder.SignPrice = lastPrice.String() + mainOrder.Price = newPrice.String() + mainOrder.Num = totalNum.String() + + for index := range mainOrder.Childs { + //主单止盈 + if mainOrder.Child[index].OrderType == 1 { + var ext models.LinePreOrderExt + + for _, v := range exts { + if v.OrderId == mainOrder.Child[index].Id { + ext = v + break + } + } + + if ext.Id > 0 { + var percent decimal.Decimal + //多 + if mainOrder.Site == "BUY" { + percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio) + } else { + percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio) + } + + childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) + mainOrder.Child[index].Price = childPrice.String() + } + } else { + //todo 重新计算 + } + } + return nil +} + +// 递归订单树 +func GetOrderByPid(order *models.LinePreOrder, orders []models.LinePreOrder, pid int) { + for _, v := range orders { + if v.Pid == pid { + GetOrderByPid(&v, orders, v.Id) + + order.Childs = append(order.Childs, v) + } + } +} diff --git a/services/cacheservice/config_service.go b/services/cacheservice/config_service.go index e22bae5..ab80e4d 100644 --- a/services/cacheservice/config_service.go +++ b/services/cacheservice/config_service.go @@ -1,10 +1,13 @@ package cacheservice import ( + "errors" "fmt" "go-admin/app/admin/models" "go-admin/common/const/rediskey" + "go-admin/common/global" "go-admin/common/helper" + models2 "go-admin/models" "github.com/bytedance/sonic" "github.com/go-admin-team/go-admin-core/logger" @@ -59,3 +62,66 @@ func GetConfigCacheByKey(db *gorm.DB, key string) models.SysConfig { return result } + +// 获取缓存交易对 +// symbolType 0-现货 1-合约 +func GetTradeSet(exchangeType string, symbol string, symbolType int) (models2.TradeSet, error) { + result := models2.TradeSet{} + val := "" + + switch symbolType { + case 0: + key := fmt.Sprintf(global.TICKER_SPOT, exchangeType, symbol) + val, _ = helper.DefaultRedis.GetString(key) + case 1: + key := fmt.Sprintf(global.TICKER_FUTURES, exchangeType, symbol) + val, _ = helper.DefaultRedis.GetString(key) + } + + if val != "" { + if err := sonic.Unmarshal([]byte(val), &result); err != nil { + return result, err + } + } else { + return result, errors.New("未找到交易对信息") + } + + return result, nil +} + +func GetSystemSetting(db *gorm.DB) (models.LineSystemSetting, error) { + key := fmt.Sprintf(rediskey.SystemSetting) + val, _ := helper.DefaultRedis.GetString(key) + setting := models.LineSystemSetting{} + + if val != "" { + sonic.UnmarshalString(val, &setting) + } + + if setting.Id > 0 { + return setting, nil + } + + var err error + setting, err = ResetSystemSetting(db) + if err != nil { + return setting, err + } + + return setting, nil +} +func ResetSystemSetting(db *gorm.DB) (models.LineSystemSetting, error) { + setting := models.LineSystemSetting{} + if err := db.Model(&setting).First(&setting).Error; err != nil { + return setting, err + } + + settVal, _ := sonic.MarshalString(&setting) + + if settVal != "" { + if err := helper.DefaultRedis.SetString(rediskey.SystemSetting, settVal); err != nil { + logger.Error("redis添加系统设置失败", err) + } + } + return models.LineSystemSetting{}, nil +}