add pause trade

This commit is contained in:
lychiyu 2024-08-04 23:49:16 +08:00
parent d973d95121
commit a07d255d77
4 changed files with 107 additions and 21 deletions

View File

@ -7,13 +7,13 @@
# db: 0 # db: 0
sessions: sessions:
binance_futures: binance:
exchange: binance exchange: binance
envVarPrefix: BINANCE envVarPrefix: BINANCE
futures: true futures: true
exchangeStrategies: exchangeStrategies:
- on: binance_futures - on: binance
ccinr: ccinr:
symbols: symbols:
- ARUSDT - ARUSDT
@ -24,17 +24,17 @@ exchangeStrategies:
- DYDXUSDT - DYDXUSDT
- XRPUSDT - XRPUSDT
- PEOPLEUSDT - PEOPLEUSDT
# - STXUSDT # - STXUSDT
# - WLDUSDT # - WLDUSDT
# - FILUSDT # - FILUSDT
# - DOGEUSDT # - DOGEUSDT
# - MKRUSDT # - MKRUSDT
# - NOTUSDT # - NOTUSDT
# - ENSUSDT # - ENSUSDT
# - BNBUSDT # - BNBUSDT
# - BTCUSDT # - BTCUSDT
# - ETHUSDT # - ETHUSDT
# - SOLUSDT # - SOLUSDT
interval: 1m interval: 1m
nrInterval: 1m nrInterval: 1m
@ -55,9 +55,33 @@ exchangeStrategies:
placePriceType: 2 placePriceType: 2
lossType: 1 lossType: 1
profitOrderType: 0 profitOrderType: 0
atrProfitRange: 0.8 atrProfitRange: 1.0
atrLossRange: 1.0 atrLossRange: 2.0
tradeStartHour: 0
tradeEndHour: 8
pauseTradeLoss: -10.0
# recalculate: false # recalculate: false
# dry_run: false # dry_run: false
# # quantity: 3 # # quantity: 3
# strict_mode: true # strict_mode: true
backtest:
startTime: "2023-10-15"
endTime: "2024-08-02"
symbols:
- ARUSDT
- ORDIUSDT
- OPUSDT
- OMUSDT
- WIFUSDT
- DYDXUSDT
- XRPUSDT
- PEOPLEUSDT
sessions: [ binance ]
syncSecKLines: true
accounts:
binance:
takerFeeRate: 0.0002
makerFeeRate: 0.0005
balances:
USDT: 200

BIN
otp.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -174,7 +174,8 @@ func (n *Notifier) NotifyTo(channel string, obj interface{}, args ...interface{}
message = fmt.Sprintf(a, args...) message = fmt.Sprintf(a, args...)
default: default:
log.Errorf("unsupported notification format: %T %+v", a, a) //log.Errorf("unsupported notification format: %T %+v", a, a)
return
} }
select { select {

View File

@ -13,6 +13,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
) )
const ID = "ccinr" const ID = "ccinr"
@ -49,6 +50,9 @@ type Strategy struct {
LossRange fixedpoint.Value `json:"lossRange"` LossRange fixedpoint.Value `json:"lossRange"`
AtrProfitRange float64 `json:"atrProfitRange"` AtrProfitRange float64 `json:"atrProfitRange"`
AtrLossRange float64 `json:"atrLossRange"` AtrLossRange float64 `json:"atrLossRange"`
TradeStartHour int `json:"tradeStartHour"`
TradeEndHour int `json:"tradeEndHour"`
PauseTradeLoss fixedpoint.Value `json:"pauseTradeLoss"`
qbtrade.QuantityOrAmount qbtrade.QuantityOrAmount
@ -89,6 +93,10 @@ type Strategy struct {
TotalOrderCount int TotalOrderCount int
TotalProfitCount int TotalProfitCount int
TotalLossCount int TotalLossCount int
// 累计暂停交易的次数
PauseTradeCount fixedpoint.Value
// 最近一次暂停交易的时间
PauseTradeTime time.Time
} }
func (s *Strategy) ID() string { func (s *Strategy) ID() string {
@ -101,7 +109,10 @@ func (s *Strategy) Subscribe(session *qbtrade.ExchangeSession) {
session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.CCIInterval}) session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.CCIInterval})
session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.ATRInterval}) session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.ATRInterval})
session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.NRInterval}) session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.NRInterval})
session.Subscribe(types.MarketTradeChannel, symbol, types.SubscribeOptions{})
if !qbtrade.IsBackTesting {
session.Subscribe(types.MarketTradeChannel, symbol, types.SubscribeOptions{})
}
} }
} }
@ -114,7 +125,7 @@ func (s *Strategy) Initialize() error {
} }
func (s *Strategy) InstanceID() string { func (s *Strategy) InstanceID() string {
return fmt.Sprintf("%s:%s:%s", ID, strings.Join(s.Symbols, "-"), s.Interval) return fmt.Sprintf("%s:%s", ID, s.Interval)
} }
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
@ -206,10 +217,17 @@ func (s *Strategy) generateOrders(ctx context.Context, kline types.KLine) ([]typ
// 止盈订单类型 // 止盈订单类型
profitOrderType := types.OrderTypeTakeProfitMarket profitOrderType := types.OrderTypeTakeProfitMarket
// 止损订单类型
lossOrderType := types.OrderTypeStopMarket
if s.ProfitOrderType == 1 { if s.ProfitOrderType == 1 {
profitOrderType = types.OrderTypeStopMarket profitOrderType = types.OrderTypeStopMarket
} }
if qbtrade.IsBackTesting {
profitOrderType = types.OrderTypeStopLimit
lossOrderType = types.OrderTypeStopLimit
}
// 计算止损止盈价格以ATR为基准或者固定百分比 // 计算止损止盈价格以ATR为基准或者固定百分比
lossPrice := fixedpoint.Zero lossPrice := fixedpoint.Zero
profitPrice := fixedpoint.Zero profitPrice := fixedpoint.Zero
@ -267,7 +285,7 @@ func (s *Strategy) generateOrders(ctx context.Context, kline types.KLine) ([]typ
s.ShortLossOrder[symbol] = types.SubmitOrder{ s.ShortLossOrder[symbol] = types.SubmitOrder{
Symbol: symbol, Symbol: symbol,
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
Type: types.OrderTypeStopMarket, Type: lossOrderType,
PositionSide: types.PositionSideTypeShort, PositionSide: types.PositionSideTypeShort,
StopPrice: lossPrice, StopPrice: lossPrice,
TimeInForce: types.TimeInForceGTC, TimeInForce: types.TimeInForceGTC,
@ -300,7 +318,7 @@ func (s *Strategy) generateOrders(ctx context.Context, kline types.KLine) ([]typ
s.LongLossOrder[symbol] = types.SubmitOrder{ s.LongLossOrder[symbol] = types.SubmitOrder{
Symbol: symbol, Symbol: symbol,
Side: types.SideTypeSell, Side: types.SideTypeSell,
Type: types.OrderTypeStopMarket, Type: lossOrderType,
PositionSide: types.PositionSideTypeLong, PositionSide: types.PositionSideTypeLong,
StopPrice: lossPrice, StopPrice: lossPrice,
TimeInForce: types.TimeInForceGTC, TimeInForce: types.TimeInForceGTC,
@ -394,6 +412,38 @@ func (s *Strategy) notifyProfit(ctx context.Context, symbol string) {
qbtrade.Notify(fmt.Sprintf("总交易次数:%v, 总收益:%v, 总手续费:%v, 盈利次数:%v, 亏损次数:%v", qbtrade.Notify(fmt.Sprintf("总交易次数:%v, 总收益:%v, 总手续费:%v, 盈利次数:%v, 亏损次数:%v",
s.TotalOrderCount, s.TotalProfit.Float64(), s.TotalFree.Float64(), s.TotalProfitCount, s.TotalLossCount)) s.TotalOrderCount, s.TotalProfit.Float64(), s.TotalFree.Float64(), s.TotalProfitCount, s.TotalLossCount))
} }
// isTradeTime 是否交易时间
func (s *Strategy) isTradeTime(ctx context.Context) bool {
// 如果时间一致则表示不限制交易时间
if s.TradeEndHour == s.TradeStartHour {
return true
}
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
return false
}
now := time.Now().In(location)
hour := now.Hour()
return hour >= s.TradeStartHour && hour < s.TradeEndHour
}
func (s *Strategy) isPauseTrade(ctx context.Context) bool {
// 被暂停次数不为0且最近一次的暂停时间和今天一致则表示暂停
if s.PauseTradeCount != fixedpoint.Zero && s.PauseTradeTime.Day() == time.Now().Day() {
return true
}
// 总收益大于(暂停次数+1)*暂停亏损,则表示暂停
if s.TotalProfit < s.PauseTradeLoss.Mul(s.PauseTradeCount.Add(fixedpoint.One)) {
s.PauseTradeCount.Add(fixedpoint.One)
s.PauseTradeTime = time.Now()
return true
}
return false
}
func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor, session *qbtrade.ExchangeSession) error { func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor, session *qbtrade.ExchangeSession) error {
s.ExchangeSession = session s.ExchangeSession = session
@ -427,6 +477,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor,
s.TotalOrderCount = 0 s.TotalOrderCount = 0
s.TotalProfitCount = 0 s.TotalProfitCount = 0
s.TotalLossCount = 0 s.TotalLossCount = 0
s.PauseTradeCount = fixedpoint.Zero
s.PauseTradeTime = time.Now().Add(-24 * time.Hour)
for _, symbol := range s.Symbols { for _, symbol := range s.Symbols {
s.Positions[symbol] = types.NewPositionFromMarket(s.markets[symbol]) s.Positions[symbol] = types.NewPositionFromMarket(s.markets[symbol])
@ -481,6 +533,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor,
if s.Traded[sym] { if s.Traded[sym] {
return return
} }
if !s.isTradeTime(ctx) || s.isPauseTrade(ctx) {
pauseMsg := fmt.Sprintf("暂停交易:总收益:%v, 暂停次数:%v, 暂停时间:%v; 暂停时间段:[%v, %v)",
s.TotalProfit.Float64(), s.PauseTradeCount.Float64(), s.PauseTradeTime, s.TradeStartHour,
s.TradeEndHour)
qbtrade.Notify(pauseMsg)
return
}
cciV := s.cci[sym].Last(0) cciV := s.cci[sym].Last(0)
if cciV <= s.LongCCI.Float64() { if cciV <= s.LongCCI.Float64() {
s.TradeType[sym] = "long" s.TradeType[sym] = "long"