Multi-Factor Strategy
Multi-Factor Strategy combines MACD, RSI, and two moving averages to trade with trend confirmation. Long trades occur when MACD line is above its signal, RSI is below 70, price above the 50-period SMA, and the 50 SMA is above the 200 SMA. Short trades use opposite conditions.
Stops and targets are based on ATR multiples.
Details
- Entry Criteria:
- Long:
MACD > Signal&&RSI < 70&&Close > SMA50&&SMA50 > SMA200. - Short:
MACD < Signal&&RSI > 30&&Close < SMA50&&SMA50 < SMA200.
- Long:
- Long/Short: Both directions.
- Exit Criteria: ATR-based stop loss and take profit.
- Stops: Yes.
- Default Values:
FastLength= 12SlowLength= 26SignalLength= 9RsiLength= 14AtrLength= 14StopAtrMultiplier= 2ProfitAtrMultiplier= 3CandleType= TimeSpan.FromMinutes(15)
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: MACD, RSI, SMA, ATR
- Stops: Yes
- Complexity: Basic
- Timeframe: Medium-term
- 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>
/// Multi-factor strategy.
/// Goes long when MACD is above its signal, RSI is below 70, the close is above the 50-period SMA and the 50 SMA is above the 200 SMA;
/// goes short on the mirrored conditions (RSI above 30), reversing an opposite position. Each position is closed by a stop loss and a
/// take profit placed StopAtrMultiplier and ProfitAtrMultiplier ATRs away from the entry price.
/// </summary>
public class MultiFactorStrategy : Strategy
{
private const int _trendFastLength = 50;
private const int _trendSlowLength = 200;
private const decimal _rsiUpper = 70m;
private const decimal _rsiLower = 30m;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _stopAtrMultiplier;
private readonly StrategyParam<decimal> _profitAtrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// MACD fast EMA length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// MACD slow EMA length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// MACD signal EMA length.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiple for the stop loss distance.
/// </summary>
public decimal StopAtrMultiplier
{
get => _stopAtrMultiplier.Value;
set => _stopAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiple for the take profit distance.
/// </summary>
public decimal ProfitAtrMultiplier
{
get => _profitAtrMultiplier.Value;
set => _profitAtrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MultiFactorStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast EMA length", "MACD");
_slowLength = Param(nameof(SlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow EMA length", "MACD");
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "MACD signal EMA length", "MACD");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Risk");
_stopAtrMultiplier = Param(nameof(StopAtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("Stop ATR Mult", "ATR multiple for the stop loss", "Risk");
_profitAtrMultiplier = Param(nameof(ProfitAtrMultiplier), 3m)
.SetNotNegative()
.SetDisplay("Profit ATR Mult", "ATR multiple for the take profit", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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 = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = null;
_takePrice = null;
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastLength },
LongMa = { Length = SlowLength },
},
SignalMa = { Length = SignalLength }
};
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var sma50 = new SimpleMovingAverage { Length = _trendFastLength };
var sma200 = new SimpleMovingAverage { Length = _trendSlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, rsi, atr, sma50, sma200, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma50);
DrawIndicator(area, sma200);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, macd);
DrawIndicator(oscillators, rsi);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue rsiValue, IIndicatorValue atrValue, IIndicatorValue sma50Value, IIndicatorValue sma200Value)
{
if (candle.State != CandleStates.Finished)
return;
// The ATR levels are checked against the candle range, so exits happen before new signals.
if (Position > 0 && _stopPrice is decimal longStop && _takePrice is decimal longTake)
{
if (candle.LowPrice <= longStop || candle.HighPrice >= longTake)
{
SellMarket(Position);
_stopPrice = null;
_takePrice = null;
return;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop && _takePrice is decimal shortTake)
{
if (candle.HighPrice >= shortStop || candle.LowPrice <= shortTake)
{
BuyMarket(-Position);
_stopPrice = null;
_takePrice = null;
return;
}
}
if (!macdValue.IsFormed || !rsiValue.IsFormed || !atrValue.IsFormed || !sma50Value.IsFormed || !sma200Value.IsFormed)
return;
if (macdValue is not IMovingAverageConvergenceDivergenceSignalValue { Macd: decimal macdLine, Signal: decimal signalLine })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var rsi = rsiValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var sma50 = sma50Value.GetValue<decimal>();
var sma200 = sma200Value.GetValue<decimal>();
var close = candle.ClosePrice;
var longSignal = macdLine > signalLine && rsi < _rsiUpper && close > sma50 && sma50 > sma200;
var shortSignal = macdLine < signalLine && rsi > _rsiLower && close < sma50 && sma50 < sma200;
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
SetLevels(close, atr, true);
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
SetLevels(close, atr, false);
}
}
private void SetLevels(decimal entry, decimal atr, bool isLong)
{
var stop = atr * StopAtrMultiplier;
var take = atr * ProfitAtrMultiplier;
// A zero multiplier disables that level.
_stopPrice = StopAtrMultiplier > 0 ? (isLong ? entry - stop : entry + stop) : (isLong ? decimal.MinValue : decimal.MaxValue);
_takePrice = ProfitAtrMultiplier > 0 ? (isLong ? entry + take : entry - take) : (isLong ? decimal.MaxValue : decimal.MinValue);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal, RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
TREND_FAST_LENGTH = 50
TREND_SLOW_LENGTH = 200
RSI_UPPER = 70
RSI_LOWER = 30
class multi_factor_strategy(Strategy):
"""
Multi-factor strategy.
Goes long when MACD is above its signal, RSI is below 70, the close is above the 50-period SMA and the 50 SMA is above the 200 SMA;
goes short on the mirrored conditions (RSI above 30), reversing an opposite position. Each position is closed by a stop loss and a
take profit placed StopAtrMultiplier and ProfitAtrMultiplier ATRs away from the entry price.
"""
def __init__(self):
super(multi_factor_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12).SetGreaterThanZero().SetDisplay("MACD Fast", "MACD fast EMA length", "MACD")
self._slow_length = self.Param("SlowLength", 26).SetGreaterThanZero().SetDisplay("MACD Slow", "MACD slow EMA length", "MACD")
self._signal_length = self.Param("SignalLength", 9).SetGreaterThanZero().SetDisplay("MACD Signal", "MACD signal EMA length", "MACD")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "RSI")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Risk")
self._stop_atr_multiplier = self.Param("StopAtrMultiplier", 2.0).SetNotNegative().SetDisplay("Stop ATR Mult", "ATR multiple for the stop loss", "Risk")
self._profit_atr_multiplier = self.Param("ProfitAtrMultiplier", 3.0).SetNotNegative().SetDisplay("Profit ATR Mult", "ATR multiple for the take profit", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(multi_factor_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(multi_factor_strategy, self).OnStarted2(time)
self._reset_state()
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_length.Value
macd.Macd.LongMa.Length = self._slow_length.Value
macd.SignalMa.Length = self._signal_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sma50 = SimpleMovingAverage()
sma50.Length = TREND_FAST_LENGTH
sma200 = SimpleMovingAverage()
sma200.Length = TREND_SLOW_LENGTH
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, rsi, atr, sma50, sma200, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma50)
self.DrawIndicator(area, sma200)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, macd)
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, macd_value, rsi_value, atr_value, sma50_value, sma200_value):
if candle.State != CandleStates.Finished:
return
# The ATR levels are checked against the candle range, so exits happen before new signals.
if self.Position > 0 and self._stop_price is not None and self._take_price is not None:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
self._reset_state()
return
elif self.Position < 0 and self._stop_price is not None and self._take_price is not None:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(-self.Position)
self._reset_state()
return
if not macd_value.IsFormed or not rsi_value.IsFormed or not atr_value.IsFormed or not sma50_value.IsFormed or not sma200_value.IsFormed:
return
if macd_value.Macd is None or macd_value.Signal is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
macd_line = macd_value.Macd
signal_line = macd_value.Signal
rsi = rsi_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
sma50 = sma50_value.GetValue[Decimal](None)
sma200 = sma200_value.GetValue[Decimal](None)
close = candle.ClosePrice
long_signal = macd_line > signal_line and rsi < Decimal(RSI_UPPER) and close > sma50 and sma50 > sma200
short_signal = macd_line < signal_line and rsi > Decimal(RSI_LOWER) and close < sma50 and sma50 < sma200
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._set_levels(close, atr, True)
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._set_levels(close, atr, False)
def _set_levels(self, entry, atr, is_long):
stop_mult = Decimal(self._stop_atr_multiplier.Value)
take_mult = Decimal(self._profit_atr_multiplier.Value)
stop = atr * stop_mult
take = atr * take_mult
# A zero multiplier disables that level.
if stop_mult > Decimal(0):
self._stop_price = entry - stop if is_long else entry + stop
else:
self._stop_price = Decimal.MinValue if is_long else Decimal.MaxValue
if take_mult > Decimal(0):
self._take_price = entry + take if is_long else entry - take
else:
self._take_price = Decimal.MaxValue if is_long else Decimal.MinValue
def CreateClone(self):
return multi_factor_strategy()