混合剥头皮策略
该策略将RSI信号与EMA趋势过滤和可选的成交量确认相结合,可根据需要调整信号灵敏度,并提供快速退出和移动止损功能。
测试显示年化收益约为35%,在流动性良好的加密货币上表现最佳。
策略根据RSI阈值和K线强度开多或开空,可选的趋势和成交量过滤。仓位通过可配置的止盈、止损、追踪止损以及每日交易限制进行保护。
细节
- 入场条件:
- 多头:RSI低于30且出现看涨K线,根据灵敏度应用趋势/成交量过滤。
- 空头:RSI高于70且出现看跌K线。
- 方向:双向。
- 出场条件:
- 止盈、止损、追踪止损或RSI/EMA反转快速退出。
- 止损:支持百分比止盈止损与可选追踪。
- 过滤器:
- 根据配置启用趋势和成交量过滤。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI based hybrid scalping strategy with adjustable sensitivity.
/// Combines RSI, EMA trend filters and optional volume filter.
/// </summary>
public class HybridScalpingBotStrategy : Strategy
{
private readonly StrategyParam<int> _dailyTradeLimit;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _useQuickExit;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<decimal> _trailingStopPercent;
private readonly StrategyParam<string> _signalSensitivity;
private readonly StrategyParam<bool> _useTrendFilter;
private readonly StrategyParam<bool> _useVolumeFilter;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _highestPrice;
private decimal _lowestPrice;
private int _tradesToday;
private DateTime _lastDate;
/// <summary>
/// Maximum trades per day.
/// </summary>
public int DailyTradeLimit { get => _dailyTradeLimit.Value; set => _dailyTradeLimit.Value = value; }
/// <summary>
/// Take profit percentage.
/// </summary>
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
/// <summary>
/// Exit early on RSI or EMA reversal.
/// </summary>
public bool UseQuickExit { get => _useQuickExit.Value; set => _useQuickExit.Value = value; }
/// <summary>
/// Enable trailing stop.
/// </summary>
public bool UseTrailingStop { get => _useTrailingStop.Value; set => _useTrailingStop.Value = value; }
/// <summary>
/// Trailing stop percent.
/// </summary>
public decimal TrailingStopPercent { get => _trailingStopPercent.Value; set => _trailingStopPercent.Value = value; }
/// <summary>
/// Signal sensitivity level.
/// </summary>
public string SignalSensitivity { get => _signalSensitivity.Value; set => _signalSensitivity.Value = value; }
/// <summary>
/// Use EMA trend filter.
/// </summary>
public bool UseTrendFilter { get => _useTrendFilter.Value; set => _useTrendFilter.Value = value; }
/// <summary>
/// Require high volume.
/// </summary>
public bool UseVolumeFilter { get => _useVolumeFilter.Value; set => _useVolumeFilter.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="HybridScalpingBotStrategy"/> class.
/// </summary>
public HybridScalpingBotStrategy()
{
_dailyTradeLimit = Param(nameof(DailyTradeLimit), 15)
.SetGreaterThanZero()
.SetDisplay("Daily Trades", "Maximum trades per day", "General");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 0.8m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
.SetOptimize(0.5m, 3m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 0.6m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
.SetOptimize(0.5m, 2m, 0.5m);
_useQuickExit = Param(nameof(UseQuickExit), true)
.SetDisplay("Use Quick Exit", "Exit on RSI or EMA pullback", "General");
_useTrailingStop = Param(nameof(UseTrailingStop), true)
.SetDisplay("Use Trailing Stop", "Trail profit after entry", "General");
_trailingStopPercent = Param(nameof(TrailingStopPercent), 0.4m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop %", "Trailing stop percent", "Risk")
.SetOptimize(0.2m, 1m, 0.2m);
_signalSensitivity = Param(nameof(SignalSensitivity), "Easy")
.SetDisplay("Signal Level", "VeryEasy / Easy / Medium / Strong", "General");
_useTrendFilter = Param(nameof(UseTrendFilter), true)
.SetDisplay("Use Trend Filter", "Trade only with trend", "General");
_useVolumeFilter = Param(nameof(UseVolumeFilter), false)
.SetDisplay("Use Volume Filter", "Require high volume", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_highestPrice = 0;
_lowestPrice = 0;
_tradesToday = 0;
_lastDate = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = 14 };
var ema9 = new ExponentialMovingAverage { Length = 9 };
var ema21 = new ExponentialMovingAverage { Length = 21 };
var ema50 = new ExponentialMovingAverage { Length = 50 };
var volumeSma = new SimpleMovingAverage { Length = 10 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ema9, ema21, ema50, volumeSma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ema9, decimal ema21, decimal ema50, decimal avgVolume)
{
if (candle.State != CandleStates.Finished)
return;
if (candle.OpenTime.Date != _lastDate)
{
_tradesToday = 0;
_lastDate = candle.OpenTime.Date;
}
var bullish = candle.ClosePrice > candle.OpenPrice;
var bearish = candle.ClosePrice < candle.OpenPrice;
var bodyRatio = candle.HighPrice == candle.LowPrice ? 0m : (candle.ClosePrice - candle.OpenPrice) / (candle.HighPrice - candle.LowPrice);
var strongBullish = bullish && bodyRatio > 0.6m;
var strongBearish = bearish && bodyRatio > 0.6m;
var uptrend = ema21 > ema50;
var downtrend = ema21 < ema50;
var strongUptrend = ema9 > ema21 && ema21 > ema50;
var strongDowntrend = ema9 < ema21 && ema21 < ema50;
var volumeOk = !UseVolumeFilter || candle.TotalVolume > avgVolume * 1.2m;
bool buySignal;
bool sellSignal;
switch (SignalSensitivity)
{
case "VeryEasy":
buySignal = rsi < 60 && bullish;
sellSignal = rsi > 40 && bearish;
break;
case "Medium":
buySignal = rsi < 30 && bullish && (!UseTrendFilter || uptrend);
sellSignal = rsi > 70 && bearish && (!UseTrendFilter || downtrend);
break;
case "Strong":
buySignal = rsi < 30 && strongBullish && (!UseTrendFilter || strongUptrend) && volumeOk && candle.ClosePrice > ema21;
sellSignal = rsi > 70 && strongBearish && (!UseTrendFilter || strongDowntrend) && volumeOk && candle.ClosePrice < ema21;
break;
default:
buySignal = rsi < 30 && bullish;
sellSignal = rsi > 70 && bearish;
break;
}
var canTrade = _tradesToday < DailyTradeLimit && Position == 0;
if (buySignal && canTrade)
{
BuyMarket();
_tradesToday++;
_entryPrice = candle.ClosePrice;
_highestPrice = candle.ClosePrice;
}
else if (sellSignal && canTrade)
{
SellMarket();
_tradesToday++;
_entryPrice = candle.ClosePrice;
_lowestPrice = candle.ClosePrice;
}
if (Position > 0)
{
_highestPrice = Math.Max(_highestPrice, candle.HighPrice);
if (UseTrailingStop)
{
var trail = _highestPrice * (1 - TrailingStopPercent / 100m);
if (candle.ClosePrice <= trail)
SellMarket();
}
if (candle.ClosePrice <= _entryPrice * (1 - StopLossPercent / 100m))
SellMarket();
else if (candle.ClosePrice >= _entryPrice * (1 + TakeProfitPercent / 100m))
SellMarket();
else if (UseQuickExit && (rsi > 70 || rsi < 25 || candle.ClosePrice < ema21))
SellMarket();
}
else if (Position < 0)
{
_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);
if (UseTrailingStop)
{
var trail = _lowestPrice * (1 + TrailingStopPercent / 100m);
if (candle.ClosePrice >= trail)
BuyMarket();
}
if (candle.ClosePrice >= _entryPrice * (1 + StopLossPercent / 100m))
BuyMarket();
else if (candle.ClosePrice <= _entryPrice * (1 - TakeProfitPercent / 100m))
BuyMarket();
else if (UseQuickExit && (rsi < 30 || rsi > 75 || candle.ClosePrice > ema21))
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class hybrid_scalping_bot_strategy(Strategy):
def __init__(self):
super(hybrid_scalping_bot_strategy, self).__init__()
self._daily_trade_limit = self.Param("DailyTradeLimit", 15) \
.SetDisplay("Daily Trades", "Maximum trades per day", "General")
self._take_profit_percent = self.Param("TakeProfitPercent", 0.8) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 0.6) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._use_quick_exit = self.Param("UseQuickExit", True) \
.SetDisplay("Use Quick Exit", "Exit on RSI or EMA pullback", "General")
self._use_trailing_stop = self.Param("UseTrailingStop", True) \
.SetDisplay("Use Trailing Stop", "Trail profit after entry", "General")
self._trailing_stop_percent = self.Param("TrailingStopPercent", 0.4) \
.SetDisplay("Trailing Stop %", "Trailing stop percent", "Risk")
self._signal_sensitivity = self.Param("SignalSensitivity", "Easy") \
.SetDisplay("Signal Level", "VeryEasy / Easy / Medium / Strong", "General")
self._use_trend_filter = self.Param("UseTrendFilter", True) \
.SetDisplay("Use Trend Filter", "Trade only with trend", "General")
self._use_volume_filter = self.Param("UseVolumeFilter", False) \
.SetDisplay("Use Volume Filter", "Require high volume", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_price = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._trades_today = 0
self._last_date = None
@property
def daily_trade_limit(self):
return self._daily_trade_limit.Value
@property
def take_profit_percent(self):
return self._take_profit_percent.Value
@property
def stop_loss_percent(self):
return self._stop_loss_percent.Value
@property
def use_quick_exit(self):
return self._use_quick_exit.Value
@property
def use_trailing_stop(self):
return self._use_trailing_stop.Value
@property
def trailing_stop_percent(self):
return self._trailing_stop_percent.Value
@property
def signal_sensitivity(self):
return self._signal_sensitivity.Value
@property
def use_trend_filter(self):
return self._use_trend_filter.Value
@property
def use_volume_filter(self):
return self._use_volume_filter.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hybrid_scalping_bot_strategy, self).OnReseted()
self._entry_price = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._trades_today = 0
self._last_date = None
def OnStarted2(self, time):
super(hybrid_scalping_bot_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = 14
ema9 = ExponentialMovingAverage()
ema9.Length = 9
ema21 = ExponentialMovingAverage()
ema21.Length = 21
ema50 = ExponentialMovingAverage()
ema50.Length = 50
volume_sma = SimpleMovingAverage()
volume_sma.Length = 10
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, ema9, ema21, ema50, volume_sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, rsi, ema9, ema21, ema50, avg_volume):
if candle.State != CandleStates.Finished:
return
if candle.OpenTime.Date != self._last_date:
self._trades_today = 0
self._last_date = candle.OpenTime.Date
bullish = candle.ClosePrice > candle.OpenPrice
bearish = candle.ClosePrice < candle.OpenPrice
body_ratio = (0 if candle.HighPrice == candle.LowPrice else (candle.ClosePrice - candle.OpenPrice) / (candle.HighPrice - candle.LowPrice))
strong_bullish = bullish and body_ratio > 0.6
strong_bearish = bearish and body_ratio > 0.6
uptrend = ema21 > ema50
downtrend = ema21 < ema50
strong_uptrend = ema9 > ema21 and ema21 > ema50
strong_downtrend = ema9 < ema21 and ema21 < ema50
volume_ok = not self.use_volume_filter or candle.TotalVolume > avg_volume * 1.2
sensitivity = self.signal_sensitivity
if sensitivity == "VeryEasy":
buy_signal = rsi < 60 and bullish
sell_signal = rsi > 40 and bearish
elif sensitivity == "Medium":
buy_signal = rsi < 30 and bullish and (not self.use_trend_filter or uptrend)
sell_signal = rsi > 70 and bearish and (not self.use_trend_filter or downtrend)
elif sensitivity == "Strong":
buy_signal = rsi < 30 and strong_bullish and (not self.use_trend_filter or strong_uptrend) and volume_ok and candle.ClosePrice > ema21
sell_signal = rsi > 70 and strong_bearish and (not self.use_trend_filter or strong_downtrend) and volume_ok and candle.ClosePrice < ema21
else:
buy_signal = rsi < 30 and bullish
sell_signal = rsi > 70 and bearish
can_trade = self._trades_today < self.daily_trade_limit and self.Position == 0
if buy_signal and can_trade:
self.BuyMarket()
self._trades_today += 1
self._entry_price = candle.ClosePrice
self._highest_price = candle.ClosePrice
elif sell_signal and can_trade:
self.SellMarket()
self._trades_today += 1
self._entry_price = candle.ClosePrice
self._lowest_price = candle.ClosePrice
if self.Position > 0:
self._highest_price = max(self._highest_price, candle.HighPrice)
if self.use_trailing_stop:
trail = self._highest_price * (1 - self.trailing_stop_percent / 100)
if candle.ClosePrice <= trail:
self.SellMarket()
if candle.ClosePrice <= self._entry_price * (1 - self.stop_loss_percent / 100):
self.SellMarket()
elif candle.ClosePrice >= self._entry_price * (1 + self.take_profit_percent / 100):
self.SellMarket()
elif self.use_quick_exit and (rsi > 70 or rsi < 25 or candle.ClosePrice < ema21):
self.SellMarket()
elif self.Position < 0:
self._lowest_price = min(self._lowest_price, candle.LowPrice)
if self.use_trailing_stop:
trail = self._lowest_price * (1 + self.trailing_stop_percent / 100)
if candle.ClosePrice >= trail:
self.BuyMarket()
if candle.ClosePrice >= self._entry_price * (1 + self.stop_loss_percent / 100):
self.BuyMarket()
elif candle.ClosePrice <= self._entry_price * (1 - self.take_profit_percent / 100):
self.BuyMarket()
elif self.use_quick_exit and (rsi < 30 or rsi > 75 or candle.ClosePrice > ema21):
self.BuyMarket()
def CreateClone(self):
return hybrid_scalping_bot_strategy()