Basic Trailing Stop
The Basic Trailing Stop strategy combines Commodity Channel Index (CCI) and Relative Strength Index (RSI) filters with a simple trailing stop. When both indicators signal oversold or overbought conditions, the strategy opens a market position and immediately places a trailing stop measured in pips. As price moves favorably, the stop level follows the trend to lock in profits.
Testing indicates an average annual return of about 32%. It performs best in the forex market.
Because the stop level continuously trails price, risk automatically tightens when the trend extends. Exits occur only if the trailing stop is hit. The system maintains one position at a time and can trade in both directions.
Details
- Entry Criteria:
- Long:
CCIbetween -150 and -100 andRSIbetween 0 and 30. - Short:
CCIbetween 100 and 250 andRSIbetween 70 and 100.
- Long:
- Long/Short: Both.
- Exit Criteria: Trailing stop hit.
- Stops: Trailing stop only.
- Default Values:
StopLossPips= 20CciPeriod= 14RsiPeriod= 14CandleType=TimeSpan.FromMinutes(1)
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: CCI, RSI
- Stops: Yes
- Complexity: Beginner
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
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>
/// Strategy implementing a basic trailing stop with CCI and RSI signals.
/// </summary>
public class BasicTrailingStopStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public BasicTrailingStopStrategy()
{
_stopLossPct = Param(nameof(StopLossPct), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Trailing stop distance as percentage", "Risk Management");
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Commodity Channel Index period", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Relative Strength Index period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var cci = new CommodityChannelIndex { Length = CciPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(cci, rsi, (candle, cciVal, rsiVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!cciVal.IsFormed || !rsiVal.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
ProcessCandle(candle, cciVal.ToDecimal(), rsiVal.ToDecimal());
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue, decimal rsiValue)
{
var stopOffset = candle.ClosePrice * StopLossPct / 100m;
if (Position > 0)
{
var newStop = candle.ClosePrice - stopOffset;
if (newStop > _stopPrice)
_stopPrice = newStop;
if (candle.LowPrice <= _stopPrice)
{
SellMarket();
_stopPrice = 0m;
}
return;
}
if (Position < 0)
{
var newStop = candle.ClosePrice + stopOffset;
if (_stopPrice == 0m || newStop < _stopPrice)
_stopPrice = newStop;
if (candle.HighPrice >= _stopPrice)
{
BuyMarket();
_stopPrice = 0m;
}
return;
}
// No position - evaluate entry signals
var longSignal = cciValue < -50m && rsiValue < 40m;
var shortSignal = cciValue > 50m && rsiValue > 60m;
if (longSignal)
{
BuyMarket();
_stopPrice = candle.ClosePrice - stopOffset;
}
else if (shortSignal)
{
SellMarket();
_stopPrice = candle.ClosePrice + stopOffset;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class basic_trailing_stop_strategy(Strategy):
def __init__(self):
super(basic_trailing_stop_strategy, self).__init__()
self._stop_loss_pct = self.Param("StopLossPct", 1.5) \
.SetDisplay("Stop Loss %", "Trailing stop distance as percentage", "Risk Management")
self._cci_period = self.Param("CciPeriod", 14) \
.SetDisplay("CCI Period", "Commodity Channel Index period", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Relative Strength Index period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_price = 0.0
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def cci_period(self):
return self._cci_period.Value
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(basic_trailing_stop_strategy, self).OnReseted()
self._stop_price = 0.0
def OnStarted2(self, time):
super(basic_trailing_stop_strategy, self).OnStarted2(time)
cci = CommodityChannelIndex()
cci.Length = self.cci_period
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(cci, rsi, self.on_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def on_candle(self, candle, cci_val, rsi_val):
if candle.State != CandleStates.Finished:
return
if not cci_val.IsFormed or not rsi_val.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
self.process_candle(candle, float(cci_val), float(rsi_val))
def process_candle(self, candle, cci_value, rsi_value):
close = float(candle.ClosePrice)
stop_offset = close * float(self.stop_loss_pct) / 100.0
if self.Position > 0:
new_stop = close - stop_offset
if new_stop > self._stop_price:
self._stop_price = new_stop
if float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
self._stop_price = 0.0
return
if self.Position < 0:
new_stop = close + stop_offset
if self._stop_price == 0.0 or new_stop < self._stop_price:
self._stop_price = new_stop
if float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
self._stop_price = 0.0
return
long_signal = cci_value < -50.0 and rsi_value < 40.0
short_signal = cci_value > 50.0 and rsi_value > 60.0
if long_signal:
self.BuyMarket()
self._stop_price = close - stop_offset
elif short_signal:
self.SellMarket()
self._stop_price = close + stop_offset
def CreateClone(self):
return basic_trailing_stop_strategy()