Syndicate Trader Strategy
This strategy is a StockSharp translation of the original MetaTrader script Syndicate_Trader_v_1_04.mq4 from folder MQL/12351.
It trades based on a crossover between fast and slow exponential moving averages with a volume spike confirmation. Optional session filters restrict trading to specific hours. Simple take profit and stop loss levels manage risk.
Details
- Entry Criteria:
- Long: Fast EMA crosses above slow EMA and volume exceeds the moving average multiplied by a configurable factor.
- Short: Fast EMA crosses below slow EMA with the same volume confirmation.
- Long/Short: Both.
- Exit Criteria:
- Opposite crossover.
- Stop loss or take profit hit.
- Outside allowed session window.
- Stops: Fixed stop loss and take profit in price points.
- Filters:
- Volume spike filter.
- Optional session time filter.
Parameters
| Name | Description |
|---|---|
FastEmaLength |
Period of the fast EMA. |
SlowEmaLength |
Period of the slow EMA. |
VolumeMaLength |
Period for averaging volume. |
VolumeMultiplier |
Multiplier applied to average volume to define a spike. |
TakeProfitPoints |
Take profit in price points. |
StopLossPoints |
Stop loss in price points. |
UseSessionFilter |
Enable or disable the session filter. |
SessionStartHour/SessionStartMinute |
Start time of trading session. |
SessionEndHour/SessionEndMinute |
End time of trading session. |
CandleType |
Candle type and timeframe. |
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA crossover strategy with volume spike confirmation.
/// </summary>
public class SyndicateTraderStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _volumeMaLength;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<bool> _useSessionFilter;
private readonly StrategyParam<int> _sessionStartHour;
private readonly StrategyParam<int> _sessionStartMinute;
private readonly StrategyParam<int> _sessionEndHour;
private readonly StrategyParam<int> _sessionEndMinute;
private readonly StrategyParam<DataType> _candleType;
private readonly ExponentialMovingAverage _fastEma = new();
private readonly ExponentialMovingAverage _slowEma = new();
private readonly SimpleMovingAverage _volumeMa = new();
private decimal _prevFast;
private decimal _prevSlow;
private bool _isInitialized;
private int _barsSinceTrade;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
/// <summary>
/// Volume moving average length.
/// </summary>
public int VolumeMaLength
{
get => _volumeMaLength.Value;
set => _volumeMaLength.Value = value;
}
/// <summary>
/// Multiplier applied to the volume average.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Take profit in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Enable session filtering.
/// </summary>
public bool UseSessionFilter
{
get => _useSessionFilter.Value;
set => _useSessionFilter.Value = value;
}
/// <summary>
/// Session start hour.
/// </summary>
public int SessionStartHour
{
get => _sessionStartHour.Value;
set => _sessionStartHour.Value = value;
}
/// <summary>
/// Session start minute.
/// </summary>
public int SessionStartMinute
{
get => _sessionStartMinute.Value;
set => _sessionStartMinute.Value = value;
}
/// <summary>
/// Session end hour.
/// </summary>
public int SessionEndHour
{
get => _sessionEndHour.Value;
set => _sessionEndHour.Value = value;
}
/// <summary>
/// Session end minute.
/// </summary>
public int SessionEndMinute
{
get => _sessionEndMinute.Value;
set => _sessionEndMinute.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public SyndicateTraderStrategy()
{
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length", "General");
_slowEmaLength = Param(nameof(SlowEmaLength), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length", "General");
_volumeMaLength = Param(nameof(VolumeMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume MA", "Volume MA length", "General");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.8m)
.SetGreaterThanZero()
.SetDisplay("Volume Mult", "Volume spike multiplier", "General");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 1200m)
.SetDisplay("Take Profit", "Take profit in price points", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 700m)
.SetDisplay("Stop Loss", "Stop loss in price points", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 2)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
_useSessionFilter = Param(nameof(UseSessionFilter), false)
.SetDisplay("Use Session", "Enable session filter", "Session");
_sessionStartHour = Param(nameof(SessionStartHour), 0)
.SetDisplay("Start Hour", "Session start hour", "Session");
_sessionStartMinute = Param(nameof(SessionStartMinute), 0)
.SetDisplay("Start Minute", "Session start minute", "Session");
_sessionEndHour = Param(nameof(SessionEndHour), 23)
.SetDisplay("End Hour", "Session end hour", "Session");
_sessionEndMinute = Param(nameof(SessionEndMinute), 59)
.SetDisplay("End Minute", "Session end minute", "Session");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma.Length = FastEmaLength;
_slowEma.Length = SlowEmaLength;
_volumeMa.Length = VolumeMaLength;
_fastEma.Reset();
_slowEma.Reset();
_volumeMa.Reset();
_prevFast = 0m;
_prevSlow = 0m;
_isInitialized = false;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma.Length = FastEmaLength;
_slowEma.Length = SlowEmaLength;
_volumeMa.Length = VolumeMaLength;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(TakeProfitPoints, UnitTypes.Absolute),
stopLoss: new Unit(StopLossPoints, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (UseSessionFilter)
{
var time = candle.OpenTime.TimeOfDay;
var start = new TimeSpan(SessionStartHour, SessionStartMinute, 0);
var end = new TimeSpan(SessionEndHour, SessionEndMinute, 0);
if (time < start || time > end)
return;
}
var fast = _fastEma.Process(new DecimalIndicatorValue(_fastEma, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var slow = _slowEma.Process(new DecimalIndicatorValue(_slowEma, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
var volumeAvg = _volumeMa.Process(new DecimalIndicatorValue(_volumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_fastEma.IsFormed || !_slowEma.IsFormed || !_volumeMa.IsFormed)
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
if (!_isInitialized)
{
_prevFast = fast;
_prevSlow = slow;
_isInitialized = true;
return;
}
var crossUp = fast > slow && _prevFast <= _prevSlow;
var crossDown = fast < slow && _prevFast >= _prevSlow;
var hasVolumeSpike = candle.TotalVolume >= volumeAvg * VolumeMultiplier;
if (_barsSinceTrade >= CooldownBars && hasVolumeSpike)
{
if (crossUp && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
else if (crossDown && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_prevFast = fast;
_prevSlow = slow;
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class syndicate_trader_strategy(Strategy):
def __init__(self):
super(syndicate_trader_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA length", "General")
self._slow_ema_length = self.Param("SlowEmaLength", 30) \
.SetDisplay("Slow EMA", "Slow EMA length", "General")
self._volume_ma_length = self.Param("VolumeMaLength", 20) \
.SetDisplay("Volume MA", "Volume MA length", "General")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.8) \
.SetDisplay("Volume Mult", "Volume spike multiplier", "General")
self._take_profit_points = self.Param("TakeProfitPoints", 1200.0) \
.SetDisplay("Take Profit", "Take profit in price points", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 700.0) \
.SetDisplay("Stop Loss", "Stop loss in price points", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 2) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._use_session_filter = self.Param("UseSessionFilter", False) \
.SetDisplay("Use Session", "Enable session filter", "Session")
self._session_start_hour = self.Param("SessionStartHour", 0) \
.SetDisplay("Start Hour", "Session start hour", "Session")
self._session_start_minute = self.Param("SessionStartMinute", 0) \
.SetDisplay("Start Minute", "Session start minute", "Session")
self._session_end_hour = self.Param("SessionEndHour", 23) \
.SetDisplay("End Hour", "Session end hour", "Session")
self._session_end_minute = self.Param("SessionEndMinute", 59) \
.SetDisplay("End Minute", "Session end minute", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._fast_ema = ExponentialMovingAverage()
self._slow_ema = ExponentialMovingAverage()
self._volume_ma = SimpleMovingAverage()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._bars_since_trade = 0
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@FastEmaLength.setter
def FastEmaLength(self, value):
self._fast_ema_length.Value = value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@SlowEmaLength.setter
def SlowEmaLength(self, value):
self._slow_ema_length.Value = value
@property
def VolumeMaLength(self):
return self._volume_ma_length.Value
@VolumeMaLength.setter
def VolumeMaLength(self, value):
self._volume_ma_length.Value = value
@property
def VolumeMultiplier(self):
return self._volume_multiplier.Value
@VolumeMultiplier.setter
def VolumeMultiplier(self, value):
self._volume_multiplier.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def UseSessionFilter(self):
return self._use_session_filter.Value
@UseSessionFilter.setter
def UseSessionFilter(self, value):
self._use_session_filter.Value = value
@property
def SessionStartHour(self):
return self._session_start_hour.Value
@SessionStartHour.setter
def SessionStartHour(self, value):
self._session_start_hour.Value = value
@property
def SessionStartMinute(self):
return self._session_start_minute.Value
@SessionStartMinute.setter
def SessionStartMinute(self, value):
self._session_start_minute.Value = value
@property
def SessionEndHour(self):
return self._session_end_hour.Value
@SessionEndHour.setter
def SessionEndHour(self, value):
self._session_end_hour.Value = value
@property
def SessionEndMinute(self):
return self._session_end_minute.Value
@SessionEndMinute.setter
def SessionEndMinute(self, value):
self._session_end_minute.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(syndicate_trader_strategy, self).OnStarted2(time)
self._fast_ema.Length = self.FastEmaLength
self._slow_ema.Length = self.SlowEmaLength
self._volume_ma.Length = self.VolumeMaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(self.TakeProfitPoints, UnitTypes.Absolute),
stopLoss=Unit(self.StopLossPoints, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.UseSessionFilter:
time_of_day = candle.OpenTime.TimeOfDay
start = TimeSpan(self.SessionStartHour, self.SessionStartMinute, 0)
end = TimeSpan(self.SessionEndHour, self.SessionEndMinute, 0)
if time_of_day < start or time_of_day > end:
return
fast = float(process_float(self._fast_ema, candle.ClosePrice, candle.OpenTime, True))
slow = float(process_float(self._slow_ema, candle.ClosePrice, candle.OpenTime, True))
volume_avg = float(process_float(self._volume_ma, candle.TotalVolume, candle.OpenTime, True))
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed or not self._volume_ma.IsFormed:
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
if not self._is_initialized:
self._prev_fast = fast
self._prev_slow = slow
self._is_initialized = True
return
cross_up = fast > slow and self._prev_fast <= self._prev_slow
cross_down = fast < slow and self._prev_fast >= self._prev_slow
has_volume_spike = float(candle.TotalVolume) >= volume_avg * float(self.VolumeMultiplier)
if self._bars_since_trade >= self.CooldownBars and has_volume_spike:
pos = self.Position
if cross_up and pos <= 0:
self.BuyMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
elif cross_down and pos >= 0:
self.SellMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
self._prev_fast = fast
self._prev_slow = slow
def OnReseted(self):
super(syndicate_trader_strategy, self).OnReseted()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema.Length = self.SlowEmaLength
self._volume_ma.Length = self.VolumeMaLength
self._fast_ema.Reset()
self._slow_ema.Reset()
self._volume_ma.Reset()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return syndicate_trader_strategy()