diff --git a/config/ccinr.yaml b/config/ccinr.yaml index cc3183e..845e97a 100644 --- a/config/ccinr.yaml +++ b/config/ccinr.yaml @@ -7,13 +7,13 @@ # db: 0 sessions: - binance_futures: + binance: exchange: binance envVarPrefix: BINANCE futures: true exchangeStrategies: - - on: binance_futures + - on: binance ccinr: symbols: - ARUSDT @@ -24,22 +24,22 @@ exchangeStrategies: - DYDXUSDT - XRPUSDT - PEOPLEUSDT -# - STXUSDT -# - WLDUSDT -# - FILUSDT -# - DOGEUSDT -# - MKRUSDT -# - NOTUSDT -# - ENSUSDT -# - BNBUSDT -# - BTCUSDT -# - ETHUSDT -# - SOLUSDT + # - STXUSDT + # - WLDUSDT + # - FILUSDT + # - DOGEUSDT + # - MKRUSDT + # - NOTUSDT + # - ENSUSDT + # - BNBUSDT + # - BTCUSDT + # - ETHUSDT + # - SOLUSDT interval: 1m nrInterval: 1m cciInterval: 5m - atrInterval: 1h + atrInterval: 2h nrCount: 4 cciWindow: 20 atrWindow: 14 @@ -55,9 +55,33 @@ exchangeStrategies: placePriceType: 2 lossType: 1 profitOrderType: 0 - atrProfitRange: 0.8 + atrProfitRange: 0.5 atrLossRange: 1.0 + tradeStartHour: 0 + tradeEndHour: 8 + pauseTradeLoss: 10.0 # recalculate: false # dry_run: false # # quantity: 3 -# strict_mode: true \ No newline at end of file +# 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 \ No newline at end of file diff --git a/otp.png b/otp.png index 54c6e89..9582954 100644 Binary files a/otp.png and b/otp.png differ diff --git a/pkg/notifier/larknotifier/lark.go b/pkg/notifier/larknotifier/lark.go index 64f0e6b..2cf113d 100644 --- a/pkg/notifier/larknotifier/lark.go +++ b/pkg/notifier/larknotifier/lark.go @@ -174,7 +174,8 @@ func (n *Notifier) NotifyTo(channel string, obj interface{}, args ...interface{} message = fmt.Sprintf(a, args...) default: - log.Errorf("unsupported notification format: %T %+v", a, a) + //log.Errorf("unsupported notification format: %T %+v", a, a) + return } select { diff --git a/pkg/strategy/ccinr/strategy.go b/pkg/strategy/ccinr/strategy.go index 9e31b41..13c413a 100644 --- a/pkg/strategy/ccinr/strategy.go +++ b/pkg/strategy/ccinr/strategy.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "sync" + "time" ) const ID = "ccinr" @@ -49,6 +50,9 @@ type Strategy struct { LossRange fixedpoint.Value `json:"lossRange"` AtrProfitRange float64 `json:"atrProfitRange"` AtrLossRange float64 `json:"atrLossRange"` + TradeStartHour int `json:"tradeStartHour"` + TradeEndHour int `json:"tradeEndHour"` + PauseTradeLoss fixedpoint.Value `json:"pauseTradeLoss"` qbtrade.QuantityOrAmount @@ -89,6 +93,10 @@ type Strategy struct { TotalOrderCount int TotalProfitCount int TotalLossCount int + // 累计暂停交易的次数 + PauseTradeCount fixedpoint.Value + // 最近一次暂停交易的时间 + PauseTradeTime time.Time } 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.ATRInterval}) 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 { - 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 { @@ -206,10 +217,17 @@ func (s *Strategy) generateOrders(ctx context.Context, kline types.KLine) ([]typ // 止盈订单类型 profitOrderType := types.OrderTypeTakeProfitMarket + // 止损订单类型 + lossOrderType := types.OrderTypeStopMarket if s.ProfitOrderType == 1 { profitOrderType = types.OrderTypeStopMarket } + if qbtrade.IsBackTesting { + profitOrderType = types.OrderTypeStopLimit + lossOrderType = types.OrderTypeStopLimit + } + // 计算止损止盈价格,以ATR为基准或者固定百分比 lossPrice := 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{ Symbol: symbol, Side: types.SideTypeBuy, - Type: types.OrderTypeStopMarket, + Type: lossOrderType, PositionSide: types.PositionSideTypeShort, StopPrice: lossPrice, TimeInForce: types.TimeInForceGTC, @@ -300,7 +318,7 @@ func (s *Strategy) generateOrders(ctx context.Context, kline types.KLine) ([]typ s.LongLossOrder[symbol] = types.SubmitOrder{ Symbol: symbol, Side: types.SideTypeSell, - Type: types.OrderTypeStopMarket, + Type: lossOrderType, PositionSide: types.PositionSideTypeLong, StopPrice: lossPrice, 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", 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 != 0 && 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 { s.ExchangeSession = session @@ -427,6 +477,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor, s.TotalOrderCount = 0 s.TotalProfitCount = 0 s.TotalLossCount = 0 + s.PauseTradeCount = fixedpoint.Zero + s.PauseTradeTime = time.Now().Add(-24 * time.Hour) for _, symbol := range s.Symbols { 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] { 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) if cciV <= s.LongCCI.Float64() { s.TradeType[sym] = "long"