1撤销限价后 市价分单

This commit is contained in:
2025-03-27 16:18:32 +08:00
parent 8cede57a70
commit ee148ed144
31 changed files with 1627 additions and 248 deletions

View File

@ -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(), "查询成功")
}

View 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, "查询成功")
}

View File

@ -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, "操作成功")
}

View File

@ -113,6 +113,7 @@ func (e SysConfig) Insert(c *gin.Context) {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}

View 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
}

View File

@ -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

View 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)
}
}

View File

@ -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
}

View 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"`
}

View File

@ -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"` //价格模式

View File

@ -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
}

View 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
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
View 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)
}

View File

@ -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}

View File

@ -0,0 +1,6 @@
package rediskey
const (
// SysConfigKey 系统配置 {configKey}
SysConfigKey = "sys_config:%s"
)

View File

@ -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
}

View File

@ -51,7 +51,7 @@ settings:
# redis 配置
redis:
addr: "192.168.1.12:6379"
addr: "127.0.0.1:6379"
password: ""
db: 2
# 雪花算法设备id

View File

@ -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双向

View 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)
}
})
})
}

View File

@ -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":

View File

@ -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 {

View 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)
}
})
})
}

View File

@ -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 {

View File

@ -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)
}
}

View 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
}