Compare commits
	
		
			7 Commits
		
	
	
		
			单边仓位_maste
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5e4286de50 | |||
| 0f9b966fdb | |||
| 7b50873de3 | |||
| 44ba8bfbf1 | |||
| 79af1ab2c1 | |||
| e3a737a7d6 | |||
| d4c8e692a7 | 
| @ -2,6 +2,7 @@ package models | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/models" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type LineApiUser struct { | ||||
| @ -9,22 +10,21 @@ type LineApiUser struct { | ||||
|  | ||||
| 	ExchangeType string `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型(字典 exchange_type)"` | ||||
| 	UserId       int64  `json:"userId" gorm:"type:int unsigned;comment:用户id"` | ||||
| 	JysId        int64  `json:"jysId" gorm:"type:int;comment:关联交易所账号id"` | ||||
| 	ApiName      string `json:"apiName" gorm:"type:varchar(255);comment:api用户名"` | ||||
| 	ApiKey       string `json:"apiKey" gorm:"type:varchar(255);comment:apiKey"` | ||||
| 	ApiSecret    string `json:"apiSecret" gorm:"type:varchar(255);comment:apiSecret"` | ||||
| 	IpAddress    string `json:"ipAddress" gorm:"type:varchar(255);comment:代理地址"` | ||||
| 	UserPass     string `json:"userPass" gorm:"type:varchar(255);comment:代码账号密码"` | ||||
| 	AdminId      int64  `json:"adminId" gorm:"type:int unsigned;comment:管理员id"` | ||||
| 	Affiliation  int64  `json:"affiliation" gorm:"type:int;comment:归属:1=现货,2=合约,3=现货合约"` | ||||
| 	AdminShow    int64  `json:"adminShow" gorm:"type:int;comment:是否超管可见:1=是,0=否"` | ||||
| 	Site         string `json:"site" gorm:"type:enum('1','2','3');comment:允许下单的方向:1=多;2=空;3=多空"` | ||||
| 	Subordinate  string `json:"subordinate" gorm:"type:enum('0','1','2');comment:从属关系:0=未绑定关系;1=主账号;2=副帐号"` | ||||
| 	GroupId      int64  `json:"groupId" gorm:"type:int unsigned;comment:所属组id"` | ||||
| 	OpenStatus   int64  `json:"openStatus" gorm:"type:int unsigned;comment:开启状态 0=关闭 1=开启"` | ||||
| 	// AdminId      int64  `json:"adminId" gorm:"type:int unsigned;comment:管理员id"` | ||||
| 	Affiliation int64  `json:"affiliation" gorm:"type:int;comment:归属:1=现货,2=合约,3=现货合约"` | ||||
| 	AdminShow   int64  `json:"adminShow" gorm:"type:int;comment:是否超管可见:1=是,0=否"` | ||||
| 	Site        string `json:"site" gorm:"type:enum('1','2','3');comment:允许下单的方向:1=多;2=空;3=多空"` | ||||
| 	Subordinate string `json:"subordinate" gorm:"type:enum('0','1','2');comment:从属关系:0=未绑定关系;1=主账号;2=副帐号"` | ||||
| 	GroupId     int64  `json:"groupId" gorm:"type:int unsigned;comment:所属组id"` | ||||
| 	OpenStatus  int64  `json:"openStatus" gorm:"type:int unsigned;comment:开启状态 0=关闭 1=开启"` | ||||
|  | ||||
| 	SpotLastTime    string `json:"spotLastTime" gorm:"-"`    //现货websocket最后通信时间 | ||||
| 	FuturesLastTime string `json:"futuresLastTime" gorm:"-"` //合约websocket最后通信时间 | ||||
| 	SpotLastTime    *time.Time `json:"spotLastTime" gorm:"-"`    //现货websocket最后通信时间 | ||||
| 	FuturesLastTime *time.Time `json:"futuresLastTime" gorm:"-"` //合约websocket最后通信时间 | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
|  | ||||
| @ -14,7 +14,7 @@ type LineStrategyTemplate struct { | ||||
| 	Percentag     decimal.Decimal `json:"percentag" gorm:"type:decimal(10,2);comment:涨跌点数"` | ||||
| 	CompareType   int             `json:"compareType" gorm:"type:tinyint;comment:比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` | ||||
| 	TimeSlotStart int             `json:"timeSlotStart" gorm:"type:int;comment:时间段开始(分)"` | ||||
| 	TimeSlotEnd   int             `json:"timeSlotEnd" gorm:"type:int;comment:时间断截至(分)"` | ||||
| 	// TimeSlotEnd   int             `json:"timeSlotEnd" gorm:"type:int;comment:时间断截至(分)"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
|  | ||||
| @ -14,9 +14,9 @@ type LineApiUserGetPageReq struct { | ||||
| } | ||||
|  | ||||
| type LineApiUserOrder struct { | ||||
| 	Id          string `form:"idOrder"  search:"type:order;column:id;table:line_api_user"` | ||||
| 	UserId      string `form:"userIdOrder"  search:"type:order;column:user_id;table:line_api_user"` | ||||
| 	JysId       string `form:"jysIdOrder"  search:"type:order;column:jys_id;table:line_api_user"` | ||||
| 	Id     string `form:"idOrder"  search:"type:order;column:id;table:line_api_user"` | ||||
| 	UserId string `form:"userIdOrder"  search:"type:order;column:user_id;table:line_api_user"` | ||||
| 	// JysId       string `form:"jysIdOrder"  search:"type:order;column:jys_id;table:line_api_user"` | ||||
| 	ApiName     string `form:"apiNameOrder"  search:"type:order;column:api_name;table:line_api_user"` | ||||
| 	ApiKey      string `form:"apiKeyOrder"  search:"type:order;column:api_key;table:line_api_user"` | ||||
| 	ApiSecret   string `form:"apiSecretOrder"  search:"type:order;column:api_secret;table:line_api_user"` | ||||
| @ -44,19 +44,19 @@ type LineApiUserInsertReq struct { | ||||
| 	Id           int    `json:"-" comment:"id"` // id | ||||
| 	ExchangeType string `json:"exchangeType" comment:"交易所code"` | ||||
| 	UserId       int64  `json:"userId" comment:"用户id"` | ||||
| 	JysId        int64  `json:"jysId" comment:"关联交易所账号id"` | ||||
| 	ApiName      string `json:"apiName" comment:"api用户名"` | ||||
| 	ApiKey       string `json:"apiKey" comment:"apiKey"` | ||||
| 	ApiSecret    string `json:"apiSecret" comment:"apiSecret"` | ||||
| 	IpAddress    string `json:"ipAddress" comment:"代理地址"` | ||||
| 	UserPass     string `json:"userPass" comment:"代码账号密码"` | ||||
| 	AdminId      int64  `json:"adminId" comment:"管理员id"` | ||||
| 	Affiliation  int64  `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"` | ||||
| 	AdminShow    int64  `json:"adminShow" comment:"是否超管可见:1=是,0=否"` | ||||
| 	Site         string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"` | ||||
| 	Subordinate  string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"` | ||||
| 	GroupId      int64  `json:"groupId" comment:"所属组id"` | ||||
| 	OpenStatus   int64  `json:"openStatus" comment:"开启状态 0=关闭 1=开启"` | ||||
| 	// JysId        int64  `json:"jysId" comment:"关联交易所账号id"` | ||||
| 	ApiName     string `json:"apiName" comment:"api用户名"` | ||||
| 	ApiKey      string `json:"apiKey" comment:"apiKey"` | ||||
| 	ApiSecret   string `json:"apiSecret" comment:"apiSecret"` | ||||
| 	IpAddress   string `json:"ipAddress" comment:"代理地址"` | ||||
| 	UserPass    string `json:"userPass" comment:"代码账号密码"` | ||||
| 	AdminId     int64  `json:"adminId" comment:"管理员id"` | ||||
| 	Affiliation int64  `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"` | ||||
| 	AdminShow   int64  `json:"adminShow" comment:"是否超管可见:1=是,0=否"` | ||||
| 	Site        string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"` | ||||
| 	Subordinate string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"` | ||||
| 	GroupId     int64  `json:"groupId" comment:"所属组id"` | ||||
| 	OpenStatus  int64  `json:"openStatus" comment:"开启状态 0=关闭 1=开启"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -66,13 +66,13 @@ func (s *LineApiUserInsertReq) Generate(model *models.LineApiUser) { | ||||
| 	} | ||||
| 	model.ExchangeType = s.ExchangeType | ||||
| 	model.UserId = s.UserId | ||||
| 	model.JysId = s.JysId | ||||
| 	// model.JysId = s.JysId | ||||
| 	model.ApiName = s.ApiName | ||||
| 	model.ApiKey = s.ApiKey | ||||
| 	model.ApiSecret = s.ApiSecret | ||||
| 	model.IpAddress = s.IpAddress | ||||
| 	model.UserPass = s.UserPass | ||||
| 	model.AdminId = s.AdminId | ||||
| 	// model.AdminId = s.AdminId | ||||
| 	model.Affiliation = s.Affiliation | ||||
| 	model.AdminShow = s.AdminShow | ||||
| 	model.Site = s.Site | ||||
| @ -90,19 +90,19 @@ type LineApiUserUpdateReq struct { | ||||
| 	Id           int    `uri:"id" comment:"id"` // id | ||||
| 	ExchangeType string `json:"exchangeType" comment:"交易所code"` | ||||
| 	UserId       int64  `json:"userId" comment:"用户id"` | ||||
| 	JysId        int64  `json:"jysId" comment:"关联交易所账号id"` | ||||
| 	ApiName      string `json:"apiName" comment:"api用户名"` | ||||
| 	ApiKey       string `json:"apiKey" comment:"apiKey"` | ||||
| 	ApiSecret    string `json:"apiSecret" comment:"apiSecret"` | ||||
| 	IpAddress    string `json:"ipAddress" comment:"代理地址"` | ||||
| 	UserPass     string `json:"userPass" comment:"代码账号密码"` | ||||
| 	AdminId      int64  `json:"adminId" comment:"管理员id"` | ||||
| 	Affiliation  int64  `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"` | ||||
| 	AdminShow    int64  `json:"adminShow" comment:"是否超管可见:1=是,0=否"` | ||||
| 	Site         string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"` | ||||
| 	Subordinate  string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"` | ||||
| 	GroupId      int64  `json:"groupId" comment:"所属组id"` | ||||
| 	OpenStatus   int64  `json:"openStatus" comment:"开启状态 0=关闭 1=开启"` | ||||
| 	// JysId        int64  `json:"jysId" comment:"关联交易所账号id"` | ||||
| 	ApiName   string `json:"apiName" comment:"api用户名"` | ||||
| 	ApiKey    string `json:"apiKey" comment:"apiKey"` | ||||
| 	ApiSecret string `json:"apiSecret" comment:"apiSecret"` | ||||
| 	IpAddress string `json:"ipAddress" comment:"代理地址"` | ||||
| 	UserPass  string `json:"userPass" comment:"代码账号密码"` | ||||
| 	// AdminId     int64  `json:"adminId" comment:"管理员id"` | ||||
| 	Affiliation int64  `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"` | ||||
| 	AdminShow   int64  `json:"adminShow" comment:"是否超管可见:1=是,0=否"` | ||||
| 	Site        string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"` | ||||
| 	Subordinate string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"` | ||||
| 	GroupId     int64  `json:"groupId" comment:"所属组id"` | ||||
| 	OpenStatus  int64  `json:"openStatus" comment:"开启状态 0=关闭 1=开启"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -112,13 +112,13 @@ func (s *LineApiUserUpdateReq) Generate(model *models.LineApiUser) { | ||||
| 	} | ||||
| 	model.ExchangeType = s.ExchangeType | ||||
| 	model.UserId = s.UserId | ||||
| 	model.JysId = s.JysId | ||||
| 	// model.JysId = s.JysId | ||||
| 	model.ApiName = s.ApiName | ||||
| 	model.ApiKey = s.ApiKey | ||||
| 	model.ApiSecret = s.ApiSecret | ||||
| 	model.IpAddress = s.IpAddress | ||||
| 	model.UserPass = s.UserPass | ||||
| 	model.AdminId = s.AdminId | ||||
| 	// model.AdminId = s.AdminId | ||||
| 	model.Affiliation = s.Affiliation | ||||
| 	model.AdminShow = s.AdminShow | ||||
| 	model.Site = s.Site | ||||
|  | ||||
| @ -197,7 +197,7 @@ type LineAddPreOrderReq struct { | ||||
| 	Site                    string          `json:"site" `                       //购买方向 | ||||
| 	BuyPrice                string          `json:"buy_price" vd:"$>0"`          //购买金额 U | ||||
| 	PricePattern            string          `json:"price_pattern"`               //价格模式 | ||||
| 	Price                   string          `json:"price" vd:"$>0"`              //下单价百分比 | ||||
| 	Price                   string          `json:"price"`                       //下单价百分比 | ||||
| 	Profit                  string          `json:"profit" vd:"$>0"`             //止盈价 | ||||
| 	ProfitNumRatio          decimal.Decimal `json:"profit_num_ratio"`            //止盈数量百分比 | ||||
| 	ProfitTpTpPriceRatio    decimal.Decimal `json:"profit_tp_tp_price_ratio"`    //止盈后止盈价百分比 | ||||
| @ -295,7 +295,7 @@ func (req LineAddPreOrderReq) Valid() error { | ||||
| 		return errors.New("主单减仓数量百分比不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if req.ReducePriceRatio.IsZero() || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 	if req.PricePattern != "mixture" && (req.ReducePriceRatio.IsZero() || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0) { | ||||
| 		return errors.New("主单减仓价格百分比错误") | ||||
| 	} | ||||
|  | ||||
| @ -447,7 +447,7 @@ func (req LineBatchAddPreOrderReq) CheckParams() error { | ||||
| 		return errors.New("主单减仓数量百分比不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 	if req.PricePattern != "mixture" && (req.ReducePriceRatio.Cmp(decimal.Zero) <= 0 || req.ReducePriceRatio.Cmp(decimal.NewFromInt(100)) >= 0) { | ||||
| 		return errors.New("主单减仓价格百分比错误") | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -41,8 +41,8 @@ type LineStrategyTemplateInsertReq struct { | ||||
| 	Direction     int             `json:"direction" comment:"涨跌方向 1-涨 2-跌"` | ||||
| 	Percentag     decimal.Decimal `json:"percentag" comment:"涨跌点数"` | ||||
| 	CompareType   int             `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` | ||||
| 	TimeSlotStart int             `json:"timeSlotStart" comment:"时间段开始(分)"` | ||||
| 	TimeSlotEnd   int             `json:"timeSlotEnd" comment:"时间断截至(分)"` | ||||
| 	TimeSlotStart int             `json:"timeSlotStart" comment:"时间段(分)"` | ||||
| 	// TimeSlotEnd   int             `json:"timeSlotEnd" comment:"时间断截至(分)"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -63,13 +63,13 @@ func (s *LineStrategyTemplateInsertReq) Valid() error { | ||||
| 		return errors.New("比较类型不合法") | ||||
| 	} | ||||
|  | ||||
| 	if s.TimeSlotStart < 0 || s.TimeSlotEnd > 59 { | ||||
| 	if s.TimeSlotStart < 0 { | ||||
| 		return errors.New("时间段不合法") | ||||
| 	} | ||||
|  | ||||
| 	if s.TimeSlotEnd < s.TimeSlotStart { | ||||
| 		return errors.New("时间段不合法") | ||||
| 	} | ||||
| 	// if s.TimeSlotEnd < s.TimeSlotStart { | ||||
| 	// 	return errors.New("时间段不合法") | ||||
| 	// } | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @ -83,7 +83,7 @@ func (s *LineStrategyTemplateInsertReq) Generate(model *models.LineStrategyTempl | ||||
| 	model.Percentag = s.Percentag | ||||
| 	model.CompareType = s.CompareType | ||||
| 	model.TimeSlotStart = s.TimeSlotStart | ||||
| 	model.TimeSlotEnd = s.TimeSlotEnd | ||||
| 	// model.TimeSlotEnd = s.TimeSlotEnd | ||||
| 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||
| } | ||||
|  | ||||
| @ -97,8 +97,8 @@ type LineStrategyTemplateUpdateReq struct { | ||||
| 	Direction     int             `json:"direction" comment:"涨跌方向 1-涨 2-跌"` | ||||
| 	Percentag     decimal.Decimal `json:"percentag" comment:"涨跌点数"` | ||||
| 	CompareType   int             `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "` | ||||
| 	TimeSlotStart int             `json:"timeSlotStart" comment:"时间段开始(分)"` | ||||
| 	TimeSlotEnd   int             `json:"timeSlotEnd" comment:"时间断截至(分)"` | ||||
| 	TimeSlotStart int             `json:"timeSlotStart" comment:"时间段(分)"` | ||||
| 	// TimeSlotEnd   int             `json:"timeSlotEnd" comment:"时间断截至(分)"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -120,13 +120,13 @@ func (s *LineStrategyTemplateUpdateReq) Valid() error { | ||||
| 		return errors.New("比较类型不合法") | ||||
| 	} | ||||
|  | ||||
| 	if s.TimeSlotStart < 0 || s.TimeSlotEnd > 59 { | ||||
| 	if s.TimeSlotStart < 0 { | ||||
| 		return errors.New("时间段不合法") | ||||
| 	} | ||||
|  | ||||
| 	if s.TimeSlotEnd < s.TimeSlotStart { | ||||
| 		return errors.New("时间段不合法") | ||||
| 	} | ||||
| 	// if s.TimeSlotEnd < s.TimeSlotStart { | ||||
| 	// 	return errors.New("时间段不合法") | ||||
| 	// } | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @ -139,7 +139,7 @@ func (s *LineStrategyTemplateUpdateReq) Generate(model *models.LineStrategyTempl | ||||
| 	model.Percentag = s.Percentag | ||||
| 	model.CompareType = s.CompareType | ||||
| 	model.TimeSlotStart = s.TimeSlotStart | ||||
| 	model.TimeSlotEnd = s.TimeSlotEnd | ||||
| 	// model.TimeSlotEnd = s.TimeSlotEnd | ||||
| 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -52,19 +52,19 @@ func (e *LineApiUser) GetPage(c *dto.LineApiUserGetPageReq, p *actions.DataPermi | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var userSub binancedto.UserSubscribeState | ||||
| 	for index := range *list { | ||||
| 		val, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.USER_SUBSCRIBE, (*list)[index].ApiKey)) | ||||
| 		var userSub binancedto.UserSubscribeState | ||||
|  | ||||
| 		if val != "" { | ||||
| 			sonic.Unmarshal([]byte(val), &userSub) | ||||
|  | ||||
| 			if userSub.FuturesLastTime != nil { | ||||
| 				(*list)[index].FuturesLastTime = userSub.FuturesLastTime.Format("2006-01-02 15:04:05") | ||||
| 				(*list)[index].FuturesLastTime = userSub.FuturesLastTime | ||||
| 			} | ||||
|  | ||||
| 			if userSub.SpotLastTime != nil { | ||||
| 				(*list)[index].SpotLastTime = userSub.SpotLastTime.Format("2006-01-02 15:04:05") | ||||
| 				(*list)[index].SpotLastTime = userSub.SpotLastTime | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -279,6 +279,8 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 	futReduceVal, _ := helper.DefaultRedis.GetAllList(spotAddPositionKey) | ||||
| 	spotAddPositionVal, _ := helper.DefaultRedis.GetAllList(futReduceKey) | ||||
| 	spotReduceVal, _ := helper.DefaultRedis.GetAllList(spotReduceKey) | ||||
| 	spotStrategyMap := e.GetStrategyOrderListMap(1) | ||||
| 	futStrategyMap := e.GetStrategyOrderListMap(2) | ||||
|  | ||||
| 	for _, v := range futAddPositionVal { | ||||
| 		sonic.Unmarshal([]byte(v), &addPosition) | ||||
| @ -337,8 +339,14 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 		if val, ok := spotRedces[order.Id]; ok { | ||||
| 			helper.DefaultRedis.LRem(spotReduceKey, val) | ||||
| 		} | ||||
| 		var tradedSetKey string | ||||
| 		if order.SymbolType == 1 { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol) | ||||
| 		} else { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_FUTURES, order.ExchangeType, order.Symbol) | ||||
| 		} | ||||
|  | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol)) | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, tradedSetKey) | ||||
| 		redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 		marshal, _ := sonic.Marshal(redisList) | ||||
| 		if order.SymbolType == 1 { | ||||
| @ -349,6 +357,13 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 			helper.DefaultRedis.LRem(listKey, string(marshal)) | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 1: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &spotStrategyMap) | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 2: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &futStrategyMap) | ||||
| 		} | ||||
|  | ||||
| 		//会影响持仓的 | ||||
| 		removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType) | ||||
|  | ||||
| @ -363,6 +378,7 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi | ||||
| 		} | ||||
|  | ||||
| 		binanceservice.MainClosePositionClearCache(order.Id, order.SymbolType) | ||||
| 		binanceservice.RemoveReduceReduceCacheByMainId(order.Id, order.SymbolType) | ||||
|  | ||||
| 		ints = append(ints, order.Id) | ||||
| 	} | ||||
| @ -576,6 +592,7 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int | ||||
|  | ||||
| 		preOrderStatus := models.LinePreOrderStatus{} | ||||
| 		preOrderStatus.OrderSn = AddOrder.OrderSn | ||||
| 		mainPrice := utility.StringToDecimal(AddOrder.Price) | ||||
|  | ||||
| 		//订单配置信息 | ||||
| 		preOrderExts := make([]models.LinePreOrderExt, 0) | ||||
| @ -590,6 +607,12 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int | ||||
| 			TpTpPriceRatio:     req.ProfitTpTpPriceRatio, | ||||
| 			TpSlPriceRatio:     req.ProfitTpSlPriceRatio, | ||||
| 		} | ||||
|  | ||||
| 		if req.PricePattern == "mixture" { | ||||
| 			defultExt.TakeProfitRatio = mainPrice.Div(utility.StrToDecimal(req.Profit)).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 			defultExt.StopLossRatio = mainPrice.Div(req.StopLoss).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 		} | ||||
|  | ||||
| 		//减仓单 | ||||
| 		defultExt2 := models.LinePreOrderExt{ | ||||
| 			AddType:            2, | ||||
| @ -601,17 +624,12 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int | ||||
| 			TakeProfitNumRatio: decimal.NewFromInt(100), //减仓止盈默认100% | ||||
| 			StopLossRatio:      req.ReduceStopLossRatio, | ||||
| 		} | ||||
| 		mainPrice := utility.StringToDecimal(AddOrder.Price) | ||||
| 		mainAmount := utility.SafeDiv(buyPrice, mainPrice) | ||||
| 		defultExt.TotalAfter = utility.StrToDecimal(AddOrder.Num).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		defultExt2.TotalBefore = defultExt.TotalAfter | ||||
|  | ||||
| 		default2NumPercent := utility.SafeDiv(decimal.NewFromInt(100).Sub(req.ReduceNumRatio), decimal.NewFromInt(100)) | ||||
| 		defultExt2.TotalAfter = mainAmount.Mul(default2NumPercent).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		defultExt2.ReTakeRatio = utility.SafeDiv(req.ReducePriceRatio, default2NumPercent).Truncate(2) | ||||
|  | ||||
| 		preOrderExts = append(preOrderExts, defultExt) | ||||
| 		preOrderExts = append(preOrderExts, defultExt2) | ||||
|  | ||||
| 		calculateResp := dto.CalculateBreakEvenRatioResp{} | ||||
| 		mainParam := dto.CalculateBreakEevenRatioReq{ | ||||
| @ -627,15 +645,22 @@ func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, apiUserIds []int | ||||
| 			AddPositionVal:   req.ReduceNumRatio, | ||||
| 		} | ||||
|  | ||||
| 		if req.PricePattern == "mixture" { | ||||
| 			mainParam.LossEndPercent = mainPrice.Div(req.ReducePriceRatio).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 			defultExt2.PriceRatio = mainParam.LossEndPercent | ||||
| 		} | ||||
|  | ||||
| 		//计算减仓后 | ||||
| 		mainParam.LossEndPercent = req.ReducePriceRatio | ||||
| 		defultExt2.ReTakeRatio = utility.SafeDiv(mainParam.LossEndPercent, default2NumPercent).Truncate(2) | ||||
| 		mainParam.RemainingQuantity = mainAmount | ||||
| 		e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet) | ||||
| 		mainParam.RemainingQuantity = calculateResp.RemainingQuantity //mainAmount.Mul(decimal.NewFromInt(100).Sub(req.ReduceNumRatio).Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU   //buyPrice.Mul(req.ReducePriceRatio.Div(decimal.NewFromInt(100)).Truncate(4)).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 		req.ReduceReTakeProfitRatio = calculateResp.Ratio | ||||
| 		mainParam.LossBeginPercent = req.ReducePriceRatio | ||||
| 		mainParam.LossBeginPercent = mainParam.LossEndPercent | ||||
| 		// defultExt.ReTakeRatio = calculateResp.Ratio | ||||
| 		preOrderExts = append(preOrderExts, defultExt) | ||||
| 		preOrderExts = append(preOrderExts, defultExt2) | ||||
|  | ||||
| 		for index, addPosition := range req.Ext { | ||||
| 			ext := models.LinePreOrderExt{ | ||||
| @ -907,7 +932,6 @@ func saveOrderCache(req *dto.LineAddPreOrderReq, AddOrder models.LinePreOrder, l | ||||
| 		list.Percentag = linestrategyTemplate.Percentag | ||||
| 		list.CompareType = linestrategyTemplate.CompareType | ||||
| 		list.TimeSlotStart = linestrategyTemplate.TimeSlotStart | ||||
| 		list.TimeSlotEnd = linestrategyTemplate.TimeSlotEnd | ||||
|  | ||||
| 		marshal, _ = sonic.Marshal(&list) | ||||
| 		if AddOrder.SymbolType == global.SYMBOL_SPOT { | ||||
| @ -1015,7 +1039,6 @@ func createPreReduceOrder(preOrder *models.LinePreOrder, ext models.LinePreOrder | ||||
|  | ||||
| 		stopOrder.OrderType = 4 | ||||
| 		stopOrder.Status = 0 | ||||
| 		stopOrder.Rate = ext.PriceRatio.String() | ||||
| 		stopOrder.Num = ext.TotalAfter.Sub(ext.TotalBefore).Abs().Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 		stopOrder.BuyPrice = "0" | ||||
| 		stopOrder.Rate = ext.PriceRatio.String() | ||||
| @ -1244,14 +1267,25 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p | ||||
| 		var symbolGroupInfo models.LineSymbolGroup | ||||
| 		e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", utility.StringToInt(batchReq.SymbolGroupId)).Find(&symbolGroupInfo) | ||||
| 		if symbolGroupInfo.Id <= 0 || symbolGroupInfo.Symbol == "" { | ||||
| 			*errs = append(*errs, errors.New(fmt.Sprintf("选择的交易对组:%s不存在或交易对组的交易对为空", batchReq.SymbolGroupId))) | ||||
| 			*errs = append(*errs, fmt.Errorf("选择的交易对组:%s不存在或交易对组的交易对为空", batchReq.SymbolGroupId)) | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		if batchReq.StrategyTemplateType == 1 && batchReq.StrategyTemplateId > 0 { | ||||
| 			cachePriceSymbols, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice) | ||||
| 			symbols := strings.Split(symbolGroupInfo.Symbol, ",") | ||||
|  | ||||
| 			for _, symbol := range symbols { | ||||
| 				if !utility.ContainsStr(cachePriceSymbols, symbol) { | ||||
| 					*errs = append(*errs, fmt.Errorf("交易对[%s]涨跌幅缓存不存在", symbol)) | ||||
| 					return nil | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		batchReq.Symbol = symbolGroupInfo.Symbol | ||||
| 	} | ||||
|  | ||||
| 	//脚本次数 | ||||
| 	// if batchReq.OrderNum > 0 { | ||||
| 	apiUserIds := strings.Split(batchReq.ApiUserId, ",") | ||||
| 	var tickerSymbol string | ||||
| 	if batchReq.SymbolType == global.SYMBOL_SPOT { | ||||
| @ -1276,7 +1310,7 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p | ||||
| 				log.ScriptParams = string(marshal) | ||||
| 				log.AdminId = 0 | ||||
| 				log.Status = "0" | ||||
| 				//scriptLogs = append(scriptLogs, log) | ||||
|  | ||||
| 				err := e.Orm.Model(&models.LinePreScript{}).Create(&log).Error | ||||
| 				if err != nil { | ||||
| 					*errs = append(*errs, fmt.Errorf("记录脚本失败:%+v", err.Error())) | ||||
| @ -1316,6 +1350,8 @@ func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p | ||||
| 				req.ReduceStopLossRatio = batchReq.ReduceStopLossRatio | ||||
| 				req.ReduceTakeProfitRatio = batchReq.ReduceTakeProfitRatio | ||||
| 				req.CreateBy = batchReq.CreateBy | ||||
| 				req.StrategyTemplateId = batchReq.StrategyTemplateId | ||||
| 				req.StrategyTemplateType = batchReq.StrategyTemplateType | ||||
|  | ||||
| 				e.AddPreOrderCheck(&req, p, errs, tickerSymbol) | ||||
| 			} | ||||
| @ -1995,6 +2031,8 @@ func (e *LinePreOrder) ClearUnTriggered() error { | ||||
| 	var orderLists []models.LinePreOrder | ||||
| 	positions := map[string]positiondto.LinePreOrderPositioinDelReq{} | ||||
| 	e.Orm.Model(&models.LinePreOrder{}).Where("main_id = 0 AND pid = 0 AND status = '0'").Find(&orderLists).Unscoped().Delete(&models.LinePreOrder{}) | ||||
| 	spotStrategyMap := e.GetStrategyOrderListMap(1) | ||||
| 	futStrategyMap := e.GetStrategyOrderListMap(2) | ||||
|  | ||||
| 	for _, order := range orderLists { | ||||
| 		redisList := dto.PreOrderRedisList{ | ||||
| @ -2006,17 +2044,35 @@ func (e *LinePreOrder) ClearUnTriggered() error { | ||||
| 			OrderSn:     order.OrderSn, | ||||
| 			QuoteSymbol: order.QuoteSymbol, | ||||
| 		} | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol)) | ||||
|  | ||||
| 		var tradedSetKey string | ||||
| 		if order.SymbolType == 1 { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_SPOT, order.ExchangeType, order.Symbol) | ||||
| 		} else { | ||||
| 			tradedSetKey = fmt.Sprintf(global.TICKER_FUTURES, order.ExchangeType, order.Symbol) | ||||
| 		} | ||||
|  | ||||
| 		tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, tradedSetKey) | ||||
| 		redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String() | ||||
| 		marshal, _ := sonic.Marshal(redisList) | ||||
|  | ||||
| 		if order.SymbolType == 1 { | ||||
| 			key := fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType) | ||||
|  | ||||
| 			helper.DefaultRedis.LRem(key, string(marshal)) | ||||
| 		} else { | ||||
| 			key := fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType) | ||||
|  | ||||
| 			helper.DefaultRedis.LRem(key, string(marshal)) | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 1: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &spotStrategyMap) | ||||
| 		case order.StrategyTemplateType == 1 && order.SymbolType == 2: | ||||
| 			e.RemoveStrategyOrderCache(order.Id, order.SymbolType, order.ExchangeType, &futStrategyMap) | ||||
| 		} | ||||
|  | ||||
| 		//会影响持仓的 | ||||
| 		removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType) | ||||
|  | ||||
| @ -2056,6 +2112,60 @@ func (e *LinePreOrder) ClearUnTriggered() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 移除待策略待触发单 | ||||
| func (e *LinePreOrder) RemoveStrategyOrderCache(orderId int, symbolType int, exchangeType string, caches *map[string][]dto.StrategyOrderRedisList) { | ||||
| 	strategys, _ := (*caches)[exchangeType] | ||||
| 	var strategyListKey string | ||||
|  | ||||
| 	if symbolType == 1 { | ||||
| 		strategyListKey = fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType) | ||||
| 	} else { | ||||
| 		strategyListKey = fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType) | ||||
| 	} | ||||
|  | ||||
| 	for _, strategy := range strategys { | ||||
| 		if strategy.Id == orderId { | ||||
| 			strategyVal, _ := sonic.MarshalString(strategy) | ||||
| 			helper.DefaultRedis.LRem(strategyListKey, strategyVal) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取策略订单缓存列表 | ||||
| // symbolType 1现货 2合约 | ||||
| func (e *LinePreOrder) GetStrategyOrderListMap(symbolType int) map[string][]dto.StrategyOrderRedisList { | ||||
| 	result := make(map[string][]dto.StrategyOrderRedisList) | ||||
| 	var key string | ||||
| 	exchanges := []string{global.EXCHANGE_BINANCE} | ||||
|  | ||||
| 	if symbolType == 1 { | ||||
| 		key = rediskey.StrategySpotOrderList | ||||
| 	} else { | ||||
| 		key = rediskey.StrategyFutOrderList | ||||
| 	} | ||||
|  | ||||
| 	for _, exchange := range exchanges { | ||||
| 		newKey := fmt.Sprintf(key, exchange) | ||||
| 		vals, _ := helper.DefaultRedis.GetAllList(newKey) | ||||
| 		itemData := make([]dto.StrategyOrderRedisList, 0) | ||||
| 		item := dto.StrategyOrderRedisList{} | ||||
|  | ||||
| 		for _, v := range vals { | ||||
| 			sonic.Unmarshal([]byte(v), &item) | ||||
|  | ||||
| 			if item.Id > 0 { | ||||
| 				itemData = append(itemData, item) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(itemData) > 0 { | ||||
| 			result[exchange] = itemData | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (e *LinePreOrder) QueryOrder(req *dto.QueryOrderReq) (res interface{}, err error) { | ||||
| 	var apiUserInfo models.LineApiUser | ||||
| 	e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo) | ||||
| @ -2149,15 +2259,18 @@ func (e *LinePreOrder) GenerateOrder(req *dto.LineAddPreOrderReq) error { | ||||
| 		AddPositionVal:   req.ReduceNumRatio, | ||||
| 	} | ||||
|  | ||||
| 	if req.PricePattern == "mixture" { | ||||
| 		mainParam.LossEndPercent = price.Div(req.ReducePriceRatio).Sub(decimal.NewFromInt(1)).Abs().Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 	} | ||||
|  | ||||
| 	//计算减仓后 | ||||
| 	mainParam.LossEndPercent = req.ReducePriceRatio | ||||
| 	mainParam.RemainingQuantity = mainAmount | ||||
| 	mainParam.AddType = 2 | ||||
| 	e.CalculateBreakEvenRatio(&mainParam, &calculateResp, tradeSet) | ||||
| 	mainParam.RemainingQuantity = calculateResp.RemainingQuantity | ||||
| 	mainParam.TotalLossAmountU = calculateResp.TotalLossAmountU | ||||
| 	req.ReduceReTakeProfitRatio = calculateResp.Ratio | ||||
| 	lossBeginPercent = req.ReducePriceRatio | ||||
| 	lossBeginPercent = mainParam.LossEndPercent | ||||
|  | ||||
| 	//顺序排序 | ||||
| 	sort.Slice(req.Ext, func(i, j int) bool { | ||||
|  | ||||
							
								
								
									
										67
									
								
								app/jobs/account_job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								app/jobs/account_job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	binancedto "go-admin/models/binancedto" | ||||
| 	"go-admin/services/binanceservice" | ||||
|  | ||||
| 	DbModels "go-admin/app/admin/models" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type BinanceSpotAccountJob struct{} | ||||
|  | ||||
| type BinanceFuturesAccountJob struct{} | ||||
|  | ||||
| // 币安账户划转 | ||||
| func (t BinanceSpotAccountJob) Exec(arg interface{}) error { | ||||
| 	db := getDefaultDb() | ||||
| 	req := binancedto.BinanceTransfer{ | ||||
| 		Type:       "MAIN_UMFUTURE", | ||||
| 		Asset:      "USDT", | ||||
| 		Amount:     decimal.NewFromFloat(0.1), | ||||
| 		FromSymbol: "USDT", | ||||
| 		ToSymbol:   "USDT", | ||||
| 	} | ||||
| 	var apis []DbModels.LineApiUser | ||||
|  | ||||
| 	if err := db.Model(&DbModels.LineApiUser{}).Where("open_status = 1").Find(&apis).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, apiUserInfo := range apis { | ||||
| 		err := binanceservice.TradeAmount(db, &req, apiUserInfo) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("现货划转合约失败, err: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 币安账户划转 | ||||
| func (t BinanceFuturesAccountJob) Exec(arg interface{}) error { | ||||
| 	db := getDefaultDb() | ||||
| 	req := binancedto.BinanceTransfer{ | ||||
| 		Type:       "UMFUTURE_MAIN", | ||||
| 		Asset:      "USDT", | ||||
| 		Amount:     decimal.NewFromFloat(0.1), | ||||
| 		FromSymbol: "USDT", | ||||
| 		ToSymbol:   "USDT", | ||||
| 	} | ||||
| 	var apis []DbModels.LineApiUser | ||||
| 	if err := db.Model(&DbModels.LineApiUser{}).Where("open_status = 1").Find(&apis).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, apiUserInfo := range apis { | ||||
| 		err := binanceservice.TradeAmount(db, &req, apiUserInfo) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("合约划转现货失败, err: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										34
									
								
								app/jobs/account_job_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/jobs/account_job_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/helper" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func TestAccountJob(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
|  | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
|  | ||||
| 	accountJob := BinanceSpotAccountJob{} | ||||
| 	accountJob.Exec(nil) | ||||
| } | ||||
|  | ||||
| func TestFutureAccountJob(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
|  | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
|  | ||||
| 	accountJob := BinanceFuturesAccountJob{} | ||||
| 	accountJob.Exec(nil) | ||||
| } | ||||
| @ -38,6 +38,9 @@ func InitJob() { | ||||
| 		"MemberExpirationJob":            MemberExpirationJob{},            //会员到期处理 | ||||
| 		"MemberRenwalOrderExpirationJob": MemberRenwalOrderExpirationJob{}, //会员续费订单过期处理 | ||||
| 		"TrxQueryJobs":                   TrxQueryJobs{},                   //订单支付监听 | ||||
| 		"StrategyJob":                    StrategyJob{},                    //下单策略触发 | ||||
| 		"BinanceSpotAccountJob":          BinanceSpotAccountJob{},          //币安现货划转 | ||||
| 		"BinanceFuturesAccountJob":       BinanceFuturesAccountJob{},       //币安合约划转 | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -3,6 +3,7 @@ package jobs | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	models2 "go-admin/app/jobs/models" | ||||
| 	"runtime" | ||||
| 	"time" | ||||
|  | ||||
| 	log "github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -43,7 +44,10 @@ type ExecJob struct { | ||||
| func (e *ExecJob) Run() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			log.Errorf("脚本任务失败:%v", err) | ||||
| 			// 获取调用栈信息 | ||||
| 			buf := make([]byte, 1<<16) // 64KB 缓冲区 | ||||
| 			n := runtime.Stack(buf, false) | ||||
| 			log.Errorf("脚本任务失败: %v\n%s", err, buf[:n]) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
|  | ||||
							
								
								
									
										25
									
								
								app/jobs/strategy_job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/jobs/strategy_job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/services/binanceservice" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| ) | ||||
|  | ||||
| type StrategyJob struct { | ||||
| } | ||||
|  | ||||
| // 策略下单任务 | ||||
| func (j StrategyJob) Exec(arg interface{}) error { | ||||
| 	strategyService := binanceservice.BinanceStrategyOrderService{} | ||||
| 	db := getDefaultDb() | ||||
| 	strategyService.Orm = db | ||||
| 	strategyService.Log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{}) | ||||
|  | ||||
| 	//触发币安策略下单 | ||||
| 	strategyService.TriggerStrategyOrder(global.EXCHANGE_BINANCE) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										22
									
								
								app/jobs/strategy_job_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/jobs/strategy_job_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| package jobs | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/helper" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func TestStrategyJob(t *testing.T) { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
|  | ||||
| 	job := StrategyJob{} | ||||
|  | ||||
| 	job.Exec([]string{}) | ||||
| } | ||||
| @ -46,9 +46,9 @@ const ( | ||||
| 	SpotTrigger            = "spot_trigger_lock:%v_%s"        //现货触发 {apiuserid|symbol} | ||||
| 	FutTrigger             = "fut_trigger_lock:%v_%s"         //合约触发 {apiuserid|symbol} | ||||
|  | ||||
| 	//波段现货触发{apiuserid|symbol} | ||||
| 	//波段现货触发{apiuserid|ordersn} | ||||
| 	StrategySpotTriggerLock = "strategy_spot_trigger_l:%v_%s" | ||||
| 	//波段合约触发{apiuserid|symbol} | ||||
| 	//波段合约触发{apiuserid|ordersn} | ||||
| 	StrategyFutTriggerLock = "strategy_fut_trigger_l:%v_%s" | ||||
|  | ||||
| 	//减仓波段合约触发 {apiuserid|symbol} | ||||
| @ -95,9 +95,9 @@ const ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	//现货最后成交价 sort set {exchangeType,symbol} | ||||
| 	//现货最后成交价 sort set key:={exchangeType,symbol}   content:={utc:price} | ||||
| 	SpotTickerLastPrice = "spot_ticker_last_price:%s:%s" | ||||
| 	//合约最后成交价 sort set {exchangeType,symbol} | ||||
| 	//合约最后成交价 sort set {exchangeType,symbol}   content:={utc:price} | ||||
| 	FutureTickerLastPrice = "fut_ticker_last_price:%s:%s" | ||||
|  | ||||
| 	//允许缓存交易对价格的交易对 list | ||||
|  | ||||
| @ -3,6 +3,7 @@ package helper | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"math/rand" | ||||
| 	"strconv" | ||||
| @ -60,6 +61,8 @@ func InitLockRedisConn(host, password, dbIndex string) { | ||||
| 			log.Error("Failed to connect to Redis", zap.Error(err)) | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Println("redis lock初始化完毕") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -106,6 +109,9 @@ func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) { | ||||
| 				baseInterval = time.Second | ||||
| 			} | ||||
|  | ||||
| 			if baseInterval <= 0 { | ||||
| 				baseInterval = time.Millisecond * 100 // 至少 100ms | ||||
| 			} | ||||
| 			// 随机退避 | ||||
| 			retryInterval := time.Duration(rand.Int63n(int64(baseInterval))) // 随机退避 | ||||
| 			if retryInterval < time.Millisecond*100 { | ||||
| @ -129,6 +135,13 @@ func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) { | ||||
| 	return false, ErrFailed | ||||
| } | ||||
|  | ||||
| func safeRandomDuration(max time.Duration) time.Duration { | ||||
| 	if max <= 0 { | ||||
| 		return 100 * time.Millisecond // fallback default | ||||
| 	} | ||||
| 	return time.Duration(rand.Int63n(int64(max))) | ||||
| } | ||||
|  | ||||
| // Release 释放锁 | ||||
| func (rl *RedisLock) Release() (bool, error) { | ||||
| 	return rl.releaseCtx(context.Background()) | ||||
|  | ||||
| @ -13,6 +13,7 @@ import ( | ||||
| 	"go-admin/services/futureservice" | ||||
| 	"go-admin/services/scriptservice" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -23,9 +24,11 @@ import ( | ||||
|  | ||||
| // 业务初始化 | ||||
| func BusinessInit(db *gorm.DB) { | ||||
|  | ||||
| 	httputils.InitProxy(config.ExtConfig.ProxyUrl) | ||||
|  | ||||
| 	if err := loadApiUser(db); err != nil { //加载api用户 | ||||
| 		fmt.Printf("加载api用户失败 err:%v", err) | ||||
| 		os.Exit(-1) | ||||
| 	} | ||||
|  | ||||
| @ -37,6 +40,9 @@ func BusinessInit(db *gorm.DB) { | ||||
| 	symbolPriceService.Log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{}) | ||||
| 	symbolPriceService.InitCache() | ||||
|  | ||||
| 	//清理交易对缓存价格 | ||||
| 	clearSymbolPrice() | ||||
|  | ||||
| 	//初始化订单配置 | ||||
| 	cacheservice.ResetSystemSetting(db) | ||||
| 	lineApiUser := service.LineApiUser{} | ||||
| @ -145,3 +151,24 @@ func loadApiUser(db *gorm.DB) error { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 清理交易对价格缓存 | ||||
| func clearSymbolPrice() error { | ||||
| 	spotAll, _ := helper.DefaultRedis.ScanKeys("spot_ticker_last_price:*") | ||||
| 	futAllKey, _ := helper.DefaultRedis.ScanKeys("fut_ticker_last_price:*") | ||||
| 	beforeTimeUtc := time.Now().UnixMilli() | ||||
|  | ||||
| 	for _, item := range spotAll { | ||||
| 		if _, err := helper.DefaultRedis.RemoveBeforeScore(item, float64(beforeTimeUtc)); err != nil { | ||||
| 			logger.Error("现货 清理交易对价格缓存失败:", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range futAllKey { | ||||
| 		if _, err := helper.DefaultRedis.RemoveBeforeScore(item, float64(beforeTimeUtc)); err != nil { | ||||
| 			logger.Error("合约 清理交易对价格缓存失败:", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										11
									
								
								models/binancedto/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								models/binancedto/account.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| package binancedto | ||||
|  | ||||
| import "github.com/shopspring/decimal" | ||||
|  | ||||
| type BinanceTransfer struct { | ||||
| 	Type       string          `json:"type" content:"枚举 MAIN_UMFUTURE-现货到u合约"` | ||||
| 	Asset      string          `json:"asset" content:"币种"` | ||||
| 	Amount     decimal.Decimal `json:"amount" content:"数量"` | ||||
| 	FromSymbol string          `json:"fromSymbol" content:"转出币种"` | ||||
| 	ToSymbol   string          `json:"toSymbol" content:"转入币种"` | ||||
| } | ||||
| @ -640,3 +640,43 @@ func GetSpotUProperty(apiUserInfo DbModels.LineApiUser, data *dto.LineUserProper | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 万象划转 | ||||
| func TradeAmount(db *gorm.DB, req *binancedto.BinanceTransfer, apiUserInfo DbModels.LineApiUser) error { | ||||
| 	url := "/sapi/v1/asset/transfer" | ||||
|  | ||||
| 	client := GetClient(&apiUserInfo) | ||||
|  | ||||
| 	params := map[string]string{ | ||||
| 		"type":       req.Type, | ||||
| 		"asset":      req.Asset, | ||||
| 		"amount":     req.Amount.String(), | ||||
| 		"fromSymbol": req.FromSymbol, | ||||
| 		"toSymbol":   req.ToSymbol, | ||||
| 		"recvWindow": "10000", | ||||
| 	} | ||||
|  | ||||
| 	_, code, err := client.SendSpotAuth(url, "POST", params) | ||||
| 	if err != nil || code != 200 { | ||||
| 		log.Error("万向划转失败 参数:", params) | ||||
| 		log.Error("万向划转失败 code:", code) | ||||
| 		log.Error("万向划转失败 err:", err) | ||||
| 		dataMap := make(map[string]interface{}) | ||||
| 		if err.Error() != "" { | ||||
| 			if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil { | ||||
| 				return fmt.Errorf("api_id:%d 万向划转失败:%+v", apiUserInfo.Id, err.Error()) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		code, ok := dataMap["code"] | ||||
| 		if ok { | ||||
| 			return fmt.Errorf("api_id:%d 万向划转失败:%s", apiUserInfo.Id, ErrorMaps[code.(float64)]) | ||||
| 		} | ||||
| 		if strings.Contains(err.Error(), "Unknown order sent.") { | ||||
| 			return fmt.Errorf("api_id:%d 万向划转失败:%+v", apiUserInfo.Id, ErrorMaps[-2011]) | ||||
| 		} | ||||
| 		return fmt.Errorf("api_id:%d 万向划转失败:%+v", apiUserInfo.Id, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -251,11 +251,15 @@ func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice | ||||
| // mainOrderId 主单id | ||||
| // symbolType 1现货 2合约 | ||||
| func MainClosePositionClearCache(mainId int, symbolType int) { | ||||
| 	strategyDto := dto.StrategyOrderRedisList{} | ||||
|  | ||||
| 	if symbolType == 1 { | ||||
| 		keySpotStop := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) | ||||
| 		keySpotAddposition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) | ||||
| 		spotStopArray, _ := helper.DefaultRedis.GetAllList(keySpotStop) | ||||
| 		spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(keySpotAddposition) | ||||
| 		spotStrategyKey := fmt.Sprintf(rediskey.StrategySpotOrderList, global.EXCHANGE_BINANCE) | ||||
| 		spotStrategyList, _ := helper.DefaultRedis.GetAllList(spotStrategyKey) | ||||
| 		var position AddPositionList | ||||
| 		var stop dto.StopLossRedisList | ||||
|  | ||||
| @ -279,11 +283,23 @@ func MainClosePositionClearCache(mainId int, symbolType int) { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, item := range spotStrategyList { | ||||
| 			sonic.Unmarshal([]byte(item), &strategyDto) | ||||
|  | ||||
| 			if strategyDto.Id == mainId { | ||||
| 				if _, err := helper.DefaultRedis.LRem(spotStrategyKey, item); err != nil { | ||||
| 					logger.Errorf("id:%d 移除缓存失败,err:%v", mainId, err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		keyFutStop := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE) | ||||
| 		keyFutAddposition := fmt.Sprintf(rediskey.FuturesStopLossList, global.EXCHANGE_BINANCE) | ||||
| 		futAddpositionArray, _ := helper.DefaultRedis.GetAllList(keyFutStop) | ||||
| 		futStopArray, _ := helper.DefaultRedis.GetAllList(keyFutAddposition) | ||||
|  | ||||
| 		futStrategyKey := fmt.Sprintf(rediskey.StrategyFutOrderList, global.EXCHANGE_BINANCE) | ||||
| 		futStrategyList, _ := helper.DefaultRedis.GetAllList(futStrategyKey) | ||||
| 		var position AddPositionList | ||||
| 		var stop dto.StopLossRedisList | ||||
|  | ||||
| @ -306,6 +322,16 @@ func MainClosePositionClearCache(mainId int, symbolType int) { | ||||
| 				helper.DefaultRedis.LRem(keyFutStop, item) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, item := range futStrategyList { | ||||
| 			sonic.Unmarshal([]byte(item), &strategyDto) | ||||
|  | ||||
| 			if strategyDto.Id == mainId { | ||||
| 				if _, err := helper.DefaultRedis.LRem(futStrategyKey, item); err != nil { | ||||
| 					logger.Errorf("id:%d 移除缓存失败,err:%v", mainId, err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -28,7 +28,7 @@ func TestFutureJudge(t *testing.T) { | ||||
| 	// } | ||||
|  | ||||
| 	key := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE) | ||||
| 	item := `{"id":50,"apiId":49,"mainId":47,"pid":47,"symbol":"ADAUSDT","price":"0.5936","side":"SELL","num":"12","orderSn":"397913127842217984"}` | ||||
| 	item := `{"id":10,"apiId":49,"mainId":7,"pid":7,"symbol":"ADAUSDT","price":"0.6244","side":"BUY","num":"12","orderSn":"398690240274890752"}` | ||||
| 	reduceOrder := ReduceListItem{} | ||||
| 	futApi := FutRestApi{} | ||||
| 	setting, err := cacheservice.GetSystemSetting(db) | ||||
|  | ||||
| @ -555,50 +555,6 @@ func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ClosePositionB 平仓B对应的交易对 | ||||
| // bApiUserInfo B 账户api-user信息 | ||||
| // symbol 需要平仓的交易对 | ||||
| // closeType 平仓模式 ALL = 全平 reduceOnly = 只减仓 | ||||
| // func (e FutRestApi) ClosePositionB(orm *gorm.DB, bApiUserInfo *DbModels.LineApiUser, symbol string, closeType string) error { | ||||
| // 	if bApiUserInfo == nil { | ||||
| // 		return errors.New("缺失平仓账户信息") | ||||
| // 	} | ||||
| // 	if symbol == "" { | ||||
| // 		return errors.New("缺失平仓交易对信息") | ||||
| // 	} | ||||
| // 	risks, err := e.GetPositionV3(bApiUserInfo, symbol) | ||||
| // 	if err != nil { | ||||
| // 		return err | ||||
| // 	} | ||||
|  | ||||
| // 	for _, risk := range risks { | ||||
| // 		if risk.Symbol == strings.ToUpper(symbol) { | ||||
| // 			//持仓数量 | ||||
| // 			positionAmt, _ := decimal.NewFromString(risk.PositionAmt) | ||||
| // 			side := "BUY" | ||||
| // 			if positionAmt.GreaterThan(decimal.Zero) { | ||||
| // 				side = "SELL" | ||||
| // 			} | ||||
| // 			var closeAmt decimal.Decimal | ||||
| // 			if strings.ToUpper(closeType) == "ALL" { //全部平仓 | ||||
| // 				closeAmt = positionAmt | ||||
| // 			} else { | ||||
| // 				//如果是只减仓的话 数量=仓位数量*比例 | ||||
| // 				closeAmt = positionAmt.Mul(decimal.NewFromFloat(reduceOnlyRate)) | ||||
| // 			} | ||||
|  | ||||
| // 			err = e.ClosePosition(symbol, closeAmt, side, *bApiUserInfo, "MARKET", "", decimal.Zero) | ||||
| // 			if err != nil { | ||||
| // 				return err | ||||
| // 			} | ||||
| // 			orm.Model(&DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ?", bApiUserInfo.Id, symbol).Updates(map[string]interface{}{ | ||||
| // 				"status": "6", | ||||
| // 			}) | ||||
| // 		} | ||||
| // 	} | ||||
| // 	return nil | ||||
| // } | ||||
|  | ||||
| // 获取合约 持仓价格、数量 | ||||
| // symbol:交易对 | ||||
| // side:方向 | ||||
|  | ||||
| @ -277,9 +277,10 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes | ||||
| 	} else if ok { | ||||
| 		defer lock.Release() | ||||
| 		takeOrders := make([]DbModels.LinePreOrder, 0) | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN (1,2,4) AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { | ||||
| 		//只取消减仓单 止盈止损减仓成功后取消 | ||||
| 		if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN 4 AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { | ||||
| 			log.Error("查询止盈单失败") | ||||
| 			return false | ||||
| 			// return false | ||||
| 		} | ||||
|  | ||||
| 		var hasrecord bool | ||||
|  | ||||
| @ -145,6 +145,8 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
| 	} | ||||
| 	// rate := utility.StringAsFloat(orderExt.AddPositionVal) | ||||
|  | ||||
| 	//取消委托中的止盈止损 | ||||
| 	cancelTakeProfitByReduce(db, apiUserInfo, preOrder.Symbol, preOrder.MainId, preOrder.SymbolType) | ||||
| 	// 100%减仓 终止流程 | ||||
| 	if orderExt.AddPositionVal.Cmp(decimal.NewFromInt(100)) >= 0 { | ||||
| 		//缓存 | ||||
| @ -179,6 +181,36 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 减仓成功后取消止盈止损 | ||||
| func cancelTakeProfitByReduce(db *gorm.DB, apiUserInfo DbModels.LineApiUser, symbol string, mainId int, symbolType int) { | ||||
| 	orders, err := GetSymbolTakeAndStop(db, mainId, symbolType) | ||||
| 	futApi := FutRestApi{} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		logger.Errorf("mainId:%d 获取委托中的止盈止损失败:%v", mainId, err) | ||||
| 	} | ||||
|  | ||||
| 	orderSns := make([]string, 0) | ||||
|  | ||||
| 	for _, v := range orders { | ||||
| 		if v.OrderType != 1 && v.OrderType != 2 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		orderSns = append(orderSns, v.OrderSn) | ||||
| 	} | ||||
|  | ||||
| 	arr := utility.SplitSlice(orderSns, 10) | ||||
|  | ||||
| 	for _, v := range arr { | ||||
| 		err := futApi.CancelBatchFutOrder(apiUserInfo, symbol, v) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("mainId:%d 取消止盈止损失败:%v", mainId, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 减仓处理止盈止损 | ||||
| func FutTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder, apiUserInfo DbModels.LineApiUser, tradeSet models2.TradeSet, | ||||
| 	positionData positiondto.PositionDto, orderExt DbModels.LinePreOrderExt, manualTakeRatio, manualStopRatio decimal.Decimal) bool { | ||||
|  | ||||
| @ -134,7 +134,7 @@ func GetTotalLossAmount(db *gorm.DB, mainId int) (decimal.Decimal, error) { | ||||
| 	return totalLossAmountU, nil | ||||
| } | ||||
|  | ||||
| // 获取交易对的 委托中的止盈止损 | ||||
| // 获取交易对的 委托中的止盈止损、减仓单 | ||||
| // mainId 主单id | ||||
| // symbolType 交易对类型 | ||||
| func GetSymbolTakeAndStop(db *gorm.DB, mainId int, symbolType int) ([]models.LinePreOrder, error) { | ||||
|  | ||||
| @ -12,30 +12,42 @@ import ( | ||||
| 	models2 "go-admin/models" | ||||
| 	"go-admin/pkg/utility" | ||||
| 	"go-admin/services/cacheservice" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| type BinanceStrategyOrderService struct { | ||||
| 	service.Service | ||||
| 	Debug bool | ||||
| } | ||||
|  | ||||
| // 判断是否触发波段订单 | ||||
| func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) { | ||||
| 	//现货 | ||||
| 	orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType)) | ||||
| 	e.DoJudge(orderStrs, 1, exchangeType) | ||||
| 	orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType)) | ||||
| 	if len(orderStrs) > 0 { | ||||
| 		e.DoJudge(orderStrs, 1, exchangeType) | ||||
| 	} | ||||
|  | ||||
| 	//合约 | ||||
| 	futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType)) | ||||
| 	e.DoJudge(futOrdedrStrs, 2, exchangeType) | ||||
|  | ||||
| 	if len(futOrdedrStrs) > 0 { | ||||
| 		e.DoJudge(futOrdedrStrs, 2, exchangeType) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 处理订单 | ||||
| // 判断是否符合条件 | ||||
| func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) { | ||||
| 	db := GetDBConnection() | ||||
| 	// setting, _ := cacheservice.GetSystemSetting(db) | ||||
|  | ||||
| 	for _, orderStr := range orderStrs { | ||||
| 		var lockKey string | ||||
| 		orderItem := dto.StrategyOrderRedisList{} | ||||
| @ -51,7 +63,7 @@ func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int | ||||
| 			lockKey = rediskey.StrategyFutTriggerLock | ||||
| 		} | ||||
|  | ||||
| 		lock := helper.NewRedisLock(fmt.Sprintf(lockKey, orderItem.ApiId, orderItem.Symbol), 200, 50, 100*time.Millisecond) | ||||
| 		lock := helper.NewRedisLock(fmt.Sprintf(lockKey, orderItem.ApiId, orderItem.OrderSn), 60, 20, 300*time.Millisecond) | ||||
|  | ||||
| 		if ok, err := lock.AcquireWait(context.Background()); err != nil { | ||||
| 			e.Log.Debug("获取锁失败", err) | ||||
| @ -60,14 +72,14 @@ func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int | ||||
| 			defer lock.Release() | ||||
|  | ||||
| 			//判断是否符合条件 | ||||
| 			success, err := e.JudgeStrategy(orderItem, 1, exchangeType) | ||||
| 			success, err := e.JudgeStrategy(orderItem, symbolType, exchangeType) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err) | ||||
| 			} | ||||
|  | ||||
| 			if success { | ||||
| 				e.TriggerOrder(orderItem, symbolType) | ||||
| 			if e.Debug || success { | ||||
| 				e.TriggerOrder(db, orderStr, orderItem, symbolType) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -95,8 +107,16 @@ func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedis | ||||
| 	} | ||||
|  | ||||
| 	score := lastPrices[0].Score | ||||
| 	startPrice := utility.StrToDecimal(beforePrice) | ||||
| 	lastPrice := utility.StrToDecimal(lastPrices[0].Member.(string)) | ||||
| 	var startPrice decimal.Decimal | ||||
| 	var lastPrice decimal.Decimal | ||||
| 	startPriceArgs := strings.Split(beforePrice, ":") | ||||
| 	endPricecArgs := strings.Split(lastPrices[0].Member.(string), ":") | ||||
|  | ||||
| 	if len(startPriceArgs) == 0 || len(endPricecArgs) == 0 { | ||||
| 		return result, errors.New("获取交易对起止价格失败") | ||||
| 	} | ||||
| 	startPrice = utility.StrToDecimal(startPriceArgs[len(startPriceArgs)-1]) | ||||
| 	lastPrice = utility.StrToDecimal(endPricecArgs[len(endPricecArgs)-1]) | ||||
|  | ||||
| 	//时间差超过10s | ||||
| 	if (nowUtc-int64(score))/1000 > 10 { | ||||
| @ -108,31 +128,35 @@ func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedis | ||||
| 	} | ||||
|  | ||||
| 	percentag := lastPrice.Div(startPrice).Sub(decimal.NewFromInt(1)).Truncate(6) | ||||
|  | ||||
| 	logger.Infof("百分比:%s", percentag.Mul(decimal.NewFromInt(100)).String()) | ||||
| 	//价格没有变动 | ||||
| 	if percentag.Cmp(decimal.Zero) == 0 { | ||||
| 		return result, nil | ||||
| 	} | ||||
|  | ||||
| 	//满足条件 | ||||
| 	switch order.CompareType { | ||||
| 	case 1: | ||||
| 		result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0 | ||||
| 	case 2: | ||||
| 		result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0 | ||||
| 	case 5: | ||||
| 		result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0 | ||||
| 	default: | ||||
| 		return result, errors.New("没有对应的类型") | ||||
| 	switch { | ||||
| 	//涨价格大于0.5% 跌价格小于-0.5% | ||||
| 	case order.CompareType == 1 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, | ||||
| 		order.CompareType == 1 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: | ||||
| 		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0 | ||||
|  | ||||
| 	case order.CompareType == 2 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, | ||||
| 		order.CompareType == 2 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: | ||||
| 		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0 | ||||
|  | ||||
| 	case order.CompareType == 5 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0, | ||||
| 		order.CompareType == 5 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0: | ||||
| 		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0 | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // 触发委托单 | ||||
| func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisList, symbolType int) error { | ||||
| func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string, order dto.StrategyOrderRedisList, symbolType int) error { | ||||
| 	orders := make([]models.LinePreOrder, 0) | ||||
| 	if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =?", order.Id).Find(&orders).Error; err != nil { | ||||
| 	if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =? or id =?", order.Id, order.Id).Find(&orders).Error; err != nil { | ||||
| 		e.Log.Errorf("order_id:%d 获取委托单失败:%s", order.Id, err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
| @ -143,7 +167,16 @@ func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisL | ||||
| 		return errors.New("获取系统设置失败") | ||||
| 	} | ||||
|  | ||||
| 	tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, symbolType) | ||||
| 	var tradeSet models2.TradeSet | ||||
|  | ||||
| 	switch symbolType { | ||||
| 	case 1: | ||||
| 		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 0) | ||||
| 	case 2: | ||||
| 		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1) | ||||
| 	default: | ||||
| 		return errors.New("获取交易对行情失败,交易对类型错误") | ||||
| 	} | ||||
|  | ||||
| 	if tradeSet.Coin == "" { | ||||
| 		return errors.New("获取交易对行情失败") | ||||
| @ -155,7 +188,7 @@ func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisL | ||||
| 		return errors.New("最新成交价小于等于0") | ||||
| 	} | ||||
|  | ||||
| 	var mainOrder models.LinePreOrder | ||||
| 	mainOrder := models.LinePreOrder{} | ||||
|  | ||||
| 	for _, v := range orders { | ||||
| 		if v.MainId == 0 { | ||||
| @ -164,62 +197,307 @@ func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisL | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if mainOrder.Id == 0 { | ||||
| 		return errors.New("获取主单失败") | ||||
| 	} | ||||
|  | ||||
| 	GetOrderByPid(&mainOrder, orders, mainOrder.Id) | ||||
|  | ||||
| 	e.RecalculateOrder(tradeSet, &mainOrder, setting) | ||||
|  | ||||
| 	//事务保存 | ||||
| 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		if err1 := tx.Save(&mainOrder).Error; err1 != nil { | ||||
| 			return err1 | ||||
| 		} | ||||
|  | ||||
| 		for _, v := range mainOrder.Childs { | ||||
| 			if err1 := tx.Save(&v).Error; err1 != nil { | ||||
| 				return err1 | ||||
| 			} | ||||
|  | ||||
| 			for _, v2 := range v.Childs { | ||||
| 				if err1 := tx.Save(&v2).Error; err1 != nil { | ||||
| 					return err1 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("order_id:%d 波段触发保存委托单失败:%s", mainOrder.Id, err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	e.StrategyOrderPlace(db, cacheVal, mainOrder, tradeSet) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 策略触发订单 | ||||
| // cacheVal:缓存值 | ||||
| // mainOrder:主订单 | ||||
| // tradeSet:交易对行情 | ||||
| func (e *BinanceStrategyOrderService) StrategyOrderPlace(db *gorm.DB, cacheVal string, mainOrder models.LinePreOrder, tradeSet models2.TradeSet) { | ||||
| 	price := utility.StringToDecimal(mainOrder.Price).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 	num := utility.StringToDecimal(mainOrder.Num).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 	futApi := FutRestApi{} | ||||
| 	var key string | ||||
|  | ||||
| 	switch mainOrder.SymbolType { | ||||
| 	case 1: | ||||
| 		key = fmt.Sprintf(rediskey.StrategySpotOrderList, global.EXCHANGE_BINANCE) | ||||
| 	case 2: | ||||
| 		key = fmt.Sprintf(rediskey.StrategyFutOrderList, global.EXCHANGE_BINANCE) | ||||
| 	default: | ||||
| 		logger.Errorf("id:%d 交易对类型不存在:%d", mainOrder.Id, mainOrder.SymbolType) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	params := FutOrderPlace{ | ||||
| 		ApiId:            mainOrder.ApiId, | ||||
| 		Symbol:           mainOrder.Symbol, | ||||
| 		Side:             mainOrder.Site, | ||||
| 		OrderType:        mainOrder.MainOrderType, | ||||
| 		SideType:         mainOrder.MainOrderType, | ||||
| 		Price:            price, | ||||
| 		Quantity:         num, | ||||
| 		NewClientOrderId: mainOrder.OrderSn, | ||||
| 	} | ||||
|  | ||||
| 	if err := futApi.OrderPlaceLoop(db, params, 3); err != nil { | ||||
| 		logger.Error("下单失败", mainOrder.Symbol, " err:", err) | ||||
| 		if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil { | ||||
| 			logger.Error("删除redis 预下单失败:", err) | ||||
| 		} | ||||
|  | ||||
| 		err := db.Model(&models.LinePreOrder{}).Where("id =? and status='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error | ||||
|  | ||||
| 		if err != nil { | ||||
| 			logger.Error("更新预下单状态失败") | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 	} else { | ||||
| 		if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil { | ||||
| 			logger.Error("删除redis 预下单失败:", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("id =? ", mainOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil { | ||||
| 		logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1") | ||||
| 	} | ||||
|  | ||||
| 	if err := db.Model(&models.LinePreOrder{}).Where("id =? AND status ='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "1"}).Error; err != nil { | ||||
| 		logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 重新计算订单单价、数量 | ||||
| // tradeSet 交易对行情 | ||||
| // mainOrder 主订单 | ||||
| // setting 系统设置 | ||||
| func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet, mainOrder *models.LinePreOrder, setting models.LineSystemSetting) error { | ||||
| 	exts := make([]models.LinePreOrderExt, 0) | ||||
| 	if err := e.Orm.Model(models.LinePreOrderExt{}).Where("main_id =?", mainOrder.Id).Find(&exts).Error; err != nil { | ||||
| 	if err := e.Orm.Model(models.LinePreOrderExt{}).Where("main_order_id =?", mainOrder.Id).Find(&exts).Error; err != nil { | ||||
| 		return errors.New("获取拓展信息失败") | ||||
| 	} | ||||
|  | ||||
| 	var newPrice decimal.Decimal | ||||
| 	lastPrice := utility.StrToDecimal(tradeSet.LastPrice) | ||||
| 	rate := utility.StrToDecimal(mainOrder.Rate) | ||||
| 	newPrice := lastPrice.Mul(decimal.NewFromInt(1).Add(rate.Div(decimal.NewFromInt(100)).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 	newPrice = lastPrice.Mul(decimal.NewFromInt(1).Add(rate.Div(decimal.NewFromInt(100)).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 	buyPrice := utility.StrToDecimal(mainOrder.BuyPrice) | ||||
| 	totalNum := buyPrice.Div(newPrice).Truncate(int32(tradeSet.AmountDigit)) | ||||
|  | ||||
| 	mainOrder.SignPrice = lastPrice.String() | ||||
| 	mainOrder.Price = newPrice.String() | ||||
| 	mainOrder.Num = totalNum.String() | ||||
| 	remainQuantity := totalNum | ||||
| 	var totalLossAmount decimal.Decimal | ||||
| 	prePrice := lastPrice | ||||
|  | ||||
| 	for index := range mainOrder.Childs { | ||||
| 		//主单止盈 | ||||
| 		if mainOrder.Child[index].OrderType == 1 { | ||||
| 			var ext models.LinePreOrderExt | ||||
| 		var ext models.LinePreOrderExt | ||||
| 		extOrderId := mainOrder.Childs[index].Id | ||||
| 		takeStopArray := []int{1, 2} | ||||
|  | ||||
| 			for _, v := range exts { | ||||
| 				if v.OrderId == mainOrder.Child[index].Id { | ||||
| 					ext = v | ||||
| 					break | ||||
| 				} | ||||
| 		//止盈止损 ext拓展id为 pid | ||||
| 		if utility.ContainsInt(takeStopArray, mainOrder.Childs[index].OrderType) { | ||||
| 			extOrderId = mainOrder.Childs[index].Pid | ||||
| 		} | ||||
|  | ||||
| 		for _, v := range exts { | ||||
| 			if v.OrderId == extOrderId { | ||||
| 				ext = v | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if ext.Id <= 0 { | ||||
| 			logger.Errorf("子订单ext不存在 id:%d", mainOrder.Childs[index].Id) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		//主单止盈、止损 | ||||
| 		if mainOrder.Childs[index].Pid == mainOrder.Childs[index].MainId && (mainOrder.Childs[index].OrderType == 1 || mainOrder.Childs[index].OrderType == 2) { | ||||
| 			var percent decimal.Decimal | ||||
|  | ||||
| 			switch { | ||||
| 			// 加价 | ||||
| 			case mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "SELL": | ||||
| 				percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio) | ||||
| 				//减价 | ||||
| 			case mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "SELL": | ||||
| 				percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio) | ||||
| 			} | ||||
|  | ||||
| 			if ext.Id > 0 { | ||||
| 				var percent decimal.Decimal | ||||
| 				//多 | ||||
| 				if mainOrder.Site == "BUY" { | ||||
| 					percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio) | ||||
| 				} else { | ||||
| 					percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio) | ||||
| 				} | ||||
| 			childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 			mainOrder.Childs[index].Price = childPrice.String() | ||||
| 			mainOrder.Childs[index].Num = totalNum.String() | ||||
|  | ||||
| 				childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 				mainOrder.Child[index].Price = childPrice.String() | ||||
| 			} | ||||
| 		} else { | ||||
| 			//todo 重新计算 | ||||
| 			lastNum := remainQuantity | ||||
|  | ||||
| 			//过期时间 | ||||
| 			if ext.ExpirateHour <= 0 { | ||||
| 				mainOrder.Childs[index].ExpireTime = time.Now().AddDate(10, 0, 0) | ||||
| 			} else { | ||||
| 				mainOrder.Childs[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour)) | ||||
| 			} | ||||
|  | ||||
| 			switch { | ||||
| 			//加仓单 | ||||
| 			case mainOrder.Childs[index].OrderType == 1 && mainOrder.Childs[index].OrderCategory == 3: | ||||
| 				var percentage decimal.Decimal | ||||
|  | ||||
| 				if mainOrder.Site == "BUY" { | ||||
| 					percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} else { | ||||
| 					percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} | ||||
|  | ||||
| 				dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 				mainOrder.Childs[index].Price = dataPrice.String() | ||||
|  | ||||
| 				priceDiff := dataPrice.Sub(prePrice).Abs() | ||||
| 				totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff)) | ||||
|  | ||||
| 				if ext.AddPositionType == 1 { | ||||
| 					buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2) | ||||
| 					mainOrder.Childs[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() | ||||
|  | ||||
| 				} else { | ||||
| 					mainOrder.Childs[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String() | ||||
| 				} | ||||
|  | ||||
| 				//加库存 | ||||
| 				lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Childs[index].Num)) | ||||
| 				// 计算子订单 | ||||
| 				if len(mainOrder.Childs[index].Childs) > 0 { | ||||
| 					calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false) | ||||
| 				} | ||||
| 				//覆盖最近的订单价 | ||||
| 				prePrice = dataPrice | ||||
| 				//减仓单 | ||||
| 			case mainOrder.Childs[index].OrderType == 4: | ||||
| 				percentage := decimal.NewFromInt(1) | ||||
|  | ||||
| 				if mainOrder.Site == "BUY" && ext.PriceRatio.Cmp(decimal.Zero) > 0 { | ||||
| 					percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} else if ext.PriceRatio.Cmp(decimal.Zero) > 0 { | ||||
| 					percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100))) | ||||
| 				} | ||||
|  | ||||
| 				dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 				mainOrder.Childs[index].Price = dataPrice.String() | ||||
| 				priceDiff := dataPrice.Sub(prePrice).Abs() | ||||
| 				totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff)) | ||||
|  | ||||
| 				//百分比减仓 | ||||
| 				if ext.AddPositionType == 1 { | ||||
| 					mainOrder.Childs[index].Num = lastNum.Mul(ext.AddPositionVal.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String() | ||||
|  | ||||
| 				} else { | ||||
| 					logger.Error("减仓不能是固定数值") | ||||
| 				} | ||||
|  | ||||
| 				//减库存 | ||||
| 				lastNum = lastNum.Sub(utility.StrToDecimal(mainOrder.Childs[index].Num)) | ||||
| 				// 计算子订单 | ||||
| 				if len(mainOrder.Childs[index].Childs) > 0 { | ||||
| 					calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false) | ||||
| 				} | ||||
| 				//覆盖最近的订单价 | ||||
| 				prePrice = dataPrice | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 计算子订单信息 | ||||
| // isTpTp 是否是止盈后止损止盈 | ||||
| // totalLossAmount 累计亏损金额 | ||||
| func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeSet, ext models.LinePreOrderExt, lastNum decimal.Decimal, price decimal.Decimal, totalLossAmount decimal.Decimal, isTpTp bool) error { | ||||
| 	for index := range *orders { | ||||
| 		orderQuantity := lastNum.Truncate(int32(tradeSet.AmountDigit)) | ||||
| 		percentage := decimal.NewFromInt(1) | ||||
| 		var addPercentage decimal.Decimal | ||||
|  | ||||
| 		switch { | ||||
| 		//止盈 | ||||
| 		case !isTpTp && (*orders)[index].OrderType == 1: | ||||
| 			addPercentage = ext.TakeProfitRatio | ||||
| 			if ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 { | ||||
| 				orderQuantity = lastNum.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) | ||||
| 			} | ||||
| 		//止损 | ||||
| 		case !isTpTp && (*orders)[index].OrderType == 2: | ||||
| 			addPercentage = ext.StopLossRatio | ||||
| 		//止盈后止盈 | ||||
| 		case isTpTp && (*orders)[index].OrderType == 1: | ||||
| 			addPercentage = ext.TpTpPriceRatio | ||||
| 		//止盈后止损 | ||||
| 		case isTpTp && (*orders)[index].OrderType == 2: | ||||
| 			addPercentage = ext.TpSlPriceRatio | ||||
| 		} | ||||
|  | ||||
| 		switch { | ||||
| 		//做多止盈、做空止损 | ||||
| 		case (*orders)[index].OrderType == 1 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 2 && (*orders)[index].Site == "BUY": | ||||
| 			percentage = decimal.NewFromInt(100).Add(addPercentage) | ||||
|  | ||||
| 			//做多止损、做空止盈 | ||||
| 		case (*orders)[index].OrderType == 2 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 1 && (*orders)[index].Site == "BUY": | ||||
| 			percentage = decimal.NewFromInt(100).Sub(addPercentage) | ||||
| 		} | ||||
|  | ||||
| 		//止盈亏损回本百分比 | ||||
| 		if (*orders)[index].OrderType == 1 && totalLossAmount.Cmp(decimal.Zero) > 0 { | ||||
| 			lossPercent := totalLossAmount.Div(lastNum).Mul(decimal.NewFromInt(100)).Truncate(2) | ||||
| 			percentage = percentage.Add(lossPercent) | ||||
| 		} | ||||
|  | ||||
| 		if percentage.Cmp(decimal.Zero) > 0 { | ||||
| 			percentage = percentage.Div(decimal.NewFromInt(100)) | ||||
| 		} | ||||
|  | ||||
| 		orderPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)) | ||||
| 		(*orders)[index].Price = orderPrice.String() | ||||
| 		(*orders)[index].Num = orderQuantity.String() | ||||
| 		lastOrderQuantity := lastNum.Sub(orderQuantity).Truncate(int32(tradeSet.AmountDigit)) | ||||
|  | ||||
| 		//止盈后止盈、止盈后止损 | ||||
| 		if len((*orders)[index].Childs) > 0 && lastOrderQuantity.Cmp(decimal.Zero) > 0 { | ||||
| 			calculateChildOrder(&(*orders)[index].Childs, tradeSet, ext, lastOrderQuantity, orderPrice, decimal.Zero, true) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 递归订单树 | ||||
| func GetOrderByPid(order *models.LinePreOrder, orders []models.LinePreOrder, pid int) { | ||||
| 	for _, v := range orders { | ||||
|  | ||||
							
								
								
									
										26
									
								
								services/binanceservice/strategy_order_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								services/binanceservice/strategy_order_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| package binanceservice | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/helper" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk" | ||||
| 	"gorm.io/driver/mysql" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // 测试策略 触发单 | ||||
| func TestTriggerOrder(t *testing.T) { | ||||
| 	service := BinanceStrategyOrderService{} | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	helper.InitDefaultRedis("127.0.0.1:6379", "", 2) | ||||
| 	helper.InitLockRedisConn("127.0.0.1:6379", "", "2") | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
|  | ||||
| 	service.Orm = db | ||||
| 	service.Debug = true | ||||
|  | ||||
| 	service.TriggerStrategyOrder(global.EXCHANGE_BINANCE) | ||||
| } | ||||
| @ -66,9 +66,11 @@ func GetConfigCacheByKey(db *gorm.DB, key string) models.SysConfig { | ||||
| // 获取缓存交易对 | ||||
| // symbolType 0-现货 1-合约 | ||||
| func GetTradeSet(exchangeType string, symbol string, symbolType int) (models2.TradeSet, error) { | ||||
| 	// 定义返回结果和val变量 | ||||
| 	result := models2.TradeSet{} | ||||
| 	val := "" | ||||
|  | ||||
| 	// 根据交易对类型选择不同的key | ||||
| 	switch symbolType { | ||||
| 	case 0: | ||||
| 		key := fmt.Sprintf(global.TICKER_SPOT, exchangeType, symbol) | ||||
| @ -78,14 +80,17 @@ func GetTradeSet(exchangeType string, symbol string, symbolType int) (models2.Tr | ||||
| 		val, _ = helper.DefaultRedis.GetString(key) | ||||
| 	} | ||||
|  | ||||
| 	// 如果val不为空,则解析val为TradeSet结构体 | ||||
| 	if val != "" { | ||||
| 		if err := sonic.Unmarshal([]byte(val), &result); err != nil { | ||||
| 			return result, err | ||||
| 		} | ||||
| 	} else { | ||||
| 		// 如果val为空,则返回错误信息 | ||||
| 		return result, errors.New("未找到交易对信息") | ||||
| 	} | ||||
|  | ||||
| 	// 返回结果 | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -162,14 +162,19 @@ func handleTickerAllMessage(msg []byte) { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//行情存储时间 | ||||
| 		lastUtc := utcTime - 1000*60*60 | ||||
| 		if _, err := helper.DefaultRedis.RemoveBeforeScore(lastPriceKey, float64(lastUtc)); err != nil { | ||||
| 			log.Errorf("移除 合约交易对:%s %d之前的最新成交价失败,err:%v", symbol, lastUtc, err) | ||||
| 		} | ||||
| 		val, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice) | ||||
|  | ||||
| 		if err := helper.DefaultRedis.AddSortSet(lastPriceKey, float64(utcTime), lastPrice.String()); err != nil { | ||||
| 			log.Errorf("添加 合约交易对:%s %d之前的最新成交价失败,err:%v", symbol, lastUtc, err) | ||||
| 		if utility.ContainsStr(val, symbol) { | ||||
| 			//行情存储时间 | ||||
| 			lastUtc := utcTime - 1000*60*60 | ||||
| 			content := fmt.Sprintf("%d:%s", utcTime, lastPrice.String()) | ||||
| 			if _, err := helper.DefaultRedis.RemoveBeforeScore(lastPriceKey, float64(lastUtc)); err != nil { | ||||
| 				log.Errorf("移除 合约交易对:%s %d之前的最新成交价失败,err:%v", symbol, lastUtc, err) | ||||
| 			} | ||||
|  | ||||
| 			if err := helper.DefaultRedis.AddSortSet(lastPriceKey, float64(utcTime), content); err != nil { | ||||
| 				log.Errorf("添加 合约交易对:%s %d之前的最新成交价失败,err:%v", symbol, lastUtc, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -250,15 +250,20 @@ func handleTickerMessage(msg []byte) { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//行情存储时间 | ||||
| 		lastUtc := utcTime - 1000*60*60 | ||||
| 		val, _ := helper.DefaultRedis.GetAllList(rediskey.CacheSymbolLastPrice) | ||||
|  | ||||
| 		if _, err := helper.DefaultRedis.RemoveBeforeScore(lastPriceKey, float64(lastUtc)); err != nil { | ||||
| 			log.Errorf("移除 现货交易对:%s %d之前的最新成交价失败,err:%v", symbolName, lastUtc, err) | ||||
| 		} | ||||
| 		if utility.ContainsStr(val, symbolName) { | ||||
| 			//行情存储时间 | ||||
| 			lastUtc := utcTime - 1000*60*60 | ||||
| 			content := fmt.Sprintf("%d:%s", utcTime, lastPrice.String()) | ||||
|  | ||||
| 		if err := helper.DefaultRedis.AddSortSet(lastPriceKey, float64(utcTime), lastPrice.String()); err != nil { | ||||
| 			log.Errorf("添加 现货交易对:%s %d之前的最新成交价失败,err:%v", symbolName, lastUtc, err) | ||||
| 			if _, err := helper.DefaultRedis.RemoveBeforeScore(lastPriceKey, float64(lastUtc)); err != nil { | ||||
| 				log.Errorf("移除 现货交易对:%s %d之前的最新成交价失败,err:%v", symbolName, lastUtc, err) | ||||
| 			} | ||||
|  | ||||
| 			if err := helper.DefaultRedis.AddSortSet(lastPriceKey, float64(utcTime), content); err != nil { | ||||
| 				log.Errorf("添加 现货交易对:%s %d之前的最新成交价失败,err:%v", symbolName, lastUtc, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user