diff --git a/app/admin/apis/line_api_user.go b/app/admin/apis/line_api_user.go index 4339c26..127b34f 100644 --- a/app/admin/apis/line_api_user.go +++ b/app/admin/apis/line_api_user.go @@ -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(), "查询成功") } diff --git a/app/admin/apis/line_api_user_group.go b/app/admin/apis/line_api_user_group.go new file mode 100644 index 0000000..8b1c441 --- /dev/null +++ b/app/admin/apis/line_api_user_group.go @@ -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, "查询成功") +} diff --git a/app/admin/apis/line_pre_order.go b/app/admin/apis/line_pre_order.go index 19b7c88..165f70c 100644 --- a/app/admin/apis/line_pre_order.go +++ b/app/admin/apis/line_pre_order.go @@ -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, "操作成功") } diff --git a/app/admin/apis/sys_config.go b/app/admin/apis/sys_config.go index 0a95c8b..458087d 100644 --- a/app/admin/apis/sys_config.go +++ b/app/admin/apis/sys_config.go @@ -113,6 +113,7 @@ func (e SysConfig) Insert(c *gin.Context) { e.Error(500, err, "创建失败") return } + e.OK(req.GetId(), "创建成功") } diff --git a/app/admin/models/line_api_user_group.go b/app/admin/models/line_api_user_group.go new file mode 100644 index 0000000..c306775 --- /dev/null +++ b/app/admin/models/line_api_user_group.go @@ -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 +} diff --git a/app/admin/models/line_pre_order.go b/app/admin/models/line_pre_order.go index 4e9caab..a3155d7 100644 --- a/app/admin/models/line_pre_order.go +++ b/app/admin/models/line_pre_order.go @@ -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 diff --git a/app/admin/router/line_api_user_group.go b/app/admin/router/line_api_user_group.go new file mode 100644 index 0000000..7ad49b6 --- /dev/null +++ b/app/admin/router/line_api_user_group.go @@ -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) + } +} diff --git a/app/admin/service/dto/line_api_user.go b/app/admin/service/dto/line_api_user.go index 60ea651..d710531 100644 --- a/app/admin/service/dto/line_api_user.go +++ b/app/admin/service/dto/line_api_user.go @@ -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 } diff --git a/app/admin/service/dto/line_api_user_group.go b/app/admin/service/dto/line_api_user_group.go new file mode 100644 index 0000000..36230df --- /dev/null +++ b/app/admin/service/dto/line_api_user_group.go @@ -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"` +} diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go index 82164b4..efae46c 100644 --- a/app/admin/service/dto/line_pre_order.go +++ b/app/admin/service/dto/line_pre_order.go @@ -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"` //价格模式 diff --git a/app/admin/service/line_api_user.go b/app/admin/service/line_api_user.go index 23b1551..55ed4bc 100644 --- a/app/admin/service/line_api_user.go +++ b/app/admin/service/line_api_user.go @@ -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 +} diff --git a/app/admin/service/line_api_user_group.go b/app/admin/service/line_api_user_group.go new file mode 100644 index 0000000..3c3996c --- /dev/null +++ b/app/admin/service/line_api_user_group.go @@ -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 +} diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index d8c2149..ba8601d 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -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 diff --git a/app/admin/service/line_symbol_group.go b/app/admin/service/line_symbol_group.go index 6b40cd0..14d7af6 100644 --- a/app/admin/service/line_symbol_group.go +++ b/app/admin/service/line_symbol_group.go @@ -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) diff --git a/app/admin/service/line_user.go b/app/admin/service/line_user.go index fbabfd5..9d56539 100644 --- a/app/admin/service/line_user.go +++ b/app/admin/service/line_user.go @@ -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 } diff --git a/app/admin/service/sys_config.go b/app/admin/service/sys_config.go index e90490c..60d1d2e 100644 --- a/app/admin/service/sys_config.go +++ b/app/admin/service/sys_config.go @@ -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 } diff --git a/app/jobs/jobbase.go b/app/jobs/jobbase.go index ea6fe3d..66e5afc 100644 --- a/app/jobs/jobbase.go +++ b/app/jobs/jobbase.go @@ -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 { diff --git a/app/jobs/jobs.go b/app/jobs/jobs.go index e85b2f5..685eb24 100644 --- a/app/jobs/jobs.go +++ b/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) diff --git a/app/jobs/jobs_test.go b/app/jobs/jobs_test.go new file mode 100644 index 0000000..aa424ef --- /dev/null +++ b/app/jobs/jobs_test.go @@ -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) +} diff --git a/common/const/rediskey/redis_key.go b/common/const/rediskey/redis_key.go index 56d5372..75aecee 100644 --- a/common/const/rediskey/redis_key.go +++ b/common/const/rediskey/redis_key.go @@ -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} diff --git a/common/const/rediskey/sys_config.go b/common/const/rediskey/sys_config.go new file mode 100644 index 0000000..41446d1 --- /dev/null +++ b/common/const/rediskey/sys_config.go @@ -0,0 +1,6 @@ +package rediskey + +const ( + // SysConfigKey 系统配置 {configKey} + SysConfigKey = "sys_config:%s" +) diff --git a/config/serverinit/business_init.go b/config/serverinit/business_init.go index 87ebca1..61e0608 100644 --- a/config/serverinit/business_init.go +++ b/config/serverinit/business_init.go @@ -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 } diff --git a/config/settings.yml b/config/settings.yml index ac81484..e666e17 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -51,7 +51,7 @@ settings: # redis 配置 redis: - addr: "192.168.1.12:6379" + addr: "127.0.0.1:6379" password: "" db: 2 # 雪花算法设备id diff --git a/models/coin.go b/models/coin.go index 4ac17f4..df01733 100644 --- a/models/coin.go +++ b/models/coin.go @@ -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双向 diff --git a/services/binanceservice/futures_callback.go b/services/binanceservice/futures_callback.go new file mode 100644 index 0000000..ef7f649 --- /dev/null +++ b/services/binanceservice/futures_callback.go @@ -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) + } + }) + }) +} diff --git a/services/binanceservice/futuresbinancerest.go b/services/binanceservice/futuresbinancerest.go index cd1ac54..147a943 100644 --- a/services/binanceservice/futuresbinancerest.go +++ b/services/binanceservice/futuresbinancerest.go @@ -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": diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index 8ca6967..81c7d45 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -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 { diff --git a/services/binanceservice/spot_callback.go b/services/binanceservice/spot_callback.go new file mode 100644 index 0000000..3587310 --- /dev/null +++ b/services/binanceservice/spot_callback.go @@ -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) + } + }) + }) +} diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index 78e0534..e921d9a 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -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 { diff --git a/services/binanceservice/spotsymbolservice.go b/services/binanceservice/spotsymbolservice.go index f3a0dec..cd1948a 100644 --- a/services/binanceservice/spotsymbolservice.go +++ b/services/binanceservice/spotsymbolservice.go @@ -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) } } diff --git a/services/cacheservice/config_service.go b/services/cacheservice/config_service.go new file mode 100644 index 0000000..e22bae5 --- /dev/null +++ b/services/cacheservice/config_service.go @@ -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 +}