1暂时提交
This commit is contained in:
		| @ -328,3 +328,24 @@ func (e LineApiUser) GetReverseApiOptions(c *gin.Context) { | |||||||
| 	} | 	} | ||||||
| 	e.OK(list, "操作成功") | 	e.OK(list, "操作成功") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 获取所有启用的反单api用户 | ||||||
|  | func (e LineApiUser) GetReverseApiOptionsAll(c *gin.Context) { | ||||||
|  | 	s := service.LineApiUser{} | ||||||
|  | 	err := e.MakeContext(c). | ||||||
|  | 		MakeOrm(). | ||||||
|  | 		MakeService(&s.Service). | ||||||
|  | 		Errors | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Logger.Error(err) | ||||||
|  | 		e.Error(500, err, err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	list := make([]dto.LineApiUserOptionResp, 0) | ||||||
|  | 	err = s.GetReverseApiOptionsAll(&list) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error())) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	e.OK(list, "操作成功") | ||||||
|  | } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ package apis | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	"github.com/go-admin-team/go-admin-core/sdk/api" | 	"github.com/go-admin-team/go-admin-core/sdk/api" | ||||||
| @ -194,3 +195,61 @@ func (e LineReversePosition) Delete(c *gin.Context) { | |||||||
| 	} | 	} | ||||||
| 	e.OK(req.GetId(), "删除成功") | 	e.OK(req.GetId(), "删除成功") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ClosePosition 平仓 | ||||||
|  | func (e LineReversePosition) ClosePosition(c *gin.Context) { | ||||||
|  | 	req := dto.LineReversePositionCloseReq{} | ||||||
|  | 	s := service.LineReversePosition{} | ||||||
|  | 	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 | ||||||
|  | 	} | ||||||
|  | 	userId := user.GetUserId(c) | ||||||
|  | 	p := actions.GetPermissionFromContext(c) | ||||||
|  |  | ||||||
|  | 	err = s.ClosePosition(&req, p, userId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, fmt.Sprintf("平仓失败,\r\n失败信息 %s", err.Error())) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	e.OK(nil, "平仓成功") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ClosePositionBatch 批量平仓 | ||||||
|  | func (e LineReversePosition) ClosePositionBatch(c *gin.Context) { | ||||||
|  | 	req := dto.LineReversePositionCloseBatchReq{} | ||||||
|  | 	s := service.LineReversePosition{} | ||||||
|  | 	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 | ||||||
|  | 	} | ||||||
|  | 	userId := user.GetUserId(c) | ||||||
|  | 	p := actions.GetPermissionFromContext(c) | ||||||
|  | 	errs := make([]string, 0) | ||||||
|  |  | ||||||
|  | 	err = s.ClosePositionBatch(&req, p, userId, &errs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Error(500, err, fmt.Sprintf("批量平仓失败,\r\n失败信息 %s", err.Error())) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(errs) > 0 { | ||||||
|  | 		content := strings.Join(errs, "</br>") | ||||||
|  |  | ||||||
|  | 		e.OK(content, "批量平仓部分失败") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	e.OK(nil, "批量平仓成功") | ||||||
|  | } | ||||||
|  | |||||||
| @ -31,5 +31,7 @@ func registerLineApiUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi | |||||||
| 		r.GET("unbind-reverse", api.GetUnBindReverseApiUser) //获取未绑定下反单用户 | 		r.GET("unbind-reverse", api.GetUnBindReverseApiUser) //获取未绑定下反单用户 | ||||||
|  |  | ||||||
| 		r.GET("reverse-options", api.GetReverseApiOptions) //获取可用反单api用户 | 		r.GET("reverse-options", api.GetReverseApiOptions) //获取可用反单api用户 | ||||||
|  |  | ||||||
|  | 		r.GET("reverse-options-all", api.GetReverseApiOptionsAll) //获取全部启用的反单api用户 | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,8 +5,8 @@ import ( | |||||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/apis" | 	"go-admin/app/admin/apis" | ||||||
| 	"go-admin/common/middleware" |  | ||||||
| 	"go-admin/common/actions" | 	"go-admin/common/actions" | ||||||
|  | 	"go-admin/common/middleware" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| @ -23,5 +23,8 @@ func registerLineReversePositionRouter(v1 *gin.RouterGroup, authMiddleware *jwt. | |||||||
| 		r.POST("", api.Insert) | 		r.POST("", api.Insert) | ||||||
| 		r.PUT("/:id", actions.PermissionAction(), api.Update) | 		r.PUT("/:id", actions.PermissionAction(), api.Update) | ||||||
| 		r.DELETE("", api.Delete) | 		r.DELETE("", api.Delete) | ||||||
|  |  | ||||||
|  | 		r.PUT("close/:id", actions.PermissionAction(), api.ClosePosition) | ||||||
|  | 		r.PUT("close-batch", actions.PermissionAction(), api.ClosePositionBatch) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -224,3 +224,8 @@ type GetReverseApiOptionsReq struct { | |||||||
| 	Id    int `json:"apiId" form:"id"` | 	Id    int `json:"apiId" form:"id"` | ||||||
| 	ApiId int `json:"apiId" form:"apiId"` | 	ApiId int `json:"apiId" form:"apiId"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type LineApiUserOptionResp struct { | ||||||
|  | 	Id   int    `json:"id"` | ||||||
|  | 	Name string `json:"name"` | ||||||
|  | } | ||||||
|  | |||||||
| @ -18,6 +18,8 @@ type LineReverseOrderGetPageReq struct { | |||||||
| 	OrderType      int    `form:"orderType"  search:"type:exact;column:order_type;table:line_reverse_order" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"` | 	OrderType      int    `form:"orderType"  search:"type:exact;column:order_type;table:line_reverse_order" comment:"订单类型 0-主单 1-止损单 2-加仓 3-减仓"` | ||||||
| 	PositionSide   string `form:"positionSide"  search:"type:exact;column:position_side;table:line_reverse_order" comment:"持仓方向 LONG-多 SHORT-空"` | 	PositionSide   string `form:"positionSide"  search:"type:exact;column:position_side;table:line_reverse_order" comment:"持仓方向 LONG-多 SHORT-空"` | ||||||
| 	Side           string `form:"side"  search:"type:exact;column:side;table:line_reverse_order" comment:"买卖方向 SELL-卖 BUY-买"` | 	Side           string `form:"side"  search:"type:exact;column:side;table:line_reverse_order" comment:"买卖方向 SELL-卖 BUY-买"` | ||||||
|  | 	Type           string `form:"type"  search:"type:exact;column:type;table:line_reverse_order" comment:"类型 LIMIT-限价 MARKET-市价 "` | ||||||
|  | 	Category       int    `form:"category" search:"-" comment:"类型 0-主单 1-反单"` | ||||||
| 	Status         int    `form:"status"  search:"type:exact;column:status;table:line_reverse_order" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | 	Status         int    `form:"status"  search:"type:exact;column:status;table:line_reverse_order" comment:"状态 1-待下单 2-已下单 3-已成交 4-已平仓 5-已止损"` | ||||||
| 	PositionId     int    `form:"positionId" search:"type:exact;column:position_id;table:line_reverse_order" comment:"持仓id"` | 	PositionId     int    `form:"positionId" search:"type:exact;column:position_id;table:line_reverse_order" comment:"持仓id"` | ||||||
| 	LineReverseOrderOrder | 	LineReverseOrderOrder | ||||||
|  | |||||||
| @ -133,3 +133,23 @@ type LineReversePositionListResp struct { | |||||||
| 	ReverseAveragePrice decimal.Decimal `json:"reverseAveragePrice"` | 	ReverseAveragePrice decimal.Decimal `json:"reverseAveragePrice"` | ||||||
| 	CreatedAt           string          `json:"createdAt"` | 	CreatedAt           string          `json:"createdAt"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type LineReversePositionCloseReq struct { | ||||||
|  | 	PositionId int `uri:"id" form:"id" comment:"仓位id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LineReversePositionCloseBatchReq struct { | ||||||
|  | 	PositionSide  string `json:"positionSide"` | ||||||
|  | 	Symbol        string `json:"symbol"` | ||||||
|  | 	ReverseApiIds []int  `json:"reverseApiIds" form:"reverseApiIds" comment:"反单api_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GetPositionSymbolReq struct { | ||||||
|  | 	ReverseApiId int    `form:"reverseApiId" comment:"反单api_id"` | ||||||
|  | 	PositionSide string `form:"positionSide" comment:"持仓方向 LONG SHORT"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PositionSymbolResp struct { | ||||||
|  | 	Symbol string `json:"symbol"` | ||||||
|  | 	Code   string `json:"code"` | ||||||
|  | } | ||||||
|  | |||||||
| @ -26,6 +26,26 @@ type LineApiUser struct { | |||||||
| 	service.Service | 	service.Service | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 获取所有启用的反单api用户 | ||||||
|  | func (e LineApiUser) GetReverseApiOptionsAll(user *[]dto.LineApiUserOptionResp) error { | ||||||
|  | 	var data models.LineApiUser | ||||||
|  | 	var datas []models.LineApiUser | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Model(&data).Where("open_status = 1 AND subordinate = '2'").Find(&datas).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("LineApiUserService GetReverseApiOptionsAll error:%s \r\n", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, item := range datas { | ||||||
|  | 		*user = append(*user, dto.LineApiUserOptionResp{ | ||||||
|  | 			Id:   item.Id, | ||||||
|  | 			Name: item.ApiName, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // 获取可以绑定的api列表 | // 获取可以绑定的api列表 | ||||||
| func (e LineApiUser) GetReverseApiOptions(req *dto.GetReverseApiOptionsReq, user *[]models.LineApiUser) error { | func (e LineApiUser) GetReverseApiOptions(req *dto.GetReverseApiOptionsReq, user *[]models.LineApiUser) error { | ||||||
| 	query := e.Orm.Model(models.LineApiUser{}). | 	query := e.Orm.Model(models.LineApiUser{}). | ||||||
| @ -142,7 +162,10 @@ func (e *LineApiUser) Insert(c *dto.LineApiUserInsertReq) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.saveCache(data) | 	if err2 := e.CacheRelation(); err2 != nil { | ||||||
|  | 		return err2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	val, _ := sonic.MarshalString(&data) | 	val, _ := sonic.MarshalString(&data) | ||||||
|  |  | ||||||
| 	if val != "" { | 	if val != "" { | ||||||
| @ -151,9 +174,6 @@ func (e *LineApiUser) Insert(c *dto.LineApiUserInsertReq) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err2 := e.CacheRelation(); err2 != nil { |  | ||||||
| 		return err2 |  | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -173,7 +193,7 @@ func (e *LineApiUser) restartWebsocket(data models.LineApiUser) { | |||||||
| 		fuSocket.Stop() | 		fuSocket.Stop() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.saveCache(data) | 	e.CacheRelation() | ||||||
|  |  | ||||||
| 	OpenUserBinanceWebsocket(data) | 	OpenUserBinanceWebsocket(data) | ||||||
| } | } | ||||||
| @ -195,24 +215,14 @@ func (e *LineApiUser) saveCache(data models.LineApiUser) { | |||||||
| // cacheAll 是否缓存所有关系 | // cacheAll 是否缓存所有关系 | ||||||
| func (e *LineApiUser) CacheRelation() error { | func (e *LineApiUser) CacheRelation() error { | ||||||
| 	var datas *[]models.LineApiUser | 	var datas *[]models.LineApiUser | ||||||
| 	cacheStrs := make([]string, 0) |  | ||||||
|  |  | ||||||
| 	if err := e.Orm.Model(&models.LineApiUser{}).Where("subordinate ='1' and reverse_api_id >0 and open_status =1 and reverse_status =1").Find(&datas).Error; err != nil { | 	if err := e.Orm.Model(&models.LineApiUser{}).Where("open_status = 1").Find(&datas).Error; err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, data := range *datas { | 	for _, data := range *datas { | ||||||
| 		cacheStrs = append(cacheStrs, fmt.Sprintf("%d:%d", data.Id, data.ReverseApiId)) | 		// cacheStrs = append(cacheStrs, fmt.Sprintf(rediskey.API_USER, data.Id)) | ||||||
| 	} | 		e.saveCache(data) | ||||||
|  |  | ||||||
| 	if len(cacheStrs) > 0 { |  | ||||||
| 		if err := helper.DefaultRedis.SetListCache(rediskey.ApiReverseRelation, 0, cacheStrs...); err != nil { |  | ||||||
| 			e.Log.Errorf("设置缓存失败 err:%v", err) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		if err := helper.DefaultRedis.DeleteString(rediskey.ApiReverseRelation); err != nil { |  | ||||||
| 			e.Log.Errorf("删除缓存失败 err:%v", err) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @ -323,7 +333,9 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.saveCache(data) | 	if err2 := e.CacheRelation(); err2 != nil { | ||||||
|  | 		return err2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	//旧key和新的key不一样,则关闭旧的websocket | 	//旧key和新的key不一样,则关闭旧的websocket | ||||||
| 	if oldApiKey != data.ApiKey { | 	if oldApiKey != data.ApiKey { | ||||||
| @ -340,10 +352,6 @@ func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermiss | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err2 := e.CacheRelation(); err2 != nil { |  | ||||||
| 		return err2 |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ package service | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  |  | ||||||
|     "github.com/go-admin-team/go-admin-core/sdk/service" | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/models" | 	"go-admin/app/admin/models" | ||||||
| @ -20,13 +20,18 @@ type LineReverseOrder struct { | |||||||
| func (e *LineReverseOrder) GetPage(c *dto.LineReverseOrderGetPageReq, p *actions.DataPermission, list *[]models.LineReverseOrder, count *int64) error { | func (e *LineReverseOrder) GetPage(c *dto.LineReverseOrderGetPageReq, p *actions.DataPermission, list *[]models.LineReverseOrder, count *int64) error { | ||||||
| 	var err error | 	var err error | ||||||
| 	var data models.LineReverseOrder | 	var data models.LineReverseOrder | ||||||
|  | 	query := e.Orm.Model(&data). | ||||||
| 	err = e.Orm.Model(&data). |  | ||||||
| 		Scopes( | 		Scopes( | ||||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | 			cDto.MakeCondition(c.GetNeedSearch()), | ||||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||||
| 			actions.Permission(data.TableName(), p), | 			actions.Permission(data.TableName(), p), | ||||||
| 		). | 		) | ||||||
|  |  | ||||||
|  | 	if c.Category >= 0 { | ||||||
|  | 		query = query.Where("category =?", c.Category) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = query. | ||||||
| 		Find(list).Limit(-1).Offset(-1). | 		Find(list).Limit(-1).Offset(-1). | ||||||
| 		Count(count).Error | 		Count(count).Error | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -59,9 +64,9 @@ func (e *LineReverseOrder) Get(d *dto.LineReverseOrderGetReq, p *actions.DataPer | |||||||
|  |  | ||||||
| // Insert 创建LineReverseOrder对象 | // Insert 创建LineReverseOrder对象 | ||||||
| func (e *LineReverseOrder) Insert(c *dto.LineReverseOrderInsertReq) error { | func (e *LineReverseOrder) Insert(c *dto.LineReverseOrderInsertReq) error { | ||||||
|     var err error | 	var err error | ||||||
|     var data models.LineReverseOrder | 	var data models.LineReverseOrder | ||||||
|     c.Generate(&data) | 	c.Generate(&data) | ||||||
| 	err = e.Orm.Create(&data).Error | 	err = e.Orm.Create(&data).Error | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Log.Errorf("LineReverseOrderService Insert error:%s \r\n", err) | 		e.Log.Errorf("LineReverseOrderService Insert error:%s \r\n", err) | ||||||
| @ -72,22 +77,22 @@ func (e *LineReverseOrder) Insert(c *dto.LineReverseOrderInsertReq) error { | |||||||
|  |  | ||||||
| // Update 修改LineReverseOrder对象 | // Update 修改LineReverseOrder对象 | ||||||
| func (e *LineReverseOrder) Update(c *dto.LineReverseOrderUpdateReq, p *actions.DataPermission) error { | func (e *LineReverseOrder) Update(c *dto.LineReverseOrderUpdateReq, p *actions.DataPermission) error { | ||||||
|     var err error | 	var err error | ||||||
|     var data = models.LineReverseOrder{} | 	var data = models.LineReverseOrder{} | ||||||
|     e.Orm.Scopes( | 	e.Orm.Scopes( | ||||||
|             actions.Permission(data.TableName(), p), | 		actions.Permission(data.TableName(), p), | ||||||
|         ).First(&data, c.GetId()) | 	).First(&data, c.GetId()) | ||||||
|     c.Generate(&data) | 	c.Generate(&data) | ||||||
|  |  | ||||||
|     db := e.Orm.Save(&data) | 	db := e.Orm.Save(&data) | ||||||
|     if err = db.Error; err != nil { | 	if err = db.Error; err != nil { | ||||||
|         e.Log.Errorf("LineReverseOrderService Save error:%s \r\n", err) | 		e.Log.Errorf("LineReverseOrderService Save error:%s \r\n", err) | ||||||
|         return err | 		return err | ||||||
|     } | 	} | ||||||
|     if db.RowsAffected == 0 { | 	if db.RowsAffected == 0 { | ||||||
|         return errors.New("无权更新该数据") | 		return errors.New("无权更新该数据") | ||||||
|     } | 	} | ||||||
|     return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Remove 删除LineReverseOrder | // Remove 删除LineReverseOrder | ||||||
| @ -99,11 +104,11 @@ func (e *LineReverseOrder) Remove(d *dto.LineReverseOrderDeleteReq, p *actions.D | |||||||
| 			actions.Permission(data.TableName(), p), | 			actions.Permission(data.TableName(), p), | ||||||
| 		).Delete(&data, d.GetId()) | 		).Delete(&data, d.GetId()) | ||||||
| 	if err := db.Error; err != nil { | 	if err := db.Error; err != nil { | ||||||
|         e.Log.Errorf("Service RemoveLineReverseOrder error:%s \r\n", err) | 		e.Log.Errorf("Service RemoveLineReverseOrder error:%s \r\n", err) | ||||||
|         return err | 		return err | ||||||
|     } | 	} | ||||||
|     if db.RowsAffected == 0 { | 	if db.RowsAffected == 0 { | ||||||
|         return errors.New("无权删除该数据") | 		return errors.New("无权删除该数据") | ||||||
|     } | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,22 +2,194 @@ package service | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
| 	"github.com/jinzhu/copier" | 	"github.com/jinzhu/copier" | ||||||
|  | 	"github.com/shopspring/decimal" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/models" | 	"go-admin/app/admin/models" | ||||||
| 	"go-admin/app/admin/service/dto" | 	"go-admin/app/admin/service/dto" | ||||||
| 	"go-admin/common/actions" | 	"go-admin/common/actions" | ||||||
| 	cDto "go-admin/common/dto" | 	cDto "go-admin/common/dto" | ||||||
|  | 	"go-admin/common/global" | ||||||
| 	"go-admin/pkg/utility" | 	"go-admin/pkg/utility" | ||||||
|  | 	"go-admin/pkg/utility/snowflakehelper" | ||||||
|  | 	"go-admin/services/binanceservice" | ||||||
|  | 	"go-admin/services/cacheservice" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type LineReversePosition struct { | type LineReversePosition struct { | ||||||
| 	service.Service | 	service.Service | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 批量关闭仓位 | ||||||
|  | func (e LineReversePosition) ClosePositionBatch(req *dto.LineReversePositionCloseBatchReq, p *actions.DataPermission, userId int, errs *[]string) error { | ||||||
|  | 	var positions []models.LineReversePosition | ||||||
|  | 	var entity models.LineReversePosition | ||||||
|  | 	query := e.Orm.Model(&entity). | ||||||
|  | 		Scopes( | ||||||
|  | 			actions.Permission(entity.TableName(), p), | ||||||
|  | 		). | ||||||
|  | 		Where("reverse_status = 1 and position_side =?", req.PositionSide) | ||||||
|  |  | ||||||
|  | 	if len(req.Symbol) > 0 { | ||||||
|  | 		query = query.Where("symbol =?", req.Symbol) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(req.ReverseApiIds) > 0 { | ||||||
|  | 		query = query.Where("reverse_api_id in (?)", req.ReverseApiIds) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := query.Find(&positions).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("LineReversePositionService ClosePositionBatch error:%s \r\n", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(positions) == 0 { | ||||||
|  | 		return errors.New("没有需要关闭的仓位") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, position := range positions { | ||||||
|  | 		if err1 := e.Close(position); err1 != nil { | ||||||
|  | 			*errs = append(*errs, err1.Error()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ClosePosition 关闭单个仓位 | ||||||
|  | func (e LineReversePosition) ClosePosition(req *dto.LineReversePositionCloseReq, p *actions.DataPermission, userId int) error { | ||||||
|  | 	var data models.LineReversePosition | ||||||
|  | 	err := e.Orm.Model(&data). | ||||||
|  | 		Scopes( | ||||||
|  | 			actions.Permission(data.TableName(), p), | ||||||
|  | 		). | ||||||
|  | 		Where("id =?", req.PositionId).First(&data).Error | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("LineReversePositionService ClosePosition error:%s \r\n", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = e.Close(data) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *LineReversePosition) Close(data models.LineReversePosition) error { | ||||||
|  | 	if data.ReverseStatus != 1 { | ||||||
|  | 		return fmt.Errorf("%s-%s 仓位无法关闭", data.Symbol, data.PositionSide) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	apiInfo, err := binanceservice.GetApiInfo(data.ReverseApiId) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("api %d不存在", data.ReverseApiId) | ||||||
|  | 	} | ||||||
|  | 	futApi := binanceservice.FutRestApi{} | ||||||
|  | 	futApiV2 := binanceservice.FuturesResetV2{} | ||||||
|  | 	holdData := binanceservice.HoldeData{} | ||||||
|  | 	err = futApi.GetPositionData(&apiInfo, data.Symbol, data.PositionSide, &holdData) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("获取币安持仓失败 %v", err) | ||||||
|  | 	} | ||||||
|  | 	setting, err := cacheservice.GetReverseSetting(e.Orm) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("获取反单设置失败") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, data.Symbol, 0) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("获取%s的交易对信息失败", data.Symbol) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	lastPrice, err := decimal.NewFromString(symbol.LastPrice) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("最新价格失败,%v", lastPrice) | ||||||
|  | 	} | ||||||
|  | 	side := "" | ||||||
|  | 	var price decimal.Decimal | ||||||
|  |  | ||||||
|  | 	if data.PositionSide == "LONG" { | ||||||
|  | 		side = "SELL" | ||||||
|  | 		price = decimal.NewFromInt(100).Sub(setting.ReversePremiumRatio).Div(decimal.NewFromInt(100)).Mul(lastPrice).Round(int32(symbol.PriceDigit)) | ||||||
|  | 	} else { | ||||||
|  | 		side = "BUY" | ||||||
|  | 		price = decimal.NewFromInt(100).Add(setting.ReversePremiumRatio).Div(decimal.NewFromInt(100)).Mul(lastPrice).Round(int32(symbol.PriceDigit)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	now := time.Now() | ||||||
|  | 	order := models.LineReverseOrder{ | ||||||
|  | 		OrderSn:      snowflakehelper.GetOrderNo(), | ||||||
|  | 		PositionId:   data.Id, | ||||||
|  | 		PositionSide: data.PositionSide, | ||||||
|  | 		Symbol:       data.Symbol, | ||||||
|  | 		TotalNum:     holdData.TotalQuantity, | ||||||
|  | 		Category:     1, | ||||||
|  | 		OrderType:    4, | ||||||
|  | 		Side:         side, | ||||||
|  | 		Price:        price, | ||||||
|  | 		SignPrice:    lastPrice, | ||||||
|  | 		Type:         "LIMIT", | ||||||
|  | 		TriggerTime:  &now, | ||||||
|  | 		Status:       1, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if holdData.TotalQuantity.Cmp(decimal.Zero) > 0 { | ||||||
|  | 		if err := e.Orm.Create(&order).Error; err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		params := binanceservice.FutOrderPlace{ | ||||||
|  | 			ApiId:            data.ReverseApiId, | ||||||
|  | 			Symbol:           data.Symbol, | ||||||
|  | 			Side:             order.Side, | ||||||
|  | 			PositionSide:     order.PositionSide, | ||||||
|  | 			Quantity:         order.TotalNum, | ||||||
|  | 			Price:            order.Price, | ||||||
|  | 			SideType:         order.Type, | ||||||
|  | 			OpenOrder:        0, | ||||||
|  | 			OrderType:        "LIMIT", | ||||||
|  | 			NewClientOrderId: order.OrderSn, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := futApiV2.OrderPlaceLoop(&apiInfo, params); err != nil { | ||||||
|  | 			e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", params.Symbol, order.OrderSn, err) | ||||||
|  | 			if err1 := e.Orm.Model(&order).Where("status = 1").Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil { | ||||||
|  | 				e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", params.Symbol, order.OrderSn, err1) | ||||||
|  | 				return err1 | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		order.Status = 8 | ||||||
|  | 		order.Remark = "已经没有持仓" | ||||||
|  |  | ||||||
|  | 		err = e.Orm.Transaction(func(tx *gorm.DB) error { | ||||||
|  | 			if err1 := tx.Create(&order).Error; err1 != nil { | ||||||
|  | 				return err1 | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err1 := tx.Model(&data).Where("reverse_status =1").Updates(map[string]interface{}{"reverse_status": 2, "updated_at": time.Now(), "reverse_amount": 0}).Error; err1 != nil { | ||||||
|  | 				return err1 | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			e.Log.Errorf("修改失败 %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // GetPage 获取LineReversePosition列表 | // GetPage 获取LineReversePosition列表 | ||||||
| func (e *LineReversePosition) GetPage(c *dto.LineReversePositionGetPageReq, p *actions.DataPermission, list *[]dto.LineReversePositionListResp, count *int64) error { | func (e *LineReversePosition) GetPage(c *dto.LineReversePositionGetPageReq, p *actions.DataPermission, list *[]dto.LineReversePositionListResp, count *int64) error { | ||||||
| 	var err error | 	var err error | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ package service | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  |  | ||||||
|     "github.com/go-admin-team/go-admin-core/sdk/service" | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
|  |  | ||||||
| 	"go-admin/app/admin/models" | 	"go-admin/app/admin/models" | ||||||
| @ -59,9 +59,9 @@ func (e *LineUserSetting) Get(d *dto.LineUserSettingGetReq, p *actions.DataPermi | |||||||
|  |  | ||||||
| // Insert 创建LineUserSetting对象 | // Insert 创建LineUserSetting对象 | ||||||
| func (e *LineUserSetting) Insert(c *dto.LineUserSettingInsertReq) error { | func (e *LineUserSetting) Insert(c *dto.LineUserSettingInsertReq) error { | ||||||
|     var err error | 	var err error | ||||||
|     var data models.LineUserSetting | 	var data models.LineUserSetting | ||||||
|     c.Generate(&data) | 	c.Generate(&data) | ||||||
| 	err = e.Orm.Create(&data).Error | 	err = e.Orm.Create(&data).Error | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Log.Errorf("LineUserSettingService Insert error:%s \r\n", err) | 		e.Log.Errorf("LineUserSettingService Insert error:%s \r\n", err) | ||||||
| @ -72,22 +72,22 @@ func (e *LineUserSetting) Insert(c *dto.LineUserSettingInsertReq) error { | |||||||
|  |  | ||||||
| // Update 修改LineUserSetting对象 | // Update 修改LineUserSetting对象 | ||||||
| func (e *LineUserSetting) Update(c *dto.LineUserSettingUpdateReq, p *actions.DataPermission) error { | func (e *LineUserSetting) Update(c *dto.LineUserSettingUpdateReq, p *actions.DataPermission) error { | ||||||
|     var err error | 	var err error | ||||||
|     var data = models.LineUserSetting{} | 	var data = models.LineUserSetting{} | ||||||
|     e.Orm.Scopes( | 	e.Orm.Scopes( | ||||||
|             actions.Permission(data.TableName(), p), | 		actions.Permission(data.TableName(), p), | ||||||
|         ).First(&data, c.GetId()) | 	).First(&data, c.GetId()) | ||||||
|     c.Generate(&data) | 	c.Generate(&data) | ||||||
|  |  | ||||||
|     db := e.Orm.Save(&data) | 	db := e.Orm.Save(&data) | ||||||
|     if err = db.Error; err != nil { | 	if err = db.Error; err != nil { | ||||||
|         e.Log.Errorf("LineUserSettingService Save error:%s \r\n", err) | 		e.Log.Errorf("LineUserSettingService Save error:%s \r\n", err) | ||||||
|         return err | 		return err | ||||||
|     } | 	} | ||||||
|     if db.RowsAffected == 0 { | 	if db.RowsAffected == 0 { | ||||||
|         return errors.New("无权更新该数据") | 		return errors.New("无权更新该数据") | ||||||
|     } | 	} | ||||||
|     return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Remove 删除LineUserSetting | // Remove 删除LineUserSetting | ||||||
| @ -99,11 +99,23 @@ func (e *LineUserSetting) Remove(d *dto.LineUserSettingDeleteReq, p *actions.Dat | |||||||
| 			actions.Permission(data.TableName(), p), | 			actions.Permission(data.TableName(), p), | ||||||
| 		).Delete(&data, d.GetId()) | 		).Delete(&data, d.GetId()) | ||||||
| 	if err := db.Error; err != nil { | 	if err := db.Error; err != nil { | ||||||
|         e.Log.Errorf("Service RemoveLineUserSetting error:%s \r\n", err) | 		e.Log.Errorf("Service RemoveLineUserSetting error:%s \r\n", err) | ||||||
|         return err | 		return err | ||||||
|     } | 	} | ||||||
|     if db.RowsAffected == 0 { | 	if db.RowsAffected == 0 { | ||||||
|         return errors.New("无权删除该数据") | 		return errors.New("无权删除该数据") | ||||||
|     } | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetDefault 获取默认LineUserSetting对象 | ||||||
|  | func (e *LineUserSetting) GetDefault() (models.LineUserSetting, error) { | ||||||
|  | 	var data models.LineUserSetting | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Model(&data).First(&data).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("GetDefault LineUserSetting error:%s \r\n", err) | ||||||
|  | 		return data, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return data, nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| package retryhelper | package retryhelper | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"math" | 	"math" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| @ -50,5 +49,5 @@ func RetryWithResult[T any](op func() (T, error), opts RetryOptions) (result T, | |||||||
| 			interval = time.Duration(math.Min(float64(opts.MaxInterval), float64(interval)*opts.BackoffFactor)) | 			interval = time.Duration(math.Min(float64(opts.MaxInterval), float64(interval)*opts.BackoffFactor)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return result, fmt.Errorf("retry failed after %d attempts, last error: %w", opts.MaxRetries+1, err) | 	return result, err | ||||||
| } | } | ||||||
|  | |||||||
| @ -479,3 +479,13 @@ func GetOpenOrderSns(db *gorm.DB, mainIds []int) ([]string, error) { | |||||||
|  |  | ||||||
| 	return result, nil | 	return result, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 回去反单默认配置 | ||||||
|  | func GetReverseSetting(db *gorm.DB) (DbModels.LineReverseSetting, error) { | ||||||
|  | 	var setting DbModels.LineReverseSetting | ||||||
|  | 	if err := db.Model(&DbModels.LineReverseSetting{}).First(&setting).Error; err != nil { | ||||||
|  | 		return setting, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return setting, nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -599,6 +599,49 @@ func (e FutRestApi) GetHoldeData(apiInfo *DbModels.LineApiUser, symbol, side str | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 获取合约 持仓价格、数量 | ||||||
|  | // symbol:交易对 | ||||||
|  | // positionSide:持仓方向 | ||||||
|  | // holdeData:持仓数据 | ||||||
|  | func (e FutRestApi) GetPositionData(apiInfo *DbModels.LineApiUser, symbol, positionSide string, holdeData *HoldeData) error { | ||||||
|  | 	opts := retryhelper.DefaultRetryOptions() | ||||||
|  | 	opts.RetryableErrFn = func(err error) bool { | ||||||
|  | 		if strings.Contains(err.Error(), "LOT_SIZE") { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		//重试 | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	holdes, err := retryhelper.RetryWithResult(func() ([]PositionRisk, error) { | ||||||
|  | 		return e.GetPositionV3(apiInfo, symbol) | ||||||
|  | 	}, opts) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, item := range holdes { | ||||||
|  | 		positionAmount, _ := decimal.NewFromString(item.PositionAmt) | ||||||
|  | 		if (positionSide == "LONG" && item.PositionSide == "BOTH" && positionAmount.Cmp(decimal.Zero) > 0) || item.PositionSide == positionSide { //多 | ||||||
|  | 			holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice) | ||||||
|  | 			holdeData.TotalQuantity = positionAmount.Abs() | ||||||
|  | 			continue | ||||||
|  | 		} else if (positionSide == "SHORT" && item.PositionSide == "BOTH" && positionAmount.Cmp(decimal.Zero) < 0) || item.PositionSide == positionSide { //空 | ||||||
|  | 			holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice) | ||||||
|  | 			holdeData.TotalQuantity = positionAmount.Abs() | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if holdeData.AveragePrice.Cmp(decimal.Zero) == 0 { | ||||||
|  | 		holdesVal, _ := sonic.MarshalString(&holdes) | ||||||
|  | 		log.Error("均价错误 symbol:", symbol, " 数据:", holdesVal) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // 获取代币持仓信息 | // 获取代币持仓信息 | ||||||
| func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, side string, holdeData *HoldeData) ([]PositionRisk, error) { | func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, side string, holdeData *HoldeData) ([]PositionRisk, error) { | ||||||
| 	holdes, err := e.GetPositionV3(apiInfo, symbol) | 	holdes, err := e.GetPositionV3(apiInfo, symbol) | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ import ( | |||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-admin-team/go-admin-core/logger" | ||||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||||
| 	"github.com/shopspring/decimal" | 	"github.com/shopspring/decimal" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| @ -108,26 +109,36 @@ func (e *ReverseService) ReverseOrder(apiKey string, mapData map[string]interfac | |||||||
| 			switch { | 			switch { | ||||||
| 			case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": | 			case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": | ||||||
| 				if mainOrder.Category == 0 { | 				if mainOrder.Category == 0 { | ||||||
| 					if err1 := e.savePosition(&mainOrder, reverseApiInfo.Id, true, false, false); err1 != nil { | 					needReverseOrder, closePosition, err1 := e.savePosition(&mainOrder, &apiInfo, true, false) | ||||||
|  |  | ||||||
|  | 					if err1 != nil { | ||||||
| 						return true, err1 | 						return true, err1 | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, false, false) | 					if needReverseOrder { | ||||||
|  | 						e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, false, closePosition) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": | 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": | ||||||
| 				if mainOrder.Category == 0 { | 				if mainOrder.Category == 0 { | ||||||
| 					closePosition := maphelper.GetBool(mapData, "R") | 					needReverseOrder, closePosition, err1 := e.savePosition(&mainOrder, &apiInfo, true, true) | ||||||
| 					if err1 := e.savePosition(&mainOrder, reverseApiInfo.Id, true, true, closePosition); err1 != nil { |  | ||||||
|  | 					if err1 != nil { | ||||||
| 						e.Log.Errorf("保存主订单失败: %v", err1) | 						e.Log.Errorf("保存主订单失败: %v", err1) | ||||||
| 						return true, err1 | 						return true, err1 | ||||||
| 					} | 					} | ||||||
| 					e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, true, closePosition) |  | ||||||
|  | 					if needReverseOrder { | ||||||
|  | 						e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, true, closePosition) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 			default: | 			default: | ||||||
| 				return true, errors.New("不支持的订单类型") | 				return true, errors.New("不支持的订单类型") | ||||||
| 			} | 			} | ||||||
| 			return true, nil | 			return true, nil | ||||||
| 		} else if apiInfo.Subordinate == "2" { | 		} else if apiInfo.Subordinate == "2" { | ||||||
|  | 			e.changeOrderStatus(3, orderSn, mapData) | ||||||
|  |  | ||||||
| 			symbol, err := maphelper.GetString(mapData, "s") | 			symbol, err := maphelper.GetString(mapData, "s") | ||||||
|  |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -153,20 +164,19 @@ func (e *ReverseService) ReverseOrder(apiKey string, mapData map[string]interfac | |||||||
| 				TotalNum:     totalNum, | 				TotalNum:     totalNum, | ||||||
| 				Side:         side, | 				Side:         side, | ||||||
| 				Price:        price, | 				Price:        price, | ||||||
|  | 				FinalPrice:   price, | ||||||
| 			} | 			} | ||||||
| 			e.changeOrderStatus(3, orderSn, mapData) |  | ||||||
|  |  | ||||||
| 			switch { | 			switch { | ||||||
| 			case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": | 			case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL": | ||||||
| 				if mainOrder.Category == 0 { | 				if mainOrder.Category == 0 { | ||||||
| 					if err1 := e.savePosition(&mainOrder, 0, false, false, false); err1 != nil { | 					if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, false); err1 != nil { | ||||||
| 						return true, err1 | 						return true, err1 | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL": | 			case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SrgetELL": | ||||||
| 				if mainOrder.Category == 0 { | 				if mainOrder.Category == 0 { | ||||||
| 					closePosition := maphelper.GetBool(mapData, "R") | 					if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, true); err1 != nil { | ||||||
| 					if err1 := e.savePosition(&mainOrder, 0, false, true, closePosition); err1 != nil { |  | ||||||
| 						return true, err1 | 						return true, err1 | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @ -298,17 +308,23 @@ func (e *ReverseService) SaveMainOrder(mapData map[string]interface{}, apiInfo D | |||||||
| } | } | ||||||
|  |  | ||||||
| // 更新仓位信息 | // 更新仓位信息 | ||||||
|  | // apiInfo: 当前下单api信息 | ||||||
|  | // return | ||||||
|  | // neeReverseOrder: 是否需要反单 | ||||||
| // closePosition: true=平仓, false=减仓 | // closePosition: true=平仓, false=减仓 | ||||||
| func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, reverseApiId int, isMain, reducePosition, closePosition bool) error { | // err error 错误信息 | ||||||
|  | func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) { | ||||||
| 	position := DbModels.LineReversePosition{} | 	position := DbModels.LineReversePosition{} | ||||||
| 	positionSide := reverseOrder.PositionSide | 	positionSide := order.PositionSide | ||||||
| 	side := reverseOrder.Side | 	side := order.Side | ||||||
| 	totalNum := reverseOrder.TotalNum | 	totalNum := order.TotalNum | ||||||
|  | 	closePosition := false | ||||||
|  | 	needReverseOrder := false | ||||||
|  |  | ||||||
| 	symbol, err1 := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reverseOrder.Symbol, 1) | 	symbol, err1 := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) | ||||||
|  |  | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		e.Log.Errorf("获取交易对失败 symbol:%s err:%v", reverseOrder.Symbol, err1) | 		e.Log.Errorf("获取交易对失败 symbol:%s err:%v", order.Symbol, err1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var querySql string | 	var querySql string | ||||||
| @ -316,12 +332,15 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
|  |  | ||||||
| 	//如果是主单,存储仓位则是反单的持仓方向 | 	//如果是主单,存储仓位则是反单的持仓方向 | ||||||
| 	if isMain { | 	if isMain { | ||||||
| 		if reverseOrder.PositionSide == "LONG" { | 		if order.PositionSide == "LONG" { | ||||||
| 			positionSide = "SHORT" | 			positionSide = "SHORT" | ||||||
| 		} else { | 		} else { | ||||||
| 			positionSide = "LONG" | 			positionSide = "LONG" | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		//减仓 判断是否为平仓 | ||||||
|  | 		closePosition = e.getClosePosition(reducePosition, apiInfo, order, order.PositionSide, isMain) | ||||||
|  |  | ||||||
| 		if !reducePosition { | 		if !reducePosition { | ||||||
| 			//反单止盈止损方向相反 | 			//反单止盈止损方向相反 | ||||||
| 			if side == "SELL" { | 			if side == "SELL" { | ||||||
| @ -330,14 +349,14 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
| 				side = "SELL" | 				side = "SELL" | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			position.ReverseApiId = reverseApiId | 			position.ReverseApiId = apiInfo.ReverseApiId | ||||||
| 			position.Side = side | 			position.Side = side | ||||||
| 			position.ApiId = reverseOrder.ApiId | 			position.ApiId = order.ApiId | ||||||
| 			position.Symbol = reverseOrder.Symbol | 			position.Symbol = order.Symbol | ||||||
| 			position.Status = 1 | 			position.Status = 1 | ||||||
| 			position.ReverseStatus = 0 | 			position.ReverseStatus = 0 | ||||||
| 			position.PositionSide = positionSide | 			position.PositionSide = positionSide | ||||||
| 			position.AveragePrice = reverseOrder.FinalPrice | 			position.AveragePrice = order.FinalPrice | ||||||
| 			position.PositionNo = snowflakehelper.GetOrderNo() | 			position.PositionNo = snowflakehelper.GetOrderNo() | ||||||
| 		} | 		} | ||||||
| 		querySql = "api_id =? and position_side =? and symbol =? and status =1" | 		querySql = "api_id =? and position_side =? and symbol =? and status =1" | ||||||
| @ -354,6 +373,8 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)" | 		querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)" | ||||||
|  | 		//减仓 判断是否为平仓 | ||||||
|  | 		closePosition = e.getClosePosition(reducePosition, apiInfo, order, order.PositionSide, isMain) | ||||||
|  |  | ||||||
| 		if closePosition { | 		if closePosition { | ||||||
| 			totalNum = decimal.Zero | 			totalNum = decimal.Zero | ||||||
| @ -366,10 +387,11 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var averagePrice decimal.Decimal | 	var averagePrice decimal.Decimal | ||||||
|  | 	var remainQuantity decimal.Decimal | ||||||
|  |  | ||||||
| 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||||
| 		err1 := tx.Model(&position).Where(querySql, | 		err1 := tx.Model(&position).Where(querySql, | ||||||
| 			reverseOrder.ApiId, positionSide, reverseOrder.Symbol).First(&position).Error | 			order.ApiId, positionSide, order.Symbol).First(&position).Error | ||||||
|  |  | ||||||
| 		if err1 != nil { | 		if err1 != nil { | ||||||
| 			//主单仓位不存在,创建新仓位 | 			//主单仓位不存在,创建新仓位 | ||||||
| @ -397,12 +419,12 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
|  |  | ||||||
| 				//加仓 | 				//加仓 | ||||||
| 				if !reducePosition { | 				if !reducePosition { | ||||||
| 					totalPrice = totalPrice.Add(reverseOrder.Price.Mul(reverseOrder.TotalNum)) | 					totalPrice = totalPrice.Add(order.Price.Mul(order.TotalNum)) | ||||||
| 					totalAmount = totalAmount.Add(reverseOrder.TotalNum) | 					totalAmount = totalAmount.Add(order.TotalNum) | ||||||
| 				} else if reducePosition && !closePosition { | 				} else if reducePosition && !closePosition { | ||||||
| 					//只减仓 | 					//只减仓 | ||||||
| 					totalPrice = totalPrice.Sub(reverseOrder.Price.Mul(reverseOrder.TotalNum)) | 					totalPrice = totalPrice.Sub(order.Price.Mul(order.TotalNum)) | ||||||
| 					totalAmount = totalAmount.Sub(reverseOrder.TotalNum) | 					totalAmount = totalAmount.Sub(order.TotalNum) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @ -411,7 +433,7 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
| 					averagePrice = position.AveragePrice | 					averagePrice = position.AveragePrice | ||||||
| 				} else { | 				} else { | ||||||
| 					if position.ReverseAveragePrice.IsZero() { | 					if position.ReverseAveragePrice.IsZero() { | ||||||
| 						averagePrice = reverseOrder.Price | 						averagePrice = order.Price | ||||||
| 					} else { | 					} else { | ||||||
| 						averagePrice = position.ReverseAveragePrice | 						averagePrice = position.ReverseAveragePrice | ||||||
| 					} | 					} | ||||||
| @ -431,7 +453,7 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		//关联订单的仓位id | 		//关联订单的仓位id | ||||||
| 		if err2 := tx.Exec("UPDATE line_reverse_order set position_id=@positionId where id=@orderId and position_id = 0", sql.Named("positionId", position.Id), sql.Named("orderId", reverseOrder.Id)).Error; err2 != nil { | 		if err2 := tx.Exec("UPDATE line_reverse_order set position_id=@positionId where id=@orderId and position_id = 0", sql.Named("positionId", position.Id), sql.Named("orderId", order.Id)).Error; err2 != nil { | ||||||
| 			return err2 | 			return err2 | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -440,16 +462,63 @@ func (e *ReverseService) savePosition(reverseOrder *DbModels.LineReverseOrder, r | |||||||
| 			return dbResult.Error | 			return dbResult.Error | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		reverseOrder.PositionId = position.Id | 		order.PositionId = position.Id | ||||||
| 		if dbResult.RowsAffected == 0 { | 		if dbResult.RowsAffected == 0 { | ||||||
| 			e.Log.Errorf("减仓数据 是否平仓单:%v :%v", closePosition, reverseOrder) | 			e.Log.Errorf("减仓数据 是否平仓单:%v :%v", closePosition, order) | ||||||
| 			return errors.New("没有找到对应的持仓信息") | 			return errors.New("没有找到对应的持仓信息") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if reducePosition && !isMain { | ||||||
|  | 			remainQuantity = position.ReverseAmount.Sub(totalNum) | ||||||
|  | 		} else if !isMain { | ||||||
|  | 			remainQuantity = position.ReverseAmount.Add(totalNum) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		//主单且对手单没有平仓 | ||||||
|  | 		if isMain && position.ReverseStatus != 2 { | ||||||
|  | 			needReverseOrder = true | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	return err | 	if !isMain && !closePosition { | ||||||
|  | 		e.doDefaultTakeStop(order, apiInfo, remainQuantity) | ||||||
|  | 	} else if !isMain && closePosition { | ||||||
|  | 		//取消剩余的委托 | ||||||
|  | 		e.DoCancelTakeAndStop(order.Symbol, position.PositionSide, apiInfo) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return needReverseOrder, closePosition, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取是否为平仓状态 | ||||||
|  | func (e *ReverseService) getClosePosition(reducePosition bool, apiInfo *DbModels.LineApiUser, order *DbModels.LineReverseOrder, positionSide string, isMain bool) bool { | ||||||
|  | 	closePosition := false | ||||||
|  |  | ||||||
|  | 	if reducePosition { | ||||||
|  | 		futApi := FutRestApi{} | ||||||
|  | 		holdData := HoldeData{} | ||||||
|  | 		err := futApi.GetPositionData(apiInfo, order.Symbol, positionSide, &holdData) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			e.Log.Errorf("获取剩余持仓信息失败 symbol:%s err:%v", order.Symbol, err) | ||||||
|  | 			lastPosition := DbModels.LineReversePosition{} | ||||||
|  |  | ||||||
|  | 			if err2 := e.Orm.Model(&DbModels.LineReversePosition{}).Where("position_side =? and symbol =? and status =1", positionSide, order.Symbol).First(&lastPosition).Error; err2 != nil { | ||||||
|  | 				e.Log.Errorf("获取上一次持仓信息失败 symbol:%s err:%v", order.Symbol, err2) | ||||||
|  | 			} else if isMain && lastPosition.Amount.Cmp(order.TotalNum) <= 0 { | ||||||
|  | 				//如果剩余仓位小于等于 | ||||||
|  | 				closePosition = true | ||||||
|  | 			} else if !isMain && lastPosition.ReverseAmount.Cmp(order.TotalNum) <= 0 { | ||||||
|  | 				closePosition = true | ||||||
|  | 			} | ||||||
|  | 		} else if holdData.TotalQuantity.IsZero() { | ||||||
|  | 			closePosition = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return closePosition | ||||||
| } | } | ||||||
|  |  | ||||||
| // 反向下单 | // 反向下单 | ||||||
| @ -556,8 +625,11 @@ func (e *ReverseService) DoAddReverseOrder(mainOrder *DbModels.LineReverseOrder, | |||||||
|  |  | ||||||
| 		//反向下单百分比 | 		//反向下单百分比 | ||||||
| 		proportion = proportion.Div(decimal.NewFromInt(100)).Truncate(4) | 		proportion = proportion.Div(decimal.NewFromInt(100)).Truncate(4) | ||||||
|  |  | ||||||
| 		amount = mainOrder.TotalNum.Mul(proportion).Truncate(int32(symbol.AmountDigit)) | 		amount = mainOrder.TotalNum.Mul(proportion).Truncate(int32(symbol.AmountDigit)) | ||||||
|  |  | ||||||
|  | 		logger.Info("反向下单比例 %d ,原始数量:%d,反向下单数量:%d", proportion, mainOrder.TotalNum, amount) | ||||||
|  |  | ||||||
| 		if amount.Cmp(decimal.Zero) <= 0 { | 		if amount.Cmp(decimal.Zero) <= 0 { | ||||||
| 			e.Log.Errorf("计算数量失败 symbol:%s custom:%s 数量小于0", mainOrder.Symbol, mainOrder.OrderSn) | 			e.Log.Errorf("计算数量失败 symbol:%s custom:%s 数量小于0", mainOrder.Symbol, mainOrder.OrderSn) | ||||||
| 			return errors.New("计算数量失败") | 			return errors.New("计算数量失败") | ||||||
| @ -620,6 +692,24 @@ func (e *ReverseService) DoCancelTakeProfitBatch(symbol, positionSide, side stri | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 取消止盈和止损单 | ||||||
|  | func (e *ReverseService) DoCancelTakeAndStop(symbol, positionSide string, apiInfo *DbModels.LineApiUser) error { | ||||||
|  | 	var orderSns []string | ||||||
|  |  | ||||||
|  | 	e.Orm.Model(&DbModels.LineReverseOrder{}).Where("symbol =? and position_side =? and status =2", symbol, positionSide).Pluck("order_sn", &orderSns) | ||||||
|  |  | ||||||
|  | 	if len(orderSns) > 0 { | ||||||
|  | 		futApi := FutRestApi{} | ||||||
|  |  | ||||||
|  | 		if err := futApi.CancelBatchFutOrderLoop(*apiInfo, symbol, orderSns); err != nil { | ||||||
|  | 			e.Log.Errorf("币安撤单失败 symbol:%s custom:%v :%v", symbol, orderSns, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // 获取止盈止损订单 | // 获取止盈止损订单 | ||||||
| // symbol: 交易对 | // symbol: 交易对 | ||||||
| // positionSide: 持仓方向 | // positionSide: 持仓方向 | ||||||
| @ -681,41 +771,92 @@ func (e *ReverseService) DoBianceOrder(order *DbModels.LineReverseOrder, apiInfo | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 处理默认止盈止损 | ||||||
|  | // order: 订单信息 | ||||||
|  | // apiInfo: api信息 | ||||||
|  | // Optimized Reverse Order Handling | ||||||
|  | // File: reverse_order_handler.go | ||||||
|  |  | ||||||
|  | func (e *ReverseService) doDefaultTakeStop(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, totalNum decimal.Decimal) error { | ||||||
|  | 	if totalNum.LessThanOrEqual(decimal.Zero) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	orders, err := e.getActiveReverseOrders(order.PositionId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if len(orders) == 2 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setting, err := GetReverseSetting(e.Orm) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("获取反单设置失败:%v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("获取交易对信息失败:%v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	side := e.getOppositeSide(order.Side) | ||||||
|  | 	lastPrice, _ := decimal.NewFromString(symbol.LastPrice) | ||||||
|  | 	now := time.Now() | ||||||
|  |  | ||||||
|  | 	types := []struct { | ||||||
|  | 		Enabled      bool | ||||||
|  | 		CheckTypes   []string | ||||||
|  | 		OrderType    string | ||||||
|  | 		Ratio        decimal.Decimal | ||||||
|  | 		IsTakeProfit bool | ||||||
|  | 	}{ | ||||||
|  | 		{!setting.TakeProfitRatio.IsZero(), []string{"TAKE_PROFIT_MARKET", "TAKE_PROFIT"}, "TAKE_PROFIT_MARKET", setting.TakeProfitRatio, true}, | ||||||
|  | 		{!setting.StopLossRatio.IsZero(), []string{"STOP_MARKET", "STOP"}, "STOP_MARKET", setting.StopLossRatio, false}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, t := range types { | ||||||
|  | 		if t.Enabled && !e.hasOrderType(orders, t.CheckTypes...) { | ||||||
|  | 			price := e.calculatePrice(order.PositionSide, order.FinalPrice, t.Ratio, t.IsTakeProfit) | ||||||
|  | 			err := e.createReverseOrder(CreateOrderParams{ | ||||||
|  | 				Order:      order, | ||||||
|  | 				ApiInfo:    apiInfo, | ||||||
|  | 				Symbol:     &symbol, | ||||||
|  | 				Side:       side, | ||||||
|  | 				OrderType:  t.OrderType, | ||||||
|  | 				Price:      price, | ||||||
|  | 				TotalNum:   totalNum, | ||||||
|  | 				Now:        now, | ||||||
|  | 				LastPrice:  lastPrice, | ||||||
|  | 				Close:      true, | ||||||
|  | 				PositionId: order.PositionId, | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				e.Log.Errorf("止盈止损下单失败:%v", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // 重下止盈止损 | // 重下止盈止损 | ||||||
| // mapData: 主单止盈止损回调 | // mapData: | ||||||
| func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error { | func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error { | ||||||
| 	orderType := 0 //订单类型 1-止盈 2-止损 |  | ||||||
| 	side, err := maphelper.GetString(*mapData, "S") | 	side, err := maphelper.GetString(*mapData, "S") | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ot, err := maphelper.GetString(*mapData, "ot") | 	ot, err := maphelper.GetString(*mapData, "ot") | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	//反单止盈止损方向相反 |  | ||||||
| 	if side == "SELL" { |  | ||||||
| 		side = "BUY" |  | ||||||
| 	} else { |  | ||||||
| 		side = "SELL" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	positionSide, err := maphelper.GetString(*mapData, "ps") | 	positionSide, err := maphelper.GetString(*mapData, "ps") | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if positionSide == "LONG" { |  | ||||||
| 		positionSide = "SHORT" |  | ||||||
| 	} else { |  | ||||||
| 		positionSide = "LONG" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	close := maphelper.GetBool(*mapData, "cp") | 	close := maphelper.GetBool(*mapData, "cp") | ||||||
| 	stopPrice := maphelper.GetDecimal(*mapData, "sp") | 	stopPrice := maphelper.GetDecimal(*mapData, "sp") | ||||||
| 	if stopPrice.IsZero() { | 	if stopPrice.IsZero() { | ||||||
| @ -723,12 +864,19 @@ func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orde | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId) | 	apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | 		e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	side = e.getOppositeSide(side) | ||||||
|  | 	if positionSide == "LONG" { | ||||||
|  | 		positionSide = "SHORT" | ||||||
|  | 	} else { | ||||||
|  | 		positionSide = "LONG" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var orderType int | ||||||
| 	switch ot { | 	switch ot { | ||||||
| 	case "STOP_MARKET", "STOP": | 	case "STOP_MARKET", "STOP": | ||||||
| 		orderType = 2 | 		orderType = 2 | ||||||
| @ -739,26 +887,19 @@ func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orde | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var reversePosition DbModels.LineReversePosition | 	var reversePosition DbModels.LineReversePosition | ||||||
|  |  | ||||||
| 	e.Orm.Model(&reversePosition). | 	e.Orm.Model(&reversePosition). | ||||||
| 		Where("symbol =? and reverse_api_id =? and position_side =? and reverse_status =1", symbol.GetSymbol(), apiInfo.Id, positionSide). | 		Where("symbol =? and reverse_api_id =? and position_side =? and reverse_status =1", symbol.GetSymbol(), apiInfo.Id, positionSide). | ||||||
| 		First(&reversePosition) | 		First(&reversePosition) | ||||||
|  |  | ||||||
| 	if reversePosition.Id == 0 { | 	if reversePosition.Id == 0 { | ||||||
| 		e.Log.Errorf("获取反单持仓失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | 		e.Log.Errorf("获取反单持仓失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mainPercent := decimal.NewFromInt(1) | 	mainPercent := stopPrice.Div(reversePosition.AveragePrice) | ||||||
|  | 	mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4) | ||||||
| 	if !stopPrice.IsZero() && !reversePosition.AveragePrice.IsZero() { |  | ||||||
| 		mainPercent = stopPrice.Div(reversePosition.AveragePrice) |  | ||||||
| 		mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var percent decimal.Decimal | 	var percent decimal.Decimal | ||||||
| 	switch { | 	switch { | ||||||
| 	//做多止损 |  | ||||||
| 	case orderType == 2 && positionSide == "LONG", orderType == 1 && positionSide == "SHORT": | 	case orderType == 2 && positionSide == "LONG", orderType == 1 && positionSide == "SHORT": | ||||||
| 		percent = decimal.NewFromInt(1).Sub(mainPercent) | 		percent = decimal.NewFromInt(1).Sub(mainPercent) | ||||||
| 	case orderType == 2 && positionSide == "SHORT", orderType == 1 && positionSide == "LONG": | 	case orderType == 2 && positionSide == "SHORT", orderType == 1 && positionSide == "LONG": | ||||||
| @ -770,55 +911,286 @@ func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orde | |||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 	price := reversePosition.AveragePrice.Mul(percent).Truncate(int32(symbol.PriceDigit)) | 	price := reversePosition.AveragePrice.Mul(percent).Truncate(int32(symbol.PriceDigit)) | ||||||
| 	lastPrice, _ := decimal.NewFromString(symbol.LastPrice) | 	lastPrice, _ := decimal.NewFromString(symbol.LastPrice) | ||||||
| 	newOrder := DbModels.LineReverseOrder{ |  | ||||||
| 		PositionId:    reversePosition.Id, |  | ||||||
| 		OrderSn:       helper.GetOrderNo(), |  | ||||||
| 		OrderType:     orderType, |  | ||||||
| 		Status:        1, |  | ||||||
| 		Price:         price, |  | ||||||
| 		TotalNum:      reversePosition.TotalReverseAmount, |  | ||||||
| 		Symbol:        symbol.GetSymbol(), |  | ||||||
| 		Side:          side, |  | ||||||
| 		PositionSide:  positionSide, |  | ||||||
| 		FollowOrderSn: orderSn, |  | ||||||
| 		Type:          ot, |  | ||||||
| 		SignPrice:     lastPrice, |  | ||||||
| 		Category:      1, |  | ||||||
| 		ApiId:         apiInfo.Id, |  | ||||||
| 		IsAddPosition: 2, |  | ||||||
| 		TriggerTime:   &now, |  | ||||||
| 		BuyPrice:      reversePosition.TotalReverseAmount.Mul(price).Truncate(int32(symbol.PriceDigit)), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err1 := e.Orm.Create(&newOrder).Error; err1 != nil { | 	return e.createReverseOrder(CreateOrderParams{ | ||||||
| 		e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", symbol, orderSn, err1) | 		ApiInfo:      &apiInfo, | ||||||
| 		return err1 | 		Symbol:       symbol, | ||||||
| 	} | 		Side:         side, | ||||||
| 	params := FutOrderPlace{ | 		OrderType:    ot, | ||||||
| 		ApiId:            apiInfo.Id, | 		Price:        price, | ||||||
| 		Symbol:           symbol.GetSymbol(), | 		TotalNum:     reversePosition.TotalReverseAmount, | ||||||
| 		PositionSide:     positionSide, | 		Now:          now, | ||||||
| 		Side:             side, | 		LastPrice:    lastPrice, | ||||||
| 		OrderType:        ot, | 		Close:        close, | ||||||
| 		Quantity:         reversePosition.TotalReverseAmount, | 		PositionId:   reversePosition.Id, | ||||||
| 		Price:            price, | 		OrderSn:      orderSn, | ||||||
| 		StopPrice:        price, | 		PositionSide: positionSide, | ||||||
| 		Profit:           price, | 	}) | ||||||
| 		NewClientOrderId: newOrder.OrderSn, | } | ||||||
| 		ClosePosition:    close, |  | ||||||
| 	} |  | ||||||
| 	futApiV2 := FuturesResetV2{Service: e.Service} |  | ||||||
| 	err = futApiV2.OrderPlaceLoop(&apiInfo, params) |  | ||||||
|  |  | ||||||
|  | func (e *ReverseService) getActiveReverseOrders(positionId int) ([]DbModels.LineReverseOrder, error) { | ||||||
|  | 	var orders []DbModels.LineReverseOrder | ||||||
|  | 	err := e.Orm.Model(&DbModels.LineReverseOrder{}). | ||||||
|  | 		Where("position_id =? and status =2", positionId). | ||||||
|  | 		Select("id,position_side,side,type").Find(&orders).Error | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", symbol.GetSymbol(), orderSn, err) | 		e.Log.Errorf("获取订单信息失败:%v", err) | ||||||
|  | 	} | ||||||
|  | 	return orders, err | ||||||
|  | } | ||||||
|  |  | ||||||
| 		if err1 := e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil { | func (e *ReverseService) getOppositeSide(side string) string { | ||||||
| 			e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", newOrder.Symbol, newOrder.OrderSn, err1) | 	if side == "SELL" { | ||||||
|  | 		return "BUY" | ||||||
|  | 	} | ||||||
|  | 	return "SELL" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *ReverseService) hasOrderType(orders []DbModels.LineReverseOrder, types ...string) bool { | ||||||
|  | 	typeSet := make(map[string]bool) | ||||||
|  | 	for _, t := range types { | ||||||
|  | 		typeSet[t] = true | ||||||
|  | 	} | ||||||
|  | 	for _, o := range orders { | ||||||
|  | 		if typeSet[o.Type] { | ||||||
|  | 			return true | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *ReverseService) calculatePrice(positionSide string, base decimal.Decimal, ratio decimal.Decimal, isTakeProfit bool) decimal.Decimal { | ||||||
|  | 	adjust := decimal.NewFromInt(100) | ||||||
|  | 	if positionSide == "LONG" { | ||||||
|  | 		if isTakeProfit { | ||||||
|  | 			return base.Mul(adjust.Add(ratio).Div(adjust)).Truncate(4) | ||||||
|  | 		} | ||||||
|  | 		return base.Mul(adjust.Sub(ratio).Div(adjust)).Truncate(4) | ||||||
|  | 	} | ||||||
|  | 	if isTakeProfit { | ||||||
|  | 		return base.Mul(adjust.Sub(ratio).Div(adjust)).Truncate(4) | ||||||
|  | 	} | ||||||
|  | 	return base.Mul(adjust.Add(ratio).Div(adjust)).Truncate(4) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type CreateOrderParams struct { | ||||||
|  | 	ApiInfo      *DbModels.LineApiUser | ||||||
|  | 	Order        *DbModels.LineReverseOrder | ||||||
|  | 	Symbol       *models.TradeSet | ||||||
|  | 	Side         string | ||||||
|  | 	OrderType    string | ||||||
|  | 	Price        decimal.Decimal | ||||||
|  | 	TotalNum     decimal.Decimal | ||||||
|  | 	Now          time.Time | ||||||
|  | 	LastPrice    decimal.Decimal | ||||||
|  | 	Close        bool | ||||||
|  | 	PositionId   int | ||||||
|  | 	OrderSn      string | ||||||
|  | 	PositionSide string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *ReverseService) createReverseOrder(params CreateOrderParams) error { | ||||||
|  | 	orderSn := params.OrderSn | ||||||
|  | 	if orderSn == "" { | ||||||
|  | 		orderSn = helper.GetOrderNo() | ||||||
|  | 	} | ||||||
|  | 	if params.PositionSide == "" && params.Order != nil { | ||||||
|  | 		params.PositionSide = params.Order.PositionSide | ||||||
|  | 	} | ||||||
|  | 	newOrder := DbModels.LineReverseOrder{ | ||||||
|  | 		PositionId:    params.PositionId, | ||||||
|  | 		OrderSn:       orderSn, | ||||||
|  | 		OrderType:     getOrderType(params.OrderType), | ||||||
|  | 		Status:        1, | ||||||
|  | 		Price:         params.Price, | ||||||
|  | 		TotalNum:      params.TotalNum, | ||||||
|  | 		Symbol:        params.Symbol.GetSymbol(), | ||||||
|  | 		Side:          params.Side, | ||||||
|  | 		PositionSide:  params.PositionSide, | ||||||
|  | 		FollowOrderSn: params.OrderSn, | ||||||
|  | 		Type:          params.OrderType, | ||||||
|  | 		SignPrice:     params.LastPrice, | ||||||
|  | 		Category:      1, | ||||||
|  | 		ApiId:         params.ApiInfo.Id, | ||||||
|  | 		IsAddPosition: 2, | ||||||
|  | 		TriggerTime:   ¶ms.Now, | ||||||
|  | 		BuyPrice:      params.TotalNum.Mul(params.Price).Truncate(int32(params.Symbol.PriceDigit)), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := e.Orm.Create(&newOrder).Error; err != nil { | ||||||
|  | 		e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", params.Symbol.GetSymbol(), newOrder.OrderSn, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	e.DoCancelTakeProfitBatch(symbol.GetSymbol(), positionSide, side, orderType, &apiInfo) | 	futApiV2 := FuturesResetV2{Service: e.Service} | ||||||
|  | 	params2 := FutOrderPlace{ | ||||||
|  | 		ApiId:            params.ApiInfo.Id, | ||||||
|  | 		Symbol:           params.Symbol.GetSymbol(), | ||||||
|  | 		PositionSide:     newOrder.PositionSide, | ||||||
|  | 		Side:             newOrder.Side, | ||||||
|  | 		OrderType:        newOrder.Type, | ||||||
|  | 		Quantity:         newOrder.TotalNum, | ||||||
|  | 		Price:            newOrder.Price, | ||||||
|  | 		StopPrice:        newOrder.Price, | ||||||
|  | 		Profit:           newOrder.Price, | ||||||
|  | 		NewClientOrderId: newOrder.OrderSn, | ||||||
|  | 		ClosePosition:    params.Close, | ||||||
|  | 	} | ||||||
|  | 	err := futApiV2.OrderPlaceLoop(params.ApiInfo, params2) | ||||||
|  | 	if err != nil { | ||||||
|  | 		e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", params.Symbol.GetSymbol(), newOrder.OrderSn, err) | ||||||
|  | 		e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	e.DoCancelTakeProfitBatch(params.Symbol.GetSymbol(), newOrder.PositionSide, newOrder.Side, newOrder.OrderType, params.ApiInfo) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func getOrderType(t string) int { | ||||||
|  | 	if t == "TAKE_PROFIT_MARKET" || t == "TAKE_PROFIT" { | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 	return 2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // // 重下止盈止损 | ||||||
|  | // // mapData: 主单止盈止损回调 | ||||||
|  | // func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error { | ||||||
|  | // 	orderType := 0 //订单类型 1-止盈 2-止损 | ||||||
|  | // 	side, err := maphelper.GetString(*mapData, "S") | ||||||
|  |  | ||||||
|  | // 	if err != nil { | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	ot, err := maphelper.GetString(*mapData, "ot") | ||||||
|  |  | ||||||
|  | // 	if err != nil { | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	//反单止盈止损方向相反 | ||||||
|  | // 	if side == "SELL" { | ||||||
|  | // 		side = "BUY" | ||||||
|  | // 	} else { | ||||||
|  | // 		side = "SELL" | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	positionSide, err := maphelper.GetString(*mapData, "ps") | ||||||
|  |  | ||||||
|  | // 	if err != nil { | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	if positionSide == "LONG" { | ||||||
|  | // 		positionSide = "SHORT" | ||||||
|  | // 	} else { | ||||||
|  | // 		positionSide = "LONG" | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	close := maphelper.GetBool(*mapData, "cp") | ||||||
|  | // 	stopPrice := maphelper.GetDecimal(*mapData, "sp") | ||||||
|  | // 	if stopPrice.IsZero() { | ||||||
|  | // 		e.Log.Errorf("获取止盈止损单触发价失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  | // 	apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId) | ||||||
|  |  | ||||||
|  | // 	if err != nil { | ||||||
|  | // 		e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	switch ot { | ||||||
|  | // 	case "STOP_MARKET", "STOP": | ||||||
|  | // 		orderType = 2 | ||||||
|  | // 	case "TAKE_PROFIT_MARKET", "TAKE_PROFIT": | ||||||
|  | // 		orderType = 1 | ||||||
|  | // 	default: | ||||||
|  | // 		return fmt.Errorf("不支持的订单类型 ot:%s", ot) | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	var reversePosition DbModels.LineReversePosition | ||||||
|  |  | ||||||
|  | // 	e.Orm.Model(&reversePosition). | ||||||
|  | // 		Where("symbol =? and reverse_api_id =? and position_side =? and reverse_status =1", symbol.GetSymbol(), apiInfo.Id, positionSide). | ||||||
|  | // 		First(&reversePosition) | ||||||
|  |  | ||||||
|  | // 	if reversePosition.Id == 0 { | ||||||
|  | // 		e.Log.Errorf("获取反单持仓失败 symbol:%s custom:%s :%v", symbol, orderSn, err) | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	mainPercent := decimal.NewFromInt(1) | ||||||
|  |  | ||||||
|  | // 	if !stopPrice.IsZero() && !reversePosition.AveragePrice.IsZero() { | ||||||
|  | // 		mainPercent = stopPrice.Div(reversePosition.AveragePrice) | ||||||
|  | // 		mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4) | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	var percent decimal.Decimal | ||||||
|  | // 	switch { | ||||||
|  | // 	//做多止损 | ||||||
|  | // 	case orderType == 2 && positionSide == "LONG", orderType == 1 && positionSide == "SHORT": | ||||||
|  | // 		percent = decimal.NewFromInt(1).Sub(mainPercent) | ||||||
|  | // 	case orderType == 2 && positionSide == "SHORT", orderType == 1 && positionSide == "LONG": | ||||||
|  | // 		percent = decimal.NewFromInt(1).Add(mainPercent) | ||||||
|  | // 	default: | ||||||
|  | // 		return fmt.Errorf("不支持的订单类型 ot:%s, ps:%s", ot, positionSide) | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	now := time.Now() | ||||||
|  | // 	price := reversePosition.AveragePrice.Mul(percent).Truncate(int32(symbol.PriceDigit)) | ||||||
|  | // 	lastPrice, _ := decimal.NewFromString(symbol.LastPrice) | ||||||
|  | // 	newOrder := DbModels.LineReverseOrder{ | ||||||
|  | // 		PositionId:    reversePosition.Id, | ||||||
|  | // 		OrderSn:       helper.GetOrderNo(), | ||||||
|  | // 		OrderType:     orderType, | ||||||
|  | // 		Status:        1, | ||||||
|  | // 		Price:         price, | ||||||
|  | // 		TotalNum:      reversePosition.TotalReverseAmount, | ||||||
|  | // 		Symbol:        symbol.GetSymbol(), | ||||||
|  | // 		Side:          side, | ||||||
|  | // 		PositionSide:  positionSide, | ||||||
|  | // 		FollowOrderSn: orderSn, | ||||||
|  | // 		Type:          ot, | ||||||
|  | // 		SignPrice:     lastPrice, | ||||||
|  | // 		Category:      1, | ||||||
|  | // 		ApiId:         apiInfo.Id, | ||||||
|  | // 		IsAddPosition: 2, | ||||||
|  | // 		TriggerTime:   &now, | ||||||
|  | // 		BuyPrice:      reversePosition.TotalReverseAmount.Mul(price).Truncate(int32(symbol.PriceDigit)), | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	if err1 := e.Orm.Create(&newOrder).Error; err1 != nil { | ||||||
|  | // 		e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", symbol, orderSn, err1) | ||||||
|  | // 		return err1 | ||||||
|  | // 	} | ||||||
|  | // 	params := FutOrderPlace{ | ||||||
|  | // 		ApiId:            apiInfo.Id, | ||||||
|  | // 		Symbol:           symbol.GetSymbol(), | ||||||
|  | // 		PositionSide:     newOrder.PositionSide, | ||||||
|  | // 		Side:             newOrder.Side, | ||||||
|  | // 		OrderType:        ot, | ||||||
|  | // 		Quantity:         newOrder.TotalNum, | ||||||
|  | // 		Price:            price, | ||||||
|  | // 		StopPrice:        price, | ||||||
|  | // 		Profit:           price, | ||||||
|  | // 		NewClientOrderId: newOrder.OrderSn, | ||||||
|  | // 		ClosePosition:    close, | ||||||
|  | // 	} | ||||||
|  | // 	futApiV2 := FuturesResetV2{Service: e.Service} | ||||||
|  | // 	err = futApiV2.OrderPlaceLoop(&apiInfo, params) | ||||||
|  |  | ||||||
|  | // 	if err != nil { | ||||||
|  | // 		e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", symbol.GetSymbol(), orderSn, err) | ||||||
|  |  | ||||||
|  | // 		if err1 := e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil { | ||||||
|  | // 			e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", newOrder.Symbol, newOrder.OrderSn, err1) | ||||||
|  | // 		} | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  |  | ||||||
|  | // 	e.DoCancelTakeProfitBatch(symbol.GetSymbol(), newOrder.PositionSide, newOrder.Side, newOrder.OrderType, &apiInfo) | ||||||
|  | // 	return nil | ||||||
|  | // } | ||||||
|  | |||||||
| @ -118,6 +118,7 @@ func (wm *BinanceWebSocketManager) triggerReconnect(force bool) { | |||||||
| 		case wm.reconnect <- struct{}{}: | 		case wm.reconnect <- struct{}{}: | ||||||
| 		default: | 		default: | ||||||
| 			// 防止阻塞,如果通道满了就跳过 | 			// 防止阻塞,如果通道满了就跳过 | ||||||
|  | 			log.Debugf("reconnect 信号已存在,跳过 key:%s", wm.apiKey) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -488,6 +489,7 @@ func (wm *BinanceWebSocketManager) handleOrderUpdate(msg []byte) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Stop 安全停止 WebSocket | ||||||
| func (wm *BinanceWebSocketManager) Stop() { | func (wm *BinanceWebSocketManager) Stop() { | ||||||
| 	wm.mu.Lock() | 	wm.mu.Lock() | ||||||
| 	defer wm.mu.Unlock() | 	defer wm.mu.Unlock() | ||||||
| @ -495,9 +497,8 @@ func (wm *BinanceWebSocketManager) Stop() { | |||||||
| 	if wm.isStopped { | 	if wm.isStopped { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	wm.isStopped = true | 	wm.isStopped = true | ||||||
| 	// 关闭 stopChannel(确保已经关闭,避免 panic) |  | ||||||
| 	select { | 	select { | ||||||
| 	case <-wm.stopChannel: | 	case <-wm.stopChannel: | ||||||
| 	default: | 	default: | ||||||
| @ -506,69 +507,106 @@ func (wm *BinanceWebSocketManager) Stop() { | |||||||
|  |  | ||||||
| 	if wm.cancelFunc != nil { | 	if wm.cancelFunc != nil { | ||||||
| 		wm.cancelFunc() | 		wm.cancelFunc() | ||||||
|  | 		wm.cancelFunc = nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if wm.ws != nil { | 	if wm.ws != nil { | ||||||
| 		if err := wm.ws.Close(); err != nil { | 		if err := wm.ws.Close(); err != nil { | ||||||
| 			log.Error(fmt.Sprintf("key【%s】close失败", wm.apiKey), err) | 			log.Errorf("WebSocket Close 错误 key:%s err:%v", wm.apiKey, err) | ||||||
| 		} else { |  | ||||||
| 			log.Info(fmt.Sprintf("key【%s】close", wm.apiKey)) |  | ||||||
| 		} | 		} | ||||||
|  | 		wm.ws = nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// **重新创建 stopChannel,避免 Restart() 时无效** | 	log.Infof("WebSocket 已完全停止 key:%s", wm.apiKey) | ||||||
| 	wm.stopChannel = make(chan struct{}) | 	wm.stopChannel = make(chan struct{}, 10) | ||||||
| } | } | ||||||
|  |  | ||||||
| // 重连机制 | // handleReconnect 使用指数退避并保持永不退出 | ||||||
| func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) { | func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) { | ||||||
| 	maxRetries := 100 // 最大重试次数 | 	const maxRetries = 100 | ||||||
|  | 	baseDelay := time.Second * 2 | ||||||
| 	retryCount := 0 | 	retryCount := 0 | ||||||
|  |  | ||||||
| 	for { | 	for { | ||||||
| 		select { | 		select { | ||||||
| 		case <-ctx.Done(): | 		case <-ctx.Done(): | ||||||
|  | 			log.Infof("handleReconnect context done: %s", wm.apiKey) | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
| 		case <-wm.reconnect: | 		case <-wm.reconnect: | ||||||
|  | 			wm.mu.Lock() | ||||||
| 			if wm.isStopped { | 			if wm.isStopped { | ||||||
|  | 				wm.mu.Unlock() | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | 			wm.mu.Unlock() | ||||||
|  |  | ||||||
| 			log.Warn("WebSocket 连接断开,尝试重连...") | 			log.Warnf("WebSocket 连接断开,准备重连 key:%s", wm.apiKey) | ||||||
|  |  | ||||||
| 			if wm.ws != nil { |  | ||||||
| 				wm.ws.Close() |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// 取消旧的上下文 |  | ||||||
| 			if wm.cancelFunc != nil { |  | ||||||
| 				wm.cancelFunc() |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			for { | 			for { | ||||||
|  | 				wm.mu.Lock() | ||||||
|  | 				if wm.ws != nil { | ||||||
|  | 					_ = wm.ws.Close() | ||||||
|  | 					wm.ws = nil | ||||||
|  | 				} | ||||||
|  | 				if wm.cancelFunc != nil { | ||||||
|  | 					wm.cancelFunc() | ||||||
|  | 					wm.cancelFunc = nil | ||||||
|  | 				} | ||||||
|  | 				wm.mu.Unlock() | ||||||
|  |  | ||||||
| 				newCtx, cancel := context.WithCancel(context.Background()) | 				newCtx, cancel := context.WithCancel(context.Background()) | ||||||
| 				wm.cancelFunc = cancel // 更新 cancelFunc | 				wm.mu.Lock() | ||||||
|  | 				wm.cancelFunc = cancel | ||||||
|  | 				wm.mu.Unlock() | ||||||
|  |  | ||||||
| 				if err := wm.connect(newCtx); err != nil { | 				if err := wm.connect(newCtx); err != nil { | ||||||
| 					log.Errorf("重连失败: %v", err) | 					log.Errorf("🔌 重连失败(%d/%d)key:%s,err: %v", retryCount+1, maxRetries, wm.apiKey, err) | ||||||
| 					cancel() | 					cancel() | ||||||
| 					retryCount++ | 					retryCount++ | ||||||
|  |  | ||||||
| 					if retryCount >= maxRetries { | 					if retryCount >= maxRetries { | ||||||
|  | 						log.Errorf("❌ 重连失败次数过多,停止重连逻辑 key:%s", wm.apiKey) | ||||||
| 						wm.reconnecting.Store(false) | 						wm.reconnecting.Store(false) | ||||||
| 						log.Error("重连失败次数过多,退出重连逻辑") |  | ||||||
| 						return | 						return | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					time.Sleep(5 * time.Second) | 					delay := baseDelay * time.Duration(1<<retryCount) | ||||||
|  | 					if delay > time.Minute*5 { | ||||||
|  | 						delay = time.Minute * 5 | ||||||
|  | 					} | ||||||
|  | 					log.Warnf("等待 %v 后重试...", delay) | ||||||
|  | 					time.Sleep(delay) | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// 重连成功,清除标记 | 				log.Infof("✅ 重连成功 key:%s", wm.apiKey) | ||||||
| 				wm.reconnecting.Store(false) |  | ||||||
| 				retryCount = 0 | 				retryCount = 0 | ||||||
|  | 				wm.reconnecting.Store(false) | ||||||
|  |  | ||||||
|  | 				// ✅ 重连成功后开启假死检测 | ||||||
|  | 				utility.SafeGo(func() { wm.startDeadCheck(newCtx) }) | ||||||
|  |  | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // startDeadCheck 替代 Start 中的定时器,绑定连接生命周期 | ||||||
|  | func (wm *BinanceWebSocketManager) startDeadCheck(ctx context.Context) { | ||||||
|  | 	ticker := time.NewTicker(1 * time.Minute) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			return | ||||||
|  | 		case <-ticker.C: | ||||||
|  | 			if wm.isStopped { | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | 			wm.DeadCheck() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user