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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -107,3 +107,15 @@ func (e *LineUserSetting) Remove(d *dto.LineUserSettingDeleteReq, p *actions.Dat
|
|||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if needReverseOrder {
|
||||||
e.DoAddReverseOrder(&mainOrder, &reverseApiInfo, apiInfo.OrderProportion, true, closePosition)
|
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)
|
||||||
|
|
||||||
if !stopPrice.IsZero() && !reversePosition.AveragePrice.IsZero() {
|
|
||||||
mainPercent = stopPrice.Div(reversePosition.AveragePrice)
|
|
||||||
mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4)
|
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,
|
||||||
}
|
|
||||||
params := FutOrderPlace{
|
|
||||||
ApiId: apiInfo.Id,
|
|
||||||
Symbol: symbol.GetSymbol(),
|
|
||||||
PositionSide: positionSide,
|
|
||||||
Side: side,
|
Side: side,
|
||||||
OrderType: ot,
|
OrderType: ot,
|
||||||
Quantity: reversePosition.TotalReverseAmount,
|
|
||||||
Price: price,
|
Price: price,
|
||||||
StopPrice: price,
|
TotalNum: reversePosition.TotalReverseAmount,
|
||||||
Profit: price,
|
Now: now,
|
||||||
NewClientOrderId: newOrder.OrderSn,
|
LastPrice: lastPrice,
|
||||||
ClosePosition: close,
|
Close: close,
|
||||||
}
|
PositionId: reversePosition.Id,
|
||||||
futApiV2 := FuturesResetV2{Service: e.Service}
|
OrderSn: orderSn,
|
||||||
err = futApiV2.OrderPlaceLoop(&apiInfo, params)
|
PositionSide: positionSide,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
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 orders, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ReverseService) getOppositeSide(side string) string {
|
||||||
|
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