Compare commits
	
		
			10 Commits
		
	
	
		
			9ca1cd9a19
			...
			7b50873de3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7b50873de3 | |||
| 44ba8bfbf1 | |||
| 79af1ab2c1 | |||
| e3a737a7d6 | |||
| d4c8e692a7 | |||
| a9fc1c87f5 | |||
| 1512216bab | |||
| 8e8c78ec0b | |||
| cdd3f951a2 | |||
| 0b95e32655 | 
							
								
								
									
										205
									
								
								app/admin/apis/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								app/admin/apis/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -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(), "删除成功") | ||||
| } | ||||
							
								
								
									
										191
									
								
								app/admin/apis/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								app/admin/apis/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							| @ -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(), "删除成功") | ||||
| } | ||||
							
								
								
									
										206
									
								
								app/admin/apis/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								app/admin/apis/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							| @ -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(), "删除成功") | ||||
| } | ||||
							
								
								
									
										193
									
								
								app/admin/apis/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								app/admin/apis/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							| @ -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(), "删除成功") | ||||
| } | ||||
							
								
								
									
										27
									
								
								app/admin/models/line_order_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/models/line_order_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
| @ -9,38 +9,41 @@ import ( | ||||
|  | ||||
| type LinePreOrder struct { | ||||
| 	models.Model | ||||
| 	ExchangeType      string          `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型 (字典 exchange_type)"` | ||||
| 	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用户"` | ||||
| 	GroupId           string          `json:"groupId" gorm:"type:int unsigned;omitempty;comment:交易对组id"` | ||||
| 	Symbol            string          `json:"symbol" gorm:"type:varchar(255);omitempty;comment:交易对"` | ||||
| 	QuoteSymbol       string          `json:"quoteSymbol" gorm:"type:varchar(255);omitempty;comment:计较货币"` | ||||
| 	SignPrice         string          `json:"signPrice" gorm:"type:decimal(18,8);omitempty;comment:对标价"` | ||||
| 	SignPriceU        decimal.Decimal `json:"signPriceU" gorm:"type:decimal(18,8);omitempty;comment:交易对对标U的行情价"` | ||||
| 	SignPriceType     string          `json:"signPriceType" gorm:"type:enum('new','tall','low','mixture','entrust','add');omitempty;comment:对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓"` | ||||
| 	Rate              string          `json:"rate" gorm:"type:decimal(18,2);omitempty;comment:下单百分比"` | ||||
| 	Price             string          `json:"price" gorm:"type:decimal(18,8);omitempty;comment:触发价格"` | ||||
| 	Num               string          `json:"num" gorm:"type:decimal(18,8);omitempty;comment:购买数量"` | ||||
| 	BuyPrice          string          `json:"buyPrice" gorm:"type:decimal(18,8);omitempty;comment:购买金额"` | ||||
| 	SymbolType        int             `json:"symbolType" gorm:"type:int;comment:交易对类型:1=现货;2=合约"` | ||||
| 	OrderCategory     int             `json:"orderCategory" gorm:"type:int;comment:订单类型: 1=主单 2=对冲单 3-加仓单"` | ||||
| 	Site              string          `json:"site" gorm:"type:enum('BUY','SELL');omitempty;comment:购买方向:BUY=买;SELL=卖"` | ||||
| 	OrderSn           string          `json:"orderSn" gorm:"type:varchar(255);omitempty;comment:订单号"` | ||||
| 	OrderType         int             `json:"orderType" gorm:"type:int;omitempty;comment:订单类型:0=主单 1=止盈 2=止损 3=平仓 4=减仓"` | ||||
| 	Desc              string          `json:"desc" gorm:"type:text;omitempty;comment:订单描述"` | ||||
| 	Status            int             `json:"status" gorm:"type:int;omitempty;comment:是否被触发:0=待触发;1=已触发;2=下单失败;4=已取消;5=委托中;6=已成交;9=已平仓"` | ||||
| 	CoverType         int             `json:"coverType" gorm:"type:int unsigned;omitempty;comment:对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货"` | ||||
| 	ExpireTime        time.Time       `json:"expireTime" gorm:"comment:过期时间"` | ||||
| 	MainOrderType     string          `json:"mainOrderType" gorm:"type:enum;comment:第一笔(主单类型) 限价(LIMIT)市价(MARKET)"` | ||||
| 	LossAmount        decimal.Decimal `json:"lossAmount" gorm:"type:decimal(18,8);comment:亏损金额(U)"` | ||||
| 	TriggerTime       *time.Time      `json:"triggerTime" gorm:"type:datetime;comment:触发时间"` | ||||
| 	Child             []LinePreOrder  `json:"child" gorm:"-"` | ||||
| 	ApiName           string          `json:"api_name" gorm:"->"` | ||||
| 	ChildNum          int64           `json:"child_num" gorm:"->"` | ||||
| 	AddPositionStatus int             `json:"add_position_status" gorm:"->"` | ||||
| 	ReduceStatus      int             `json:"reduce_status" gorm:"->"` | ||||
| 	Childs            []LinePreOrder  `json:"childs" gorm:"foreignKey:pid;references:id"` | ||||
| 	ExchangeType         string          `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型 (字典 exchange_type)"` | ||||
| 	StrategyTemplateType int             `json:"strategyTemplateType" gorm:"type:tinyint;comment:策略类型 0-无 1-波段涨跌幅"` | ||||
| 	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用户"` | ||||
| 	GroupId              string          `json:"groupId" gorm:"type:int unsigned;omitempty;comment:交易对组id"` | ||||
| 	Symbol               string          `json:"symbol" gorm:"type:varchar(255);omitempty;comment:交易对"` | ||||
| 	QuoteSymbol          string          `json:"quoteSymbol" gorm:"type:varchar(255);omitempty;comment:计较货币"` | ||||
| 	SignPrice            string          `json:"signPrice" gorm:"type:decimal(18,8);omitempty;comment:对标价"` | ||||
| 	SignPriceU           decimal.Decimal `json:"signPriceU" gorm:"type:decimal(18,8);omitempty;comment:交易对对标U的行情价"` | ||||
| 	SignPriceType        string          `json:"signPriceType" gorm:"type:enum('new','tall','low','mixture','entrust','add');omitempty;comment:对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓"` | ||||
| 	Rate                 string          `json:"rate" gorm:"type:decimal(18,2);omitempty;comment:下单百分比"` | ||||
| 	Price                string          `json:"price" gorm:"type:decimal(18,8);omitempty;comment:触发价格"` | ||||
| 	Num                  string          `json:"num" gorm:"type:decimal(18,8);omitempty;comment:购买数量"` | ||||
| 	BuyPrice             string          `json:"buyPrice" gorm:"type:decimal(18,8);omitempty;comment:购买金额"` | ||||
| 	SymbolType           int             `json:"symbolType" gorm:"type:int;comment:交易对类型:1=现货;2=合约"` | ||||
| 	OrderCategory        int             `json:"orderCategory" gorm:"type:int;comment:订单类型: 1=主单 2=对冲单 3-加仓单"` | ||||
| 	Site                 string          `json:"site" gorm:"type:enum('BUY','SELL');omitempty;comment:购买方向:BUY=买;SELL=卖"` | ||||
| 	OrderSn              string          `json:"orderSn" gorm:"type:varchar(255);omitempty;comment:订单号"` | ||||
| 	OrderType            int             `json:"orderType" gorm:"type:int;omitempty;comment:订单类型:0=主单 1=止盈 2=止损 3=平仓 4=减仓"` | ||||
| 	Desc                 string          `json:"desc" gorm:"type:text;omitempty;comment:订单描述"` | ||||
| 	Status               int             `json:"status" gorm:"type:int;omitempty;comment:是否被触发:0=待触发;1=已触发;2=下单失败;4=已取消;5=委托中;6=已成交;9=已平仓"` | ||||
| 	CoverType            int             `json:"coverType" gorm:"type:int unsigned;omitempty;comment:对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货"` | ||||
| 	ExpireTime           time.Time       `json:"expireTime" gorm:"comment:过期时间"` | ||||
| 	MainOrderType        string          `json:"mainOrderType" gorm:"type:enum;comment:第一笔(主单类型) 限价(LIMIT)市价(MARKET)"` | ||||
| 	LossAmount           decimal.Decimal `json:"lossAmount" gorm:"type:decimal(18,8);comment:亏损金额(U)"` | ||||
| 	TriggerTime          *time.Time      `json:"triggerTime" gorm:"type:datetime;comment:触发时间"` | ||||
| 	Child                []LinePreOrder  `json:"child" gorm:"-"` | ||||
| 	ApiName              string          `json:"api_name" gorm:"->"` | ||||
| 	ChildNum             int64           `json:"child_num" gorm:"->"` | ||||
| 	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 | ||||
|  | ||||
							
								
								
									
										28
									
								
								app/admin/models/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/admin/models/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										32
									
								
								app/admin/models/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/admin/models/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| 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:亏损百分比"` | ||||
| 	QuantityPercent  decimal.Decimal    `json:"quantityPercent" 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 | ||||
| } | ||||
							
								
								
									
										33
									
								
								app/admin/models/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/admin/models/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										29
									
								
								app/admin/models/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/admin/models/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										27
									
								
								app/admin/router/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								app/admin/router/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								app/admin/router/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								app/admin/router/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										21
									
								
								app/admin/service/dto/line_order_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/admin/service/dto/line_order_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| package dto | ||||
|  | ||||
| import "github.com/shopspring/decimal" | ||||
|  | ||||
| type LineOrderReduceStrategyResp struct { | ||||
| 	MainId  int                               `json:"mainId" comment:"主单id"` | ||||
| 	OrderId int                               `json:"orderId" comment:"减仓单id"` | ||||
| 	Symbol  string                            `json:"symbol"` | ||||
| 	Side    string                            `json:"side" comment:"BUY SELL"` | ||||
| 	Items   []LineOrderReduceStrategyRespItem `json:"items"` | ||||
| } | ||||
|  | ||||
| // 减仓节点 | ||||
| type LineOrderReduceStrategyRespItem struct { | ||||
| 	Price        decimal.Decimal `json:"p" comment:"下单价"` | ||||
| 	Num          decimal.Decimal `json:"n" 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:"是否触发 "` | ||||
| } | ||||
| @ -186,6 +186,9 @@ func (s *LinePreOrderDeleteReq) GetId() interface{} { | ||||
|  | ||||
| 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" `                     //下单用户 | ||||
| @ -194,11 +197,12 @@ type LineAddPreOrderReq struct { | ||||
| 	Site                    string          `json:"site" `                       //购买方向 | ||||
| 	BuyPrice                string          `json:"buy_price" vd:"$>0"`          //购买金额 U | ||||
| 	PricePattern            string          `json:"price_pattern"`               //价格模式 | ||||
| 	Price                   string          `json:"price" vd:"$>0"`              //下单价百分比 | ||||
| 	Price                   string          `json:"price"`                       //下单价百分比 | ||||
| 	Profit                  string          `json:"profit" vd:"$>0"`             //止盈价 | ||||
| 	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"`               //模板名字 | ||||
| @ -283,11 +287,15 @@ func (req LineAddPreOrderReq) Valid() error { | ||||
| 		return errors.New("主单减仓价格百分比不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if req.StrategyTemplateType == 1 && req.StrategyTemplateId == 0 { | ||||
| 		return errors.New("请选择策略") | ||||
| 	} | ||||
|  | ||||
| 	if req.ReduceNumRatio.IsZero() { | ||||
| 		return errors.New("主单减仓数量百分比不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if req.ReducePriceRatio.IsZero() || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 	if req.PricePattern != "mixture" && (req.ReducePriceRatio.IsZero() || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0) { | ||||
| 		return errors.New("主单减仓价格百分比错误") | ||||
| 	} | ||||
|  | ||||
| @ -318,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("止盈价格不正确") | ||||
| 		} | ||||
|  | ||||
| @ -367,6 +377,8 @@ type LineTreeOrder struct { | ||||
| // LineBatchAddPreOrderReq 批量添加订单请求参数 | ||||
| type LineBatchAddPreOrderReq struct { | ||||
| 	ExchangeType          string                  `json:"exchange_type"`            //交易所类型 字典exchange_type | ||||
| 	StrategyTemplateType  int                     `json:"strategy_template_type"`   //策略类型 0-无 1-波段涨跌幅 | ||||
| 	StrategyTemplateId    int                     `json:"strategy_template_id"`     //策略id | ||||
| 	SymbolType            int                     `json:"symbol_type"`              //主单交易对类型 0-现货 1-合约 | ||||
| 	OrderType             int                     `json:"order_type"`               //订单类型 | ||||
| 	SymbolGroupId         string                  `json:"symbol_group_id"`          //交易对组id | ||||
| @ -404,6 +416,10 @@ func (req LineBatchAddPreOrderReq) CheckParams() error { | ||||
| 		return errors.New("请选择交易所") | ||||
| 	} | ||||
|  | ||||
| 	if req.StrategyTemplateType == 1 && req.StrategyTemplateId == 0 { | ||||
| 		return errors.New("请选择策略") | ||||
| 	} | ||||
|  | ||||
| 	if req.ApiUserId == "" { | ||||
| 		return errors.New("选择下单用户") | ||||
| 	} | ||||
| @ -431,7 +447,7 @@ func (req LineBatchAddPreOrderReq) CheckParams() error { | ||||
| 		return errors.New("主单减仓数量百分比不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 	if req.PricePattern != "mixture" && (req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0) { | ||||
| 		return errors.New("主单减仓价格百分比错误") | ||||
| 	} | ||||
|  | ||||
| @ -462,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("止盈价格不正确") | ||||
| 		} | ||||
|  | ||||
| @ -557,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 | ||||
|  | ||||
							
								
								
									
										159
									
								
								app/admin/service/dto/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								app/admin/service/dto/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | ||||
| 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-1].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 | ||||
| 		strategyItem.QuantityPercent = item.QuantityPercent | ||||
|  | ||||
| 		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 | ||||
| 		strategyItem.QuantityPercent = item.QuantityPercent | ||||
|  | ||||
| 		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 | ||||
| } | ||||
							
								
								
									
										134
									
								
								app/admin/service/dto/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								app/admin/service/dto/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| 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:"止损百分比"` | ||||
| 	QuantityPercent decimal.Decimal `json:"quantityPercent" 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") | ||||
| 	} | ||||
|  | ||||
| 	if s.QuantityPercent.Cmp(decimal.Zero) <= 0 { | ||||
| 		return errors.New("百分比不能小于等于0") | ||||
| 	} | ||||
|  | ||||
| 	if s.QuantityPercent.Cmp(decimal.NewFromInt(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 | ||||
| } | ||||
							
								
								
									
										174
									
								
								app/admin/service/dto/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								app/admin/service/dto/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 { | ||||
| 		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 { | ||||
| 		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:"时间断截至(分)"` | ||||
| } | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										88
									
								
								app/admin/service/dto/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								app/admin/service/dto/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
| @ -57,14 +57,15 @@ func (s *LineSystemSettingInsertReq) GetId() interface{} { | ||||
| } | ||||
|  | ||||
| type LineSystemSettingUpdateReq struct { | ||||
| 	Id                  int             `uri:"id" comment:"id"` // id | ||||
| 	Time                int64           `json:"time" comment:"导入:挂单时长达到时间后失效"` | ||||
| 	BatchTime           int64           `json:"batchTime" comment:"批量:挂单时长达到时间后失效"` | ||||
| 	ProfitRate          string          `json:"profitRate" comment:"平仓盈利比例"` | ||||
| 	CoverOrderTypeBRate string          `json:"coverOrderTypeBRate" comment:"b账户限价补单的买入百分比"` | ||||
| 	StopLossPremium     decimal.Decimal `json:"stopLossPremium" comment:"限价止损溢价"` | ||||
| 	AddPositionPremium  decimal.Decimal `json:"addPositionPremium" comment:"限价加仓溢价"` | ||||
| 	ReducePremium       decimal.Decimal `json:"reducePremium" comment:"限价减仓溢价"` | ||||
| 	Id                        int             `uri:"id" comment:"id"` // id | ||||
| 	Time                      int64           `json:"time" comment:"导入:挂单时长达到时间后失效"` | ||||
| 	BatchTime                 int64           `json:"batchTime" comment:"批量:挂单时长达到时间后失效"` | ||||
| 	ProfitRate                string          `json:"profitRate" comment:"平仓盈利比例"` | ||||
| 	CoverOrderTypeBRate       string          `json:"coverOrderTypeBRate" comment:"b账户限价补单的买入百分比"` | ||||
| 	StopLossPremium           decimal.Decimal `json:"stopLossPremium" comment:"限价止损溢价"` | ||||
| 	AddPositionPremium        decimal.Decimal `json:"addPositionPremium" comment:"限价加仓溢价"` | ||||
| 	ReducePremium             decimal.Decimal `json:"reducePremium" comment:"限价减仓溢价"` | ||||
| 	ReduceEarlyTriggerPercent decimal.Decimal `json:"reduceEarlyTriggerPercent" comment:"减仓提前触发百分比"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -79,6 +80,7 @@ func (s *LineSystemSettingUpdateReq) Generate(model *models.LineSystemSetting) { | ||||
| 	model.StopLossPremium = s.StopLossPremium | ||||
| 	model.AddPositionPremium = s.AddPositionPremium | ||||
| 	model.ReducePremium = s.ReducePremium | ||||
| 	model.ReduceEarlyTriggerPercent = s.ReduceEarlyTriggerPercent | ||||
| 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -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" | ||||
| @ -278,6 +279,8 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 	futReduceVal, _ := helper.DefaultRedis.GetAllList(spotAddPositionKey) | ||||
| 	spotAddPositionVal, _ := helper.DefaultRedis.GetAllList(futReduceKey) | ||||
| 	spotReduceVal, _ := helper.DefaultRedis.GetAllList(spotReduceKey) | ||||
| 	spotStrategyMap := e.GetStrategyOrderListMap(1) | ||||
| 	futStrategyMap := e.GetStrategyOrderListMap(2) | ||||
|  | ||||
| 	for _, v := range futAddPositionVal { | ||||
| 		sonic.Unmarshal([]byte(v), &addPosition) | ||||
| @ -336,8 +339,14 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 		if val, ok := spotRedces[order.Id]; ok { | ||||
| 			helper.DefaultRedis.LRem(spotReduceKey, val) | ||||
| 		} | ||||
| 		var tradedSetKey string | ||||
| 		if order.SymbolType == 1 { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol) | ||||
| 		} else { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_FUTURES, order.ExchangeType, order.Symbol) | ||||
| 		} | ||||
|  | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol)) | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, tradedSetKey) | ||||
| 		redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 		marshal, _ := sonic.Marshal(redisList) | ||||
| 		if order.SymbolType == 1 { | ||||
| @ -348,6 +357,13 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 			helper.DefaultRedis.LRem(listKey, string(marshal)) | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 1: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &spotStrategyMap) | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 2: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &futStrategyMap) | ||||
| 		} | ||||
|  | ||||
| 		//会影响持仓的 | ||||
| 		removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType) | ||||
|  | ||||
| @ -362,6 +378,7 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 		} | ||||
|  | ||||
| 		binanceservice.MainClosePositionClearCache(order.Id, order.SymbolType) | ||||
| 		binanceservice.RemoveReduceReduceCacheByMainId(order.Id, order.SymbolType) | ||||
|  | ||||
| 		ints = append(ints, order.Id) | ||||
| 	} | ||||
| @ -403,6 +420,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 +464,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 +498,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 +577,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,10 +588,11 @@ 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 | ||||
| 		mainPrice := utility.StringToDecimal(AddOrder.Price) | ||||
|  | ||||
| 		//订单配置信息 | ||||
| 		preOrderExts := make([]models.LinePreOrderExt, 0) | ||||
| @ -565,9 +603,16 @@ 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, | ||||
| 		} | ||||
|  | ||||
| 		if req.PricePattern == "mixture" { | ||||
| 			defultExt.TakeProfitRatio = mainPrice.Div(utility.StrToDecimal(req.Profit)).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 			defultExt.StopLossRatio = mainPrice.Div(req.StopLoss).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 		} | ||||
|  | ||||
| 		//减仓单 | ||||
| 		defultExt2 := models.LinePreOrderExt{ | ||||
| 			AddType:            2, | ||||
| @ -579,18 +624,12 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int | ||||
| 			TakeProfitNumRatio: decimal.NewFromInt(100), //减仓止盈默认100% | ||||
| 			StopLossRatio:      req.ReduceStopLossRatio, | ||||
| 		} | ||||
| 		mainPrice := utility.StringToDecimal(AddOrder.Price) | ||||
| 		mainAmount := utility.SafeDiv(buyPrice, mainPrice) | ||||
| 		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) | ||||
|  | ||||
| 		preOrderExts = append(preOrderExts, defultExt) | ||||
| 		preOrderExts = append(preOrderExts, defultExt2) | ||||
|  | ||||
| 		calculateResp := dto.CalculateBreakEvenRatioResp{} | ||||
| 		mainParam := dto.CalculateBreakEevenRatioReq{ | ||||
| @ -606,15 +645,22 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int | ||||
| 			AddPositionVal:   req.ReduceNumRatio, | ||||
| 		} | ||||
|  | ||||
| 		if req.PricePattern == "mixture" { | ||||
| 			mainParam.LossEndPercent = mainPrice.Div(req.ReducePriceRatio).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 			defultExt2.PriceRatio = mainParam.LossEndPercent | ||||
| 		} | ||||
|  | ||||
| 		//计算减仓后 | ||||
| 		mainParam.LossEndPercent = req.ReducePriceRatio | ||||
| 		defultExt2.ReTakeRatio = utility.SafeDiv(mainParam.LossEndPercent, default2NumPercent).Truncate(2) | ||||
| 		mainParam.RemainingQuantity = mainAmount | ||||
| 		e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet) | ||||
| 		mainParam.RemainingQuantity = calculateResp.RemainingQuantity //mainAmount.Mul(decimal.NewFromInt(100).Sub(req.ReduceNumRatio).Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		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 | ||||
| 		mainParam.LossBeginPercent = mainParam.LossEndPercent | ||||
| 		// defultExt.ReTakeRatio = calculateResp.Ratio | ||||
| 		preOrderExts = append(preOrderExts, defultExt) | ||||
| 		preOrderExts = append(preOrderExts, defultExt2) | ||||
|  | ||||
| 		for index, addPosition := range req.Ext { | ||||
| 			ext := models.LinePreOrderExt{ | ||||
| @ -651,11 +697,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 +719,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 +794,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 +839,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 +858,112 @@ 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 | ||||
|  | ||||
| 		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{} | ||||
| @ -932,7 +1039,6 @@ func createPreReduceOrder(preOrder *models.LinePreOrder, ext models.LinePreOrder | ||||
|  | ||||
| 		stopOrder.OrderType = 4 | ||||
| 		stopOrder.Status = 0 | ||||
| 		stopOrder.Rate = ext.PriceRatio.String() | ||||
| 		stopOrder.Num = ext.TotalAfter.Sub(ext.TotalBefore).Abs().Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 		stopOrder.BuyPrice = "0" | ||||
| 		stopOrder.Rate = ext.PriceRatio.String() | ||||
| @ -959,6 +1065,7 @@ func createPreReduceOrder(preOrder *models.LinePreOrder, ext models.LinePreOrder | ||||
| // 构建止盈、止盈止损 | ||||
| func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreOrderExt, tradeSet models2.TradeSet) ([]models.LinePreOrder, error) { | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	mainId := preOrder.Id | ||||
| 	var side string | ||||
|  | ||||
| 	if (preOrder.OrderType != 0 && strings.ToUpper(preOrder.Site) == "BUY") || (preOrder.OrderType == 0 && strings.ToUpper(preOrder.Site) == "SELL") { | ||||
| @ -967,6 +1074,12 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO | ||||
| 		side = "SELL" | ||||
| 	} | ||||
|  | ||||
| 	if preOrder.MainId > 0 { | ||||
| 		mainId = preOrder.MainId | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("take", ext.TakeProfitRatio.String()) | ||||
| 	fmt.Println("boo", ext.TakeProfitRatio.Cmp(decimal.Zero)) | ||||
| 	if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 { | ||||
| 		// 止盈单 | ||||
| 		profitOrder := models.LinePreOrder{} | ||||
| @ -977,11 +1090,7 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO | ||||
| 		profitOrder.Pid = preOrder.Id | ||||
| 		profitOrder.OrderType = 1 | ||||
| 		profitOrder.Status = 0 | ||||
| 		profitOrder.MainId = preOrder.Id | ||||
|  | ||||
| 		if preOrder.MainId > 0 { | ||||
| 			profitOrder.MainId = preOrder.MainId | ||||
| 		} | ||||
| 		profitOrder.MainId = mainId | ||||
| 		profitOrder.BuyPrice = "0" | ||||
| 		profitOrder.Site = side | ||||
|  | ||||
| @ -1007,7 +1116,7 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO | ||||
| 		lossOrder.Pid = preOrder.Id | ||||
| 		lossOrder.OrderType = 2 | ||||
| 		lossOrder.Status = 0 | ||||
| 		lossOrder.MainId = preOrder.MainId | ||||
| 		lossOrder.MainId = mainId | ||||
| 		lossOrder.BuyPrice = "0" | ||||
| 		lossOrder.Num = ext.TotalAfter.Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 		lossOrder.Rate = ext.StopLossRatio.Truncate(2).String() | ||||
| @ -1134,31 +1243,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 +1635,12 @@ func (e *LinePreOrder) ClearAll() error { | ||||
| 		"_PreFutOrderList_", | ||||
| 		"spot_reduce_list", | ||||
| 		"futures_reduce_list", | ||||
| 		"spot_reduce_strategy_list", | ||||
| 		"fut_reduce_strategy_list", | ||||
| 		"future_position", | ||||
| 		"spot_position", | ||||
| 		"strategy_spot_order_list", | ||||
| 		"strategy_fut_order_list", | ||||
| 	} | ||||
| 	err = helper.DefaultRedis.DeleteKeysByPrefix(prefixs...) | ||||
| 	if err != nil { | ||||
| @ -1558,9 +1648,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 | ||||
| } | ||||
|  | ||||
| @ -1929,6 +2020,8 @@ func (e *LinePreOrder) ClearUnTriggered() error { | ||||
| 	var orderLists []models.LinePreOrder | ||||
| 	positions := map[string]positiondto.LinePreOrderPositioinDelReq{} | ||||
| 	e.Orm.Model(&models.LinePreOrder{}).Where("main_id = 0 AND pid = 0 AND status = '0'").Find(&orderLists).Unscoped().Delete(&models.LinePreOrder{}) | ||||
| 	spotStrategyMap := e.GetStrategyOrderListMap(1) | ||||
| 	futStrategyMap := e.GetStrategyOrderListMap(2) | ||||
|  | ||||
| 	for _, order := range orderLists { | ||||
| 		redisList := dto.PreOrderRedisList{ | ||||
| @ -1940,17 +2033,35 @@ func (e *LinePreOrder) ClearUnTriggered() error { | ||||
| 			OrderSn:     order.OrderSn, | ||||
| 			QuoteSymbol: order.QuoteSymbol, | ||||
| 		} | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol)) | ||||
|  | ||||
| 		var tradedSetKey string | ||||
| 		if order.SymbolType == 1 { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol) | ||||
| 		} else { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_FUTURES, order.ExchangeType, order.Symbol) | ||||
| 		} | ||||
|  | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, tradedSetKey) | ||||
| 		redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 		marshal, _ := sonic.Marshal(redisList) | ||||
|  | ||||
| 		if order.SymbolType == 1 { | ||||
| 			key := fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType) | ||||
|  | ||||
| 			helper.DefaultRedis.LRem(key, string(marshal)) | ||||
| 		} else { | ||||
| 			key := fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType) | ||||
|  | ||||
| 			helper.DefaultRedis.LRem(key, string(marshal)) | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 1: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &spotStrategyMap) | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 2: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &futStrategyMap) | ||||
| 		} | ||||
|  | ||||
| 		//会影响持仓的 | ||||
| 		removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType) | ||||
|  | ||||
| @ -1990,6 +2101,60 @@ func (e *LinePreOrder) ClearUnTriggered() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 移除待策略待触发单 | ||||
| func (e *LinePreOrder) RemoveStrategyOrderCache(orderId int, symbolType int, exchangeType string, caches *map[string][]dto.StrategyOrderRedisList) { | ||||
| 	strategys, _ := (*caches)[exchangeType] | ||||
| 	var strategyListKey string | ||||
|  | ||||
| 	if symbolType == 1 { | ||||
| 		strategyListKey = fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType) | ||||
| 	} else { | ||||
| 		strategyListKey = fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType) | ||||
| 	} | ||||
|  | ||||
| 	for _, strategy := range strategys { | ||||
| 		if strategy.Id == orderId { | ||||
| 			strategyVal, _ := sonic.MarshalString(strategy) | ||||
| 			helper.DefaultRedis.LRem(strategyListKey, strategyVal) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取策略订单缓存列表 | ||||
| // symbolType 1现货 2合约 | ||||
| func (e *LinePreOrder) GetStrategyOrderListMap(symbolType int) map[string][]dto.StrategyOrderRedisList { | ||||
| 	result := make(map[string][]dto.StrategyOrderRedisList) | ||||
| 	var key string | ||||
| 	exchanges := []string{global.EXCHANGE_BINANCE} | ||||
|  | ||||
| 	if symbolType == 1 { | ||||
| 		key = rediskey.StrategySpotOrderList | ||||
| 	} else { | ||||
| 		key = rediskey.StrategyFutOrderList | ||||
| 	} | ||||
|  | ||||
| 	for _, exchange := range exchanges { | ||||
| 		newKey := fmt.Sprintf(key, exchange) | ||||
| 		vals, _ := helper.DefaultRedis.GetAllList(newKey) | ||||
| 		itemData := make([]dto.StrategyOrderRedisList, 0) | ||||
| 		item := dto.StrategyOrderRedisList{} | ||||
|  | ||||
| 		for _, v := range vals { | ||||
| 			sonic.Unmarshal([]byte(v), &item) | ||||
|  | ||||
| 			if item.Id > 0 { | ||||
| 				itemData = append(itemData, item) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(itemData) > 0 { | ||||
| 			result[exchange] = itemData | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (e *LinePreOrder) QueryOrder(req *dto.QueryOrderReq) (res interface{}, err error) { | ||||
| 	var apiUserInfo models.LineApiUser | ||||
| 	e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo) | ||||
| @ -2042,9 +2207,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 == "" { | ||||
| @ -2083,15 +2248,18 @@ func (e *LinePreOrder) GenerateOrder(req *dto.LineAddPreOrderReq) error { | ||||
| 		AddPositionVal:   req.ReduceNumRatio, | ||||
| 	} | ||||
|  | ||||
| 	if req.PricePattern == "mixture" { | ||||
| 		mainParam.LossEndPercent = price.Div(req.ReducePriceRatio).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 	} | ||||
|  | ||||
| 	//计算减仓后 | ||||
| 	mainParam.LossEndPercent = req.ReducePriceRatio | ||||
| 	mainParam.RemainingQuantity = mainAmount | ||||
| 	mainParam.AddType = 2 | ||||
| 	e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet) | ||||
| 	mainParam.RemainingQuantity = calculateResp.RemainingQuantity | ||||
| 	mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU | ||||
| 	req.ReduceReTakeProfitRatio = calculateResp.Ratio | ||||
| 	lossBeginPercent = req.ReducePriceRatio | ||||
| 	lossBeginPercent = mainParam.LossEndPercent | ||||
|  | ||||
| 	//顺序排序 | ||||
| 	sort.Slice(req.Ext, func(i, j int) bool { | ||||
| @ -2161,164 +2329,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 | ||||
| // } | ||||
|  | ||||
							
								
								
									
										1
									
								
								app/admin/service/line_pre_order_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/admin/service/line_pre_order_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| package service | ||||
							
								
								
									
										178
									
								
								app/admin/service/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								app/admin/service/line_reduce_strategy.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										109
									
								
								app/admin/service/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								app/admin/service/line_reduce_strategy_item.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										109
									
								
								app/admin/service/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								app/admin/service/line_strategy_template.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										147
									
								
								app/admin/service/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								app/admin/service/line_symbol_price.go
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
| } | ||||
							
								
								
									
										67
									
								
								app/jobs/account_job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								app/jobs/account_job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	binancedto "go-admin/models/binancedto" | ||||
| 	"go-admin/services/binanceservice" | ||||
|  | ||||
| 	DbModels "go-admin/app/admin/models" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type BinanceSpotAccountJob struct{} | ||||
|  | ||||
| type BinanceFuturesAccountJob struct{} | ||||
|  | ||||
| // 币安账户划转 | ||||
| func (t BinanceSpotAccountJob) Exec(arg interface{}) error { | ||||
| 	db := getDefaultDb() | ||||
| 	req := binancedto.BinanceTransfer{ | ||||
| 		Type:       "MAIN_UMFUTURE", | ||||
| 		Asset:      "USDT", | ||||
| 		Amount:     decimal.NewFromFloat(0.1), | ||||
| 		FromSymbol: "USDT", | ||||
| 		ToSymbol:   "USDT", | ||||
| 	} | ||||
| 	var apis []DbModels.LineApiUser | ||||
|  | ||||
| 	if err := db.Model(&DbModels.LineApiUser{}).Where("open_status = 1").Find(&apis).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, apiUserInfo := range apis { | ||||
| 		err := binanceservice.TradeAmount(db, &req, apiUserInfo) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("现货划转合约失败, err: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 币安账户划转 | ||||
| func (t BinanceFuturesAccountJob) Exec(arg interface{}) error { | ||||
| 	db := getDefaultDb() | ||||
| 	req := binancedto.BinanceTransfer{ | ||||
| 		Type:       "UMFUTURE_MAIN", | ||||
| 		Asset:      "USDT", | ||||
| 		Amount:     decimal.NewFromFloat(0.1), | ||||
| 		FromSymbol: "USDT", | ||||
| 		ToSymbol:   "USDT", | ||||
| 	} | ||||
| 	var apis []DbModels.LineApiUser | ||||
| 	if err := db.Model(&DbModels.LineApiUser{}).Where("open_status = 1").Find(&apis).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, apiUserInfo := range apis { | ||||
| 		err := binanceservice.TradeAmount(db, &req, apiUserInfo) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("合约划转现货失败, err: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										34
									
								
								app/jobs/account_job_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/jobs/account_job_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/helper" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func TestAccountJob(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
|  | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
|  | ||||
| 	accountJob := BinanceSpotAccountJob{} | ||||
| 	accountJob.Exec(nil) | ||||
| } | ||||
|  | ||||
| func TestFutureAccountJob(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
|  | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
|  | ||||
| 	accountJob := BinanceFuturesAccountJob{} | ||||
| 	accountJob.Exec(nil) | ||||
| } | ||||
| @ -38,6 +38,9 @@ func InitJob() { | ||||
| 		"MemberExpirationJob":            MemberExpirationJob{},            //会员到期处理 | ||||
| 		"MemberRenwalOrderExpirationJob": MemberRenwalOrderExpirationJob{}, //会员续费订单过期处理 | ||||
| 		"TrxQueryJobs":                   TrxQueryJobs{},                   //订单支付监听 | ||||
| 		"StrategyJob":                    StrategyJob{},                    //下单策略触发 | ||||
| 		"BinanceSpotAccountJob":          BinanceSpotAccountJob{},          //币安现货划转 | ||||
| 		"BinanceFuturesAccountJob":       BinanceFuturesAccountJob{},       //币安合约划转 | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -3,6 +3,7 @@ package jobs | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	models2 "go-admin/app/jobs/models" | ||||
| 	"runtime" | ||||
| 	"time" | ||||
|  | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -43,7 +44,10 @@ type ExecJob struct { | ||||
| func (e *ExecJob) Run() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			log.Errorf("脚本任务失败:%v", err) | ||||
| 			// 获取调用栈信息 | ||||
| 			buf := make([]byte, 1<<16) // 64KB 缓冲区 | ||||
| 			n := runtime.Stack(buf, false) | ||||
| 			log.Errorf("脚本任务失败: %v\n%s", err, buf[:n]) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
							
								
								
									
										12
									
								
								app/jobs/order_job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/jobs/order_job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| package jobs | ||||
|  | ||||
| //波段交易策略任务 | ||||
| type StrategyOrderJob struct { | ||||
| } | ||||
|  | ||||
| func (t StrategyOrderJob) Exec(arg interface{}) error { | ||||
| 	// strategyOrderService:= | ||||
| 	//todo 策略订单任务 | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										25
									
								
								app/jobs/strategy_job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/jobs/strategy_job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/services/binanceservice" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| ) | ||||
|  | ||||
| type StrategyJob struct { | ||||
| } | ||||
|  | ||||
| // 策略下单任务 | ||||
| func (j StrategyJob) Exec(arg interface{}) error { | ||||
| 	strategyService := binanceservice.BinanceStrategyOrderService{} | ||||
| 	db := getDefaultDb() | ||||
| 	strategyService.Orm = db | ||||
| 	strategyService.Log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{}) | ||||
|  | ||||
| 	//触发币安策略下单 | ||||
| 	strategyService.TriggerStrategyOrder(global.EXCHANGE_BINANCE) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										22
									
								
								app/jobs/strategy_job_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/jobs/strategy_job_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/helper" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func TestStrategyJob(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
|  | ||||
| 	job := StrategyJob{} | ||||
|  | ||||
| 	job.Exec([]string{}) | ||||
| } | ||||
| @ -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,16 @@ const ( | ||||
| 	SpotTrigger            = "spot_trigger_lock:%v_%s"        //现货触发 {apiuserid|symbol} | ||||
| 	FutTrigger             = "fut_trigger_lock:%v_%s"         //合约触发 {apiuserid|symbol} | ||||
|  | ||||
| 	//波段现货触发{apiuserid|ordersn} | ||||
| 	StrategySpotTriggerLock = "strategy_spot_trigger_l:%v_%s" | ||||
| 	//波段合约触发{apiuserid|ordersn} | ||||
| 	StrategyFutTriggerLock = "strategy_fut_trigger_l:%v_%s" | ||||
|  | ||||
| 	//减仓波段合约触发 {apiuserid|symbol} | ||||
| 	ReduceStrategyFutTriggerLock = "reduce_strategy_fut_trigger_l:%v_%s" | ||||
| 	//减仓波段现货触发 {apiuserid|symbol} | ||||
| 	ReduceStrategySpotTriggerLock = "reduce_strategy_spot_trigger_l:%v_%s" | ||||
|  | ||||
| 	SpotCallBack = "spot_callback:%s" //现货回调 {ordersn} | ||||
| 	FutCallBack  = "fut_callback:%s"  //合约回调 {ordersn} | ||||
|  | ||||
| @ -61,6 +76,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----------------- | ||||
|  | ||||
| 	//定时取消限价并下市价锁 | ||||
| @ -74,6 +94,19 @@ const ( | ||||
| 	AveRequestToken     = "ave_request_token"      // AVE请求token | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	//现货最后成交价 sort set key:={exchangeType,symbol}   content:={utc:price} | ||||
| 	SpotTickerLastPrice = "spot_ticker_last_price:%s:%s" | ||||
| 	//合约最后成交价 sort set {exchangeType,symbol}   content:={utc:price} | ||||
| 	FutureTickerLastPrice = "fut_ticker_last_price:%s:%s" | ||||
|  | ||||
| 	//允许缓存交易对价格的交易对 list | ||||
| 	CacheSymbolLastPrice = "cache_symbol_price" | ||||
|  | ||||
| 	//减仓策略缓存 {id} | ||||
| 	ReduceStrategy = "reduce_stragy:%d" | ||||
| ) | ||||
|  | ||||
| // 用户下单 | ||||
| const ( | ||||
| 	MemberShipPre = "member_ship_pre:%v" //用户开通会员预下单 单价缓存{payable_amount} | ||||
|  | ||||
| @ -5,6 +5,7 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
| @ -572,6 +573,33 @@ func (r *RedisHelper) HKeys(key string) ([]string, error) { | ||||
| 	return fields, nil | ||||
| } | ||||
|  | ||||
| func (r *RedisHelper) HExists(key, field, value string) (bool, error) { | ||||
| 	exists, err := r.client.HExists(r.ctx, key, field).Result() | ||||
| 	if err != nil { | ||||
| 		return false, fmt.Errorf("check existence failed: %v", err) | ||||
| 	} | ||||
| 	if !exists { | ||||
| 		return false, nil | ||||
| 	} | ||||
|  | ||||
| 	storedValue, err := r.client.HGet(r.ctx, key, field).Result() | ||||
| 	if err != nil { | ||||
| 		return false, fmt.Errorf("get value failed: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 如果值是 JSON,比较前反序列化 | ||||
| 	var storedObj, inputObj interface{} | ||||
| 	if err := sonic.UnmarshalString(storedValue, &storedObj); err != nil { | ||||
| 		return false, fmt.Errorf("unmarshal stored value failed: %v", err) | ||||
| 	} | ||||
| 	if err := sonic.UnmarshalString(value, &inputObj); err != nil { | ||||
| 		return false, fmt.Errorf("unmarshal input value failed: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 比较两个对象(需要根据实际类型调整) | ||||
| 	return fmt.Sprintf("%v", storedObj) == fmt.Sprintf("%v", inputObj), nil | ||||
| } | ||||
|  | ||||
| // DelSet 从集合中删除元素 | ||||
| func (r *RedisHelper) DelSet(key string, value string) error { | ||||
| 	_, err := r.client.SRem(r.ctx, key, value).Result() | ||||
| @ -609,7 +637,7 @@ func (e *RedisHelper) SignelAdd(key string, score float64, member string) error | ||||
| 	_, err := e.client.ZRemRangeByScore(e.ctx, key, scoreStr, scoreStr).Result() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("删除score失败", err.Error()) | ||||
| 		fmt.Printf("删除score失败,err:%s", err.Error()) | ||||
| 	} | ||||
| 	_, err = e.client.ZAdd(e.ctx, key, &redis.Z{ | ||||
| 		Score:  score, | ||||
| @ -642,6 +670,46 @@ func (e *RedisHelper) DelSortSet(key, member string) error { | ||||
| 	return e.client.ZRem(e.ctx, key, member).Err() | ||||
| } | ||||
|  | ||||
| // RemoveBeforeScore 移除 Sorted Set 中分数小于等于指定值的数据 | ||||
| // key: Sorted Set 的键 | ||||
| // score: 分数上限,所有小于等于此分数的元素将被移除 | ||||
| // 返回值: 移除的元素数量和可能的错误 | ||||
| func (e *RedisHelper) RemoveBeforeScore(key string, score float64) (int64, error) { | ||||
| 	if key == "" { | ||||
| 		return 0, errors.New("key 不能为空") | ||||
| 	} | ||||
| 	if math.IsNaN(score) || math.IsInf(score, 0) { | ||||
| 		return 0, errors.New("score 必须是有效数字") | ||||
| 	} | ||||
|  | ||||
| 	// 使用 ZRemRangeByScore 移除数据 | ||||
| 	count, err := e.client.ZRemRangeByScore(e.ctx, key, "-inf", strconv.FormatFloat(score, 'f', -1, 64)).Result() | ||||
| 	if err != nil { | ||||
| 		return 0, fmt.Errorf("移除 Sorted Set 数据失败, key: %s, score: %f, err: %v", key, score, err) | ||||
| 	} | ||||
|  | ||||
| 	return count, nil | ||||
| } | ||||
|  | ||||
| // GetNextAfterScore 获取指定分数及之后的第一条数据(包含指定分数) | ||||
| func (e *RedisHelper) GetNextAfterScore(key string, score float64) (string, error) { | ||||
| 	// 使用 ZRangeByScore 获取大于等于 score 的第一条数据 | ||||
| 	zs, err := e.client.ZRangeByScoreWithScores(e.ctx, key, &redis.ZRangeBy{ | ||||
| 		Min:    fmt.Sprintf("%f", score), // 包含指定分数 | ||||
| 		Max:    "+inf",                   // 上限为正无穷 | ||||
| 		Offset: 0,                        // 从第 0 条开始 | ||||
| 		Count:  1,                        // 只取 1 条 | ||||
| 	}).Result() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("获取数据失败: %v", err) | ||||
| 	} | ||||
| 	if len(zs) == 0 { | ||||
| 		return "", nil // 没有符合条件的元素 | ||||
| 	} | ||||
| 	return zs[0].Member.(string), nil | ||||
| } | ||||
|  | ||||
| /* | ||||
| 获取sort set 所有数据 | ||||
| */ | ||||
| @ -656,6 +724,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() | ||||
|  | ||||
| @ -106,6 +106,9 @@ func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) { | ||||
| 				baseInterval = time.Second | ||||
| 			} | ||||
|  | ||||
| 			if baseInterval <= 0 { | ||||
| 				baseInterval = time.Millisecond * 100 // 至少 100ms | ||||
| 			} | ||||
| 			// 随机退避 | ||||
| 			retryInterval := time.Duration(rand.Int63n(int64(baseInterval))) // 随机退避 | ||||
| 			if retryInterval < time.Millisecond*100 { | ||||
| @ -129,6 +132,13 @@ func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) { | ||||
| 	return false, ErrFailed | ||||
| } | ||||
|  | ||||
| func safeRandomDuration(max time.Duration) time.Duration { | ||||
| 	if max <= 0 { | ||||
| 		return 100 * time.Millisecond // fallback default | ||||
| 	} | ||||
| 	return time.Duration(rand.Int63n(int64(max))) | ||||
| } | ||||
|  | ||||
| // Release 释放锁 | ||||
| func (rl *RedisLock) Release() (bool, error) { | ||||
| 	return rl.releaseCtx(context.Background()) | ||||
|  | ||||
| @ -13,6 +13,7 @@ import ( | ||||
| 	"go-admin/services/futureservice" | ||||
| 	"go-admin/services/scriptservice" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -31,9 +32,17 @@ 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() | ||||
|  | ||||
| 	//清理交易对缓存价格 | ||||
| 	clearSymbolPrice() | ||||
|  | ||||
| 	//初始化订单配置 | ||||
| 	binanceservice.ResetSystemSetting(db) | ||||
| 	cacheservice.ResetSystemSetting(db) | ||||
| 	lineApiUser := service.LineApiUser{} | ||||
| 	lineApiUser.Orm = db | ||||
| 	if err := lineApiUser.InitCache(); err != nil { | ||||
| @ -140,3 +149,24 @@ func loadApiUser(db *gorm.DB) error { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 清理交易对价格缓存 | ||||
| func clearSymbolPrice() error { | ||||
| 	spotAll, _ := helper.DefaultRedis.ScanKeys("spot_ticker_last_price:*") | ||||
| 	futAllKey, _ := helper.DefaultRedis.ScanKeys("fut_ticker_last_price:*") | ||||
| 	beforeTimeUtc := time.Now().UnixMilli() | ||||
|  | ||||
| 	for _, item := range spotAll { | ||||
| 		if _, err := helper.DefaultRedis.RemoveBeforeScore(item, float64(beforeTimeUtc)); err != nil { | ||||
| 			logger.Error("现货 清理交易对价格缓存失败:", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range futAllKey { | ||||
| 		if _, err := helper.DefaultRedis.RemoveBeforeScore(item, float64(beforeTimeUtc)); err != nil { | ||||
| 			logger.Error("合约 清理交易对价格缓存失败:", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								exchange-single
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								exchange-single
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										11
									
								
								models/binancedto/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								models/binancedto/account.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| package binancedto | ||||
|  | ||||
| import "github.com/shopspring/decimal" | ||||
|  | ||||
| type BinanceTransfer struct { | ||||
| 	Type       string          `json:"type" content:"枚举 MAIN_UMFUTURE-现货到u合约"` | ||||
| 	Asset      string          `json:"asset" content:"币种"` | ||||
| 	Amount     decimal.Decimal `json:"amount" content:"数量"` | ||||
| 	FromSymbol string          `json:"fromSymbol" content:"转出币种"` | ||||
| 	ToSymbol   string          `json:"toSymbol" content:"转入币种"` | ||||
| } | ||||
| @ -640,3 +640,43 @@ func GetSpotUProperty(apiUserInfo DbModels.LineApiUser, data *dto.LineUserProper | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 万象划转 | ||||
| func TradeAmount(db *gorm.DB, req *binancedto.BinanceTransfer, apiUserInfo DbModels.LineApiUser) error { | ||||
| 	url := "/sapi/v1/asset/transfer" | ||||
|  | ||||
| 	client := GetClient(&apiUserInfo) | ||||
|  | ||||
| 	params := map[string]string{ | ||||
| 		"type":       req.Type, | ||||
| 		"asset":      req.Asset, | ||||
| 		"amount":     req.Amount.String(), | ||||
| 		"fromSymbol": req.FromSymbol, | ||||
| 		"toSymbol":   req.ToSymbol, | ||||
| 		"recvWindow": "10000", | ||||
| 	} | ||||
|  | ||||
| 	_, code, err := client.SendSpotAuth(url, "POST", params) | ||||
| 	if err != nil || code != 200 { | ||||
| 		log.Error("万向划转失败 参数:", params) | ||||
| 		log.Error("万向划转失败 code:", code) | ||||
| 		log.Error("万向划转失败 err:", err) | ||||
| 		dataMap := make(map[string]interface{}) | ||||
| 		if err.Error() != "" { | ||||
| 			if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil { | ||||
| 				return fmt.Errorf("api_id:%d 万向划转失败:%+v", apiUserInfo.Id, err.Error()) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		code, ok := dataMap["code"] | ||||
| 		if ok { | ||||
| 			return fmt.Errorf("api_id:%d 万向划转失败:%s", apiUserInfo.Id, ErrorMaps[code.(float64)]) | ||||
| 		} | ||||
| 		if strings.Contains(err.Error(), "Unknown order sent.") { | ||||
| 			return fmt.Errorf("api_id:%d 万向划转失败:%+v", apiUserInfo.Id, ErrorMaps[-2011]) | ||||
| 		} | ||||
| 		return fmt.Errorf("api_id:%d 万向划转失败:%+v", apiUserInfo.Id, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| @ -279,11 +251,15 @@ func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice | ||||
| // mainOrderId 主单id | ||||
| // symbolType 1现货 2合约 | ||||
| func MainClosePositionClearCache(mainId int, symbolType int) { | ||||
| 	strategyDto := dto.StrategyOrderRedisList{} | ||||
|  | ||||
| 	if symbolType == 1 { | ||||
| 		keySpotStop := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) | ||||
| 		keySpotAddposition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) | ||||
| 		spotStopArray, _ := helper.DefaultRedis.GetAllList(keySpotStop) | ||||
| 		spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(keySpotAddposition) | ||||
| 		spotStrategyKey := fmt.Sprintf(rediskey.StrategySpotOrderList, global.EXCHANGE_BINANCE) | ||||
| 		spotStrategyList, _ := helper.DefaultRedis.GetAllList(spotStrategyKey) | ||||
| 		var position AddPositionList | ||||
| 		var stop dto.StopLossRedisList | ||||
|  | ||||
| @ -307,11 +283,23 @@ func MainClosePositionClearCache(mainId int, symbolType int) { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, item := range spotStrategyList { | ||||
| 			sonic.Unmarshal([]byte(item), &strategyDto) | ||||
|  | ||||
| 			if strategyDto.Id == mainId { | ||||
| 				if _, err := helper.DefaultRedis.LRem(spotStrategyKey, item); err != nil { | ||||
| 					logger.Errorf("id:%d 移除缓存失败,err:%v", mainId, err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		keyFutStop := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE) | ||||
| 		keyFutAddposition := fmt.Sprintf(rediskey.FuturesStopLossList, global.EXCHANGE_BINANCE) | ||||
| 		futAddpositionArray, _ := helper.DefaultRedis.GetAllList(keyFutStop) | ||||
| 		futStopArray, _ := helper.DefaultRedis.GetAllList(keyFutAddposition) | ||||
|  | ||||
| 		futStrategyKey := fmt.Sprintf(rediskey.StrategyFutOrderList, global.EXCHANGE_BINANCE) | ||||
| 		futStrategyList, _ := helper.DefaultRedis.GetAllList(futStrategyKey) | ||||
| 		var position AddPositionList | ||||
| 		var stop dto.StopLossRedisList | ||||
|  | ||||
| @ -334,6 +322,16 @@ func MainClosePositionClearCache(mainId int, symbolType int) { | ||||
| 				helper.DefaultRedis.LRem(keyFutStop, item) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, item := range futStrategyList { | ||||
| 			sonic.Unmarshal([]byte(item), &strategyDto) | ||||
|  | ||||
| 			if strategyDto.Id == mainId { | ||||
| 				if _, err := helper.DefaultRedis.LRem(futStrategyKey, item); err != nil { | ||||
| 					logger.Errorf("id:%d 移除缓存失败,err:%v", mainId, err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										64
									
								
								services/binanceservice/futures_judge_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								services/binanceservice/futures_judge_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/common/const/rediskey" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	"go-admin/models" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func TestFutureJudge(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
| 	// tradeSet := models.TradeSet{ | ||||
| 	// 	Coin:      "ADA", | ||||
| 	// 	Currency:  "USDT", | ||||
| 	// 	LastPrice: "0.516", | ||||
| 	// } | ||||
|  | ||||
| 	key := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE) | ||||
| 	item := `{"id":10,"apiId":49,"mainId":7,"pid":7,"symbol":"ADAUSDT","price":"0.6244","side":"BUY","num":"12","orderSn":"398690240274890752"}` | ||||
| 	reduceOrder := ReduceListItem{} | ||||
| 	futApi := FutRestApi{} | ||||
| 	setting, err := cacheservice.GetSystemSetting(db) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		logger.Error("获取系统设置失败") | ||||
| 		return | ||||
| 	} | ||||
| 	if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { | ||||
| 		logger.Error("反序列化失败") | ||||
| 		return | ||||
| 	} | ||||
| 	// JudgeFuturesReduce(tradeSet) | ||||
| 	FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false, 0) | ||||
| } | ||||
|  | ||||
| // 测试减仓后减仓触发 | ||||
| func TestFutureReduceReduce(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
| 	tradeSet := models.TradeSet{ | ||||
| 		Coin:       "ADA", | ||||
| 		Currency:   "USDT", | ||||
| 		LastPrice:  "0.5817", | ||||
| 		PriceDigit: 4, | ||||
| 	} | ||||
|  | ||||
| 	// JudgeFuturesReduce(tradeSet) | ||||
| 	JudgeFuturesReduce(tradeSet) | ||||
| } | ||||
| @ -555,50 +555,6 @@ func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ClosePositionB 平仓B对应的交易对 | ||||
| // bApiUserInfo B 账户api-user信息 | ||||
| // symbol 需要平仓的交易对 | ||||
| // closeType 平仓模式 ALL = 全平 reduceOnly = 只减仓 | ||||
| // func (e FutRestApi) ClosePositionB(orm *gorm.DB, bApiUserInfo *DbModels.LineApiUser, symbol string, closeType string) error { | ||||
| // 	if bApiUserInfo == nil { | ||||
| // 		return errors.New("缺失平仓账户信息") | ||||
| // 	} | ||||
| // 	if symbol == "" { | ||||
| // 		return errors.New("缺失平仓交易对信息") | ||||
| // 	} | ||||
| // 	risks, err := e.GetPositionV3(bApiUserInfo, symbol) | ||||
| // 	if err != nil { | ||||
| // 		return err | ||||
| // 	} | ||||
|  | ||||
| // 	for _, risk := range risks { | ||||
| // 		if risk.Symbol == strings.ToUpper(symbol) { | ||||
| // 			//持仓数量 | ||||
| // 			positionAmt, _ := decimal.NewFromString(risk.PositionAmt) | ||||
| // 			side := "BUY" | ||||
| // 			if positionAmt.GreaterThan(decimal.Zero) { | ||||
| // 				side = "SELL" | ||||
| // 			} | ||||
| // 			var closeAmt decimal.Decimal | ||||
| // 			if strings.ToUpper(closeType) == "ALL" { //全部平仓 | ||||
| // 				closeAmt = positionAmt | ||||
| // 			} else { | ||||
| // 				//如果是只减仓的话 数量=仓位数量*比例 | ||||
| // 				closeAmt = positionAmt.Mul(decimal.NewFromFloat(reduceOnlyRate)) | ||||
| // 			} | ||||
|  | ||||
| // 			err = e.ClosePosition(symbol, closeAmt, side, *bApiUserInfo, "MARKET", "", decimal.Zero) | ||||
| // 			if err != nil { | ||||
| // 				return err | ||||
| // 			} | ||||
| // 			orm.Model(&DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ?", bApiUserInfo.Id, symbol).Updates(map[string]interface{}{ | ||||
| // 				"status": "6", | ||||
| // 			}) | ||||
| // 		} | ||||
| // 	} | ||||
| // 	return nil | ||||
| // } | ||||
|  | ||||
| // 获取合约 持仓价格、数量 | ||||
| // symbol:交易对 | ||||
| // side:方向 | ||||
|  | ||||
| @ -10,6 +10,8 @@ import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	"go-admin/models" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @ -151,21 +153,92 @@ 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 | ||||
| 	} | ||||
|  | ||||
| 	reduceOrder := ReduceListItem{} | ||||
| 	tradePrice, _ := decimal.NewFromString(trade.LastPrice) | ||||
| 	//减仓单减仓策略 | ||||
| 	reduceReduceListKey := fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE) | ||||
| 	orderReduceVal, _ := helper.DefaultRedis.HGetAllFields(reduceReduceListKey) | ||||
| 	reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} | ||||
|  | ||||
| 	for _, item := range orderReduceVal { | ||||
| 		sonic.Unmarshal([]byte(item), &reduceOrderStrategy) | ||||
|  | ||||
| 		for index, item2 := range reduceOrderStrategy.Items { | ||||
| 			if reduceOrderStrategy.Symbol == trade.Coin+trade.Currency && !item2.Actived { | ||||
| 				//买入 | ||||
| 				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)) { | ||||
| 					lock := helper.NewRedisLock(fmt.Sprintf(rediskey.ReduceStrategyFutTriggerLock, reduceOrder.ApiId, reduceOrder.Symbol), 50, 15, 100*time.Millisecond) | ||||
|  | ||||
| 					if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 						log.Error("获取锁失败", err) | ||||
| 						return | ||||
| 					} else if ok { | ||||
| 						defer lock.Release() | ||||
| 						hasrecord, _ := helper.DefaultRedis.HExists(reduceReduceListKey, utility.IntToString(reduceOrderStrategy.OrderId), item) | ||||
|  | ||||
| 						if !hasrecord { | ||||
| 							log.Debug("减仓缓存中不存在", item) | ||||
| 							return | ||||
| 						} | ||||
|  | ||||
| 						order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.PriceDigit) | ||||
|  | ||||
| 						if err != nil { | ||||
| 							log.Errorf("%d 生成订单失败", reduceOrderStrategy.OrderId) | ||||
| 						} | ||||
|  | ||||
| 						reduceOrder.ApiId = order.ApiId | ||||
| 						reduceOrder.Id = order.Id | ||||
| 						reduceOrder.Pid = order.Pid | ||||
| 						reduceOrder.MainId = order.MainId | ||||
| 						reduceOrder.Symbol = order.Symbol | ||||
| 						reduceOrder.Side = reduceOrderStrategy.Side | ||||
| 						reduceOrder.OrderSn = order.OrderSn | ||||
| 						reduceOrder.Price = item2.Price | ||||
| 						reduceOrder.Num = item2.Num | ||||
| 						//下单成功修改策略节点状态 | ||||
| 						if FuturesReduceTrigger(db, reduceOrder, futApi, setting, reduceReduceListKey, item, true, reduceOrderStrategy.OrderId) { | ||||
| 							reduceOrderStrategy.Items[index].Actived = true | ||||
| 							allActive := true | ||||
| 							orderId := utility.IntToString(reduceOrderStrategy.OrderId) | ||||
|  | ||||
| 							for _, item3 := range reduceOrderStrategy.Items { | ||||
| 								if !item3.Actived { | ||||
| 									allActive = false | ||||
| 									break | ||||
| 								} | ||||
| 							} | ||||
|  | ||||
| 							if allActive { | ||||
| 								if err := helper.DefaultRedis.HDelField(reduceReduceListKey, orderId); err != nil { | ||||
| 									log.Errorf("删除redis reduceReduceListKey失败 %s", err.Error()) | ||||
| 								} | ||||
| 							} else { | ||||
| 								str, _ := sonic.MarshalString(reduceOrderStrategy) | ||||
| 								if err := helper.DefaultRedis.HSetField(reduceReduceListKey, orderId, str); err != nil { | ||||
| 									log.Errorf("更新redis reduceReduceListKey失败 %s", err.Error()) | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range reduceVal { | ||||
| 		reduceOrder := ReduceListItem{} | ||||
| 		if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { | ||||
| 			log.Error("反序列化失败") | ||||
| 			continue | ||||
| @ -173,59 +246,68 @@ 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 && | ||||
| 				((strings.ToUpper(reduceOrder.Side) == "SELL" && orderPrice.Cmp(tradePrice) >= 0) || | ||||
| 					(strings.ToUpper(reduceOrder.Side) == "BUY" && orderPrice.Cmp(tradePrice) <= 0)) { | ||||
|  | ||||
| 				FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item) | ||||
| 				FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false, 0) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 触发合约减仓 | ||||
| func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string) { | ||||
| 	tradeSet, _ := GetTradeSet(reduceOrder.Symbol, 1) | ||||
| // isStrategy 是否是策略减仓 | ||||
| // reduceId 父减仓单id | ||||
| func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool, reduceId int) bool { | ||||
| 	tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 1) | ||||
| 	result := true | ||||
|  | ||||
| 	if tradeSet.LastPrice == "" { | ||||
| 		return | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	lock := helper.NewRedisLock(fmt.Sprintf(rediskey.FutTrigger, reduceOrder.ApiId, reduceOrder.Symbol), 20, 5, 100*time.Millisecond) | ||||
|  | ||||
| 	if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 		log.Error("获取锁失败", err) | ||||
| 		return | ||||
| 		return false | ||||
| 	} else if ok { | ||||
| 		defer lock.Release() | ||||
| 		takeOrders := make([]DbModels.LinePreOrder, 0) | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type =1 AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { | ||||
| 		//只取消减仓单 止盈止损减仓成功后取消 | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN 4 AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { | ||||
| 			log.Error("查询止盈单失败") | ||||
| 			return | ||||
| 			// return false | ||||
| 		} | ||||
|  | ||||
| 		hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) | ||||
| 		var hasrecord bool | ||||
|  | ||||
| 		if isStrategy { | ||||
| 			hasrecord, _ = helper.DefaultRedis.HExists(key, utility.IntToString(reduceId), item) | ||||
| 		} else { | ||||
| 			hasrecord, _ = helper.DefaultRedis.IsElementInList(key, item) | ||||
| 		} | ||||
|  | ||||
| 		if !hasrecord { | ||||
| 			log.Debug("减仓缓存中不存在", item) | ||||
| 			return | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		apiInfo, _ := GetApiInfo(reduceOrder.ApiId) | ||||
|  | ||||
| 		if apiInfo.Id == 0 { | ||||
| 			log.Error("现货减仓 查询api用户不存在") | ||||
| 			return | ||||
| 			return false | ||||
| 		} | ||||
| 		for _, takeOrder := range takeOrders { | ||||
| 			err := CancelFutOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				log.Error("合约止盈撤单失败", err) | ||||
| 				return | ||||
| 				log.Error("撤单失败", err) | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -239,18 +321,8 @@ 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 { | ||||
| 			result = false | ||||
| 			log.Errorf("合约减仓挂单失败 id:%s err:%v", reduceOrder.Id, err) | ||||
|  | ||||
| 			if err2 := db.Model(&DbModels.LinePreOrder{}). | ||||
| @ -267,14 +339,24 @@ 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) | ||||
| 			} | ||||
|  | ||||
| 			//处理减仓单减仓策略 | ||||
| 			if err := CacheOrderStrategyAndReCreate(db, reduceOrder, 2, tradeSet, setting); err != nil { | ||||
| 				log.Errorf("合约减仓策略处理失败 id:%s err:%v", reduceOrder.Id, err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if _, err := helper.DefaultRedis.LRem(key, item); err != nil { | ||||
| 			log.Errorf("合约减仓 删除缓存失败 id:%v err:%v", reduceOrder.Id, err) | ||||
| 		if !isStrategy { | ||||
| 			if _, err := helper.DefaultRedis.LRem(key, item); err != nil { | ||||
| 				log.Errorf("合约减仓 删除缓存失败 id:%v err:%v", reduceOrder.Id, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		log.Error("获取锁失败") | ||||
| 		result = false | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // 判断合约加仓 | ||||
| @ -322,8 +404,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) | ||||
|  | ||||
| @ -13,6 +13,7 @@ import ( | ||||
| 	models2 "go-admin/models" | ||||
| 	"go-admin/models/positiondto" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @ -126,17 +127,26 @@ 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) | ||||
| 	//减仓策略单获取主减仓单的拓展信息 | ||||
| 	if preOrder.ReduceOrderId > 0 { | ||||
| 		db.Model(&orderExt).Where("order_id =?", preOrder.ReduceOrderId).First(&orderExt) | ||||
| 	} else { | ||||
| 		db.Model(&orderExt).Where("order_id =?", preOrder.Id).First(&orderExt) | ||||
| 	} | ||||
| 	// rate := utility.StringAsFloat(orderExt.AddPositionVal) | ||||
|  | ||||
| 	//取消委托中的止盈止损 | ||||
| 	cancelTakeProfitByReduce(db, apiUserInfo, preOrder.Symbol, preOrder.MainId, preOrder.SymbolType) | ||||
| 	// 100%减仓 终止流程 | ||||
| 	if orderExt.AddPositionVal.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 		//缓存 | ||||
| @ -160,9 +170,9 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
| 		positionData := savePosition(db, preOrder) | ||||
|  | ||||
| 		//市价单就跳出 市价减仓不设止盈止损 | ||||
| 		if preOrder.MainOrderType == "MARKET" { | ||||
| 			return | ||||
| 		} | ||||
| 		// if preOrder.MainOrderType == "MARKET" { | ||||
| 		// 	return | ||||
| 		// } | ||||
|  | ||||
| 		//亏损大于0 重新计算比例 | ||||
| 		FutTakeProfit(db, preOrder, apiUserInfo, tradeSet, positionData, orderExt, decimal.Zero, decimal.Zero) | ||||
| @ -171,6 +181,36 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 减仓成功后取消止盈止损 | ||||
| func cancelTakeProfitByReduce(db *gorm.DB, apiUserInfo DbModels.LineApiUser, symbol string, mainId int, symbolType int) { | ||||
| 	orders, err := GetSymbolTakeAndStop(db, mainId, symbolType) | ||||
| 	futApi := FutRestApi{} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		logger.Errorf("mainId:%d 获取委托中的止盈止损失败:%v", mainId, err) | ||||
| 	} | ||||
|  | ||||
| 	orderSns := make([]string, 0) | ||||
|  | ||||
| 	for _, v := range orders { | ||||
| 		if v.OrderType != 1 && v.OrderType != 2 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		orderSns = append(orderSns, v.OrderSn) | ||||
| 	} | ||||
|  | ||||
| 	arr := utility.SplitSlice(orderSns, 10) | ||||
|  | ||||
| 	for _, v := range arr { | ||||
| 		err := futApi.CancelBatchFutOrder(apiUserInfo, symbol, v) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("mainId:%d 取消止盈止损失败:%v", mainId, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 减仓处理止盈止损 | ||||
| func FutTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder, apiUserInfo DbModels.LineApiUser, tradeSet models2.TradeSet, | ||||
| 	positionData positiondto.PositionDto, orderExt DbModels.LinePreOrderExt, manualTakeRatio, manualStopRatio decimal.Decimal) bool { | ||||
| @ -245,7 +285,7 @@ func nextFuturesReduceTrigger(db *gorm.DB, mainId int, totalNum decimal.Decimal, | ||||
| 	nextOrder := DbModels.LinePreOrder{} | ||||
| 	nextExt := DbModels.LinePreOrderExt{} | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0 AND reduce_order_id=0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { | ||||
| 		logger.Errorf("获取下一个单失败 err:%v", err) | ||||
| 		return | ||||
| 	} | ||||
| @ -432,6 +472,8 @@ func removeFutLossAndAddPosition(mainId int, orderSn string) { | ||||
| 	stoploss := dto.StopLossRedisList{} | ||||
| 	addPosition := AddPositionList{} | ||||
| 	reduce := ReduceListItem{} | ||||
| 	//移除减仓后减仓策略 | ||||
| 	RemoveReduceReduceCacheByMainId(mainId, 2) | ||||
|  | ||||
| 	//止损缓存 | ||||
| 	for _, v := range stoplossVal { | ||||
| @ -473,7 +515,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) | ||||
| @ -595,7 +637,7 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder, extOrd | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			processFutStopLossOrder(db, order, price, num) | ||||
| 			processFutStopLossOrder(db, order, utility.StrToDecimal(order.Price), num) | ||||
| 			// case 4: // 减仓 | ||||
| 			// processFutReduceOrder(order, price, num) | ||||
| 		} | ||||
| @ -697,9 +739,13 @@ func updateOrderQuantity(db *gorm.DB, order models.LinePreOrder, preOrder *model | ||||
| 	// 	order.Num = num.String() | ||||
| 	// } else | ||||
|  | ||||
| 	if first && (order.OrderCategory == 1 || order.OrderCategory == 3) && order.OrderType == 1 && ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && ext.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) != 0 { | ||||
| 	//止盈止损重算数量 | ||||
| 	if first && (order.OrderCategory == 1 || order.OrderCategory == 3) && ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && ext.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) != 0 { | ||||
| 		// 计算止盈数量 | ||||
| 		num = num.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		if order.OrderType == 1 { | ||||
| 			num = num.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		} | ||||
|  | ||||
| 		order.Num = num.String() | ||||
| 	} | ||||
|  | ||||
| @ -742,7 +788,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 +825,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, | ||||
|  | ||||
| @ -1,10 +1,15 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	DbModels "go-admin/app/admin/models" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"go-admin/pkg/utility/snowflakehelper" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/jinzhu/copier" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
| @ -129,7 +134,7 @@ func GetTotalLossAmount(db *gorm.DB, mainId int) (decimal.Decimal, error) { | ||||
| 	return totalLossAmountU, nil | ||||
| } | ||||
|  | ||||
| // 获取交易对的 委托中的止盈止损 | ||||
| // 获取交易对的 委托中的止盈止损、减仓单 | ||||
| // mainId 主单id | ||||
| // symbolType 交易对类型 | ||||
| func GetSymbolTakeAndStop(db *gorm.DB, mainId int, symbolType int) ([]models.LinePreOrder, error) { | ||||
| @ -170,3 +175,69 @@ func GetChildTpOrder(db *gorm.DB, pid int) (int, error) { | ||||
|  | ||||
| 	return int(count), nil | ||||
| } | ||||
|  | ||||
| // 创建减仓后减仓单 | ||||
| func CreateReduceReduceOrder(db *gorm.DB, pid int, price, num decimal.Decimal, priceDigit int) (models.LinePreOrder, error) { | ||||
| 	var preOrder models.LinePreOrder | ||||
| 	var result models.LinePreOrder | ||||
| 	var ext models.LinePreOrderExt | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Preload("Childs").Where("id =? ", pid).First(&preOrder).Error; err != nil { | ||||
| 		return preOrder, err | ||||
| 	} | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrderExt{}).Where("order_id =? ", pid).Find(&ext).Error; err != nil { | ||||
| 		return preOrder, err | ||||
| 	} | ||||
|  | ||||
| 	copier.Copy(&result, &preOrder) | ||||
|  | ||||
| 	result.Id = 0 | ||||
| 	result.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) | ||||
| 	result.Status = 0 | ||||
| 	result.CreatedAt = time.Now() | ||||
| 	result.TriggerTime = nil | ||||
| 	result.UpdatedAt = time.Now() | ||||
| 	result.BuyPrice = decimal.Zero.String() | ||||
| 	result.Price = price.String() | ||||
| 	result.Num = num.String() | ||||
| 	result.ReduceOrderId = preOrder.Id | ||||
|  | ||||
| 	for index := range result.Childs { | ||||
| 		result.Childs[index].Id = 0 | ||||
| 		result.Childs[index].OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) | ||||
| 		result.Childs[index].Status = 0 | ||||
| 		result.Childs[index].CreatedAt = time.Now() | ||||
| 		result.Childs[index].TriggerTime = nil | ||||
| 		result.Childs[index].UpdatedAt = time.Now() | ||||
| 		result.Childs[index].BuyPrice = decimal.Zero.String() | ||||
| 		var pricePercent decimal.Decimal | ||||
|  | ||||
| 		if result.Childs[index].OrderType == 1 && ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 { | ||||
| 			// 减仓单卖出 | ||||
| 			if preOrder.Site == "SELL" { | ||||
| 				pricePercent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio).Div(decimal.NewFromInt(100)) | ||||
| 			} else { | ||||
| 				pricePercent = decimal.NewFromInt(100).Sub(ext.TakeProfitRatio).Div(decimal.NewFromInt(100)) | ||||
| 			} | ||||
|  | ||||
| 		} else if result.Childs[index].OrderType == 2 && ext.StopLossRatio.Cmp(decimal.Zero) > 0 { | ||||
| 			if preOrder.Site == "SELL" { | ||||
| 				pricePercent = decimal.NewFromInt(100).Sub(ext.StopLossRatio).Div(decimal.NewFromInt(100)) | ||||
| 			} else { | ||||
| 				pricePercent = decimal.NewFromInt(100).Add(ext.StopLossRatio).Div(decimal.NewFromInt(100)) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//重新计算止盈止损价 | ||||
| 		if pricePercent.Cmp(decimal.Zero) > 0 { | ||||
| 			result.Childs[index].Price = price.Mul(pricePercent).Truncate(int32(priceDigit)).String() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := db.Create(&result).Error; err != nil { | ||||
| 		return result, fmt.Errorf("复制减仓单失败:pid:%d err:%v", pid, err) | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										178
									
								
								services/binanceservice/reduce_order_strategy_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								services/binanceservice/reduce_order_strategy_service.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,178 @@ | ||||
| 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" | ||||
| 	"go-admin/pkg/utility" | ||||
|  | ||||
| 	"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.LineOrderReduceStrategy{} | ||||
| 	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 { | ||||
| 		items := make([]models.LineReduceStrategyItem, 0) | ||||
| 		strategyCache := dto.LineOrderReduceStrategyResp{ | ||||
| 			OrderId: reduceOrder.Id, | ||||
| 			Symbol:  reduceOrder.Symbol, | ||||
| 			MainId:  reduceOrder.MainId, | ||||
| 			Side:    reduceOrder.Side, | ||||
| 		} | ||||
|  | ||||
| 		sonic.Unmarshal([]byte(reduceOrderStrategy.ItemContent), &items) | ||||
|  | ||||
| 		for _, item := range items { | ||||
| 			var rate decimal.Decimal | ||||
| 			var triggerRate decimal.Decimal | ||||
|  | ||||
| 			if reduceOrder.Side == "SELL" { | ||||
| 				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)) | ||||
| 			num := reduceOrder.Num | ||||
|  | ||||
| 			//百分比大于0就重新计算 否则就是主减仓单数量 | ||||
| 			if item.QuantityPercent.Cmp(decimal.Zero) > 0 { | ||||
| 				num = reduceOrder.Num.Mul(item.QuantityPercent.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 			} | ||||
|  | ||||
| 			strategyCache.Items = append(strategyCache.Items, dto.LineOrderReduceStrategyRespItem{ | ||||
| 				LossPercent:  item.LossPercent, | ||||
| 				OrderType:    item.OrderType, | ||||
| 				Price:        price, | ||||
| 				TriggerPrice: triggerPrice, | ||||
| 				Num:          num, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		str, _ := sonic.MarshalString(strategyCache) | ||||
|  | ||||
| 		if str != "" { | ||||
| 			if err := helper.DefaultRedis.HSetField(key, utility.IntToString(reduceOrder.Id), 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.HGetAllFields(key) | ||||
| 	cache := dto.LineOrderReduceStrategyResp{} | ||||
|  | ||||
| 	for _, v := range arrays { | ||||
| 		sonic.Unmarshal([]byte(v), &cache) | ||||
|  | ||||
| 		if cache.OrderId == reduceOrderId { | ||||
| 			if err := helper.DefaultRedis.HDelField(key, utility.IntToString(cache.OrderId)); 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 | ||||
| } | ||||
|  | ||||
| // 移除减仓后减仓策略 | ||||
| // mainId 主单id | ||||
| // symbolType 交易对类型 | ||||
| func RemoveReduceReduceCacheByMainId(mainId int, symbolType int) error { | ||||
| 	var key string | ||||
| 	switch 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.HGetAllFields(key) | ||||
| 	cache := dto.LineOrderReduceStrategyResp{} | ||||
|  | ||||
| 	for _, v := range arrays { | ||||
| 		sonic.Unmarshal([]byte(v), &cache) | ||||
|  | ||||
| 		if cache.MainId == mainId { | ||||
| 			if err := helper.DefaultRedis.HDelField(key, utility.IntToString(cache.OrderId)); err != nil { | ||||
| 				logger.Errorf("移除减仓单减仓策略失败redis err:%v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										64
									
								
								services/binanceservice/spot_judge_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								services/binanceservice/spot_judge_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/common/const/rediskey" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	"go-admin/models" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func TestSpotJudge(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
| 	// tradeSet := models.TradeSet{ | ||||
| 	// 	Coin:      "ADA", | ||||
| 	// 	Currency:  "USDT", | ||||
| 	// 	LastPrice: "0.516", | ||||
| 	// } | ||||
|  | ||||
| 	key := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE) | ||||
| 	item := `{"id":66,"apiId":49,"mainId":63,"pid":63,"symbol":"ADAUSDT","price":"0.5912","side":"SELL","num":"12.6","orderSn":"397919643961917440"}` | ||||
| 	reduceOrder := ReduceListItem{} | ||||
| 	spotApi := SpotRestApi{} | ||||
| 	setting, err := cacheservice.GetSystemSetting(db) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		logger.Error("获取系统设置失败") | ||||
| 		return | ||||
| 	} | ||||
| 	if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { | ||||
| 		logger.Error("反序列化失败") | ||||
| 		return | ||||
| 	} | ||||
| 	// JudgeFuturesReduce(tradeSet) | ||||
| 	SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false, 0) | ||||
| } | ||||
|  | ||||
| // 测试减仓后减仓触发 | ||||
| func TestSpotReduceReduce(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
| 	tradeSet := models.TradeSet{ | ||||
| 		Coin:       "ADA", | ||||
| 		Currency:   "USDT", | ||||
| 		LastPrice:  "0.5793", | ||||
| 		PriceDigit: 4, | ||||
| 	} | ||||
|  | ||||
| 	// JudgeFuturesReduce(tradeSet) | ||||
| 	JudgeSpotReduce(tradeSet) | ||||
| } | ||||
| @ -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,23 +257,94 @@ 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 | ||||
| 	} | ||||
|  | ||||
| 	reduceOrder := ReduceListItem{} | ||||
| 	tradePrice, _ := decimal.NewFromString(trade.LastPrice) | ||||
| 	//减仓单减仓策略 | ||||
| 	reduceReduceListKey := fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE) | ||||
| 	orderReduceVal, _ := helper.DefaultRedis.HGetAllFields(reduceReduceListKey) | ||||
| 	reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} | ||||
|  | ||||
| 	for _, item := range orderReduceVal { | ||||
| 		sonic.Unmarshal([]byte(item), &reduceOrderStrategy) | ||||
|  | ||||
| 		for index, 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 { | ||||
| 					lock := helper.NewRedisLock(fmt.Sprintf(rediskey.ReduceStrategySpotTriggerLock, reduceOrder.ApiId, reduceOrder.Symbol), 50, 15, 100*time.Millisecond) | ||||
|  | ||||
| 					if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 						log.Error("获取锁失败", err) | ||||
| 						return | ||||
| 					} else if ok { | ||||
| 						defer lock.Release() | ||||
| 						hasrecord, _ := helper.DefaultRedis.HExists(reduceReduceListKey, utility.IntTostring(reduceOrderStrategy.OrderId), item) | ||||
|  | ||||
| 						if !hasrecord { | ||||
| 							log.Debug("减仓缓存中不存在", item) | ||||
| 							return | ||||
| 						} | ||||
|  | ||||
| 						order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.PriceDigit) | ||||
|  | ||||
| 						if err != nil { | ||||
| 							log.Errorf("%d 生成订单失败", reduceOrderStrategy.OrderId) | ||||
| 						} | ||||
|  | ||||
| 						reduceOrder.ApiId = order.ApiId | ||||
| 						reduceOrder.Id = order.Id | ||||
| 						reduceOrder.Pid = order.Pid | ||||
| 						reduceOrder.MainId = order.MainId | ||||
| 						reduceOrder.Symbol = order.Symbol | ||||
| 						reduceOrder.Side = reduceOrderStrategy.Side | ||||
| 						reduceOrder.OrderSn = order.OrderSn | ||||
| 						reduceOrder.Price = item2.Price | ||||
| 						reduceOrder.Num = item2.Num | ||||
| 						if SpotReduceTrigger(db, reduceOrder, spotApi, setting, reduceReduceListKey, item, true, reduceOrderStrategy.OrderId) { | ||||
| 							reduceOrderStrategy.Items[index].Actived = true | ||||
| 							allActive := true | ||||
| 							orderId := utility.IntToString(reduceOrderStrategy.OrderId) | ||||
|  | ||||
| 							for _, item3 := range reduceOrderStrategy.Items { | ||||
| 								if !item3.Actived { | ||||
| 									allActive = false | ||||
| 									break | ||||
| 								} | ||||
| 							} | ||||
|  | ||||
| 							if allActive { | ||||
| 								if err := helper.DefaultRedis.HDelField(reduceReduceListKey, orderId); err != nil { | ||||
| 									log.Errorf("删除redis reduceReduceListKey失败 %s", err.Error()) | ||||
| 								} | ||||
| 							} else { | ||||
| 								str, _ := sonic.MarshalString(reduceOrderStrategy) | ||||
| 								if err := helper.DefaultRedis.HSetField(reduceReduceListKey, orderId, str); err != nil { | ||||
| 									log.Errorf("更新redis reduceReduceListKey失败 %s", err.Error()) | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	//减仓单 | ||||
| 	reduceVal, _ := helper.DefaultRedis.GetAllList(key) | ||||
|  | ||||
| 	for _, item := range reduceVal { | ||||
| 		reduceOrder := ReduceListItem{} | ||||
| 		if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { | ||||
| 			log.Error("反序列化失败") | ||||
| 			continue | ||||
| @ -280,59 +352,67 @@ 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 && | ||||
| 				orderPrice.Cmp(decimal.Zero) > 0 && | ||||
| 				tradePrice.Cmp(decimal.Zero) > 0 { | ||||
|  | ||||
| 				SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item) | ||||
| 				SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false, 0) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 触发现货减仓 | ||||
| func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string) { | ||||
| 	tradeSet, err := GetTradeSet(reduceOrder.Symbol, 0) | ||||
| // isStrategy 是否是策略减仓单 | ||||
| // reduceId 策略主减仓单id | ||||
| func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool, reduceId int) bool { | ||||
| 	tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 0) | ||||
| 	result := true | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Error("获取交易设置失败") | ||||
| 		return | ||||
| 		return false | ||||
| 	} | ||||
| 	lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, reduceOrder.ApiId, reduceOrder.Symbol), 20, 5, 100*time.Millisecond) | ||||
|  | ||||
| 	if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 		log.Error("获取锁失败", err) | ||||
| 		return | ||||
| 		return false | ||||
| 	} else if ok { | ||||
| 		defer lock.Release() | ||||
| 		takeOrders := make([]DbModels.LinePreOrder, 0) | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type =1 AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN (1,2,4) AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { | ||||
| 			log.Error("查询止盈单失败") | ||||
| 			return | ||||
| 			return false | ||||
| 		} | ||||
| 		var hasrecord bool | ||||
|  | ||||
| 		if isStrategy { | ||||
| 			hasrecord, _ = helper.DefaultRedis.HExists(key, utility.IntToString(reduceId), item) | ||||
| 		} else { | ||||
| 			hasrecord, _ = helper.DefaultRedis.IsElementInList(key, item) | ||||
| 		} | ||||
| 		hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) | ||||
|  | ||||
| 		if !hasrecord { | ||||
| 			log.Debug("减仓缓存中不存在", item) | ||||
| 			return | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		apiInfo, _ := GetApiInfo(reduceOrder.ApiId) | ||||
|  | ||||
| 		if apiInfo.Id == 0 { | ||||
| 			log.Error("现货减仓 查询api用户不存在") | ||||
| 			return | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		for _, takeOrder := range takeOrders { | ||||
| 			err := CancelOpenOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				log.Error("现货止盈撤单失败", err) | ||||
| 				return | ||||
| 				log.Error("现货撤单失败", err) | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		price := reduceOrder.Price.Mul(decimal.NewFromInt(1).Sub(setting.ReducePremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)) | ||||
| @ -349,6 +429,7 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest | ||||
| 		} | ||||
|  | ||||
| 		if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { | ||||
| 			result = false | ||||
| 			log.Errorf("现货减仓挂单失败 id:%s err:%v", reduceOrder.Id, err) | ||||
|  | ||||
| 			if err2 := db.Model(&DbModels.LinePreOrder{}). | ||||
| @ -366,14 +447,23 @@ 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) | ||||
| 			} | ||||
|  | ||||
| 			//处理减仓单减仓策略 | ||||
| 			if err := CacheOrderStrategyAndReCreate(db, reduceOrder, 1, tradeSet, setting); err != nil { | ||||
| 				log.Errorf("现货减仓 处理减仓策略失败 id:%v err:%v", reduceOrder.Id, err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if _, err := helper.DefaultRedis.LRem(key, item); err != nil { | ||||
| 			result = false | ||||
| 			log.Errorf("现货减仓 删除缓存失败 id:%v err:%v", reduceOrder.Id, err) | ||||
| 		} | ||||
| 	} else { | ||||
| 		log.Error("获取锁失败") | ||||
| 		result = false | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // 判断现货加仓 | ||||
| @ -414,8 +504,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) | ||||
|  | ||||
| @ -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,20 +166,29 @@ 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) | ||||
|  | ||||
| 	if err := db.Model(&DbModels.LinePreOrderStatus{}).Where("order_id =? ", preOrder.MainId).Update("reduce_status", 1).Error; err != nil { | ||||
| 		logger.Errorf("handleMainReduceFilled 更新主单减仓状态失败,订单号:%s", preOrder.OrderSn) | ||||
| 	} | ||||
| 	db.Model(&orderExt).Where("order_id =?", preOrder.Id).Find(&orderExt) | ||||
|  | ||||
| 	//策略减仓单 获取主减仓单拓展信息 | ||||
| 	if preOrder.ReduceOrderId > 0 { | ||||
| 		db.Model(&orderExt).Where("order_id =?", preOrder.ReduceOrderId).Find(&orderExt) | ||||
| 	} else { | ||||
| 		db.Model(&orderExt).Where("order_id =?", preOrder.Id).Find(&orderExt) | ||||
| 	} | ||||
|  | ||||
| 	lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotReduceCallback, preOrder.ApiId, preOrder.Symbol), 120, 20, 100*time.Millisecond) | ||||
|  | ||||
| @ -278,7 +288,7 @@ func nextSpotReduceTrigger(db *gorm.DB, mainId int, totalNum decimal.Decimal, tr | ||||
| 	nextExt := DbModels.LinePreOrderExt{} | ||||
| 	// var percentag decimal.Decimal | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0 AND reduce_order_id =0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { | ||||
| 		logger.Errorf("获取下一个单失败 err:%v", err) | ||||
| 		return true | ||||
| 	} | ||||
| @ -546,6 +556,9 @@ func removeSpotLossAndAddPosition(mainId int, orderSn string) { | ||||
| 	addPosition := AddPositionList{} | ||||
| 	reduce := ReduceListItem{} | ||||
|  | ||||
| 	//移除减仓后减仓策略 | ||||
| 	RemoveReduceReduceCacheByMainId(mainId, 1) | ||||
|  | ||||
| 	//止损缓存 | ||||
| 	for _, v := range stoplossVal { | ||||
| 		sonic.Unmarshal([]byte(v), &stoploss) | ||||
| @ -731,7 +744,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 +882,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 +954,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{} | ||||
|  | ||||
							
								
								
									
										510
									
								
								services/binanceservice/strategy_order_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										510
									
								
								services/binanceservice/strategy_order_service.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,510 @@ | ||||
| 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" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| type BinanceStrategyOrderService struct { | ||||
| 	service.Service | ||||
| 	Debug bool | ||||
| } | ||||
|  | ||||
| // 判断是否触发波段订单 | ||||
| func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) { | ||||
| 	//现货 | ||||
| 	orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType)) | ||||
| 	if len(orderStrs) > 0 { | ||||
| 		e.DoJudge(orderStrs, 1, exchangeType) | ||||
| 	} | ||||
|  | ||||
| 	//合约 | ||||
| 	futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType)) | ||||
|  | ||||
| 	if len(futOrdedrStrs) > 0 { | ||||
| 		e.DoJudge(futOrdedrStrs, 2, exchangeType) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 判断是否符合条件 | ||||
| func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) { | ||||
| 	db := GetDBConnection() | ||||
| 	// setting, _ := cacheservice.GetSystemSetting(db) | ||||
|  | ||||
| 	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.OrderSn), 60, 20, 300*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, symbolType, exchangeType) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err) | ||||
| 			} | ||||
|  | ||||
| 			if e.Debug || success { | ||||
| 				e.TriggerOrder(db, orderStr, 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 | ||||
| 	var startPrice decimal.Decimal | ||||
| 	var lastPrice decimal.Decimal | ||||
| 	startPriceArgs := strings.Split(beforePrice, ":") | ||||
| 	endPricecArgs := strings.Split(lastPrices[0].Member.(string), ":") | ||||
|  | ||||
| 	if len(startPriceArgs) == 0 || len(endPricecArgs) == 0 { | ||||
| 		return result, errors.New("获取交易对起止价格失败") | ||||
| 	} | ||||
| 	startPrice = utility.StrToDecimal(startPriceArgs[len(startPriceArgs)-1]) | ||||
| 	lastPrice = utility.StrToDecimal(endPricecArgs[len(endPricecArgs)-1]) | ||||
|  | ||||
| 	//时间差超过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) | ||||
| 	logger.Infof("百分比:%s", percentag.Mul(decimal.NewFromInt(100)).String()) | ||||
| 	//价格没有变动 | ||||
| 	if percentag.Cmp(decimal.Zero) == 0 { | ||||
| 		return result, nil | ||||
| 	} | ||||
|  | ||||
| 	//满足条件 | ||||
| 	switch { | ||||
| 	//涨价格大于0.5% 跌价格小于-0.5% | ||||
| 	case order.CompareType == 1 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, | ||||
| 		order.CompareType == 1 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: | ||||
| 		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0 | ||||
|  | ||||
| 	case order.CompareType == 2 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, | ||||
| 		order.CompareType == 2 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: | ||||
| 		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0 | ||||
|  | ||||
| 	case order.CompareType == 5 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, | ||||
| 		order.CompareType == 5 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: | ||||
| 		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0 | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // 触发委托单 | ||||
| func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string, order dto.StrategyOrderRedisList, symbolType int) error { | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =? or id =?", order.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("获取系统设置失败") | ||||
| 	} | ||||
|  | ||||
| 	var tradeSet models2.TradeSet | ||||
|  | ||||
| 	switch symbolType { | ||||
| 	case 1: | ||||
| 		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 0) | ||||
| 	case 2: | ||||
| 		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) | ||||
| 	default: | ||||
| 		return errors.New("获取交易对行情失败,交易对类型错误") | ||||
| 	} | ||||
|  | ||||
| 	if tradeSet.Coin == "" { | ||||
| 		return errors.New("获取交易对行情失败") | ||||
| 	} | ||||
|  | ||||
| 	lastPrice := utility.StrToDecimal(tradeSet.LastPrice) | ||||
|  | ||||
| 	if lastPrice.Cmp(decimal.Zero) <= 0 { | ||||
| 		return errors.New("最新成交价小于等于0") | ||||
| 	} | ||||
|  | ||||
| 	mainOrder := models.LinePreOrder{} | ||||
|  | ||||
| 	for _, v := range orders { | ||||
| 		if v.MainId == 0 { | ||||
| 			mainOrder = v | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if mainOrder.Id == 0 { | ||||
| 		return errors.New("获取主单失败") | ||||
| 	} | ||||
|  | ||||
| 	GetOrderByPid(&mainOrder, orders, mainOrder.Id) | ||||
|  | ||||
| 	e.RecalculateOrder(tradeSet, &mainOrder, setting) | ||||
|  | ||||
| 	//事务保存 | ||||
| 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		if err1 := tx.Save(&mainOrder).Error; err1 != nil { | ||||
| 			return err1 | ||||
| 		} | ||||
|  | ||||
| 		for _, v := range mainOrder.Childs { | ||||
| 			if err1 := tx.Save(&v).Error; err1 != nil { | ||||
| 				return err1 | ||||
| 			} | ||||
|  | ||||
| 			for _, v2 := range v.Childs { | ||||
| 				if err1 := tx.Save(&v2).Error; err1 != nil { | ||||
| 					return err1 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("order_id:%d 波段触发保存委托单失败:%s", mainOrder.Id, err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	e.StrategyOrderPlace(db, cacheVal, mainOrder, tradeSet) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 策略触发订单 | ||||
| // cacheVal:缓存值 | ||||
| // mainOrder:主订单 | ||||
| // tradeSet:交易对行情 | ||||
| func (e *BinanceStrategyOrderService) StrategyOrderPlace(db *gorm.DB, cacheVal string, mainOrder models.LinePreOrder, tradeSet models2.TradeSet) { | ||||
| 	price := utility.StringToDecimal(mainOrder.Price).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 	num := utility.StringToDecimal(mainOrder.Num).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 	futApi := FutRestApi{} | ||||
| 	var key string | ||||
|  | ||||
| 	switch mainOrder.SymbolType { | ||||
| 	case 1: | ||||
| 		key = fmt.Sprintf(rediskey.StrategySpotOrderList, global.EXCHANGE_BINANCE) | ||||
| 	case 2: | ||||
| 		key = fmt.Sprintf(rediskey.StrategyFutOrderList, global.EXCHANGE_BINANCE) | ||||
| 	default: | ||||
| 		logger.Errorf("id:%d 交易对类型不存在:%d", mainOrder.Id, mainOrder.SymbolType) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	params := FutOrderPlace{ | ||||
| 		ApiId:            mainOrder.ApiId, | ||||
| 		Symbol:           mainOrder.Symbol, | ||||
| 		Side:             mainOrder.Site, | ||||
| 		OrderType:        mainOrder.MainOrderType, | ||||
| 		SideType:         mainOrder.MainOrderType, | ||||
| 		Price:            price, | ||||
| 		Quantity:         num, | ||||
| 		NewClientOrderId: mainOrder.OrderSn, | ||||
| 	} | ||||
|  | ||||
| 	if err := futApi.OrderPlaceLoop(db, params, 3); err != nil { | ||||
| 		logger.Error("下单失败", mainOrder.Symbol, " err:", err) | ||||
| 		if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil { | ||||
| 			logger.Error("删除redis 预下单失败:", err) | ||||
| 		} | ||||
|  | ||||
| 		err := db.Model(&models.LinePreOrder{}).Where("id =? and status='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Error("更新预下单状态失败") | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 	} else { | ||||
| 		if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil { | ||||
| 			logger.Error("删除redis 预下单失败:", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("id =? ", mainOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil { | ||||
| 		logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1") | ||||
| 	} | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("id =? AND status ='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "1"}).Error; err != nil { | ||||
| 		logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 重新计算订单单价、数量 | ||||
| // 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_order_id =?", mainOrder.Id).Find(&exts).Error; err != nil { | ||||
| 		return errors.New("获取拓展信息失败") | ||||
| 	} | ||||
|  | ||||
| 	var newPrice decimal.Decimal | ||||
| 	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() | ||||
| 	remainQuantity := totalNum | ||||
| 	var totalLossAmount decimal.Decimal | ||||
| 	prePrice := lastPrice | ||||
|  | ||||
| 	for index := range mainOrder.Childs { | ||||
| 		var ext models.LinePreOrderExt | ||||
| 		extOrderId := mainOrder.Childs[index].Id | ||||
| 		takeStopArray := []int{1, 2} | ||||
|  | ||||
| 		//止盈止损 ext拓展id为 pid | ||||
| 		if utility.ContainsInt(takeStopArray, mainOrder.Childs[index].OrderType) { | ||||
| 			extOrderId = mainOrder.Childs[index].Pid | ||||
| 		} | ||||
|  | ||||
| 		for _, v := range exts { | ||||
| 			if v.OrderId == extOrderId { | ||||
| 				ext = v | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if ext.Id <= 0 { | ||||
| 			logger.Errorf("子订单ext不存在 id:%d", mainOrder.Childs[index].Id) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		//主单止盈、止损 | ||||
| 		if mainOrder.Childs[index].Pid == mainOrder.Childs[index].MainId && (mainOrder.Childs[index].OrderType == 1 || mainOrder.Childs[index].OrderType == 2) { | ||||
| 			var percent decimal.Decimal | ||||
|  | ||||
| 			switch { | ||||
| 			// 加价 | ||||
| 			case mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "SELL": | ||||
| 				percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio) | ||||
| 				//减价 | ||||
| 			case mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "SELL": | ||||
| 				percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio) | ||||
| 			} | ||||
|  | ||||
| 			childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 			mainOrder.Childs[index].Price = childPrice.String() | ||||
| 			mainOrder.Childs[index].Num = totalNum.String() | ||||
|  | ||||
| 		} else { | ||||
| 			lastNum := remainQuantity | ||||
|  | ||||
| 			//过期时间 | ||||
| 			if ext.ExpirateHour <= 0 { | ||||
| 				mainOrder.Childs[index].ExpireTime = time.Now().AddDate(10, 0, 0) | ||||
| 			} else { | ||||
| 				mainOrder.Childs[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour)) | ||||
| 			} | ||||
|  | ||||
| 			switch { | ||||
| 			//加仓单 | ||||
| 			case mainOrder.Childs[index].OrderType == 1 && mainOrder.Childs[index].OrderCategory == 3: | ||||
| 				var percentage decimal.Decimal | ||||
|  | ||||
| 				if mainOrder.Site == "BUY" { | ||||
| 					percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} else { | ||||
| 					percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} | ||||
|  | ||||
| 				dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 				mainOrder.Childs[index].Price = dataPrice.String() | ||||
|  | ||||
| 				priceDiff := dataPrice.Sub(prePrice).Abs() | ||||
| 				totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff)) | ||||
|  | ||||
| 				if ext.AddPositionType == 1 { | ||||
| 					buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2) | ||||
| 					mainOrder.Childs[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() | ||||
|  | ||||
| 				} else { | ||||
| 					mainOrder.Childs[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 				} | ||||
|  | ||||
| 				//加库存 | ||||
| 				lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Childs[index].Num)) | ||||
| 				// 计算子订单 | ||||
| 				if len(mainOrder.Childs[index].Childs) > 0 { | ||||
| 					calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false) | ||||
| 				} | ||||
| 				//覆盖最近的订单价 | ||||
| 				prePrice = dataPrice | ||||
| 				//减仓单 | ||||
| 			case mainOrder.Childs[index].OrderType == 4: | ||||
| 				percentage := decimal.NewFromInt(1) | ||||
|  | ||||
| 				if mainOrder.Site == "BUY" && ext.PriceRatio.Cmp(decimal.Zero) > 0 { | ||||
| 					percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} else if ext.PriceRatio.Cmp(decimal.Zero) > 0 { | ||||
| 					percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} | ||||
|  | ||||
| 				dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 				mainOrder.Childs[index].Price = dataPrice.String() | ||||
| 				priceDiff := dataPrice.Sub(prePrice).Abs() | ||||
| 				totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff)) | ||||
|  | ||||
| 				//百分比减仓 | ||||
| 				if ext.AddPositionType == 1 { | ||||
| 					mainOrder.Childs[index].Num = lastNum.Mul(ext.AddPositionVal.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String() | ||||
|  | ||||
| 				} else { | ||||
| 					logger.Error("减仓不能是固定数值") | ||||
| 				} | ||||
|  | ||||
| 				//减库存 | ||||
| 				lastNum = lastNum.Sub(utility.StrToDecimal(mainOrder.Childs[index].Num)) | ||||
| 				// 计算子订单 | ||||
| 				if len(mainOrder.Childs[index].Childs) > 0 { | ||||
| 					calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false) | ||||
| 				} | ||||
| 				//覆盖最近的订单价 | ||||
| 				prePrice = dataPrice | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 计算子订单信息 | ||||
| // isTpTp 是否是止盈后止损止盈 | ||||
| // totalLossAmount 累计亏损金额 | ||||
| func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeSet, ext models.LinePreOrderExt, lastNum decimal.Decimal, price decimal.Decimal, totalLossAmount decimal.Decimal, isTpTp bool) error { | ||||
| 	for index := range *orders { | ||||
| 		orderQuantity := lastNum.Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		percentage := decimal.NewFromInt(1) | ||||
| 		var addPercentage decimal.Decimal | ||||
|  | ||||
| 		switch { | ||||
| 		//止盈 | ||||
| 		case !isTpTp && (*orders)[index].OrderType == 1: | ||||
| 			addPercentage = ext.TakeProfitRatio | ||||
| 			if ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 { | ||||
| 				orderQuantity = lastNum.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 			} | ||||
| 		//止损 | ||||
| 		case !isTpTp && (*orders)[index].OrderType == 2: | ||||
| 			addPercentage = ext.StopLossRatio | ||||
| 		//止盈后止盈 | ||||
| 		case isTpTp && (*orders)[index].OrderType == 1: | ||||
| 			addPercentage = ext.TpTpPriceRatio | ||||
| 		//止盈后止损 | ||||
| 		case isTpTp && (*orders)[index].OrderType == 2: | ||||
| 			addPercentage = ext.TpSlPriceRatio | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		//做多止盈、做空止损 | ||||
| 		case (*orders)[index].OrderType == 1 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 2 && (*orders)[index].Site == "BUY": | ||||
| 			percentage = decimal.NewFromInt(100).Add(addPercentage) | ||||
|  | ||||
| 			//做多止损、做空止盈 | ||||
| 		case (*orders)[index].OrderType == 2 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 1 && (*orders)[index].Site == "BUY": | ||||
| 			percentage = decimal.NewFromInt(100).Sub(addPercentage) | ||||
| 		} | ||||
|  | ||||
| 		//止盈亏损回本百分比 | ||||
| 		if (*orders)[index].OrderType == 1 && totalLossAmount.Cmp(decimal.Zero) > 0 { | ||||
| 			lossPercent := totalLossAmount.Div(lastNum).Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 			percentage = percentage.Add(lossPercent) | ||||
| 		} | ||||
|  | ||||
| 		if percentage.Cmp(decimal.Zero) > 0 { | ||||
| 			percentage = percentage.Div(decimal.NewFromInt(100)) | ||||
| 		} | ||||
|  | ||||
| 		orderPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 		(*orders)[index].Price = orderPrice.String() | ||||
| 		(*orders)[index].Num = orderQuantity.String() | ||||
| 		lastOrderQuantity := lastNum.Sub(orderQuantity).Truncate(int32(tradeSet.AmountDigit)) | ||||
|  | ||||
| 		//止盈后止盈、止盈后止损 | ||||
| 		if len((*orders)[index].Childs) > 0 && lastOrderQuantity.Cmp(decimal.Zero) > 0 { | ||||
| 			calculateChildOrder(&(*orders)[index].Childs, tradeSet, ext, lastOrderQuantity, orderPrice, decimal.Zero, true) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										26
									
								
								services/binanceservice/strategy_order_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								services/binanceservice/strategy_order_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // 测试策略 触发单 | ||||
| func TestTriggerOrder(t *testing.T) { | ||||
| 	service := BinanceStrategyOrderService{} | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
|  | ||||
| 	service.Orm = db | ||||
| 	service.Debug = true | ||||
|  | ||||
| 	service.TriggerStrategyOrder(global.EXCHANGE_BINANCE) | ||||
| } | ||||
| @ -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,71 @@ 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) { | ||||
| 	// 定义返回结果和val变量 | ||||
| 	result := models2.TradeSet{} | ||||
| 	val := "" | ||||
|  | ||||
| 	// 根据交易对类型选择不同的key | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| 	// 如果val不为空,则解析val为TradeSet结构体 | ||||
| 	if val != "" { | ||||
| 		if err := sonic.Unmarshal([]byte(val), &result); err != nil { | ||||
| 			return result, err | ||||
| 		} | ||||
| 	} else { | ||||
| 		// 如果val为空,则返回错误信息 | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"go-admin/common/const/rediskey" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	"go-admin/config" | ||||
| @ -15,6 +16,7 @@ import ( | ||||
| 	"go-admin/models" | ||||
|  | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/shopspring/decimal" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"go.uber.org/zap" | ||||
| @ -133,7 +135,19 @@ func handleTickerAllMessage(msg []byte) { | ||||
| 			// log.Debug(symbol, "ticker@all ws处理失败", err) | ||||
| 			continue | ||||
| 		} | ||||
| 		tradeSet.LastPrice = utility.StringFloat64Cut(data["c"].(string), int32(tradeSet.PriceDigit)) | ||||
| 		var utcTime int64 | ||||
| 		var lastPrice decimal.Decimal | ||||
| 		lastPriceKey := fmt.Sprintf(rediskey.FutureTickerLastPrice, global.EXCHANGE_BINANCE, symbol) | ||||
|  | ||||
| 		if e, ok := data["E"]; ok { | ||||
| 			utcTime = utility.ToInt64(e) | ||||
| 		} | ||||
|  | ||||
| 		if e, ok := data["c"]; ok { | ||||
| 			lastPrice = utility.StrToDecimal(e.(string)).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 		} | ||||
|  | ||||
| 		tradeSet.LastPrice = lastPrice.String() | ||||
| 		tradeSet.OpenPrice = utility.StrToFloatCut(data["o"].(string), int32(tradeSet.PriceDigit)) | ||||
| 		tradeSet.HighPrice = utility.StringFloat64Cut(data["h"].(string), int32(tradeSet.PriceDigit)) | ||||
| 		tradeSet.LowPrice = utility.StringFloat64Cut(data["l"].(string), int32(tradeSet.PriceDigit)) | ||||
| @ -147,6 +161,21 @@ func handleTickerAllMessage(msg []byte) { | ||||
| 				log.Error(symbol, "ticker@all ws处理失败", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		val, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice) | ||||
|  | ||||
| 		if utility.ContainsStr(val, symbol) { | ||||
| 			//行情存储时间 | ||||
| 			lastUtc := utcTime - 1000*60*60 | ||||
| 			content := fmt.Sprintf("%d:%s", utcTime, lastPrice.String()) | ||||
| 			if _, err := helper.DefaultRedis.RemoveBeforeScore(lastPriceKey, float64(lastUtc)); err != nil { | ||||
| 				log.Errorf("移除 合约交易对:%s %d之前的最新成交价失败,err:%v", symbol, lastUtc, err) | ||||
| 			} | ||||
|  | ||||
| 			if err := helper.DefaultRedis.AddSortSet(lastPriceKey, float64(utcTime), content); err != nil { | ||||
| 				log.Errorf("添加 合约交易对:%s %d之前的最新成交价失败,err:%v", symbol, lastUtc, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(trades) > 0 { | ||||
|  | ||||
| @ -204,6 +204,7 @@ func handleTickerMessage(msg []byte) { | ||||
| 		} | ||||
|  | ||||
| 		key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbolName) | ||||
| 		lastPriceKey := fmt.Sprintf(rediskey.SpotTickerLastPrice, global.EXCHANGE_BINANCE, symbolName) | ||||
| 		tcVal, _ := helper.DefaultRedis.GetString(key) | ||||
| 		tradeSet := models.TradeSet{} | ||||
| 		if err := sonic.UnmarshalString(tcVal, &tradeSet); err != nil { | ||||
| @ -212,7 +213,19 @@ func handleTickerMessage(msg []byte) { | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		tradeSet.LastPrice = utility.StringFloat64Cut(dataMap["c"].(string), int32(tradeSet.PriceDigit)) | ||||
|  | ||||
| 		var utcTime int64 | ||||
| 		var lastPrice decimal.Decimal | ||||
|  | ||||
| 		if e, ok := dataMap["E"]; ok { | ||||
| 			utcTime = utility.ToInt64(e) | ||||
| 		} | ||||
|  | ||||
| 		if e, ok := dataMap["c"]; ok { | ||||
| 			lastPrice = utility.StrToDecimal(e.(string)).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 		} | ||||
|  | ||||
| 		tradeSet.LastPrice = lastPrice.String() | ||||
| 		tradeSet.OpenPrice = utility.StrToFloatCut(dataMap["o"].(string), int32(tradeSet.PriceDigit)) | ||||
| 		tradeSet.HighPrice = utility.StringFloat64Cut(dataMap["h"].(string), int32(tradeSet.PriceDigit)) | ||||
| 		tradeSet.LowPrice = utility.StringFloat64Cut(dataMap["l"].(string), int32(tradeSet.PriceDigit)) | ||||
| @ -236,6 +249,22 @@ func handleTickerMessage(msg []byte) { | ||||
| 				log.Error("redis保存交易对失败", tradeSet.Coin, tradeSet.Currency) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		val, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice) | ||||
|  | ||||
| 		if utility.ContainsStr(val, symbolName) { | ||||
| 			//行情存储时间 | ||||
| 			lastUtc := utcTime - 1000*60*60 | ||||
| 			content := fmt.Sprintf("%d:%s", utcTime, lastPrice.String()) | ||||
|  | ||||
| 			if _, err := helper.DefaultRedis.RemoveBeforeScore(lastPriceKey, float64(lastUtc)); err != nil { | ||||
| 				log.Errorf("移除 现货交易对:%s %d之前的最新成交价失败,err:%v", symbolName, lastUtc, err) | ||||
| 			} | ||||
|  | ||||
| 			if err := helper.DefaultRedis.AddSortSet(lastPriceKey, float64(utcTime), content); err != nil { | ||||
| 				log.Errorf("添加 现货交易对:%s %d之前的最新成交价失败,err:%v", symbolName, lastUtc, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	//判断触发现货下单 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user