using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend-following strategy inspired by the "Mc_valute" MetaTrader expert advisor.
/// Combines smoothed moving averages, an Ichimoku cloud filter and a MACD confirmation.
/// </summary>
public class McValuteCloudStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _filterMaLength;
private readonly StrategyParam<int> _blueMaLength;
private readonly StrategyParam<int> _limeMaLength;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdSignalLength;
private readonly StrategyParam<int> _tenkanLength;
private readonly StrategyParam<int> _kijunLength;
private readonly StrategyParam<int> _senkouLength;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private ExponentialMovingAverage _filterMa;
private SmoothedMovingAverage _blueMa;
private SmoothedMovingAverage _limeMa;
private MovingAverageConvergenceDivergenceSignal _macd;
private Ichimoku _ichimoku;
private decimal? _filterValue;
private decimal? _blueValue;
private decimal? _limeValue;
private decimal? _senkouAValue;
private decimal? _senkouBValue;
private decimal? _macdMainValue;
private decimal? _macdSignalValue;
private DateTimeOffset _lastProcessedTime;
/// <summary>
/// Initializes a new instance of the <see cref="McValuteCloudStrategy"/> class.
/// </summary>
public McValuteCloudStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used for signals", "General");
_filterMaLength = Param(nameof(FilterMaLength), 3)
.SetDisplay("Filter EMA", "Length of the trend filter EMA", "Trend")
.SetOptimize(2, 20, 1);
_blueMaLength = Param(nameof(BlueMaLength), 13)
.SetDisplay("Blue SMMA", "Length of the slower smoothed MA", "Trend")
.SetOptimize(5, 60, 1);
_limeMaLength = Param(nameof(LimeMaLength), 5)
.SetDisplay("Lime SMMA", "Length of the faster smoothed MA", "Trend")
.SetOptimize(3, 40, 1);
_macdFastLength = Param(nameof(MacdFastLength), 12)
.SetDisplay("MACD Fast", "Short EMA length for the MACD", "Momentum")
.SetOptimize(5, 30, 1);
_macdSlowLength = Param(nameof(MacdSlowLength), 26)
.SetDisplay("MACD Slow", "Long EMA length for the MACD", "Momentum")
.SetOptimize(10, 80, 1);
_macdSignalLength = Param(nameof(MacdSignalLength), 9)
.SetDisplay("MACD Signal", "Signal EMA length for the MACD", "Momentum")
.SetOptimize(3, 30, 1);
_tenkanLength = Param(nameof(TenkanLength), 12)
.SetDisplay("Tenkan", "Tenkan-sen length for the Ichimoku cloud", "Ichimoku")
.SetOptimize(5, 30, 1);
_kijunLength = Param(nameof(KijunLength), 20)
.SetDisplay("Kijun", "Kijun-sen length for the Ichimoku cloud", "Ichimoku")
.SetOptimize(10, 60, 1);
_senkouLength = Param(nameof(SenkouLength), 40)
.SetDisplay("Senkou Span B", "Span B length for the Ichimoku cloud", "Ichimoku")
.SetOptimize(20, 120, 1);
_takeProfit = Param(nameof(TakeProfit), 30)
.SetDisplay("Take Profit", "Take profit distance in points", "Risk")
.SetOptimize(10, 200, 5);
_stopLoss = Param(nameof(StopLoss), 350)
.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk")
.SetOptimize(50, 600, 10);
_lastProcessedTime = DateTimeOffset.MinValue;
}
/// <summary>
/// Timeframe used for the working candles.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Length of the EMA filter.
/// </summary>
public int FilterMaLength
{
get => _filterMaLength.Value;
set => _filterMaLength.Value = value;
}
/// <summary>
/// Length of the slower smoothed moving average.
/// </summary>
public int BlueMaLength
{
get => _blueMaLength.Value;
set => _blueMaLength.Value = value;
}
/// <summary>
/// Length of the faster smoothed moving average.
/// </summary>
public int LimeMaLength
{
get => _limeMaLength.Value;
set => _limeMaLength.Value = value;
}
/// <summary>
/// Fast EMA length used by the MACD.
/// </summary>
public int MacdFastLength
{
get => _macdFastLength.Value;
set => _macdFastLength.Value = value;
}
/// <summary>
/// Slow EMA length used by the MACD.
/// </summary>
public int MacdSlowLength
{
get => _macdSlowLength.Value;
set => _macdSlowLength.Value = value;
}
/// <summary>
/// Signal EMA length used by the MACD.
/// </summary>
public int MacdSignalLength
{
get => _macdSignalLength.Value;
set => _macdSignalLength.Value = value;
}
/// <summary>
/// Tenkan-sen length for the Ichimoku indicator.
/// </summary>
public int TenkanLength
{
get => _tenkanLength.Value;
set => _tenkanLength.Value = value;
}
/// <summary>
/// Kijun-sen length for the Ichimoku indicator.
/// </summary>
public int KijunLength
{
get => _kijunLength.Value;
set => _kijunLength.Value = value;
}
/// <summary>
/// Senkou Span B length for the Ichimoku indicator.
/// </summary>
public int SenkouLength
{
get => _senkouLength.Value;
set => _senkouLength.Value = value;
}
/// <summary>
/// Take profit distance in points.
/// </summary>
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss distance in points.
/// </summary>
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_filterMa = null;
_blueMa = null;
_limeMa = null;
_macd = null;
_ichimoku = null;
_filterValue = null;
_blueValue = null;
_limeValue = null;
_senkouAValue = null;
_senkouBValue = null;
_macdMainValue = null;
_macdSignalValue = null;
_lastProcessedTime = DateTimeOffset.MinValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_filterMa = new ExponentialMovingAverage { Length = FilterMaLength };
_blueMa = new SmoothedMovingAverage { Length = BlueMaLength };
_limeMa = new SmoothedMovingAverage { Length = LimeMaLength };
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastLength },
LongMa = { Length = MacdSlowLength },
},
SignalMa = { Length = MacdSignalLength }
};
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanLength },
Kijun = { Length = KijunLength },
SenkouB = { Length = SenkouLength }
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_filterMa, _blueMa, _limeMa, ProcessMovingAverages)
.BindEx(_macd, ProcessMacd)
.BindEx(_ichimoku, ProcessIchimoku)
.Start();
StartProtection(null, null);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _filterMa);
DrawIndicator(area, _blueMa);
DrawIndicator(area, _limeMa);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
}
private void ProcessMovingAverages(ICandleMessage candle, decimal filter, decimal blue, decimal lime)
{
if (candle.State != CandleStates.Finished)
return;
_filterValue = filter;
_blueValue = blue;
_limeValue = lime;
TryTrade(candle);
}
private void ProcessMacd(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
var typed = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (typed.Macd is not decimal macd || typed.Signal is not decimal signal)
return;
_macdMainValue = macd;
_macdSignalValue = signal;
TryTrade(candle);
}
private void ProcessIchimoku(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
var ichimokuValue = (IchimokuValue)value;
if (ichimokuValue.SenkouA is not decimal spanA || ichimokuValue.SenkouB is not decimal spanB)
return;
_senkouAValue = spanA;
_senkouBValue = spanB;
TryTrade(candle);
}
private void TryTrade(ICandleMessage candle)
{
// indicators formed check removed
if (candle.State != CandleStates.Finished)
return;
if (_lastProcessedTime == candle.OpenTime)
return;
if (_filterValue is not decimal filter ||
_blueValue is not decimal blue ||
_limeValue is not decimal lime ||
_senkouAValue is not decimal spanA ||
_senkouBValue is not decimal spanB ||
_macdMainValue is not decimal macd ||
_macdSignalValue is not decimal signal)
{
return;
}
_lastProcessedTime = candle.OpenTime;
var cloudTop = Math.Max(spanA, spanB);
var cloudBottom = Math.Min(spanA, spanB);
var maUpper = Math.Max(blue, lime);
var maLower = Math.Min(blue, lime);
var allowLong = filter > maUpper && filter > cloudTop && macd > signal;
var allowShort = filter < maLower && filter < cloudBottom && macd < signal;
var closePrice = candle.ClosePrice;
if (allowLong && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (allowShort && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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, SmoothedMovingAverage,
MovingAverageConvergenceDivergenceSignal, Ichimoku
)
from StockSharp.Algo.Strategies import Strategy
class mc_valute_cloud_strategy(Strategy):
"""Trend-following strategy combining smoothed MAs, Ichimoku cloud filter and MACD confirmation."""
def __init__(self):
super(mc_valute_cloud_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe used for signals", "General")
self._filter_ma_length = self.Param("FilterMaLength", 3) \
.SetDisplay("Filter EMA", "Length of the trend filter EMA", "Trend")
self._blue_ma_length = self.Param("BlueMaLength", 13) \
.SetDisplay("Blue SMMA", "Length of the slower smoothed MA", "Trend")
self._lime_ma_length = self.Param("LimeMaLength", 5) \
.SetDisplay("Lime SMMA", "Length of the faster smoothed MA", "Trend")
self._macd_fast_length = self.Param("MacdFastLength", 12) \
.SetDisplay("MACD Fast", "Short EMA length for the MACD", "Momentum")
self._macd_slow_length = self.Param("MacdSlowLength", 26) \
.SetDisplay("MACD Slow", "Long EMA length for the MACD", "Momentum")
self._macd_signal_length = self.Param("MacdSignalLength", 9) \
.SetDisplay("MACD Signal", "Signal EMA length for the MACD", "Momentum")
self._tenkan_length = self.Param("TenkanLength", 12) \
.SetDisplay("Tenkan", "Tenkan-sen length for the Ichimoku cloud", "Ichimoku")
self._kijun_length = self.Param("KijunLength", 20) \
.SetDisplay("Kijun", "Kijun-sen length for the Ichimoku cloud", "Ichimoku")
self._senkou_length = self.Param("SenkouLength", 40) \
.SetDisplay("Senkou Span B", "Span B length for the Ichimoku cloud", "Ichimoku")
self._take_profit = self.Param("TakeProfit", 30) \
.SetDisplay("Take Profit", "Take profit distance in points", "Risk")
self._stop_loss = self.Param("StopLoss", 350) \
.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk")
self._filter_value = None
self._blue_value = None
self._lime_value = None
self._senkou_a_value = None
self._senkou_b_value = None
self._macd_main_value = None
self._macd_signal_value = None
self._last_processed_time = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FilterMaLength(self):
return self._filter_ma_length.Value
@property
def BlueMaLength(self):
return self._blue_ma_length.Value
@property
def LimeMaLength(self):
return self._lime_ma_length.Value
@property
def MacdFastLength(self):
return self._macd_fast_length.Value
@property
def MacdSlowLength(self):
return self._macd_slow_length.Value
@property
def MacdSignalLength(self):
return self._macd_signal_length.Value
@property
def TenkanLength(self):
return self._tenkan_length.Value
@property
def KijunLength(self):
return self._kijun_length.Value
@property
def SenkouLength(self):
return self._senkou_length.Value
@property
def TakeProfit(self):
return self._take_profit.Value
@property
def StopLoss(self):
return self._stop_loss.Value
def OnReseted(self):
super(mc_valute_cloud_strategy, self).OnReseted()
self._filter_value = None
self._blue_value = None
self._lime_value = None
self._senkou_a_value = None
self._senkou_b_value = None
self._macd_main_value = None
self._macd_signal_value = None
self._last_processed_time = None
def OnStarted2(self, time):
super(mc_valute_cloud_strategy, self).OnStarted2(time)
filter_ma = ExponentialMovingAverage()
filter_ma.Length = self.FilterMaLength
blue_ma = SmoothedMovingAverage()
blue_ma.Length = self.BlueMaLength
lime_ma = SmoothedMovingAverage()
lime_ma.Length = self.LimeMaLength
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.MacdFastLength
macd.Macd.LongMa.Length = self.MacdSlowLength
macd.SignalMa.Length = self.MacdSignalLength
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self.TenkanLength
ichimoku.Kijun.Length = self.KijunLength
ichimoku.SenkouB.Length = self.SenkouLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(filter_ma, blue_ma, lime_ma, self._process_moving_averages)
subscription.BindEx(macd, self._process_macd)
subscription.BindEx(ichimoku, self._process_ichimoku)
subscription.Start()
def _process_moving_averages(self, candle, filter_val, blue_val, lime_val):
if candle.State != CandleStates.Finished:
return
self._filter_value = float(filter_val)
self._blue_value = float(blue_val)
self._lime_value = float(lime_val)
self._try_trade(candle)
def _process_macd(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
macd_raw = macd_value.Macd
signal_raw = macd_value.Signal
if macd_raw is None or signal_raw is None:
return
self._macd_main_value = float(macd_raw)
self._macd_signal_value = float(signal_raw)
self._try_trade(candle)
def _process_ichimoku(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
senkou_a = ichimoku_value.SenkouA
senkou_b = ichimoku_value.SenkouB
if senkou_a is None or senkou_b is None:
return
self._senkou_a_value = float(senkou_a)
self._senkou_b_value = float(senkou_b)
self._try_trade(candle)
def _try_trade(self, candle):
if candle.State != CandleStates.Finished:
return
if self._last_processed_time == candle.OpenTime:
return
if self._filter_value is None or self._blue_value is None or self._lime_value is None:
return
if self._senkou_a_value is None or self._senkou_b_value is None:
return
if self._macd_main_value is None or self._macd_signal_value is None:
return
self._last_processed_time = candle.OpenTime
filter_val = self._filter_value
blue = self._blue_value
lime = self._lime_value
span_a = self._senkou_a_value
span_b = self._senkou_b_value
macd = self._macd_main_value
signal = self._macd_signal_value
cloud_top = max(span_a, span_b)
cloud_bottom = min(span_a, span_b)
ma_upper = max(blue, lime)
ma_lower = min(blue, lime)
allow_long = filter_val > ma_upper and filter_val > cloud_top and macd > signal
allow_short = filter_val < ma_lower and filter_val < cloud_bottom and macd < signal
if allow_long and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif allow_short and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return mc_valute_cloud_strategy()