1、波段触发修改
This commit is contained in:
@ -14,7 +14,7 @@ type LineStrategyTemplate struct {
|
|||||||
Percentag decimal.Decimal `json:"percentag" gorm:"type:decimal(10,2);comment:涨跌点数"`
|
Percentag decimal.Decimal `json:"percentag" gorm:"type:decimal(10,2);comment:涨跌点数"`
|
||||||
CompareType int `json:"compareType" gorm:"type:tinyint;comment:比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "`
|
CompareType int `json:"compareType" gorm:"type:tinyint;comment:比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "`
|
||||||
TimeSlotStart int `json:"timeSlotStart" gorm:"type:int;comment:时间段开始(分)"`
|
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.ModelTime
|
||||||
models.ControlBy
|
models.ControlBy
|
||||||
}
|
}
|
||||||
|
|||||||
@ -197,7 +197,7 @@ type LineAddPreOrderReq struct {
|
|||||||
Site string `json:"site" ` //购买方向
|
Site string `json:"site" ` //购买方向
|
||||||
BuyPrice string `json:"buy_price" vd:"$>0"` //购买金额 U
|
BuyPrice string `json:"buy_price" vd:"$>0"` //购买金额 U
|
||||||
PricePattern string `json:"price_pattern"` //价格模式
|
PricePattern string `json:"price_pattern"` //价格模式
|
||||||
Price string `json:"price" vd:"$>0"` //下单价百分比
|
Price string `json:"price"` //下单价百分比
|
||||||
Profit string `json:"profit" vd:"$>0"` //止盈价
|
Profit string `json:"profit" vd:"$>0"` //止盈价
|
||||||
ProfitNumRatio decimal.Decimal `json:"profit_num_ratio"` //止盈数量百分比
|
ProfitNumRatio decimal.Decimal `json:"profit_num_ratio"` //止盈数量百分比
|
||||||
ProfitTpTpPriceRatio decimal.Decimal `json:"profit_tp_tp_price_ratio"` //止盈后止盈价百分比
|
ProfitTpTpPriceRatio decimal.Decimal `json:"profit_tp_tp_price_ratio"` //止盈后止盈价百分比
|
||||||
|
|||||||
@ -41,8 +41,8 @@ type LineStrategyTemplateInsertReq struct {
|
|||||||
Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"`
|
Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"`
|
||||||
Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"`
|
Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"`
|
||||||
CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "`
|
CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "`
|
||||||
TimeSlotStart int `json:"timeSlotStart" comment:"时间段开始(分)"`
|
TimeSlotStart int `json:"timeSlotStart" comment:"时间段(分)"`
|
||||||
TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"`
|
// TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"`
|
||||||
common.ControlBy
|
common.ControlBy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,13 +63,13 @@ func (s *LineStrategyTemplateInsertReq) Valid() error {
|
|||||||
return errors.New("比较类型不合法")
|
return errors.New("比较类型不合法")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TimeSlotStart < 0 || s.TimeSlotEnd > 59 {
|
if s.TimeSlotStart < 0 {
|
||||||
return errors.New("时间段不合法")
|
return errors.New("时间段不合法")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TimeSlotEnd < s.TimeSlotStart {
|
// if s.TimeSlotEnd < s.TimeSlotStart {
|
||||||
return errors.New("时间段不合法")
|
// return errors.New("时间段不合法")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ func (s *LineStrategyTemplateInsertReq) Generate(model *models.LineStrategyTempl
|
|||||||
model.Percentag = s.Percentag
|
model.Percentag = s.Percentag
|
||||||
model.CompareType = s.CompareType
|
model.CompareType = s.CompareType
|
||||||
model.TimeSlotStart = s.TimeSlotStart
|
model.TimeSlotStart = s.TimeSlotStart
|
||||||
model.TimeSlotEnd = s.TimeSlotEnd
|
// model.TimeSlotEnd = s.TimeSlotEnd
|
||||||
model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
|
model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +97,8 @@ type LineStrategyTemplateUpdateReq struct {
|
|||||||
Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"`
|
Direction int `json:"direction" comment:"涨跌方向 1-涨 2-跌"`
|
||||||
Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"`
|
Percentag decimal.Decimal `json:"percentag" comment:"涨跌点数"`
|
||||||
CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "`
|
CompareType int `json:"compareType" comment:"比较类型 1-大于 2-大于等于 3-小于 4-小于等于 5等于 "`
|
||||||
TimeSlotStart int `json:"timeSlotStart" comment:"时间段开始(分)"`
|
TimeSlotStart int `json:"timeSlotStart" comment:"时间段(分)"`
|
||||||
TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"`
|
// TimeSlotEnd int `json:"timeSlotEnd" comment:"时间断截至(分)"`
|
||||||
common.ControlBy
|
common.ControlBy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +120,13 @@ func (s *LineStrategyTemplateUpdateReq) Valid() error {
|
|||||||
return errors.New("比较类型不合法")
|
return errors.New("比较类型不合法")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TimeSlotStart < 0 || s.TimeSlotEnd > 59 {
|
if s.TimeSlotStart < 0 {
|
||||||
return errors.New("时间段不合法")
|
return errors.New("时间段不合法")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TimeSlotEnd < s.TimeSlotStart {
|
// if s.TimeSlotEnd < s.TimeSlotStart {
|
||||||
return errors.New("时间段不合法")
|
// return errors.New("时间段不合法")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ func (s *LineStrategyTemplateUpdateReq) Generate(model *models.LineStrategyTempl
|
|||||||
model.Percentag = s.Percentag
|
model.Percentag = s.Percentag
|
||||||
model.CompareType = s.CompareType
|
model.CompareType = s.CompareType
|
||||||
model.TimeSlotStart = s.TimeSlotStart
|
model.TimeSlotStart = s.TimeSlotStart
|
||||||
model.TimeSlotEnd = s.TimeSlotEnd
|
// model.TimeSlotEnd = s.TimeSlotEnd
|
||||||
model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
|
model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -279,6 +279,8 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi
|
|||||||
futReduceVal, _ := helper.DefaultRedis.GetAllList(spotAddPositionKey)
|
futReduceVal, _ := helper.DefaultRedis.GetAllList(spotAddPositionKey)
|
||||||
spotAddPositionVal, _ := helper.DefaultRedis.GetAllList(futReduceKey)
|
spotAddPositionVal, _ := helper.DefaultRedis.GetAllList(futReduceKey)
|
||||||
spotReduceVal, _ := helper.DefaultRedis.GetAllList(spotReduceKey)
|
spotReduceVal, _ := helper.DefaultRedis.GetAllList(spotReduceKey)
|
||||||
|
spotStrategyMap := e.GetStrategyOrderListMap(1)
|
||||||
|
futStrategyMap := e.GetStrategyOrderListMap(2)
|
||||||
|
|
||||||
for _, v := range futAddPositionVal {
|
for _, v := range futAddPositionVal {
|
||||||
sonic.Unmarshal([]byte(v), &addPosition)
|
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 {
|
if val, ok := spotRedces[order.Id]; ok {
|
||||||
helper.DefaultRedis.LRem(spotReduceKey, val)
|
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()
|
redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String()
|
||||||
marshal, _ := sonic.Marshal(redisList)
|
marshal, _ := sonic.Marshal(redisList)
|
||||||
if order.SymbolType == 1 {
|
if order.SymbolType == 1 {
|
||||||
@ -349,6 +357,13 @@ func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermi
|
|||||||
helper.DefaultRedis.LRem(listKey, string(marshal))
|
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)
|
removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType)
|
||||||
|
|
||||||
@ -908,7 +923,6 @@ func saveOrderCache(req *dto.LineAddPreOrderReq, AddOrder models.LinePreOrder, l
|
|||||||
list.Percentag = linestrategyTemplate.Percentag
|
list.Percentag = linestrategyTemplate.Percentag
|
||||||
list.CompareType = linestrategyTemplate.CompareType
|
list.CompareType = linestrategyTemplate.CompareType
|
||||||
list.TimeSlotStart = linestrategyTemplate.TimeSlotStart
|
list.TimeSlotStart = linestrategyTemplate.TimeSlotStart
|
||||||
list.TimeSlotEnd = linestrategyTemplate.TimeSlotEnd
|
|
||||||
|
|
||||||
marshal, _ = sonic.Marshal(&list)
|
marshal, _ = sonic.Marshal(&list)
|
||||||
if AddOrder.SymbolType == global.SYMBOL_SPOT {
|
if AddOrder.SymbolType == global.SYMBOL_SPOT {
|
||||||
@ -1995,6 +2009,8 @@ func (e *LinePreOrder) ClearUnTriggered() error {
|
|||||||
var orderLists []models.LinePreOrder
|
var orderLists []models.LinePreOrder
|
||||||
positions := map[string]positiondto.LinePreOrderPositioinDelReq{}
|
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{})
|
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 {
|
for _, order := range orderLists {
|
||||||
redisList := dto.PreOrderRedisList{
|
redisList := dto.PreOrderRedisList{
|
||||||
@ -2006,17 +2022,35 @@ func (e *LinePreOrder) ClearUnTriggered() error {
|
|||||||
OrderSn: order.OrderSn,
|
OrderSn: order.OrderSn,
|
||||||
QuoteSymbol: order.QuoteSymbol,
|
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()
|
redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String()
|
||||||
marshal, _ := sonic.Marshal(redisList)
|
marshal, _ := sonic.Marshal(redisList)
|
||||||
|
|
||||||
if order.SymbolType == 1 {
|
if order.SymbolType == 1 {
|
||||||
key := fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType)
|
key := fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType)
|
||||||
|
|
||||||
helper.DefaultRedis.LRem(key, string(marshal))
|
helper.DefaultRedis.LRem(key, string(marshal))
|
||||||
} else {
|
} else {
|
||||||
key := fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType)
|
key := fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType)
|
||||||
|
|
||||||
helper.DefaultRedis.LRem(key, string(marshal))
|
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)
|
removeSymbolKey := fmt.Sprintf("%v_%s_%s_%s_%v", order.ApiId, order.ExchangeType, order.Symbol, order.Site, order.SymbolType)
|
||||||
|
|
||||||
@ -2056,6 +2090,60 @@ func (e *LinePreOrder) ClearUnTriggered() error {
|
|||||||
return nil
|
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) {
|
func (e *LinePreOrder) QueryOrder(req *dto.QueryOrderReq) (res interface{}, err error) {
|
||||||
var apiUserInfo models.LineApiUser
|
var apiUserInfo models.LineApiUser
|
||||||
e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo)
|
e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package jobs
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
models2 "go-admin/app/jobs/models"
|
models2 "go-admin/app/jobs/models"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/go-admin-team/go-admin-core/logger"
|
log "github.com/go-admin-team/go-admin-core/logger"
|
||||||
@ -43,7 +44,10 @@ type ExecJob struct {
|
|||||||
func (e *ExecJob) Run() {
|
func (e *ExecJob) Run() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
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])
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
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}
|
SpotTrigger = "spot_trigger_lock:%v_%s" //现货触发 {apiuserid|symbol}
|
||||||
FutTrigger = "fut_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"
|
StrategySpotTriggerLock = "strategy_spot_trigger_l:%v_%s"
|
||||||
//波段合约触发{apiuserid|symbol}
|
//波段合约触发{apiuserid|ordersn}
|
||||||
StrategyFutTriggerLock = "strategy_fut_trigger_l:%v_%s"
|
StrategyFutTriggerLock = "strategy_fut_trigger_l:%v_%s"
|
||||||
|
|
||||||
//减仓波段合约触发 {apiuserid|symbol}
|
//减仓波段合约触发 {apiuserid|symbol}
|
||||||
|
|||||||
@ -106,6 +106,9 @@ func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) {
|
|||||||
baseInterval = time.Second
|
baseInterval = time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if baseInterval <= 0 {
|
||||||
|
baseInterval = time.Millisecond * 100 // 至少 100ms
|
||||||
|
}
|
||||||
// 随机退避
|
// 随机退避
|
||||||
retryInterval := time.Duration(rand.Int63n(int64(baseInterval))) // 随机退避
|
retryInterval := time.Duration(rand.Int63n(int64(baseInterval))) // 随机退避
|
||||||
if retryInterval < time.Millisecond*100 {
|
if retryInterval < time.Millisecond*100 {
|
||||||
@ -129,6 +132,13 @@ func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) {
|
|||||||
return false, ErrFailed
|
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 释放锁
|
// Release 释放锁
|
||||||
func (rl *RedisLock) Release() (bool, error) {
|
func (rl *RedisLock) Release() (bool, error) {
|
||||||
return rl.releaseCtx(context.Background())
|
return rl.releaseCtx(context.Background())
|
||||||
|
|||||||
@ -24,22 +24,29 @@ import (
|
|||||||
|
|
||||||
type BinanceStrategyOrderService struct {
|
type BinanceStrategyOrderService struct {
|
||||||
service.Service
|
service.Service
|
||||||
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否触发波段订单
|
// 判断是否触发波段订单
|
||||||
func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) {
|
func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) {
|
||||||
//现货
|
//现货
|
||||||
orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType))
|
orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType))
|
||||||
e.DoJudge(orderStrs, 1, exchangeType)
|
if len(orderStrs) > 0 {
|
||||||
|
e.DoJudge(orderStrs, 1, exchangeType)
|
||||||
|
}
|
||||||
|
|
||||||
//合约
|
//合约
|
||||||
futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, 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) {
|
func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) {
|
||||||
db := GetDBConnection()
|
db := GetDBConnection()
|
||||||
|
// setting, _ := cacheservice.GetSystemSetting(db)
|
||||||
|
|
||||||
for _, orderStr := range orderStrs {
|
for _, orderStr := range orderStrs {
|
||||||
var lockKey string
|
var lockKey string
|
||||||
@ -56,7 +63,7 @@ func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int
|
|||||||
lockKey = rediskey.StrategyFutTriggerLock
|
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 {
|
if ok, err := lock.AcquireWait(context.Background()); err != nil {
|
||||||
e.Log.Debug("获取锁失败", err)
|
e.Log.Debug("获取锁失败", err)
|
||||||
@ -65,13 +72,13 @@ func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int
|
|||||||
defer lock.Release()
|
defer lock.Release()
|
||||||
|
|
||||||
//判断是否符合条件
|
//判断是否符合条件
|
||||||
success, err := e.JudgeStrategy(orderItem, 1, exchangeType)
|
success, err := e.JudgeStrategy(orderItem, symbolType, exchangeType)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err)
|
e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if success {
|
if e.Debug || success {
|
||||||
e.TriggerOrder(db, orderStr, orderItem, symbolType)
|
e.TriggerOrder(db, orderStr, orderItem, symbolType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,22 +128,26 @@ func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedis
|
|||||||
}
|
}
|
||||||
|
|
||||||
percentag := lastPrice.Div(startPrice).Sub(decimal.NewFromInt(1)).Truncate(6)
|
percentag := lastPrice.Div(startPrice).Sub(decimal.NewFromInt(1)).Truncate(6)
|
||||||
fmt.Printf("百分比:%s", percentag.Mul(decimal.NewFromInt(100)).String())
|
logger.Infof("百分比:%s", percentag.Mul(decimal.NewFromInt(100)).String())
|
||||||
//价格没有变动
|
//价格没有变动
|
||||||
if percentag.Cmp(decimal.Zero) == 0 {
|
if percentag.Cmp(decimal.Zero) == 0 {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//满足条件
|
//满足条件
|
||||||
switch order.CompareType {
|
switch {
|
||||||
case 1:
|
//涨价格大于0.5% 跌价格小于-0.5%
|
||||||
result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0
|
case order.CompareType == 1 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0,
|
||||||
case 2:
|
order.CompareType == 1 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0:
|
||||||
result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0
|
result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0
|
||||||
case 5:
|
|
||||||
result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0
|
case order.CompareType == 2 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0,
|
||||||
default:
|
order.CompareType == 2 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0:
|
||||||
return result, errors.New("没有对应的类型")
|
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
|
return result, nil
|
||||||
@ -145,7 +156,7 @@ func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedis
|
|||||||
// 触发委托单
|
// 触发委托单
|
||||||
func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string, 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)
|
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())
|
e.Log.Errorf("order_id:%d 获取委托单失败:%s", order.Id, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -156,7 +167,16 @@ func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string,
|
|||||||
return errors.New("获取系统设置失败")
|
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 == "" {
|
if tradeSet.Coin == "" {
|
||||||
return errors.New("获取交易对行情失败")
|
return errors.New("获取交易对行情失败")
|
||||||
@ -177,11 +197,36 @@ func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mainOrder.Id == 0 {
|
||||||
|
return errors.New("获取主单失败")
|
||||||
|
}
|
||||||
|
|
||||||
GetOrderByPid(&mainOrder, orders, mainOrder.Id)
|
GetOrderByPid(&mainOrder, orders, mainOrder.Id)
|
||||||
|
|
||||||
e.RecalculateOrder(tradeSet, &mainOrder, setting)
|
e.RecalculateOrder(tradeSet, &mainOrder, setting)
|
||||||
|
|
||||||
if err := e.Orm.Save(&mainOrder).Error; err != nil {
|
//事务保存
|
||||||
|
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())
|
e.Log.Errorf("order_id:%d 波段触发保存委托单失败:%s", mainOrder.Id, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -224,16 +269,29 @@ func (e *BinanceStrategyOrderService) StrategyOrderPlace(db *gorm.DB, cacheVal s
|
|||||||
|
|
||||||
if err := futApi.OrderPlaceLoop(db, params, 3); err != nil {
|
if err := futApi.OrderPlaceLoop(db, params, 3); err != nil {
|
||||||
logger.Error("下单失败", mainOrder.Symbol, " err:", err)
|
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
|
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 {
|
if err != nil {
|
||||||
logger.Error("更新预下单状态失败")
|
logger.Error("更新预下单状态失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
} else {
|
||||||
if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil {
|
if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil {
|
||||||
logger.Error("删除redis 预下单失败:", err)
|
logger.Error("删除redis 预下单失败:", err)
|
||||||
}
|
}
|
||||||
return
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,9 +305,10 @@ func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet
|
|||||||
return errors.New("获取拓展信息失败")
|
return errors.New("获取拓展信息失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newPrice decimal.Decimal
|
||||||
lastPrice := utility.StrToDecimal(tradeSet.LastPrice)
|
lastPrice := utility.StrToDecimal(tradeSet.LastPrice)
|
||||||
rate := utility.StrToDecimal(mainOrder.Rate)
|
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)
|
buyPrice := utility.StrToDecimal(mainOrder.BuyPrice)
|
||||||
totalNum := buyPrice.Div(newPrice).Truncate(int32(tradeSet.AmountDigit))
|
totalNum := buyPrice.Div(newPrice).Truncate(int32(tradeSet.AmountDigit))
|
||||||
|
|
||||||
@ -257,51 +316,60 @@ func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet
|
|||||||
mainOrder.Price = newPrice.String()
|
mainOrder.Price = newPrice.String()
|
||||||
mainOrder.Num = totalNum.String()
|
mainOrder.Num = totalNum.String()
|
||||||
remainQuantity := totalNum
|
remainQuantity := totalNum
|
||||||
|
var totalLossAmount decimal.Decimal
|
||||||
|
prePrice := lastPrice
|
||||||
|
|
||||||
for index := range mainOrder.Childs {
|
for index := range mainOrder.Childs {
|
||||||
var ext models.LinePreOrderExt
|
var ext models.LinePreOrderExt
|
||||||
|
extOrderId := mainOrder.Childs[index].Id
|
||||||
|
takeStopArray := []int{1, 2}
|
||||||
|
|
||||||
|
//止盈止损 ext拓展id为 pid
|
||||||
|
if utility.ContainsInt(takeStopArray, mainOrder.Childs[index].OrderType) {
|
||||||
|
extOrderId = mainOrder.Childs[index].Pid
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range exts {
|
for _, v := range exts {
|
||||||
if v.OrderId == mainOrder.Child[index].Id {
|
if v.OrderId == extOrderId {
|
||||||
ext = v
|
ext = v
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ext.Id <= 0 {
|
if ext.Id <= 0 {
|
||||||
logger.Errorf("子订单ext不存在 id:%d", mainOrder.Child[index].Id)
|
logger.Errorf("子订单ext不存在 id:%d", mainOrder.Childs[index].Id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//主单止盈、止损
|
//主单止盈、止损
|
||||||
if mainOrder.Child[index].Pid == mainOrder.Child[index].MainId && (mainOrder.Child[index].OrderType == 1 || mainOrder.Child[index].OrderType == 2) {
|
if mainOrder.Childs[index].Pid == mainOrder.Childs[index].MainId && (mainOrder.Childs[index].OrderType == 1 || mainOrder.Childs[index].OrderType == 2) {
|
||||||
var percent decimal.Decimal
|
var percent decimal.Decimal
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// 加价
|
// 加价
|
||||||
case mainOrder.Child[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Child[index].OrderType == 2 && mainOrder.Site == "SELL":
|
case mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "SELL":
|
||||||
percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio)
|
percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio)
|
||||||
//减价
|
//减价
|
||||||
case mainOrder.Child[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Child[index].OrderType == 1 && mainOrder.Site == "SELL":
|
case mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "SELL":
|
||||||
percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio)
|
percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio)
|
||||||
}
|
}
|
||||||
|
|
||||||
childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
|
childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
|
||||||
mainOrder.Child[index].Price = childPrice.String()
|
mainOrder.Childs[index].Price = childPrice.String()
|
||||||
mainOrder.Child[index].Num = totalNum.String()
|
mainOrder.Childs[index].Num = totalNum.String()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//todo 重新计算
|
|
||||||
lastNum := remainQuantity
|
lastNum := remainQuantity
|
||||||
|
|
||||||
//过期时间
|
//过期时间
|
||||||
if ext.ExpirateHour <= 0 {
|
if ext.ExpirateHour <= 0 {
|
||||||
mainOrder.Child[index].ExpireTime = time.Now().AddDate(10, 0, 0)
|
mainOrder.Childs[index].ExpireTime = time.Now().AddDate(10, 0, 0)
|
||||||
} else {
|
} else {
|
||||||
mainOrder.Child[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour))
|
mainOrder.Childs[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
//加仓单
|
//加仓单
|
||||||
case mainOrder.Child[index].OrderType == 1 && mainOrder.Child[index].OrderCategory == 3:
|
case mainOrder.Childs[index].OrderType == 1 && mainOrder.Childs[index].OrderCategory == 3:
|
||||||
var percentage decimal.Decimal
|
var percentage decimal.Decimal
|
||||||
|
|
||||||
if mainOrder.Site == "BUY" {
|
if mainOrder.Site == "BUY" {
|
||||||
@ -311,24 +379,29 @@ func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
|
dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
|
||||||
mainOrder.Child[index].Price = dataPrice.String()
|
mainOrder.Childs[index].Price = dataPrice.String()
|
||||||
|
|
||||||
|
priceDiff := dataPrice.Sub(prePrice).Abs()
|
||||||
|
totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff))
|
||||||
|
|
||||||
if ext.AddPositionType == 1 {
|
if ext.AddPositionType == 1 {
|
||||||
buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2)
|
buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2)
|
||||||
mainOrder.Child[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
|
mainOrder.Childs[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mainOrder.Child[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
|
mainOrder.Childs[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
//加库存
|
//加库存
|
||||||
lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Child[index].Num))
|
lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Childs[index].Num))
|
||||||
// 计算子订单
|
// 计算子订单
|
||||||
if len(mainOrder.Child[index].Child) > 0 {
|
if len(mainOrder.Childs[index].Childs) > 0 {
|
||||||
calculateChildOrder(&mainOrder.Child, &tradeSet, ext, lastNum, dataPrice, false)
|
calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false)
|
||||||
}
|
}
|
||||||
|
//覆盖最近的订单价
|
||||||
|
prePrice = dataPrice
|
||||||
//减仓单
|
//减仓单
|
||||||
case mainOrder.Child[index].OrderType == 4:
|
case mainOrder.Childs[index].OrderType == 4:
|
||||||
percentage := decimal.NewFromInt(1)
|
percentage := decimal.NewFromInt(1)
|
||||||
|
|
||||||
if mainOrder.Site == "BUY" && ext.PriceRatio.Cmp(decimal.Zero) > 0 {
|
if mainOrder.Site == "BUY" && ext.PriceRatio.Cmp(decimal.Zero) > 0 {
|
||||||
@ -338,22 +411,26 @@ func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
|
dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
|
||||||
mainOrder.Child[index].Price = dataPrice.String()
|
mainOrder.Childs[index].Price = dataPrice.String()
|
||||||
|
priceDiff := dataPrice.Sub(prePrice).Abs()
|
||||||
|
totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff))
|
||||||
|
|
||||||
//百分比减仓
|
//百分比减仓
|
||||||
if ext.AddPositionType == 1 {
|
if ext.AddPositionType == 1 {
|
||||||
mainOrder.Child[index].Num = lastNum.Mul(ext.AddPositionVal.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String()
|
mainOrder.Childs[index].Num = lastNum.Mul(ext.AddPositionVal.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.Error("减仓不能是固定数值")
|
logger.Error("减仓不能是固定数值")
|
||||||
}
|
}
|
||||||
|
|
||||||
//减库存
|
//减库存
|
||||||
lastNum = lastNum.Sub(utility.StrToDecimal(mainOrder.Child[index].Num))
|
lastNum = lastNum.Sub(utility.StrToDecimal(mainOrder.Childs[index].Num))
|
||||||
// 计算子订单
|
// 计算子订单
|
||||||
if len(mainOrder.Child[index].Child) > 0 {
|
if len(mainOrder.Childs[index].Childs) > 0 {
|
||||||
calculateChildOrder(&mainOrder.Child, &tradeSet, ext, lastNum, dataPrice, false)
|
calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false)
|
||||||
}
|
}
|
||||||
|
//覆盖最近的订单价
|
||||||
|
prePrice = dataPrice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,7 +439,8 @@ func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet
|
|||||||
|
|
||||||
// 计算子订单信息
|
// 计算子订单信息
|
||||||
// isTpTp 是否是止盈后止损止盈
|
// isTpTp 是否是止盈后止损止盈
|
||||||
func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeSet, ext models.LinePreOrderExt, lastNum decimal.Decimal, price decimal.Decimal, isTpTp bool) error {
|
// 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 {
|
for index := range *orders {
|
||||||
orderQuantity := lastNum.Truncate(int32(tradeSet.AmountDigit))
|
orderQuantity := lastNum.Truncate(int32(tradeSet.AmountDigit))
|
||||||
percentage := decimal.NewFromInt(1)
|
percentage := decimal.NewFromInt(1)
|
||||||
@ -389,11 +467,21 @@ func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeS
|
|||||||
switch {
|
switch {
|
||||||
//做多止盈、做空止损
|
//做多止盈、做空止损
|
||||||
case (*orders)[index].OrderType == 1 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 2 && (*orders)[index].Site == "BUY":
|
case (*orders)[index].OrderType == 1 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 2 && (*orders)[index].Site == "BUY":
|
||||||
percentage = decimal.NewFromInt(100).Add(addPercentage).Div(decimal.NewFromInt(100))
|
percentage = decimal.NewFromInt(100).Add(addPercentage)
|
||||||
|
|
||||||
//做多止损、做空止盈
|
//做多止损、做空止盈
|
||||||
case (*orders)[index].OrderType == 2 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 1 && (*orders)[index].Site == "BUY":
|
case (*orders)[index].OrderType == 2 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 1 && (*orders)[index].Site == "BUY":
|
||||||
percentage = decimal.NewFromInt(100).Sub(addPercentage).Div(decimal.NewFromInt(100))
|
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))
|
orderPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
|
||||||
@ -402,8 +490,8 @@ func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeS
|
|||||||
lastOrderQuantity := lastNum.Sub(orderQuantity).Truncate(int32(tradeSet.AmountDigit))
|
lastOrderQuantity := lastNum.Sub(orderQuantity).Truncate(int32(tradeSet.AmountDigit))
|
||||||
|
|
||||||
//止盈后止盈、止盈后止损
|
//止盈后止盈、止盈后止损
|
||||||
if len((*orders)[index].Child) > 0 && lastOrderQuantity.Cmp(decimal.Zero) > 0 {
|
if len((*orders)[index].Childs) > 0 && lastOrderQuantity.Cmp(decimal.Zero) > 0 {
|
||||||
calculateChildOrder(&(*orders)[index].Child, tradeSet, ext, lastOrderQuantity, orderPrice, true)
|
calculateChildOrder(&(*orders)[index].Childs, tradeSet, ext, lastOrderQuantity, orderPrice, decimal.Zero, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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-合约
|
// symbolType 0-现货 1-合约
|
||||||
func GetTradeSet(exchangeType string, symbol string, symbolType int) (models2.TradeSet, error) {
|
func GetTradeSet(exchangeType string, symbol string, symbolType int) (models2.TradeSet, error) {
|
||||||
|
// 定义返回结果和val变量
|
||||||
result := models2.TradeSet{}
|
result := models2.TradeSet{}
|
||||||
val := ""
|
val := ""
|
||||||
|
|
||||||
|
// 根据交易对类型选择不同的key
|
||||||
switch symbolType {
|
switch symbolType {
|
||||||
case 0:
|
case 0:
|
||||||
key := fmt.Sprintf(global.TICKER_SPOT, exchangeType, symbol)
|
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, _ = helper.DefaultRedis.GetString(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果val不为空,则解析val为TradeSet结构体
|
||||||
if val != "" {
|
if val != "" {
|
||||||
if err := sonic.Unmarshal([]byte(val), &result); err != nil {
|
if err := sonic.Unmarshal([]byte(val), &result); err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 如果val为空,则返回错误信息
|
||||||
return result, errors.New("未找到交易对信息")
|
return result, errors.New("未找到交易对信息")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user