Multi Strategy Combo Strategy
Overview
The Multi Strategy Combo Strategy is a C# conversion of the MetaTrader 4 "Multi-Strategy iFSF" expert advisor. The original EA combines multiple indicators (MA, RSI, MACD, Stochastic, SAR) and wraps them with trend, Bollinger range and noise filters. The StockSharp port preserves the same idea using high-level SubscribeCandles().Bind(...) streams and indicator classes. Every enabled indicator produces a BUY/SELL vote; only when all votes agree does the strategy execute an order. Additional filters emulate the EA's combo modes.
Core logic
- Consensus engine – Moving averages, RSI, MACD, Stochastic and Parabolic SAR each provide a discrete signal. If all enabled indicators agree on BUY (or SELL) the consensus becomes bullish (or bearish).
- Combo factor (1–3) – Mirrors the EA's
Combo_Trader_Factor logic. Each factor mixes consensus with ADX trend detection and Bollinger range confirmation differently:
- Factor 1 prefers trending conditions. Range states rely on Bollinger reversals when enabled.
- Factor 2 requires stronger confirmation: trend and range filters must agree with the consensus.
- Factor 3 is the strictest variant, demanding alignment between all active modules.
- Trend detection – ADX on a configurable timeframe labels the market as trending up/down or ranging up/down.
- Bollinger filter – Uses medium (2σ) and wide (3σ) bands. Long signals require a bounce from the lower band confirmed by recent oversold RSI values; shorts mirror the behaviour on the upper band.
- Noise filter – ATR-based check that blocks new trades when volatility is too small (replacement for the Damiani Volatmeter).
- Auto-close – When enabled the strategy instantly exits if the consensus flips to the opposite direction.
Indicators and signals
- Moving averages – Three configurable MAs (method + length). Modes 1–5 reproduce the original crossover combinations (fast vs mid, mid vs slow, aggregated logic).
- RSI – Modes 1–4 cover overbought/oversold, momentum, combined and zone checks. All thresholds are adjustable.
- MACD – Four modes mimic the EA: trend slope, histogram cross while below/above zero, combined confirmation and signal line zero crossing.
- Stochastic oscillator – Either simple %K vs %D cross or cross with high/low thresholds.
- Parabolic SAR – Optional directional vote, supporting the "remember last signal" behaviour to avoid multiple triggers per trend.
Risk management
- Optional stop-loss/take-profit offsets (absolute price distance) configured via
StopLossOffset and TakeProfitOffset.
- Built-in trailing stop support through the StockSharp
StartProtection helper.
- Daily position protection follows the base
Strategy mechanics; no manual lot management is required.
Key parameters
- General –
ComboFactor, CandleType.
- Moving averages –
UseMa, MaMode, individual lengths/methods, candle timeframe, "remember last" flag.
- RSI –
UseRsi, RsiMode, RsiPeriod, overbought/oversold levels, zone thresholds, "remember last" flag.
- MACD –
UseMacd, MacdMode, fast/slow/signal lengths, candle timeframe, "remember last" flag.
- Stochastic –
UseStochastic, smoothing parameters, thresholds and candle timeframe.
- SAR –
UseSar, acceleration settings, candle timeframe.
- Trend filter –
UseTrendDetection, AdxPeriod, AdxLevel, candle timeframe.
- Bollinger filter –
UseBollingerFilter, BollingerPeriod, medium/wide deviations, RSI range length.
- Noise filter –
UseNoiseFilter, NoiseAtrLength, NoiseThreshold, candle timeframe.
- Auto close & risk –
UseAutoClose, AllowOppositeAfterClose, StopLossOffset, TakeProfitOffset, UseTrailingStop.
All parameters are exposed as StrategyParam<T> to support optimisation, validation and UI grouping.
Differences vs the MT4 EA
- Only StockSharp built-in indicators are used. The original option between ZeroLag and classic MACD is replaced with the native MACD implementation.
- All moving averages and oscillators operate on candle close prices. Price-type and shift offsets from MT4 (e.g.,
FastMa_Price, FastMa_Shift) are not available.
- The Damiani noise filter is approximated with ATR; the behaviour can be tuned via
NoiseThreshold.
- Money management and order re-tries are handled by StockSharp (no manual
OrderSend loops). The strategy works with aggregated positions (BuyMarket/SellMarket).
- The EA's comment panel and chart objects are omitted; instead logging is available through
LogInfo.
Usage
- Add the
MultiStrategyComboStrategy class to your StockSharp solution and compile.
- Instantiate the strategy, set
Security, Portfolio and desired Volume.
- Configure timeframes for each indicator if multi-timeframe confirmation is required (defaults follow the EA's inputs).
- Optionally adjust stop/take offsets, trailing behaviour and filter thresholds.
- Start the strategy. Trades will trigger on closed candles when all enabled modules agree according to the selected combo factor.
Conversion notes
- The strategy relies exclusively on high-level subscription APIs (
SubscribeCandles().Bind(...)) – no manual indicator buffers are used.
- Tabs are used for indentation per repository guidelines.
- Extensive inline comments highlight how EA concepts map to StockSharp code.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Multi Combo: Dual EMA crossover with RSI filter and ATR stops.
/// </summary>
public class MultiComboStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MultiComboStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 9)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 21)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (fastVal < slowVal && _prevFast >= _prevSlow)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 45)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 55)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class multi_combo_strategy(Strategy):
"""
Multi Combo: EMA crossover with RSI filter and ATR-based stops.
"""
def __init__(self):
super(multi_combo_strategy, self).__init__()
self._fast_ema = self.Param("FastEmaLength", 9).SetDisplay("Fast EMA", "Fast EMA", "Indicators")
self._slow_ema = self.Param("SlowEmaLength", 21).SetDisplay("Slow EMA", "Slow EMA", "Indicators")
self._rsi_length = self.Param("RsiLength", 14).SetDisplay("RSI", "RSI period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR", "ATR period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(multi_combo_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(multi_combo_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_ema.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_ema.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, rsi, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
rsi = float(rsi_val)
atr = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_fast == 0 or self._prev_slow == 0 or atr <= 0:
self._prev_fast = fast
self._prev_slow = slow
return
if self.Position > 0:
if fast < slow and self._prev_fast >= self._prev_slow:
self.SellMarket()
self._entry_price = 0
elif close <= self._entry_price - atr * 2.0:
self.SellMarket()
self._entry_price = 0
elif self.Position < 0:
if fast > slow and self._prev_fast <= self._prev_slow:
self.BuyMarket()
self._entry_price = 0
elif close >= self._entry_price + atr * 2.0:
self.BuyMarket()
self._entry_price = 0
if self.Position == 0:
if fast > slow and self._prev_fast <= self._prev_slow and rsi > 45:
self._entry_price = close
self.BuyMarket()
elif fast < slow and self._prev_fast >= self._prev_slow and rsi < 55:
self._entry_price = close
self.SellMarket()
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return multi_combo_strategy()