1撤销限价后 市价分单
This commit is contained in:
		| @ -52,11 +52,11 @@ func (e LineApiUser) GetPage(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for i, apiUser := range list { | ||||
| 		if apiUser.UserId <= 0 { | ||||
| 			list[i].OpenStatus = 1 | ||||
| 		} | ||||
| 	} | ||||
| 	// for i, apiUser := range list { | ||||
| 	// 	if apiUser.UserId <= 0 { | ||||
| 	// 		list[i].OpenStatus = 1 | ||||
| 	// 	} | ||||
| 	// } | ||||
|  | ||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||
| } | ||||
|  | ||||
							
								
								
									
										225
									
								
								app/admin/apis/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								app/admin/apis/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,225 @@ | ||||
| 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 LineApiUserGroup struct { | ||||
| 	api.Api | ||||
| } | ||||
|  | ||||
| // GetPage 获取api用户分组列表 | ||||
| // @Summary 获取api用户分组列表 | ||||
| // @Description 获取api用户分组列表 | ||||
| // @Tags api用户分组 | ||||
| // @Param groupName query string false "分组名称" | ||||
| // @Param pageSize query int false "页条数" | ||||
| // @Param pageIndex query int false "页码" | ||||
| // @Success 200 {object} response.Response{data=response.Page{list=[]models.LineApiUserGroup}} "{"code": 200, "data": [...]}" | ||||
| // @Router /api/v1/line-api-user-group [get] | ||||
| // @Security Bearer | ||||
| func (e LineApiUserGroup) GetPage(c *gin.Context) { | ||||
| 	req := dto.LineApiUserGroupGetPageReq{} | ||||
| 	s := service.LineApiUserGroup{} | ||||
| 	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.LineApiUserGroup, 0) | ||||
| 	var count int64 | ||||
|  | ||||
| 	err = s.GetPage(&req, p, &list, &count) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("获取api用户分组失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||
| } | ||||
|  | ||||
| // Get 获取api用户分组 | ||||
| // @Summary 获取api用户分组 | ||||
| // @Description 获取api用户分组 | ||||
| // @Tags api用户分组 | ||||
| // @Param id path int false "id" | ||||
| // @Success 200 {object} response.Response{data=models.LineApiUserGroup} "{"code": 200, "data": [...]}" | ||||
| // @Router /api/v1/line-api-user-group/{id} [get] | ||||
| // @Security Bearer | ||||
| func (e LineApiUserGroup) Get(c *gin.Context) { | ||||
| 	req := dto.LineApiUserGroupGetReq{} | ||||
| 	s := service.LineApiUserGroup{} | ||||
| 	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.LineApiUserGroup | ||||
|  | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
| 	err = s.Get(&req, p, &object) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("获取api用户分组失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(object, "查询成功") | ||||
| } | ||||
|  | ||||
| // Insert 创建api用户分组 | ||||
| // @Summary 创建api用户分组 | ||||
| // @Description 创建api用户分组 | ||||
| // @Tags api用户分组 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.LineApiUserGroupInsertReq true "data" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}" | ||||
| // @Router /api/v1/line-api-user-group [post] | ||||
| // @Security Bearer | ||||
| func (e LineApiUserGroup) Insert(c *gin.Context) { | ||||
| 	req := dto.LineApiUserGroupInsertReq{} | ||||
| 	s := service.LineApiUserGroup{} | ||||
| 	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)) | ||||
|  | ||||
| 	if err := req.Valid(); err != nil { | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = s.Insert(&req) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("创建api用户分组失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(req.GetId(), "创建成功") | ||||
| } | ||||
|  | ||||
| // Update 修改api用户分组 | ||||
| // @Summary 修改api用户分组 | ||||
| // @Description 修改api用户分组 | ||||
| // @Tags api用户分组 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param id path int true "id" | ||||
| // @Param data body dto.LineApiUserGroupUpdateReq true "body" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}" | ||||
| // @Router /api/v1/line-api-user-group/{id} [put] | ||||
| // @Security Bearer | ||||
| func (e LineApiUserGroup) Update(c *gin.Context) { | ||||
| 	req := dto.LineApiUserGroupUpdateReq{} | ||||
| 	s := service.LineApiUserGroup{} | ||||
| 	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) | ||||
|  | ||||
| 	if err := req.Valid(); err != nil { | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = s.Update(&req, p) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("修改api用户分组失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK(req.GetId(), "修改成功") | ||||
| } | ||||
|  | ||||
| // Delete 删除api用户分组 | ||||
| // @Summary 删除api用户分组 | ||||
| // @Description 删除api用户分组 | ||||
| // @Tags api用户分组 | ||||
| // @Param data body dto.LineApiUserGroupDeleteReq true "body" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}" | ||||
| // @Router /api/v1/line-api-user-group [delete] | ||||
| // @Security Bearer | ||||
| func (e LineApiUserGroup) Delete(c *gin.Context) { | ||||
| 	s := service.LineApiUserGroup{} | ||||
| 	req := dto.LineApiUserGroupDeleteReq{} | ||||
| 	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("删除api用户分组失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK(req.GetId(), "删除成功") | ||||
| } | ||||
|  | ||||
| func (e LineApiUserGroup) GetOptions(c *gin.Context) { | ||||
| 	s := service.LineApiUserGroup{} | ||||
| 	req := dto.LineApiUserGroupGetOptionsReq{} | ||||
| 	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) | ||||
| 	var datas []dto.LineApiUserGroupOptions | ||||
| 	if err = s.GetOptions(&req, &datas, p); err != nil { | ||||
| 		e.Error(500, err, "") | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK(datas, "查询成功") | ||||
| } | ||||
| @ -296,23 +296,27 @@ func (e LinePreOrder) AddPreOrder(c *gin.Context) { | ||||
| 	req.SetCreateBy(userId) | ||||
| 	errs := make([]error, 0) | ||||
| 	errStr := make([]string, 0) | ||||
| 	var msg string | ||||
| 	var tickerSymbol string | ||||
| 	if req.SymbolType == global.SYMBOL_SPOT { | ||||
| 		tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() | ||||
| 	} else { | ||||
| 		tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() | ||||
| 	} | ||||
| 	s.AddPreOrder(&req, p, &errs, tickerSymbol) | ||||
| 	if err := s.AddPreOrderCheck(&req, p, &errs, tickerSymbol); err != nil { | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(errs) > 0 { | ||||
| 		//e.Logger.Error(err) | ||||
| 		for _, err2 := range errs { | ||||
| 			errStr = append(errStr, err2.Error()) | ||||
| 		} | ||||
| 		e.Error(500, nil, strings.Join(errStr, ",")) | ||||
| 		return | ||||
|  | ||||
| 		msg = strings.Join(errStr, ",") | ||||
| 	} | ||||
| 	e.OK(nil, "操作成功") | ||||
|  | ||||
| 	e.OK(msg, "操作成功") | ||||
| } | ||||
|  | ||||
| // // 手动加仓 | ||||
| @ -368,22 +372,26 @@ func (e LinePreOrder) BatchAddOrder(c *gin.Context) { | ||||
| 	errs := make([]error, 0) | ||||
| 	errStr := make([]string, 0) | ||||
| 	userId := user.GetUserId(c) | ||||
|  | ||||
| 	var msg string | ||||
| 	if userId <= 0 { | ||||
| 		e.Error(500, nil, "用户不存在") | ||||
| 		return | ||||
| 	} | ||||
| 	req.SetCreateBy(userId) | ||||
| 	s.AddBatchPreOrder(&req, p, &errs) | ||||
| 	if err := s.AddBatchPreOrder(&req, p, &errs); err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(errs) > 0 { | ||||
| 		//e.Logger.Error(err) | ||||
| 		for _, err2 := range errs { | ||||
| 			errStr = append(errStr, err2.Error()) | ||||
| 		} | ||||
| 		e.Error(500, nil, strings.Join(errStr, ",")) | ||||
| 		return | ||||
| 		msg = strings.Join(errStr, ",") | ||||
| 	} | ||||
| 	e.OK(nil, "操作成功") | ||||
| 	e.OK(msg, "操作成功") | ||||
| } | ||||
|  | ||||
| // QuickAddPreOrder 模板快速下单 | ||||
| @ -409,22 +417,27 @@ func (e LinePreOrder) QuickAddPreOrder(c *gin.Context) { | ||||
| 	errs := make([]error, 0) | ||||
| 	errStr := make([]string, 0) | ||||
| 	userId := user.GetUserId(c) | ||||
| 	var msg string | ||||
|  | ||||
| 	if userId <= 0 { | ||||
| 		e.Error(500, nil, "用户不存在") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = s.QuickAddPreOrder(&req, p, userId, &errs) | ||||
| 	if err := s.QuickAddPreOrder(&req, p, userId, &errs); err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	if len(errs) > 0 { | ||||
| 		//e.Logger.Error(err) | ||||
| 		for _, err2 := range errs { | ||||
| 			errStr = append(errStr, err2.Error()) | ||||
| 		} | ||||
| 		e.Error(500, nil, strings.Join(errStr, ",")) | ||||
| 		return | ||||
|  | ||||
| 		msg = strings.Join(errStr, ",") | ||||
| 	} | ||||
| 	e.OK(nil, "操作成功") | ||||
| 	e.OK(msg, "操作成功") | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -113,6 +113,7 @@ func (e SysConfig) Insert(c *gin.Context) { | ||||
| 		e.Error(500, err, "创建失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(req.GetId(), "创建成功") | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										31
									
								
								app/admin/models/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/admin/models/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| package models | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/models" | ||||
| ) | ||||
|  | ||||
| type LineApiUserGroup struct { | ||||
| 	models.Model | ||||
|  | ||||
| 	ExchangeType string `json:"exchangeType" gorm:"type:varchar(30);comment:交易所"` | ||||
| 	GroupName    string `json:"groupName" gorm:"type:varchar(30);comment:分组名称"` | ||||
| 	UserId       string `json:"userId" gorm:"type:text;comment:用户ids 逗号分割"` | ||||
| 	Status       int    `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||
| 	UserIds      []int  `json:"userIds" gorm:"-"` | ||||
| 	Count        int    `json:"count" gorm:"-"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
|  | ||||
| func (LineApiUserGroup) TableName() string { | ||||
| 	return "line_api_user_group" | ||||
| } | ||||
|  | ||||
| func (e *LineApiUserGroup) Generate() models.ActiveRecord { | ||||
| 	o := *e | ||||
| 	return &o | ||||
| } | ||||
|  | ||||
| func (e *LineApiUserGroup) GetId() interface{} { | ||||
| 	return e.Id | ||||
| } | ||||
| @ -40,6 +40,7 @@ type LinePreOrder struct { | ||||
| 	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"` | ||||
| 	// LinePreOrder 线上预埋单\ | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
|  | ||||
							
								
								
									
										29
									
								
								app/admin/router/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/admin/router/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| 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/actions" | ||||
| 	"go-admin/common/middleware" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	routerCheckRole = append(routerCheckRole, registerLineApiUserGroupRouter) | ||||
| } | ||||
|  | ||||
| // registerLineApiUserGroupRouter | ||||
| func registerLineApiUserGroupRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | ||||
| 	api := apis.LineApiUserGroup{} | ||||
| 	r := v1.Group("/line-api-user-group").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) | ||||
|  | ||||
| 		r.GET("/options", actions.PermissionAction(), api.GetOptions) | ||||
| 	} | ||||
| } | ||||
| @ -9,6 +9,7 @@ import ( | ||||
| type LineApiUserGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	ExchangeType   string `form:"exchangeType" search:"-"` | ||||
| 	OpenStatus     *int   `form:"openStatus" search:"-" comment:"开启状态 0-关闭 1-开启"` | ||||
| 	LineApiUserOrder | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										151
									
								
								app/admin/service/dto/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								app/admin/service/dto/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/dto" | ||||
| 	common "go-admin/common/models" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type LineApiUserGroupGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	GroupName      string `form:"groupName"  search:"type:contains;column:group_name;table:line_api_user_group" comment:"分组名称"` | ||||
| 	LineApiUserGroupOrder | ||||
| } | ||||
|  | ||||
| type LineApiUserGroupOrder struct { | ||||
| 	Id        string `form:"idOrder"  search:"type:order;column:id;table:line_api_user_group"` | ||||
| 	GroupName string `form:"groupNameOrder"  search:"type:order;column:group_name;table:line_api_user_group"` | ||||
| 	UserId    string `form:"userIdOrder"  search:"type:order;column:user_id;table:line_api_user_group"` | ||||
| 	Status    string `form:"statusOrder"  search:"type:order;column:status;table:line_api_user_group"` | ||||
| 	CreatedAt string `form:"createdAtOrder"  search:"type:order;column:created_at;table:line_api_user_group"` | ||||
| 	UpdatedAt string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:line_api_user_group"` | ||||
| 	DeletedAt string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:line_api_user_group"` | ||||
| 	CreateBy  string `form:"createByOrder"  search:"type:order;column:create_by;table:line_api_user_group"` | ||||
| 	UpdateBy  string `form:"updateByOrder"  search:"type:order;column:update_by;table:line_api_user_group"` | ||||
| } | ||||
|  | ||||
| func (m *LineApiUserGroupGetPageReq) GetNeedSearch() interface{} { | ||||
| 	return *m | ||||
| } | ||||
|  | ||||
| type LineApiUserGroupInsertReq struct { | ||||
| 	Id           int    `json:"-" comment:"组件"` // 组件 | ||||
| 	ExchangeType string `json:"exchangeType" comment:"交易类型"` | ||||
|  | ||||
| 	GroupName string `json:"groupName" comment:"分组名称"` | ||||
| 	UserId    string `json:"userId" comment:"用户ids 逗号分割"` | ||||
| 	UserIds   []int  `json:"userIds" comment:"用户ids"` | ||||
| 	Status    int    `json:"status" comment:"状态 1-启用 2-禁用"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupInsertReq) Valid() error { | ||||
| 	if s.ExchangeType == "" { | ||||
| 		return errors.New("交易类型不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if s.GroupName == "" { | ||||
| 		return errors.New("分组名称不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if len(s.UserIds) == 0 { | ||||
| 		return errors.New("用户不能为空") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupInsertReq) Generate(model *models.LineApiUserGroup) { | ||||
| 	if s.Id == 0 { | ||||
| 		model.Model = common.Model{Id: s.Id} | ||||
| 	} | ||||
| 	strIds := make([]string, len(s.UserIds)) | ||||
| 	for i, id := range s.UserIds { | ||||
| 		strIds[i] = strconv.Itoa(id) | ||||
| 	} | ||||
| 	model.GroupName = s.GroupName | ||||
| 	model.ExchangeType = s.ExchangeType | ||||
| 	model.UserId = strings.Join(strIds, ",") | ||||
| 	model.Status = s.Status | ||||
| 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupInsertReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| type LineApiUserGroupUpdateReq struct { | ||||
| 	Id           int    `uri:"id" comment:"组件"` // 组件 | ||||
| 	ExchangeType string `json:"exchangeType" comment:"交易类型"` | ||||
| 	GroupName    string `json:"groupName" comment:"分组名称"` | ||||
| 	UserId       string `json:"userId" comment:"用户ids 逗号分割"` | ||||
| 	UserIds      []int  `json:"userIds" comment:"用户ids"` | ||||
| 	Status       int    `json:"status" comment:"状态 1-启用 2-禁用"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupUpdateReq) Valid() error { | ||||
| 	if s.ExchangeType == "" { | ||||
| 		return errors.New("交易类型不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if s.GroupName == "" { | ||||
| 		return errors.New("分组名称不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if len(s.UserIds) == 0 { | ||||
| 		return errors.New("用户不能为空") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| func (s *LineApiUserGroupUpdateReq) Generate(model *models.LineApiUserGroup) { | ||||
| 	if s.Id == 0 { | ||||
| 		model.Model = common.Model{Id: s.Id} | ||||
| 	} | ||||
| 	strIds := make([]string, len(s.UserIds)) | ||||
| 	for i, id := range s.UserIds { | ||||
| 		strIds[i] = strconv.Itoa(id) | ||||
| 	} | ||||
| 	model.GroupName = s.GroupName | ||||
| 	model.ExchangeType = s.ExchangeType | ||||
| 	model.UserId = strings.Join(strIds, ",") | ||||
| 	model.Status = s.Status | ||||
| 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupUpdateReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| // LineApiUserGroupGetReq 功能获取请求参数 | ||||
| type LineApiUserGroupGetReq struct { | ||||
| 	Id int `uri:"id"` | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupGetReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| // LineApiUserGroupDeleteReq 功能删除请求参数 | ||||
| type LineApiUserGroupDeleteReq struct { | ||||
| 	Ids []int `json:"ids"` | ||||
| } | ||||
|  | ||||
| func (s *LineApiUserGroupDeleteReq) GetId() interface{} { | ||||
| 	return s.Ids | ||||
| } | ||||
|  | ||||
| type LineApiUserGroupGetOptionsReq struct { | ||||
| 	ExchangeType string `form:"exchangeType"` | ||||
| } | ||||
|  | ||||
| type LineApiUserGroupOptions struct { | ||||
| 	Id       int    `json:"id"` | ||||
| 	Label    string `json:"label"` | ||||
| 	Value    []int  `json:"value"` | ||||
| 	Disabled bool   `json:"disabled"` | ||||
| } | ||||
| @ -187,6 +187,8 @@ type LineAddPreOrderReq struct { | ||||
| 	OrderType               int             `json:"order_type"`                  //订单类型 | ||||
| 	Symbol                  string          `json:"symbol"`                      //交易对 | ||||
| 	ApiUserId               string          `json:"api_id" `                     //下单用户 | ||||
| 	ApiIdType               int             `json:"api_id_type"`                 //下单用户类型 | ||||
| 	ApiUserGroupId          int             `json:"api_user_group_id"`           //下单用户组 | ||||
| 	Site                    string          `json:"site" `                       //购买方向 | ||||
| 	BuyPrice                string          `json:"buy_price" vd:"$>0"`          //购买金额 U | ||||
| 	PricePattern            string          `json:"price_pattern"`               //价格模式 | ||||
| @ -368,6 +370,8 @@ type LineBatchAddPreOrderReq struct { | ||||
| 	SymbolGroupId         string                  `json:"symbol_group_id"`          //交易对组id | ||||
| 	Symbol                string                  `json:"symbol"`                   //交易对 | ||||
| 	ApiUserId             string                  `json:"api_id"`                   //下单用户 | ||||
| 	ApiIdType             int                     `json:"api_id_type"`              //下单用户类型 | ||||
| 	ApiUserGroupId        int                     `json:"api_user_group_id"`        //下单用户组 | ||||
| 	Site                  string                  `json:"site"`                     //购买方向 | ||||
| 	BuyPrice              string                  `json:"buy_price"`                //购买金额 U | ||||
| 	PricePattern          string                  `json:"price_pattern"`            //价格模式 | ||||
|  | ||||
| @ -40,6 +40,10 @@ func (e *LineApiUser) GetPage(c *dto.LineApiUserGetPageReq, p *actions.DataPermi | ||||
| 		query.Where("exchange_type = ?", c.ExchangeType) | ||||
| 	} | ||||
|  | ||||
| 	if c.OpenStatus != nil { | ||||
| 		query.Where("open_status = ?", c.OpenStatus) | ||||
| 	} | ||||
|  | ||||
| 	err = query. | ||||
| 		Find(list).Limit(-1).Offset(-1). | ||||
| 		Count(count).Error | ||||
| @ -188,6 +192,7 @@ func OpenUserBinanceWebsocket(item models.LineApiUser) { | ||||
| func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermission) error { | ||||
| 	var err error | ||||
| 	var data = models.LineApiUser{} | ||||
|  | ||||
| 	e.Orm.Scopes( | ||||
| 		actions.Permission(data.TableName(), p), | ||||
| 	).First(&data, c.GetId()) | ||||
| @ -195,14 +200,31 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss | ||||
|  | ||||
| 	c.Generate(&data) | ||||
|  | ||||
| 	db := e.Orm.Save(&data) | ||||
| 	if err = db.Error; err != nil { | ||||
| 		e.Log.Errorf("LineApiUserService Save error:%s \r\n", err) | ||||
| 	updateGroups, err := e.GetUpdateGroups(c.Id) | ||||
|  | ||||
| 	//事务 | ||||
| 	err = e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		db := tx.Save(&data) | ||||
| 		if err = db.Error; err != nil { | ||||
| 			e.Log.Errorf("LineApiUserService Save error:%s \r\n", err) | ||||
| 			return err | ||||
| 		} | ||||
| 		if db.RowsAffected == 0 { | ||||
| 			return errors.New("无权更新该数据") | ||||
| 		} | ||||
|  | ||||
| 		if len(updateGroups) > 0 { | ||||
| 			if err := tx.Save(&updateGroups).Error; err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权更新该数据") | ||||
| 	} | ||||
|  | ||||
| 	e.saveCache(data) | ||||
|  | ||||
| @ -224,6 +246,24 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *LineApiUser) GetUpdateGroups(apiUserId int) ([]models.LineApiUserGroup, error) { | ||||
| 	updateGroups := make([]models.LineApiUserGroup, 0) | ||||
| 	var groups []models.LineApiUserGroup | ||||
| 	if err := e.Orm.Model(models.LineApiUserGroup{}).Where(" concat(',',user_id,',') like ? ", fmt.Sprintf("%%,%v,%%", apiUserId)).Find(&groups).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	delId := fmt.Sprintf(",%v,", apiUserId) | ||||
| 	for _, group := range groups { | ||||
|  | ||||
| 		userId := strings.ReplaceAll(("," + group.UserId + ","), delId, "") | ||||
| 		group.UserId = strings.Trim(userId, ",") | ||||
|  | ||||
| 		updateGroups = append(updateGroups, group) | ||||
| 	} | ||||
| 	return updateGroups, nil | ||||
| } | ||||
|  | ||||
| // Remove 删除LineApiUser | ||||
| func (e *LineApiUser) Remove(d *dto.LineApiUserDeleteReq, p *actions.DataPermission) error { | ||||
| 	var data models.LineApiUser | ||||
| @ -347,3 +387,30 @@ func (e *LineApiUser) GetMainUser(req *dto.GetMainUserReq, list *[]models.LineAp | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // InitCache 初始化缓存 | ||||
| func (e *LineApiUser) InitCache() error { | ||||
| 	var items []models.LineApiUser | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.LineApiUser{}).Find(&items).Error; err != nil { | ||||
| 		e.Log.Errorf("LineApiUserService error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range items { | ||||
| 		e.saveCache(item) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetActiveApis 获取激活的api | ||||
| func (e *LineApiUser) GetActiveApis(apiIds []int) ([]int, error) { | ||||
| 	result := make([]int, 0) | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.LineApiUser{}).Where("open_status = 1 and id IN ?", apiIds).Pluck("id", &result).Error; err != nil { | ||||
| 		return result, err | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										165
									
								
								app/admin/service/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								app/admin/service/line_api_user_group.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,165 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"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" | ||||
| 	"go-admin/pkg/utility" | ||||
| ) | ||||
|  | ||||
| type LineApiUserGroup struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // GetPage 获取LineApiUserGroup列表 | ||||
| func (e *LineApiUserGroup) GetPage(c *dto.LineApiUserGroupGetPageReq, p *actions.DataPermission, list *[]models.LineApiUserGroup, count *int64) error { | ||||
| 	var err error | ||||
| 	var data models.LineApiUserGroup | ||||
|  | ||||
| 	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("LineApiUserGroupService GetPage error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for index := range *list { | ||||
| 		userIds := strings.Split((*list)[index].UserId, ",") | ||||
| 		var filteredUserIds []string | ||||
| 		for _, id := range userIds { | ||||
| 			if id != "" { | ||||
| 				filteredUserIds = append(filteredUserIds, id) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		(*list)[index].Count = len(filteredUserIds) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Get 获取LineApiUserGroup对象 | ||||
| func (e *LineApiUserGroup) Get(d *dto.LineApiUserGroupGetReq, p *actions.DataPermission, model *models.LineApiUserGroup) error { | ||||
| 	var data models.LineApiUserGroup | ||||
|  | ||||
| 	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 GetLineApiUserGroup error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("db error:%s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	userIds := []int{} | ||||
|  | ||||
| 	for _, userId := range strings.Split(model.UserId, ",") { | ||||
| 		if userId == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 		userIds = append(userIds, utility.ToInt(userId)) | ||||
| 	} | ||||
| 	model.UserIds = userIds | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Insert 创建LineApiUserGroup对象 | ||||
| func (e *LineApiUserGroup) Insert(c *dto.LineApiUserGroupInsertReq) error { | ||||
| 	var err error | ||||
| 	var data models.LineApiUserGroup | ||||
| 	c.Generate(&data) | ||||
| 	err = e.Orm.Create(&data).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("LineApiUserGroupService Insert error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update 修改LineApiUserGroup对象 | ||||
| func (e *LineApiUserGroup) Update(c *dto.LineApiUserGroupUpdateReq, p *actions.DataPermission) error { | ||||
| 	var err error | ||||
| 	var data = models.LineApiUserGroup{} | ||||
| 	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("LineApiUserGroupService Save error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权更新该数据") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Remove 删除LineApiUserGroup | ||||
| func (e *LineApiUserGroup) Remove(d *dto.LineApiUserGroupDeleteReq, p *actions.DataPermission) error { | ||||
| 	var data models.LineApiUserGroup | ||||
|  | ||||
| 	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 RemoveLineApiUserGroup error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权删除该数据") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *LineApiUserGroup) GetOptions(req *dto.LineApiUserGroupGetOptionsReq, data *[]dto.LineApiUserGroupOptions, p *actions.DataPermission) error { | ||||
| 	var err error | ||||
| 	var items []models.LineApiUserGroup | ||||
|  | ||||
| 	e.Orm.Model(&models.LineApiUserGroup{}). | ||||
| 		Where("exchange_type =?", req.ExchangeType). | ||||
| 		Scopes( | ||||
| 			actions.Permission(models.LineApiUserGroup{}.TableName(), p), | ||||
| 		).Find(&items) | ||||
|  | ||||
| 	for _, item := range items { | ||||
| 		ids := []int{} | ||||
| 		userIds := strings.Split(item.UserId, ",") | ||||
|  | ||||
| 		for _, id := range userIds { | ||||
| 			val, _ := strconv.Atoi(id) | ||||
| 			ids = append(ids, val) | ||||
| 		} | ||||
|  | ||||
| 		*data = append(*data, dto.LineApiUserGroupOptions{ | ||||
| 			Label:    item.GroupName, | ||||
| 			Id:       item.Id, | ||||
| 			Value:    ids, | ||||
| 			Disabled: item.Status != 1, | ||||
| 		}) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| @ -374,9 +374,34 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // AddPreOrder 单个添加 | ||||
| func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataPermission, errs *[]error, tickerSymbol string) error { | ||||
| // 单个新增订单 | ||||
| func (e *LinePreOrder) AddPreOrderCheck(req *dto.LineAddPreOrderReq, p *actions.DataPermission, errs *[]error, tickerSymbol string) error { | ||||
| 	apiUserService := LineApiUser{Service: e.Service} | ||||
| 	apiIds := []int{} | ||||
| 	apiUserIds := strings.Split(req.ApiUserId, ",") | ||||
| 	for _, v := range apiUserIds { | ||||
| 		apiId, _ := strconv.Atoi(v) | ||||
| 		apiIds = append(apiIds, apiId) | ||||
| 	} | ||||
|  | ||||
| 	activeApiIds, _ := apiUserService.GetActiveApis(apiIds) | ||||
|  | ||||
| 	if len(activeApiIds) == 0 { | ||||
| 		*errs = append(*errs, errors.New("api用户不存在或未激活")) | ||||
| 	} else { | ||||
| 		for _, item := range apiIds { | ||||
| 			if !utility.ContainsInt(activeApiIds, item) { | ||||
| 				*errs = append(*errs, fmt.Errorf("api用户未激活:%v", item)) | ||||
| 			} | ||||
| 		} | ||||
| 		e.AddPreOrder(req, activeApiIds, p, errs, tickerSymbol) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // AddPreOrder 单个添加 | ||||
| func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int, p *actions.DataPermission, errs *[]error, tickerSymbol string) error { | ||||
| 	if req.SaveTemplate == "2" || req.SaveTemplate == "1" { //2 = 只保存模板 1= 保存模板并下单 | ||||
| 		var templateLog dto.LineAddPreOrderReq | ||||
| 		copier.Copy(&templateLog, req) | ||||
| @ -412,7 +437,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP | ||||
|  | ||||
| 	for _, id := range apiUserIds { | ||||
| 		if req.Site == "SELL" && req.SymbolType == global.SYMBOL_SPOT { | ||||
| 			*errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 现货不支持卖出操作", id, req.Symbol)) | ||||
| 			*errs = append(*errs, fmt.Errorf("api_id:%v 获取交易对:%s 现货不支持卖出操作", id, req.Symbol)) | ||||
| 			continue | ||||
| 		} | ||||
| 		var AddOrder models.LinePreOrder | ||||
| @ -500,7 +525,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP | ||||
| 			continue | ||||
| 		} | ||||
| 		AddOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) | ||||
| 		AddOrder.ApiId, _ = strconv.Atoi(id) | ||||
| 		AddOrder.ApiId = id | ||||
| 		AddOrder.Symbol = req.Symbol | ||||
| 		AddOrder.QuoteSymbol = symbolInfo.QuoteAsset | ||||
| 		AddOrder.Pid = 0 | ||||
| @ -651,14 +676,20 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP | ||||
| 					profitOrder.Rate = "0" | ||||
| 				} else { | ||||
| 					if strings.ToUpper(req.Site) == "BUY" { | ||||
| 						profitOrder.Site = "SELL" | ||||
| 						// 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.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 | ||||
| @ -696,15 +727,21 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataP | ||||
| 					stopOrder.Rate = "0" | ||||
| 				} else { | ||||
| 					if strings.ToUpper(req.Site) == "BUY" { | ||||
| 						stopOrder.Site = "SELL" | ||||
| 						// 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() | ||||
| 					} else { | ||||
| 						stopOrder.Site = "BUY" | ||||
| 						// 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() | ||||
| 					} | ||||
|  | ||||
| 					stopOrder.Rate = req.ReducePriceRatio.String() | ||||
| 				} | ||||
|  | ||||
| 				if strings.ToUpper(req.Site) == "BUY" { | ||||
| 					stopOrder.Site = "SELL" | ||||
| 				} else { | ||||
| 					stopOrder.Site = "BUY" | ||||
| 				} | ||||
| 				stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) | ||||
| 				stopOrder.Pid = AddOrder.Id | ||||
| 				stopOrder.MainId = AddOrder.Id | ||||
| @ -1078,10 +1115,34 @@ 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) | ||||
| 		//templateLog = *batchReq | ||||
| 		templateLog.SaveTemplate = "0" | ||||
| 		templateLog.TemplateName = "" | ||||
| 		marshal, _ := sonic.Marshal(templateLog) | ||||
| @ -1112,80 +1173,81 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p | ||||
| 	} | ||||
|  | ||||
| 	//脚本次数 | ||||
| 	if batchReq.OrderNum > 0 { | ||||
| 		var tickerSymbol string | ||||
| 		if batchReq.SymbolType == global.SYMBOL_SPOT { | ||||
| 			tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() | ||||
| 		} else { | ||||
| 			tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() | ||||
| 		} | ||||
| 		apiUserIds := strings.Split(batchReq.ApiUserId, ",") | ||||
| 		if batchReq.Script == "1" { | ||||
| 			//scriptLogs := make([]models.LinePreScript, 0) | ||||
| 			logParams := *batchReq | ||||
| 			for _, id := range apiUserIds { | ||||
| 				for j := 1; j <= batchReq.OrderNum; j++ { | ||||
| 					var log models.LinePreScript | ||||
| 					logParams.SaveTemplate = "0" | ||||
| 					logParams.TemplateName = "" | ||||
| 					logParams.Script = "" | ||||
| 					marshal, _ := sonic.Marshal(logParams) | ||||
| 					log.ApiId = int64(utility.StringToInt(id)) | ||||
| 					log.ScriptNum = int64(j) | ||||
| 					log.ScriptParams = string(marshal) | ||||
| 					log.AdminId = 0 | ||||
| 					log.Status = "0" | ||||
| 					//scriptLogs = append(scriptLogs, log) | ||||
| 					err := e.Orm.Model(&models.LinePreScript{}).Create(&log).Error | ||||
| 					if err != nil { | ||||
| 						*errs = append(*errs, fmt.Errorf("记录脚本失败:%+v", err.Error())) | ||||
| 						return nil | ||||
| 					} | ||||
| 					helper.DefaultRedis.RPushList(rediskey.PreOrderScriptList, utility.IntToString(log.Id)) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		} | ||||
| 		for _, id := range apiUserIds { | ||||
| 			for j := 0; j < batchReq.OrderNum; j++ { | ||||
| 				symbols := strings.Split(batchReq.Symbol, ",") | ||||
| 				for _, symbol := range symbols { | ||||
| 					var req dto.LineAddPreOrderReq | ||||
| 					req.ExchangeType = batchReq.ExchangeType | ||||
| 					req.OrderType = batchReq.OrderType | ||||
| 					req.Symbol = symbol | ||||
| 					req.ApiUserId = id | ||||
| 					req.Site = batchReq.Site | ||||
| 					req.BuyPrice = batchReq.BuyPrice | ||||
| 					req.PricePattern = batchReq.PricePattern | ||||
| 					req.Price = batchReq.Price | ||||
| 					req.Profit = batchReq.Profit | ||||
| 					req.ProfitNumRatio = batchReq.ProfitNumRatio | ||||
| 					req.ProfitTpTpPriceRatio = batchReq.ProfitTpTpPriceRatio | ||||
| 					req.ProfitTpSlPriceRatio = batchReq.ProfitTpSlPriceRatio | ||||
| 					req.Ext = batchReq.Ext | ||||
| 					req.SymbolType = batchReq.SymbolType | ||||
| 					// req.StopPrice = batchReq.StopPrice | ||||
| 					req.ReducePriceRatio = batchReq.ReducePriceRatio | ||||
| 					req.PriceType = batchReq.PriceType | ||||
| 					req.CoverType = batchReq.CoverType | ||||
| 					req.ExpireHour = batchReq.ExpireHour | ||||
| 					req.MainOrderType = batchReq.MainOrderType | ||||
| 					req.ReduceNumRatio = batchReq.ReduceNumRatio | ||||
| 					req.ReduceStopLossRatio = batchReq.ReduceStopLossRatio | ||||
| 					req.ReduceTakeProfitRatio = batchReq.ReduceTakeProfitRatio | ||||
| 					req.CreateBy = batchReq.CreateBy | ||||
|  | ||||
| 					e.AddPreOrder(&req, p, errs, tickerSymbol) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	// if batchReq.OrderNum > 0 { | ||||
| 	apiUserIds := strings.Split(batchReq.ApiUserId, ",") | ||||
| 	var tickerSymbol string | ||||
| 	if batchReq.SymbolType == global.SYMBOL_SPOT { | ||||
| 		tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() | ||||
| 	} else { | ||||
| 		*errs = append(*errs, errors.New("请选择运行次数")) | ||||
| 		tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() | ||||
| 	} | ||||
|  | ||||
| 	if batchReq.Script == "1" { | ||||
| 		//scriptLogs := make([]models.LinePreScript, 0) | ||||
| 		logParams := *batchReq | ||||
| 		for _, id := range apiUserIds { | ||||
| 			for j := 1; j <= batchReq.OrderNum; j++ { | ||||
| 				var log models.LinePreScript | ||||
| 				logParams.SaveTemplate = "0" | ||||
| 				logParams.TemplateName = "" | ||||
| 				logParams.Script = "" | ||||
| 				logParams.ApiUserId = id | ||||
| 				marshal, _ := sonic.Marshal(logParams) | ||||
| 				log.ApiId = utility.ToInt64(id) | ||||
| 				log.ScriptNum = int64(j) | ||||
| 				log.ScriptParams = string(marshal) | ||||
| 				log.AdminId = 0 | ||||
| 				log.Status = "0" | ||||
| 				//scriptLogs = append(scriptLogs, log) | ||||
| 				err := e.Orm.Model(&models.LinePreScript{}).Create(&log).Error | ||||
| 				if err != nil { | ||||
| 					*errs = append(*errs, fmt.Errorf("记录脚本失败:%+v", err.Error())) | ||||
| 					return nil | ||||
| 				} | ||||
| 				helper.DefaultRedis.RPushList(rediskey.PreOrderScriptList, utility.IntToString(log.Id)) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
| 	for _, id := range apiUserIds { | ||||
| 		for j := 0; j < batchReq.OrderNum; j++ { | ||||
| 			symbols := strings.Split(batchReq.Symbol, ",") | ||||
| 			for _, symbol := range symbols { | ||||
| 				var req dto.LineAddPreOrderReq | ||||
| 				req.ExchangeType = batchReq.ExchangeType | ||||
| 				req.OrderType = batchReq.OrderType | ||||
| 				req.Symbol = symbol | ||||
| 				req.ApiUserId = id | ||||
| 				req.Site = batchReq.Site | ||||
| 				req.BuyPrice = batchReq.BuyPrice | ||||
| 				req.PricePattern = batchReq.PricePattern | ||||
| 				req.Price = batchReq.Price | ||||
| 				req.Profit = batchReq.Profit | ||||
| 				req.ProfitNumRatio = batchReq.ProfitNumRatio | ||||
| 				req.ProfitTpTpPriceRatio = batchReq.ProfitTpTpPriceRatio | ||||
| 				req.ProfitTpSlPriceRatio = batchReq.ProfitTpSlPriceRatio | ||||
| 				req.Ext = batchReq.Ext | ||||
| 				req.SymbolType = batchReq.SymbolType | ||||
| 				req.ReducePriceRatio = batchReq.ReducePriceRatio | ||||
| 				req.PriceType = batchReq.PriceType | ||||
| 				req.CoverType = batchReq.CoverType | ||||
| 				req.ExpireHour = batchReq.ExpireHour | ||||
| 				req.MainOrderType = batchReq.MainOrderType | ||||
| 				req.ReduceNumRatio = batchReq.ReduceNumRatio | ||||
| 				req.ReduceStopLossRatio = batchReq.ReduceStopLossRatio | ||||
| 				req.ReduceTakeProfitRatio = batchReq.ReduceTakeProfitRatio | ||||
| 				req.CreateBy = batchReq.CreateBy | ||||
|  | ||||
| 				e.AddPreOrderCheck(&req, p, errs, tickerSymbol) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| 	// } else { | ||||
| 	// 	*errs = append(*errs, errors.New("请选择运行次数")) | ||||
| 	// 	return nil | ||||
| 	// } | ||||
| } | ||||
|  | ||||
| // QuickAddPreOrder 模板快速下单 | ||||
| @ -1204,7 +1266,8 @@ func (e *LinePreOrder) QuickAddPreOrder(quickReq *dto.QuickAddPreOrderReq, p *ac | ||||
| 			} else { | ||||
| 				tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() | ||||
| 			} | ||||
| 			err := e.AddPreOrder(&addPreOrderParams, p, errs, tickerSymbol) | ||||
| 			err := e.AddPreOrderCheck(&addPreOrderParams, p, errs, tickerSymbol) | ||||
| 			// err := e.AddPreOrder(&addPreOrderParams, p, errs, tickerSymbol) | ||||
| 			if err != nil { | ||||
| 				*errs = append(*errs, fmt.Errorf("api_id:%s 获取交易对:%s 生成订单失败", addPreOrderParams.ApiUserId, addPreOrderParams.Symbol)) | ||||
| 				continue | ||||
|  | ||||
| @ -26,7 +26,7 @@ func (e *LineSymbolGroup) GetPage(c *dto.LineSymbolGroupGetPageReq, p *actions.D | ||||
| 		Scopes( | ||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | ||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 			// actions.Permission(data.TableName(), p), | ||||
| 		) | ||||
| 	if c.ExchangeType != "" { | ||||
| 		query = query.Where("exchange_type = ?", c.ExchangeType) | ||||
|  | ||||
| @ -718,6 +718,7 @@ func (e *LineUser) ResetPassword(req *dto.LineUserResetPwdReq) int { | ||||
| func (e *LineUser) OpenStatus(req *dto.OpenStatusReq, userId int) int { | ||||
| 	var apiUser models.LineApiUser | ||||
| 	user := models.LineUser{} | ||||
| 	apiUserService := LineApiUser{Service: e.Service} | ||||
| 	e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUser) | ||||
|  | ||||
| 	if apiUser.ApiSecret == "" || apiUser.ApiKey == "" { | ||||
| @ -752,10 +753,31 @@ func (e *LineUser) OpenStatus(req *dto.OpenStatusReq, userId int) int { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err := e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Update("open_status", req.Status).Error | ||||
| 	apiUser.OpenStatus = int64(req.Status) | ||||
| 	updateGroups, _ := apiUserService.GetUpdateGroups(apiUser.Id) | ||||
|  | ||||
| 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		if err := e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Update("open_status", req.Status).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if err := tx.Save(&updateGroups).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	val, _ := sonic.MarshalString(apiUser) | ||||
|  | ||||
| 	if val != "" { | ||||
| 		if err := helper.DefaultRedis.SetString(fmt.Sprintf(rediskey.API_USER, userId), val); err != nil { | ||||
| 			logger.Error("保存api user redis 失败", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return statuscode.OK | ||||
| } | ||||
|  | ||||
| @ -2,11 +2,15 @@ package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/const/rediskey" | ||||
| 	cDto "go-admin/common/dto" | ||||
| 	"go-admin/common/helper" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| ) | ||||
|  | ||||
| @ -59,6 +63,13 @@ func (e *SysConfig) Insert(c *dto.SysConfigControl) error { | ||||
| 		e.Log.Errorf("Service InsertSysConfig error:%s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	key := fmt.Sprintf(rediskey.SysConfigKey, c.ConfigKey) | ||||
| 	content, _ := sonic.MarshalString(data) | ||||
|  | ||||
| 	if content != "" { | ||||
| 		helper.DefaultRedis.SetString(key, content) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -76,7 +87,13 @@ func (e *SysConfig) Update(c *dto.SysConfigControl) error { | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权更新该数据") | ||||
| 	} | ||||
|  | ||||
| 	key := fmt.Sprintf(rediskey.SysConfigKey, c.ConfigKey) | ||||
| 	content, _ := sonic.MarshalString(model) | ||||
|  | ||||
| 	if content != "" { | ||||
| 		helper.DefaultRedis.SetString(key, content) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @ -133,7 +150,6 @@ func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @ -143,6 +159,9 @@ func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error { | ||||
| func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error { | ||||
| 	var err error | ||||
| 	var data models.SysConfig | ||||
| 	keys := make([]string, 0) | ||||
|  | ||||
| 	e.Orm.Model(&data).Where("id IN ?", d.Ids).Pluck("config_key", &keys) | ||||
|  | ||||
| 	db := e.Orm.Delete(&data, d.Ids) | ||||
| 	if err = db.Error; err != nil { | ||||
| @ -153,6 +172,14 @@ func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error { | ||||
| 		err = errors.New("无权删除该数据") | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, key := range keys { | ||||
| 		cacheKey := fmt.Sprintf(rediskey.SysConfigKey, key) | ||||
| 		if err = helper.DefaultRedis.DeleteString(cacheKey); err != nil { | ||||
| 			e.Log.Errorf("Service RemoveSysConfig error:%s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -2,11 +2,12 @@ package jobs | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	models2 "go-admin/app/jobs/models" | ||||
| 	"time" | ||||
|  | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	models2 "go-admin/app/jobs/models" | ||||
| 	"gorm.io/gorm" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/robfig/cron/v3" | ||||
|  | ||||
| @ -40,6 +41,12 @@ type ExecJob struct { | ||||
| } | ||||
|  | ||||
| func (e *ExecJob) Run() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			log.Errorf("脚本任务失败:%v", err) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	startTime := time.Now() | ||||
| 	var obj = jobList[e.InvokeTarget] | ||||
| 	if obj == nil { | ||||
|  | ||||
							
								
								
									
										261
									
								
								app/jobs/jobs.go
									
									
									
									
									
								
							
							
						
						
									
										261
									
								
								app/jobs/jobs.go
									
									
									
									
									
								
							| @ -26,6 +26,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/jinzhu/copier" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/driver/mysql" | ||||
|  | ||||
| @ -158,7 +159,6 @@ func (t AutoPlaceOrder) Exec(arg interface{}) error { | ||||
| } | ||||
|  | ||||
| func (t LimitOrderTimeoutDuration) Exec(arg interface{}) error { | ||||
|  | ||||
| 	str := time.Now().Format(timeFormat) + " [INFO] JobCore ClearLogJob exec success" | ||||
| 	defer logger.Info(str) | ||||
| 	var db *gorm.DB | ||||
| @ -188,7 +188,10 @@ func (t LimitOrderTimeoutDuration) Exec(arg interface{}) error { | ||||
| 		defer lock.Release() | ||||
| 		limitOrderTimeoutDuration := utility.StringAsInt64(resp.ConfigValue) | ||||
| 		orders := make([]models.LinePreOrder, 0) | ||||
| 		err := db.Model(&models.LinePreOrder{}).Where("status = '5' AND main_order_type = 'LIMIT' AND order_type in ('4')  AND updated_at < ?", time.Now().Add(-time.Duration(limitOrderTimeoutDuration)*time.Second)).Find(&orders).Error | ||||
| 		err := db.Model(&models.LinePreOrder{}). | ||||
| 			Where("status = '5' AND main_order_type = 'LIMIT' AND order_type in ('4')  AND updated_at < ?", time.Now().Add(-time.Duration(limitOrderTimeoutDuration)*time.Second)). | ||||
| 			Preload("Childs"). | ||||
| 			Find(&orders).Error | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @ -228,6 +231,7 @@ func (t LimitOrderTimeoutDuration) ReSpotOrderPlace(db *gorm.DB, order models.Li | ||||
| 	for i := 0; i < 3; i++ { | ||||
| 		err = spotApi.CancelOpenOrderByOrderSn(apiUserinfo, order.Symbol, order.OrderSn) | ||||
| 		if err == nil || strings.Contains(err.Error(), "该交易对没有订单") { | ||||
| 			err = nil | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| @ -237,11 +241,12 @@ 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) | ||||
|  | ||||
| 		if err == nil { | ||||
| 			origQty := utility.StrToDecimal(spotOrder.OrigQuoteOrderQty) | ||||
| 			origQty := utility.StrToDecimal(spotOrder.OrigQty) | ||||
| 			excuteQty := utility.StrToDecimal(spotOrder.ExecutedQty) | ||||
| 			remainingQuantity = origQty.Sub(excuteQty).Abs() | ||||
| 			remainingQuantity = origQty.Sub(excuteQty).Abs().Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		} | ||||
|  | ||||
| 		if remainingQuantity.Cmp(decimal.Zero) <= 0 { | ||||
| @ -249,36 +254,62 @@ func (t LimitOrderTimeoutDuration) ReSpotOrderPlace(db *gorm.DB, order models.Li | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		tradeSet, _ := binanceservice.GetTradeSet(order.Symbol, 0) | ||||
| 		newClientOrderId := snowflakehelper.GetOrderId() | ||||
| 		maxMarketQty := decimal.NewFromFloat(tradeSet.MarketMaxQty).Truncate(int32(tradeSet.AmountDigit)) | ||||
|  | ||||
| 		order.Num = remainingQuantity.Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 		order.Desc = fmt.Sprintf("取消限价单,重下市价单源订单号:%s  ", order.OrderSn) | ||||
| 		order.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) | ||||
| 		order.MainOrderType = "MARKET" | ||||
| 		err = db.Model(&order).Updates(map[string]interface{}{"desc": order.Desc, "order_sn": order.OrderSn, "main_order_type": order.MainOrderType}).Error | ||||
| 		// if config.ApplicationConfig.Mode == "dev" { | ||||
| 		// 	maxMarketQty = decimal.NewFromFloat(10) | ||||
| 		// } | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Error(fmt.Sprintf("生成新市价单失败 err:%+v", err)) | ||||
| 			return err | ||||
| 		} | ||||
| 		params := binanceservice.OrderPlacementService{ | ||||
| 			ApiId:            order.ApiId, | ||||
| 			Symbol:           order.Symbol, | ||||
| 			Side:             order.Site, | ||||
| 			Type:             "MARKET", | ||||
| 			TimeInForce:      "GTC", | ||||
| 			Price:            utility.StringToDecimal(order.Price), | ||||
| 			StopPrice:        utility.StrToDecimal(order.Price), | ||||
| 			Quantity:         remainingQuantity, | ||||
| 			NewClientOrderId: utility.Int64ToString(newClientOrderId), | ||||
| 		} | ||||
| 		if err := spotApi.OrderPlace(db, params); err != nil { | ||||
| 			logger.Error(fmt.Sprintf("重新下市价单失败 err:%+v", err)) | ||||
| 			err := db.Model(&order).Updates(map[string]interface{}{"status": "2", "desc": order.Desc + "err:" + err.Error()}).Error | ||||
| 		if remainingQuantity.Cmp(maxMarketQty) > 0 && maxMarketQty.Cmp(decimal.Zero) > 0 { | ||||
| 			multiple := remainingQuantity.Div(maxMarketQty).Abs().IntPart() | ||||
| 			remainder := remainingQuantity.Mod(maxMarketQty) | ||||
| 			saveOrders := make([]models.LinePreOrder, 0) | ||||
|  | ||||
| 			desc := fmt.Sprintf("取消限价单,重下市价单 源订单号:%s", order.OrderSn) | ||||
|  | ||||
| 			// 创建 multiple 个订单 | ||||
| 			for i := 0; i < int(multiple); i++ { | ||||
| 				saveOrders = append(saveOrders, createNewOrder(order, maxMarketQty, desc)) | ||||
| 			} | ||||
|  | ||||
| 			// 处理余数 | ||||
| 			if remainder.Cmp(decimal.Zero) > 0 { | ||||
| 				saveOrders = append(saveOrders, createNewOrder(order, remainder.Truncate(int32(tradeSet.AmountDigit)), desc)) | ||||
| 			} | ||||
|  | ||||
| 			if err := db.Create(&saveOrders).Error; err != nil { | ||||
| 				logger.Errorf("市价订单拆分后保存失败,err:", err) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			for index := range saveOrders { | ||||
| 				newNum := utility.StrToDecimal(saveOrders[index].Num) | ||||
|  | ||||
| 				if err := newSpotOrderClosePosition(saveOrders[index], newNum, spotApi, db); err != nil { | ||||
| 					logger.Errorf("市价订单拆分后保存失败,err:", err) | ||||
| 				} | ||||
|  | ||||
| 				if index == len(saveOrders)-1 { | ||||
| 					orderCopy := saveOrders[index] // 复制数据 | ||||
|  | ||||
| 					binanceservice.HandleSpotMarketSliceTakeProfit(db, orderCopy, order.Id, apiUserinfo, tradeSet) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			newClientOrderId := snowflakehelper.GetOrderId() | ||||
|  | ||||
| 			order.Num = remainingQuantity.Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 			order.Desc = fmt.Sprintf("取消限价单,重下市价单源订单号:%s  ", order.OrderSn) | ||||
| 			order.OrderSn = utility.Int64ToString(newClientOrderId) | ||||
| 			order.MainOrderType = "MARKET" | ||||
| 			err = db.Model(&order).Updates(map[string]interface{}{"desc": order.Desc, "order_sn": order.OrderSn, "main_order_type": order.MainOrderType}).Error | ||||
|  | ||||
| 			if err != nil { | ||||
| 				logger.Error("下单失败后修改订单失败") | ||||
| 				logger.Error(fmt.Sprintf("生成新市价单失败 err:%+v", err)) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if err := newSpotOrderClosePosition(order, remainingQuantity, spotApi, db); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| @ -287,12 +318,38 @@ func (t LimitOrderTimeoutDuration) ReSpotOrderPlace(db *gorm.DB, order models.Li | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 现货市价单平仓 | ||||
| func newSpotOrderClosePosition(order models.LinePreOrder, remainingQuantity decimal.Decimal, spotApi binanceservice.SpotRestApi, db *gorm.DB) error { | ||||
| 	params := binanceservice.OrderPlacementService{ | ||||
| 		ApiId:            order.ApiId, | ||||
| 		Symbol:           order.Symbol, | ||||
| 		Side:             order.Site, | ||||
| 		Type:             "MARKET", | ||||
| 		TimeInForce:      "GTC", | ||||
| 		Price:            utility.StringToDecimal(order.Price), | ||||
| 		StopPrice:        utility.StrToDecimal(order.Price), | ||||
| 		Quantity:         remainingQuantity, | ||||
| 		NewClientOrderId: order.OrderSn, | ||||
| 	} | ||||
| 	if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { | ||||
| 		logger.Error(fmt.Sprintf("重新下市价单失败 err:%+v", err)) | ||||
| 		err := db.Model(&order).Updates(map[string]interface{}{"status": "2", "desc": order.Desc + "err:" + err.Error()}).Error | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Error("下单失败后修改订单失败") | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ReFutOrderPlace 重下合约市价单 | ||||
| func (t LimitOrderTimeoutDuration) ReFutOrderPlace(db *gorm.DB, order models.LinePreOrder, apiUserinfo models.LineApiUser, futApi binanceservice.FutRestApi) error { | ||||
| 	var err error | ||||
| 	for i := 0; i < 3; i++ { | ||||
| 		err := futApi.CancelFutOrder(apiUserinfo, order.Symbol, order.OrderSn) | ||||
| 		if err == nil || strings.Contains(err.Error(), "该交易对没有订单") { | ||||
| 			err = nil | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| @ -302,11 +359,12 @@ 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) | ||||
|  | ||||
| 		if err == nil { | ||||
| 			origQty := utility.StrToDecimal(spotOrder.OrigQty) | ||||
| 			excuteQty := utility.StrToDecimal(spotOrder.ExecutedQty) | ||||
| 			remainingQuantity = origQty.Sub(excuteQty).Abs() | ||||
| 			remainingQuantity = origQty.Sub(excuteQty).Abs().Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		} | ||||
|  | ||||
| 		if remainingQuantity.Cmp(decimal.Zero) <= 0 { | ||||
| @ -314,51 +372,62 @@ func (t LimitOrderTimeoutDuration) ReFutOrderPlace(db *gorm.DB, order models.Lin | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		tradeSet, _ := binanceservice.GetTradeSet(order.Symbol, 0) | ||||
| 		qty := decimal.NewFromFloat(tradeSet.MarketMaxQty) | ||||
| 		maxMarketQty := qty.Truncate(int32(tradeSet.AmountDigit)) | ||||
|  | ||||
| 		newClientOrderId := snowflakehelper.GetOrderId() | ||||
| 		order.Num = remainingQuantity.Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 		order.Desc = fmt.Sprintf("取消限价单,重下市价单 源订单号:%s", order.OrderSn) | ||||
| 		order.OrderSn = utility.Int64ToString(newClientOrderId) | ||||
|  | ||||
| 		// var newOrder models.LinePreOrder | ||||
| 		// copier.Copy(&newOrder, order) | ||||
| 		// newOrder.Id = 0 | ||||
| 		// newOrder.OrderSn = utility.Int64ToString(newClientOrderId) | ||||
| 		// newOrder.CreatedAt = time.Now() | ||||
| 		// newOrder.MainOrderType = "MARKET" | ||||
| 		// err = db.Model(&models.LinePreOrder{}).Create(&newOrder).Error | ||||
|  | ||||
| 		var positionSide string | ||||
|  | ||||
| 		if order.Site == "BUY" { | ||||
| 			positionSide = "SHORT" | ||||
| 		} else { | ||||
| 			positionSide = "LONG" | ||||
| 		} | ||||
| 		err = db.Model(&order).Updates(map[string]interface{}{"desc": order.Desc, "order_sn": order.OrderSn}).Error | ||||
| 		if err != nil { | ||||
| 			logger.Error(fmt.Sprintf("生成合约新市价单失败 err:%+v", err)) | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// params := binanceservice.FutOrderPlace{ | ||||
| 		// 	ApiId:            order.ApiId, | ||||
| 		// 	Symbol:           order.Symbol, | ||||
| 		// 	Side:             order.Site, | ||||
| 		// 	Quantity:         remainingQuantity, | ||||
| 		// 	Price:            utility.StringToDecimal(order.Price), | ||||
| 		// 	SideType:         "MARKET", | ||||
| 		// 	OpenOrder:        0, | ||||
| 		// 	OrderType:        "MARKET", | ||||
| 		// 	NewClientOrderId: utility.Int64ToString(newClientOrderId), | ||||
| 		// if config.ApplicationConfig.Mode == "dev" { | ||||
| 		// 	maxMarketQty = decimal.NewFromFloat(10) | ||||
| 		// } | ||||
|  | ||||
| 		if err := futApi.ClosePositionLoop(order.Symbol, order.OrderSn, remainingQuantity, order.Site, positionSide, apiUserinfo, "MARKET", "0", decimal.Zero, 3); err != nil { | ||||
| 			logger.Error(fmt.Sprintf("重新下合约市价单失败 err:%+v", err)) | ||||
| 			err := db.Model(&order).Updates(map[string]interface{}{"status": "2", "desc": order.Desc + " err:" + err.Error()}).Error | ||||
| 		//数量超过最大数量,则拆单 | ||||
| 		if remainingQuantity.Cmp(maxMarketQty) > 0 && maxMarketQty.Cmp(decimal.Zero) > 0 { | ||||
| 			multiple := remainingQuantity.Div(maxMarketQty).Abs().IntPart() | ||||
| 			remainder := remainingQuantity.Mod(maxMarketQty) | ||||
| 			saveOrders := make([]models.LinePreOrder, 0) | ||||
|  | ||||
| 			desc := fmt.Sprintf("取消限价单,重下市价单 源订单号:%s", order.OrderSn) | ||||
|  | ||||
| 			// 创建 multiple 个订单 | ||||
| 			for i := 0; i < int(multiple); i++ { | ||||
| 				saveOrders = append(saveOrders, createNewOrder(order, maxMarketQty, desc)) | ||||
| 			} | ||||
|  | ||||
| 			// 处理余数 | ||||
| 			if remainder.Cmp(decimal.Zero) > 0 { | ||||
| 				saveOrders = append(saveOrders, createNewOrder(order, remainder.Truncate(int32(tradeSet.AmountDigit)), desc)) | ||||
| 			} | ||||
|  | ||||
| 			if err := db.Create(&saveOrders).Error; err != nil { | ||||
| 				logger.Errorf("市价订单拆分后保存失败,err:", err) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			for index := range saveOrders { | ||||
| 				newNum := utility.StrToDecimal(saveOrders[index].Num) | ||||
|  | ||||
| 				if err := newOrderClosePosition(saveOrders[index], futApi, newNum.Truncate(int32(tradeSet.AmountDigit)), apiUserinfo, db); err != nil { | ||||
| 					logger.Errorf("市价单拆分后下单失败 orderSn:%s, err:%s", saveOrders[index].OrderSn, err) | ||||
| 				} | ||||
|  | ||||
| 				if index == len(saveOrders)-1 { | ||||
| 					orderCopy := saveOrders[index] // 复制数据 | ||||
| 					binanceservice.HandleMarketSliceTakeProfit(db, orderCopy, order.Id, apiUserinfo, tradeSet) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			newClientOrderId := snowflakehelper.GetOrderId() | ||||
| 			order.Num = remainingQuantity.Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 			order.Desc = fmt.Sprintf("取消限价单,重下市价单 源订单号:%s", order.OrderSn) | ||||
| 			order.OrderSn = utility.Int64ToString(newClientOrderId) | ||||
|  | ||||
| 			err = db.Model(&order).Updates(map[string]interface{}{"desc": order.Desc, "order_sn": order.OrderSn}).Error | ||||
| 			if err != nil { | ||||
| 				logger.Error(fmt.Sprintf("生成合约新市价单失败 err:%+v", err)) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = newOrderClosePosition(order, futApi, remainingQuantity, apiUserinfo, db) | ||||
| 			if err != nil { | ||||
| 				logger.Error("下单失败后修改订单失败") | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| @ -366,6 +435,54 @@ func (t LimitOrderTimeoutDuration) ReFutOrderPlace(db *gorm.DB, order models.Lin | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // 复制订单 | ||||
| func createNewOrder(order models.LinePreOrder, num decimal.Decimal, desc string) models.LinePreOrder { | ||||
| 	newOrder := models.LinePreOrder{} | ||||
| 	copier.Copy(&newOrder, order) | ||||
|  | ||||
| 	newOrder.Id = 0 | ||||
| 	newOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) | ||||
| 	newOrder.Num = num.String() | ||||
| 	newOrder.Desc = desc | ||||
| 	newOrder.MainOrderType = "MARKET" | ||||
| 	newOrder.Status = 0 | ||||
|  | ||||
| 	// 重新创建 Childs,并正确赋值 `Pid` | ||||
| 	var newChilds []models.LinePreOrder | ||||
| 	for _, child := range order.Childs { | ||||
| 		newChild := child | ||||
| 		newChild.Id = 0 | ||||
| 		newChild.Pid = 0 | ||||
| 		newChild.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) | ||||
| 		newChild.CreatedAt = time.Now() | ||||
| 		newChilds = append(newChilds, newChild) | ||||
| 	} | ||||
| 	newOrder.Childs = newChilds // 重新赋值子订单,避免浅拷贝问题 | ||||
|  | ||||
| 	return newOrder | ||||
| } | ||||
|  | ||||
| // 新市价单 | ||||
| func newOrderClosePosition(order models.LinePreOrder, futApi binanceservice.FutRestApi, remainingQuantity decimal.Decimal, apiUserinfo models.LineApiUser, db *gorm.DB) error { | ||||
| 	var positionSide string | ||||
|  | ||||
| 	if order.Site == "BUY" { | ||||
| 		positionSide = "SHORT" | ||||
| 	} else { | ||||
| 		positionSide = "LONG" | ||||
| 	} | ||||
|  | ||||
| 	if err := futApi.ClosePositionLoop(order.Symbol, order.OrderSn, remainingQuantity, order.Site, positionSide, apiUserinfo, "MARKET", "0", decimal.Zero, 3); err != nil { | ||||
| 		logger.Error(fmt.Sprintf("重新下合约市价单失败 err:%+v", err)) | ||||
| 		err := db.Model(&order).Updates(map[string]interface{}{"status": "2", "desc": order.Desc + " err:" + err.Error()}).Error | ||||
| 		if err != nil { | ||||
| 			logger.Error("下单失败后修改订单失败") | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (l ListenSymbol) Exec(arg interface{}) error { | ||||
| 	str := time.Now().Format(timeFormat) + " [INFO] JobCore ClearLogJob exec success" | ||||
| 	defer logger.Info(str) | ||||
|  | ||||
							
								
								
									
										159
									
								
								app/jobs/jobs_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								app/jobs/jobs_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/helper" | ||||
| 	"go-admin/services/binanceservice" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/config" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // 测试现货限价转市价订单 | ||||
| func TestSpotLimitTransferJob(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", "", "3") | ||||
|  | ||||
| 	job := LimitOrderTimeoutDuration{} | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	err := db.Model(&models.LinePreOrder{}). | ||||
| 		Where("order_sn =?", "393609596205268992"). | ||||
| 		Preload("Childs"). | ||||
| 		Find(&orders).Error | ||||
| 	// job.Exec([]string{}) | ||||
|  | ||||
| 	config.ApplicationConfig.Mode = "dev" | ||||
|  | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("获取订单失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, order := range orders { | ||||
| 		apiInfo, err := binanceservice.GetApiInfo(49) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("获取api信息失败 %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		spotApi := binanceservice.SpotRestApi{} | ||||
| 		err = job.ReSpotOrderPlace(db, order, apiInfo, spotApi) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("下单失败 %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	select {} | ||||
| } | ||||
|  | ||||
| // 测试限价转市价订单 | ||||
| func TestLimitTransferJob(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", "", "3") | ||||
|  | ||||
| 	job := LimitOrderTimeoutDuration{} | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	err := db.Model(&models.LinePreOrder{}). | ||||
| 		Where("order_sn =?", "393573282378416128"). | ||||
| 		Preload("Childs"). | ||||
| 		Find(&orders).Error | ||||
| 	// job.Exec([]string{}) | ||||
|  | ||||
| 	config.ApplicationConfig.Mode = "dev" | ||||
|  | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("获取订单失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, order := range orders { | ||||
| 		apiInfo, err := binanceservice.GetApiInfo(49) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("获取api信息失败 %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		futApi := binanceservice.FutRestApi{} | ||||
| 		err = job.ReFutOrderPlace(db, order, apiInfo, futApi) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("下单失败 %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	select {} | ||||
| } | ||||
|  | ||||
| func TestReduce(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
|  | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "3") | ||||
|  | ||||
| 	futApi := binanceservice.FutRestApi{} | ||||
| 	// params := binanceservice.FutOrderPlace{ | ||||
| 	// 	Symbol: "ADAUSDT", | ||||
| 	// 	ApiId: 49, | ||||
| 	// 	Side: , | ||||
| 	// } | ||||
| 	apiInfo, err := binanceservice.GetApiInfo(49) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("获取api信息失败 %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = futApi.ClosePositionLoop("ADAUSDT", "393573282378416128", decimal.NewFromInt(21), "SELL", "LONG", apiInfo, "LIMIT", "0", decimal.NewFromFloat(0.76), 3) | ||||
|  | ||||
| 	fmt.Printf("报错 %v", err) | ||||
| } | ||||
|  | ||||
| // 测试现货下单 | ||||
| func TestReduceSpot(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", "", "3") | ||||
|  | ||||
| 	spotApi := binanceservice.SpotRestApi{} | ||||
| 	// params := binanceservice.FutOrderPlace{ | ||||
| 	// 	Symbol: "ADAUSDT", | ||||
| 	// 	ApiId: 49, | ||||
| 	// 	Side: , | ||||
| 	// } | ||||
| 	apiInfo, err := binanceservice.GetApiInfo(49) | ||||
| 	fmt.Sprintf("%v", apiInfo) | ||||
|  | ||||
| 	// if err != nil { | ||||
| 	// 	fmt.Printf("获取api信息失败 %v", err) | ||||
| 	// 	return | ||||
| 	// } | ||||
|  | ||||
| 	err = spotApi.CancelOpenOrderByOrderSn(apiInfo, "ADAUSDT", "393609596188491776") | ||||
|  | ||||
| 	fmt.Printf("取消报错 %v", err) | ||||
|  | ||||
| 	params := binanceservice.OrderPlacementService{ | ||||
| 		ApiId:            49, | ||||
| 		Side:             "SELL", | ||||
| 		Type:             "LIMIT", | ||||
| 		TimeInForce:      "GTC", | ||||
| 		Symbol:           "ADAUSDT", | ||||
| 		Price:            decimal.NewFromFloat(0.76), | ||||
| 		Quantity:         decimal.NewFromInt(21), | ||||
| 		NewClientOrderId: "393609596205268992", | ||||
| 	} | ||||
| 	err = spotApi.OrderPlaceLoop(db, params, 3) | ||||
|  | ||||
| 	fmt.Printf("报错 %v", err) | ||||
| } | ||||
| @ -44,6 +44,9 @@ const ( | ||||
| 	SpotCallBack = "spot_callback:%s" //现货回调 {ordersn} | ||||
| 	FutCallBack  = "fut_callback:%s"  //合约回调 {ordersn} | ||||
|  | ||||
| 	FutReducecCallback = "fut_reduce_callback:%v_%s"  //合约减仓回调 {apiid,symbol} | ||||
| 	SpotReduceCallback = "spot_reduce_callback:%v_%s" //现货减仓回调 {apiid,symbol} | ||||
|  | ||||
| 	//需要清理键值---------BEGIN--------------- | ||||
|  | ||||
| 	SpotStopLossList    = "spot_stoploss_list:%s"    //现货止损待触发列表 {交易所类型code} | ||||
|  | ||||
							
								
								
									
										6
									
								
								common/const/rediskey/sys_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/const/rediskey/sys_config.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package rediskey | ||||
|  | ||||
| const ( | ||||
| 	// SysConfigKey 系统配置 {configKey} | ||||
| 	SysConfigKey = "sys_config:%s" | ||||
| ) | ||||
| @ -9,13 +9,13 @@ import ( | ||||
| 	"go-admin/config" | ||||
| 	"go-admin/pkg/httputils" | ||||
| 	"go-admin/services/binanceservice" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"go-admin/services/futureservice" | ||||
| 	"go-admin/services/scriptservice" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"github.com/robfig/cron/v3" | ||||
| 	"gorm.io/gorm" | ||||
| @ -29,12 +29,20 @@ func BusinessInit(db *gorm.DB) { | ||||
| 		os.Exit(-1) | ||||
| 	} | ||||
|  | ||||
| 	//初始化参数配置 | ||||
| 	cacheservice.InitConfigCache(db) | ||||
|  | ||||
| 	//初始化订单配置 | ||||
| 	binanceservice.ResetSystemSetting(db) | ||||
| 	lineApiUser := service.LineApiUser{} | ||||
| 	lineApiUser.Orm = db | ||||
| 	if err := lineApiUser.InitCache(); err != nil { | ||||
| 		logger.Errorf("初始化api用户失败:%v", err) | ||||
| 		os.Exit(-1) | ||||
| 	} | ||||
|  | ||||
| 	//币安 现货交易对 | ||||
| 	SpotCurrencyInit() | ||||
|  | ||||
| 	//币安 合约交易对 | ||||
| 	FuturesInit() | ||||
|  | ||||
| @ -101,7 +109,7 @@ func loadApiUser(db *gorm.DB) error { | ||||
| 	users := make([]models.LineApiUser, 0) | ||||
|  | ||||
| 	if err := db.Model(&models.LineApiUser{}).Where("open_status =1").Find(&users).Error; err != nil { | ||||
| 		log.Error("loadApiUser:", err) | ||||
| 		logger.Error("loadApiUser:", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| @ -111,7 +119,7 @@ func loadApiUser(db *gorm.DB) error { | ||||
|  | ||||
| 		if val != "" { | ||||
| 			if err := helper.DefaultRedis.SetString(key, val); err != nil { | ||||
| 				log.Error("loadApiUser 保存redis:", err) | ||||
| 				logger.Error("loadApiUser 保存redis:", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -119,7 +127,7 @@ func loadApiUser(db *gorm.DB) error { | ||||
| 	groups := make([]models.LineApiGroup, 0) | ||||
|  | ||||
| 	if err := db.Model(&models.LineApiGroup{}).Find(&groups).Error; err != nil { | ||||
| 		log.Error("loadApiGroup:", err) | ||||
| 		logger.Error("loadApiGroup:", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -51,7 +51,7 @@ settings: | ||||
|  | ||||
|     # redis 配置 | ||||
|     redis: | ||||
|       addr: "192.168.1.12:6379" | ||||
|       addr: "127.0.0.1:6379" | ||||
|       password: "" | ||||
|       db: 2 | ||||
|     # 雪花算法设备id | ||||
|  | ||||
| @ -15,24 +15,26 @@ type Coin struct { | ||||
|  | ||||
| // TradeSet vts_tradeset 交易配置 | ||||
| type TradeSet struct { | ||||
| 	ID          int     `db:"id" json:"id"` | ||||
| 	AmountDigit int     `db:"amountdigit"`                    //基础币种计数精度 | ||||
| 	PriceDigit  int     `db:"pricecdigit" json:"pricecdigit"` //价格小数点位数 | ||||
| 	Currency    string  `db:"currency" json:"currency"`       //法币 | ||||
| 	Coin        string  `db:"coin" json:"coin"`               //币种 | ||||
| 	PriceChange float64 `db:"pricechange" json:"pricechange"` //价格波动价位 | ||||
| 	MinBuyVal   float64 `db:"minbuyval"`                      //最小下单金额 | ||||
| 	OpenPrice   float64 `db:"openprice"`                      //开盘价格 | ||||
| 	LastPrice   string  `json:"last"`                         //最新价格 | ||||
| 	HighPrice   string  `json:"high"`                         //24小时最高价 | ||||
| 	LowPrice    string  `json:"low"`                          //24小时最低价 | ||||
| 	Volume      string  `json:"volume"`                       //24小时成数量 | ||||
| 	QuoteVolume string  `json:"quote"`                        //24小时成交金额 | ||||
| 	MinQty      float64 `json:"minQty"`                       //最小交易数量 | ||||
| 	MaxQty      float64 `json:"maxQty"`                       //最大交易数量 | ||||
| 	MaxNotional string  `json:"MaxNotional`                   //最大名义价值 | ||||
| 	MinNotional string  `json:"MinNotional`                   //最大名义价值 | ||||
| 	E           int64   `json:"-"`                            //推送时间 | ||||
| 	ID           int     `db:"id" json:"id"` | ||||
| 	AmountDigit  int     `db:"amountdigit"`                    //基础币种计数精度 | ||||
| 	PriceDigit   int     `db:"pricecdigit" json:"pricecdigit"` //价格小数点位数 | ||||
| 	Currency     string  `db:"currency" json:"currency"`       //法币 | ||||
| 	Coin         string  `db:"coin" json:"coin"`               //币种 | ||||
| 	PriceChange  float64 `db:"pricechange" json:"pricechange"` //价格波动价位 | ||||
| 	MinBuyVal    float64 `db:"minbuyval"`                      //最小下单金额 | ||||
| 	OpenPrice    float64 `db:"openprice"`                      //开盘价格 | ||||
| 	LastPrice    string  `json:"last"`                         //最新价格 | ||||
| 	HighPrice    string  `json:"high"`                         //24小时最高价 | ||||
| 	LowPrice     string  `json:"low"`                          //24小时最低价 | ||||
| 	Volume       string  `json:"volume"`                       //24小时成数量 | ||||
| 	QuoteVolume  string  `json:"quote"`                        //24小时成交金额 | ||||
| 	MinQty       float64 `json:"minQty"`                       //限价单最小交易数量 | ||||
| 	MaxQty       float64 `json:"maxQty"`                       //限价单最大交易数量 | ||||
| 	MarketMaxQty float64 `json:"marketQty"`                    //市价单最大交易数量 | ||||
| 	MarketMinQty float64 `json:"marketMinQty"`                 //市价单最小交易数量 | ||||
| 	MaxNotional  string  `json:"MaxNotional`                   //最大名义价值 | ||||
| 	MinNotional  string  `json:"MinNotional`                   //最大名义价值 | ||||
| 	E            int64   `json:"-"`                            //推送时间 | ||||
| } | ||||
|  | ||||
| //CommissionType  int     `db:"commissiontype"`                 //手续费:1买,2卖,3双向 | ||||
|  | ||||
							
								
								
									
										54
									
								
								services/binanceservice/futures_callback.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								services/binanceservice/futures_callback.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"go-admin/services/positionservice" | ||||
| 	"time" | ||||
|  | ||||
| 	models2 "go-admin/models" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // HandleMarketSliceTakeProfit | ||||
| // @Description: 限价拆分为市价后处理止盈止损 | ||||
| func HandleMarketSliceTakeProfit(db *gorm.DB, orderCopy models.LinePreOrder, extOrderId int, apiUserInfo models.LineApiUser, tradeSet models2.TradeSet) { | ||||
| 	HandleNextFuturesReduce(db, apiUserInfo, orderCopy, tradeSet) | ||||
|  | ||||
| 	//延时执行 | ||||
| 	time.AfterFunc(15*time.Second, func() { | ||||
| 		utility.SafeGo(func() { | ||||
| 			orderExt := models.LinePreOrderExt{} | ||||
| 			db.Model(&orderExt).Where("order_id =?", extOrderId).First(&orderExt) | ||||
| 			positionService := positionservice.BinancePositionManagement{} | ||||
| 			var side = "" | ||||
|  | ||||
| 			if orderCopy.OrderType != 0 { | ||||
| 				if orderCopy.Site == "BUY" { | ||||
| 					side = "SELL" | ||||
| 				} else { | ||||
| 					side = "BUY" | ||||
| 				} | ||||
|  | ||||
| 			} else { | ||||
| 				side = orderCopy.Site | ||||
| 			} | ||||
|  | ||||
| 			positionData, err := positionService.GetPosition(orderCopy.ApiId, orderCopy.SymbolType, orderCopy.ExchangeType, orderCopy.Symbol, side) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				logger.Errorf("获取持仓信息失败,err:", err) | ||||
| 			} else { | ||||
| 				sysConfig := cacheservice.GetConfigCacheByKey(db, "market_take_profit_ratio") | ||||
| 				stopSysConfig := cacheservice.GetConfigCacheByKey(db, "market_stop_loss_ratio") | ||||
| 				marketTakeProfitRatio := utility.StrToDecimal(sysConfig.ConfigValue) | ||||
| 				marketStopLossRatio := utility.StrToDecimal(stopSysConfig.ConfigValue) | ||||
|  | ||||
| 				FutTakeProfit(db, &orderCopy, apiUserInfo, tradeSet, positionData, orderExt, marketTakeProfitRatio, marketStopLossRatio) | ||||
| 			} | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
| @ -226,6 +226,11 @@ func GetAndReloadSymbols(data *map[string]models.TradeSet) error { | ||||
| 						tradeSet.MinBuyVal = utility.StringAsFloat(*filter.MinPrice) | ||||
| 					case "LOT_SIZE": | ||||
| 						tradeSet.AmountDigit = utility.GetPrecision(*filter.StepSize) | ||||
| 						tradeSet.MaxQty = utility.StringAsFloat(*filter.MaxQty) | ||||
| 						tradeSet.MinQty = utility.StringAsFloat(*filter.MinQty) | ||||
| 					case "MARKET_LOT_SIZE": | ||||
| 						tradeSet.MarketMaxQty = utility.StringAsFloat(*filter.MaxQty) | ||||
| 						tradeSet.MarketMinQty = utility.StringAsFloat(*filter.MinQty) | ||||
| 					case "MIN_NOTIONAL": | ||||
| 						tradeSet.MinNotional = *filter.Notional | ||||
| 					case "MAX_NOTIONAL": | ||||
|  | ||||
| @ -11,12 +11,14 @@ import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	models2 "go-admin/models" | ||||
| 	"go-admin/models/positiondto" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
| @ -148,26 +150,53 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	positionData := savePosition(db, preOrder) | ||||
| 	lock := helper.NewRedisLock(fmt.Sprintf(rediskey.FutReducecCallback, preOrder.ApiId, preOrder.Symbol), 120, 20, 100*time.Millisecond) | ||||
|  | ||||
| 	if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 		log.Error("获取锁失败", err) | ||||
| 		return | ||||
| 	} else if ok { | ||||
| 		defer lock.Release() | ||||
| 		positionData := savePosition(db, preOrder) | ||||
|  | ||||
| 		//市价单就跳出 市价减仓不设止盈止损 | ||||
| 		if preOrder.MainOrderType == "MARKET" { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		//亏损大于0 重新计算比例 | ||||
| 		FutTakeProfit(db, preOrder, apiUserInfo, tradeSet, positionData, orderExt, decimal.Zero, decimal.Zero) | ||||
| 		//处理下一个减仓 | ||||
| 		HandleNextFuturesReduce(db, apiUserInfo, *preOrder, tradeSet) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 减仓处理止盈止损 | ||||
| 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 { | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("pid =? AND order_type IN (1,2) AND status = 0", preOrder.Id).Find(&orders).Error; err != nil { | ||||
| 		logger.Errorf("handleMainReduceFilled 获取待触发订单失败,订单号:%s", preOrder.OrderSn) | ||||
| 		return | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	totalNum := getFuturesPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) | ||||
| 	price := utility.StrToDecimal(preOrder.Price).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 	futApi := FutRestApi{} | ||||
| 	mainId := preOrder.Id | ||||
|  | ||||
| 	if preOrder.MainId > 0 { | ||||
| 		mainId = preOrder.MainId | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range orders { | ||||
| 		if v.OrderType == 1 { | ||||
| 			//亏损大于0 重新计算比例 | ||||
| 			if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { | ||||
| 			//手动设置百分比 | ||||
| 			if manualTakeRatio.Cmp(decimal.Zero) > 0 { | ||||
| 				v.Rate = manualTakeRatio.String() | ||||
| 				percentag := manualTakeRatio.Div(decimal.NewFromInt(100)) | ||||
|  | ||||
| 				if positionData.PositionSide == "LONG" { | ||||
| 					v.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} else { | ||||
| 					v.Price = price.Mul(decimal.NewFromInt(1).Sub(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} | ||||
| 			} else if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { | ||||
| 				percentag := positionData.TotalLoss.Div(totalNum).Div(price).Mul(decimal.NewFromInt(100)) | ||||
| 				percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) | ||||
| 				v.Rate = percentag.String() | ||||
| @ -182,9 +211,32 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
|  | ||||
| 			processFutTakeProfitOrder(db, futApi, v, totalNum) | ||||
| 		} else if v.OrderType == 2 { | ||||
| 			if manualStopRatio.Cmp(decimal.Zero) > 0 { | ||||
| 				v.Rate = manualStopRatio.String() | ||||
| 				percentag := manualStopRatio.Div(decimal.NewFromInt(100)) | ||||
|  | ||||
| 				if positionData.PositionSide == "LONG" { | ||||
| 					v.Price = price.Mul(decimal.NewFromInt(1).Sub(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} else { | ||||
| 					v.Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} | ||||
| 			} | ||||
| 			processFutStopLossOrder(db, v, utility.StrToDecimal(v.Price), totalNum) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // 处理下一个待触发 | ||||
| func HandleNextFuturesReduce(db *gorm.DB, apiUserInfo DbModels.LineApiUser, preOrdedr DbModels.LinePreOrder, tradeSet models2.TradeSet) { | ||||
| 	mainId := preOrdedr.Id | ||||
|  | ||||
| 	if preOrdedr.MainId > 0 { | ||||
| 		mainId = preOrdedr.MainId | ||||
| 	} | ||||
|  | ||||
| 	totalNum := getFuturesPositionAvailableQuantity(db, apiUserInfo, &preOrdedr, tradeSet) | ||||
| 	nextFuturesReduceTrigger(db, mainId, totalNum, tradeSet) | ||||
| } | ||||
|  | ||||
| @ -307,7 +359,7 @@ func handleClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
|  | ||||
| 	if apiUserInfo.Id > 0 { | ||||
| 		mainIds := []int{preOrder.MainId} | ||||
| 		if err := cancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, false); err != nil { | ||||
| 		if err := CancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, false); err != nil { | ||||
| 			logger.Errorf("平仓单成功 取消其它订单失败 订单号:%s:", err) | ||||
| 		} | ||||
| 	} | ||||
| @ -328,7 +380,7 @@ func handleStopLoss(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
|  | ||||
| 	if apiUserInfo.Id > 0 { | ||||
| 		mainIds := []int{preOrder.MainId} | ||||
| 		if err := cancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, false); err != nil { | ||||
| 		if err := CancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, false); err != nil { | ||||
| 			logger.Errorf("止损单成功 取消其它订单失败 订单号:%s:", err) | ||||
| 		} | ||||
| 	} | ||||
| @ -356,7 +408,7 @@ func handleTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
|  | ||||
| 		if apiUserInfo.Id > 0 { | ||||
| 			mainIds := []int{preOrder.MainId} | ||||
| 			if err := cancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, false); err != nil { | ||||
| 			if err := CancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, false); err != nil { | ||||
| 				logger.Errorf("止损单成功 取消其它订单失败 订单号:%s:", err) | ||||
| 			} | ||||
| 		} | ||||
| @ -508,7 +560,7 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder, extOrd | ||||
| 		switch order.OrderType { | ||||
| 		case 1: // 止盈 | ||||
| 			//亏损大于0 重新计算比例 | ||||
| 			if first && positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { | ||||
| 			if first && positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 && preOrder.SignPriceType != "mixture" { | ||||
| 				percentag := positionData.TotalLoss.Div(num).Div(price).Mul(decimal.NewFromInt(100)) | ||||
| 				percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) | ||||
| 				order.Rate = percentag.String() | ||||
| @ -591,14 +643,14 @@ func cancelPositionOtherOrders(apiUserInfo DbModels.LineApiUser, db *gorm.DB, pr | ||||
|  | ||||
| 	// 批量取消订单 | ||||
|  | ||||
| 	err = cancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, changeMainOrderStatus) | ||||
| 	err = CancelMainOrders(mainIds, db, apiUserInfo, preOrder.Symbol, changeMainOrderStatus) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // 根据mainid 取消订单 | ||||
| // @changeMainOrderStatus 是否更新主单状态 | ||||
| func cancelMainOrders(mainIds []int, db *gorm.DB, apiUserInfo DbModels.LineApiUser, symbol string, changeMainOrderStatus bool) error { | ||||
| func CancelMainOrders(mainIds []int, db *gorm.DB, apiUserInfo DbModels.LineApiUser, symbol string, changeMainOrderStatus bool) error { | ||||
| 	if len(mainIds) > 0 { | ||||
| 		orderSns, err := GetOpenOrderSns(db, mainIds) | ||||
| 		if err != nil { | ||||
|  | ||||
							
								
								
									
										63
									
								
								services/binanceservice/spot_callback.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								services/binanceservice/spot_callback.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/app/admin/models" | ||||
| 	models2 "go-admin/models" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"go-admin/services/positionservice" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // HandleMarketSliceTakeProfit | ||||
| // @Description: 限价拆分为市价后处理止盈止损 | ||||
| func HandleSpotMarketSliceTakeProfit(db *gorm.DB, orderCopy models.LinePreOrder, extOrderId int, apiUserInfo models.LineApiUser, tradeSet models2.TradeSet) { | ||||
| 	mainId := orderCopy.Id | ||||
|  | ||||
| 	if orderCopy.MainId > 0 { | ||||
| 		mainId = orderCopy.MainId | ||||
| 	} | ||||
| 	totalNum := getSpotPositionAvailableQuantity(db, apiUserInfo, &orderCopy, tradeSet) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet) | ||||
| 	totalNum = totalNum.Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 	nextSpotReduceTrigger(db, mainId, totalNum, tradeSet) | ||||
|  | ||||
| 	//延时执行 | ||||
| 	time.AfterFunc(15*time.Second, func() { | ||||
| 		utility.SafeGo(func() { | ||||
| 			orderExt := models.LinePreOrderExt{} | ||||
| 			db.Model(&orderExt).Where("order_id =?", extOrderId).First(&orderExt) | ||||
| 			positionService := positionservice.BinancePositionManagement{} | ||||
| 			var side = "" | ||||
|  | ||||
| 			if orderCopy.OrderType != 0 { | ||||
| 				if orderCopy.Site == "BUY" { | ||||
| 					side = "SELL" | ||||
| 				} else { | ||||
| 					side = "BUY" | ||||
| 				} | ||||
|  | ||||
| 			} else { | ||||
| 				side = orderCopy.Site | ||||
| 			} | ||||
|  | ||||
| 			positionData, err := positionService.GetPosition(orderCopy.ApiId, orderCopy.SymbolType, orderCopy.ExchangeType, orderCopy.Symbol, side) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				logger.Errorf("获取持仓信息失败,err:", err) | ||||
| 			} else { | ||||
| 				sysConfig := cacheservice.GetConfigCacheByKey(db, "market_take_profit_ratio") | ||||
| 				stopSysConfig := cacheservice.GetConfigCacheByKey(db, "market_stop_loss_ratio") | ||||
| 				marketTakeProfitRatio := utility.StrToDecimal(sysConfig.ConfigValue) | ||||
| 				marketStopLossRatio := utility.StrToDecimal(stopSysConfig.ConfigValue) | ||||
| 				totalNum := getSpotPositionAvailableQuantity(db, apiUserInfo, &orderCopy, tradeSet) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet) | ||||
| 				totalNum = totalNum.Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit)) | ||||
|  | ||||
| 				SpotTakeProfit(db, &orderCopy, totalNum, positionData, orderExt, tradeSet, marketTakeProfitRatio, marketStopLossRatio) | ||||
| 			} | ||||
| 		}) | ||||
| 	}) | ||||
| } | ||||
| @ -20,6 +20,7 @@ import ( | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
| @ -173,34 +174,60 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
|  | ||||
| 	orderExt := models.LinePreOrderExt{} | ||||
| 	positionData := savePosition(db, preOrder) | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	// rate := utility.StringAsFloat(preOrder.Rate) | ||||
|  | ||||
| 	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) | ||||
|  | ||||
| 	// 100%减仓 终止流程 | ||||
| 	if orderExt.AddPositionVal.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 		//缓存 | ||||
| 		removeSpotLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) | ||||
| 		removePosition(db, preOrder) | ||||
| 	lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotReduceCallback, preOrder.ApiId, preOrder.Symbol), 120, 20, 100*time.Millisecond) | ||||
|  | ||||
| 		ids := []int{preOrder.MainId, preOrder.Pid} | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status =6", ids).Update("status", 9).Error; err != nil { | ||||
| 			logger.Info("100%减仓完毕,终结流程") | ||||
| 		} | ||||
| 	if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 		log.Error("获取锁失败", err) | ||||
| 		return | ||||
| 	} | ||||
| 	} else if ok { | ||||
| 		defer lock.Release() | ||||
| 		// 100%减仓 终止流程 | ||||
| 		if orderExt.AddPositionVal.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 			//缓存 | ||||
| 			removeSpotLossAndAddPosition(preOrder.MainId, preOrder.OrderSn) | ||||
| 			removePosition(db, preOrder) | ||||
|  | ||||
| 	totalNum := getSpotPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet) | ||||
| 	totalNum = totalNum.Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 			ids := []int{preOrder.MainId, preOrder.Pid} | ||||
| 			if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status =6", ids).Update("status", 9).Error; err != nil { | ||||
| 				logger.Info("100%减仓完毕,终结流程") | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		//市价单跳出 | ||||
| 		if preOrder.MainOrderType == "MARKET" { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		totalNum := getSpotPositionAvailableQuantity(db, apiUserInfo, preOrder, tradeSet) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet) | ||||
| 		totalNum = totalNum.Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		//亏损大于0 重新计算比例 | ||||
| 		SpotTakeProfit(db, preOrder, totalNum, positionData, orderExt, tradeSet, decimal.Zero, decimal.Zero) | ||||
|  | ||||
| 		mainId := preOrder.Id | ||||
|  | ||||
| 		if preOrder.MainId > 0 { | ||||
| 			mainId = preOrder.MainId | ||||
| 		} | ||||
|  | ||||
| 		nextSpotReduceTrigger(db, mainId, totalNum, tradeSet) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func SpotTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder, totalNum decimal.Decimal, | ||||
| 	positionData positiondto.PositionDto, orderExt DbModels.LinePreOrderExt, tradeSet models2.TradeSet, manualTakeRatio, manualStopRatio decimal.Decimal) bool { | ||||
| 	price := utility.StrToDecimal(preOrder.Price) | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("pid =? AND order_type IN (1,2) AND status=0", preOrder.Id).Find(&orders).Error; err != nil { | ||||
| 		logger.Errorf("获取减仓单止盈止损失败 err:%v", err) | ||||
| 		return | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	spotApi := SpotRestApi{} | ||||
| @ -209,8 +236,17 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
| 		orders[index].Num = totalNum.String() | ||||
|  | ||||
| 		if orders[index].OrderType == 1 { | ||||
| 			//亏损大于0 重新计算比例 | ||||
| 			if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { | ||||
| 			//手动设置百分比 | ||||
| 			if manualTakeRatio.Cmp(decimal.Zero) > 0 { | ||||
| 				orders[index].Rate = manualTakeRatio.String() | ||||
| 				percentag := manualTakeRatio.Div(decimal.NewFromInt(100)) | ||||
|  | ||||
| 				if positionData.PositionSide == "LONG" { | ||||
| 					orders[index].Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} else { | ||||
| 					orders[index].Price = price.Mul(decimal.NewFromInt(1).Sub(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} | ||||
| 			} else if positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { | ||||
| 				percentag := positionData.TotalLoss.Div(totalNum).Div(price).Mul(decimal.NewFromInt(100)) | ||||
| 				percentag = percentag.Add(orderExt.TakeProfitRatio).Truncate(2) | ||||
| 				orders[index].Rate = percentag.String() | ||||
| @ -220,17 +256,20 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
|  | ||||
| 			processTakeProfitOrder(db, spotApi, orders[index]) | ||||
| 		} else if orders[index].OrderType == 2 { | ||||
| 			if manualStopRatio.Cmp(decimal.Zero) > 0 { | ||||
| 				orders[index].Rate = manualStopRatio.String() | ||||
| 				percentag := manualStopRatio.Div(decimal.NewFromInt(100)) | ||||
|  | ||||
| 				if positionData.PositionSide == "LONG" { | ||||
| 					orders[index].Price = price.Mul(decimal.NewFromInt(1).Sub(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} else { | ||||
| 					orders[index].Price = price.Mul(decimal.NewFromInt(1).Add(percentag)).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 				} | ||||
| 			} | ||||
| 			processStopLossOrder(orders[index]) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	mainId := preOrder.Id | ||||
|  | ||||
| 	if preOrder.MainId > 0 { | ||||
| 		mainId = preOrder.MainId | ||||
| 	} | ||||
|  | ||||
| 	nextSpotReduceTrigger(db, mainId, totalNum, tradeSet) | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // 缓存下一个减仓单 | ||||
| @ -751,7 +790,7 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd | ||||
| 		switch order.OrderType { | ||||
| 		case 1: // 止盈 | ||||
| 			//亏损大于0 重新计算比例 | ||||
| 			if fist && positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 { | ||||
| 			if fist && positionData.TotalLoss.Cmp(decimal.Zero) > 0 && orderExt.Id > 0 && preOrder.SignPriceType != "mixture" { | ||||
| 				percentag := positionData.TotalLoss.Div(num).Div(price).Mul(decimal.NewFromInt(100)) | ||||
|  | ||||
| 				if fist { | ||||
|  | ||||
| @ -43,6 +43,9 @@ func GetSpotSymbols() (map[string]models.TradeSet, []string, error) { | ||||
| 						tradeSet.AmountDigit = utility.GetPrecision(filter.StepSize) | ||||
| 						tradeSet.MinQty = utility.StringAsFloat(filter.MinQty) | ||||
| 						tradeSet.MaxQty = utility.StringAsFloat(filter.MaxQty) | ||||
| 					case "MARKET_LOT_SIZE": | ||||
| 						tradeSet.MarketMinQty = utility.StringAsFloat(filter.MinQty) | ||||
| 						tradeSet.MarketMinQty = utility.StringAsFloat(filter.MaxQty) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
|  | ||||
							
								
								
									
										61
									
								
								services/cacheservice/config_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								services/cacheservice/config_service.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| package cacheservice | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/const/rediskey" | ||||
| 	"go-admin/common/helper" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // 加载config缓存 | ||||
| func InitConfigCache(db *gorm.DB) { | ||||
| 	datas := []models.SysConfig{} | ||||
| 	db.Model(&models.SysConfig{}).Find(&datas) | ||||
| 	for _, item := range datas { | ||||
| 		cacheKey := fmt.Sprintf(rediskey.SysConfigKey, item.ConfigKey) | ||||
|  | ||||
| 		content, err := sonic.Marshal(item) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Error(err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if len(content) > 0 { | ||||
| 			helper.DefaultRedis.SetString(cacheKey, item.ConfigValue) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取 sys config缓存 | ||||
| func GetConfigCacheByKey(db *gorm.DB, key string) models.SysConfig { | ||||
| 	cacheKey := fmt.Sprintf(rediskey.SysConfigKey, key) | ||||
| 	val, _ := helper.DefaultRedis.GetString(key) | ||||
| 	result := models.SysConfig{} | ||||
|  | ||||
| 	if val == "" { | ||||
| 		sonic.Unmarshal([]byte(val), &result) | ||||
| 	} | ||||
|  | ||||
| 	if result.Id == 0 { | ||||
| 		if err := db.Model(&result).Where("config_key = ?", key).First(&result).Error; err != nil { | ||||
| 			logger.Errorf("GetConfigCacheByKey %s", err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if result.Id > 0 { | ||||
| 		content, err := sonic.Marshal(result) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("GetConfigCacheByKey %s", err.Error()) | ||||
| 		} else { | ||||
| 			helper.DefaultRedis.SetString(cacheKey, string(content)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
		Reference in New Issue
	
	Block a user