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>
/// Simple MACD slope-following strategy converted from MQL5 Simple_MACD.mq5.
/// The strategy evaluates the MACD main line on completed candles and builds positions accordingly.
/// </summary>
public class SimpleMacdStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergence _macd = null!;
private decimal? _previousMacdValue;
private decimal? _prePreviousMacdValue;
private int? _previousSlope;
/// <summary>
/// Fast EMA period used for the MACD main line.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period used for the MACD main line.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Signal EMA period used by the MACD indicator.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Trading volume applied when new orders are sent.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Candle type used to feed the MACD indicator.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize Simple MACD strategy with default parameters.
/// </summary>
public SimpleMacdStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetDisplay("MACD Fast Period", "Fast EMA length for MACD calculation", "Indicators")
.SetOptimize(6, 18, 2);
_slowPeriod = Param(nameof(SlowPeriod), 26)
.SetDisplay("MACD Slow Period", "Slow EMA length for MACD calculation", "Indicators")
.SetOptimize(20, 40, 2);
_signalPeriod = Param(nameof(SignalPeriod), 9)
.SetDisplay("MACD Signal Period", "Signal EMA length maintained for compatibility", "Indicators")
.SetOptimize(6, 18, 1);
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetDisplay("Trade Volume", "Order volume used for each signal", "Risk")
.SetOptimize(0.1m, 1m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousMacdValue = null;
_prePreviousMacdValue = null;
_previousSlope = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Configure MACD indicator to match the source MQL strategy settings.
_macd = new MovingAverageConvergenceDivergence
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
};
// Subscribe to candle data and bind the MACD indicator.
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, ProcessCandle)
.Start();
// Prepare visual elements when charts are available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
// React only to completed candles to avoid premature decisions.
if (candle.State != CandleStates.Finished)
return;
// Ensure the indicator produced a valid value.
if (!_macd.IsFormed || !macdValue.IsFinal)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var macdLine = macdValue.ToDecimal();
// Accumulate historical MACD values for slope calculations.
if (_previousMacdValue is null)
{
_previousMacdValue = macdLine;
return;
}
if (_prePreviousMacdValue is null)
{
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
return;
}
var macdPrev = _previousMacdValue.Value;
var macdPrevPrev = _prePreviousMacdValue.Value;
var currentSlope = macdPrev > macdPrevPrev ? 1 : macdPrev < macdPrevPrev ? -1 : 0;
if (currentSlope == 0)
{
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
return;
}
if (_previousSlope == currentSlope)
{
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
return;
}
if (currentSlope > 0)
{
// Close shorts and open (or add to) longs when the MACD slope turns positive.
var volumeToBuy = TradeVolume + Math.Max(0m, -Position);
if (volumeToBuy > 0m)
{
BuyMarket(volumeToBuy);
LogInfo($"Bullish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Buying {volumeToBuy}.");
}
}
else
{
// Close longs and open (or add to) shorts when the MACD slope turns negative.
var volumeToSell = TradeVolume + Math.Max(0m, Position);
if (volumeToSell > 0m)
{
SellMarket(volumeToSell);
LogInfo($"Bearish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Selling {volumeToSell}.");
}
}
// Update stored values so the next candle compares the two previous MACD readings.
_previousSlope = currentSlope;
_prePreviousMacdValue = _previousMacdValue;
_previousMacdValue = macdLine;
}
}
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 MovingAverageConvergenceDivergence
from StockSharp.Algo.Strategies import Strategy
class simple_macd_strategy(Strategy):
def __init__(self):
super(simple_macd_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12)
self._slow_period = self.Param("SlowPeriod", 26)
self._signal_period = self.Param("SignalPeriod", 9)
self._trade_volume = self.Param("TradeVolume", 0.1)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(simple_macd_strategy, self).OnReseted()
self._prev_macd = None
self._prev_prev_macd = None
self._prev_slope = None
def OnStarted2(self, time):
super(simple_macd_strategy, self).OnStarted2(time)
self._prev_macd = None
self._prev_prev_macd = None
self._prev_slope = None
self._macd = MovingAverageConvergenceDivergence()
self._macd.ShortMa.Length = self._fast_period.Value
self._macd.LongMa.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.BindEx(self._macd, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._macd)
self.DrawOwnTrades(area)
def OnProcess(self, candle, macd_val):
if candle.State != CandleStates.Finished:
return
if not self._macd.IsFormed or not macd_val.IsFinal:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
macd_line = float(macd_val)
if self._prev_macd is None:
self._prev_macd = macd_line
return
if self._prev_prev_macd is None:
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
return
prev = self._prev_macd
prev_prev = self._prev_prev_macd
if prev > prev_prev:
current_slope = 1
elif prev < prev_prev:
current_slope = -1
else:
current_slope = 0
if current_slope == 0:
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
return
if self._prev_slope == current_slope:
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
return
trade_vol = self._trade_volume.Value
if current_slope > 0:
volume_to_buy = trade_vol + max(0, -self.Position)
if volume_to_buy > 0:
self.BuyMarket(volume_to_buy)
else:
volume_to_sell = trade_vol + max(0, self.Position)
if volume_to_sell > 0:
self.SellMarket(volume_to_sell)
self._prev_slope = current_slope
self._prev_prev_macd = self._prev_macd
self._prev_macd = macd_line
def CreateClone(self):
return simple_macd_strategy()