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>
/// Moving Average Trade System originally written for MetaTrader.
/// Uses four simple moving averages on median price to detect medium-term trend reversals.
/// </summary>
public class MovingAverageTradeSystemStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfitSteps;
private readonly StrategyParam<decimal> _stopLossSteps;
private readonly StrategyParam<decimal> _trailingStopSteps;
private readonly StrategyParam<decimal> _slopeThresholdSteps;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _mediumPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _smaFast;
private SimpleMovingAverage _smaMedium;
private SimpleMovingAverage _smaSignal;
private SimpleMovingAverage _smaSlow;
private decimal? _previousSignal;
private decimal? _previousSlow;
private decimal? _longEntryPrice;
private decimal? _longTakeProfit;
private decimal? _longStopLoss;
private decimal _longHigh;
private decimal? _shortEntryPrice;
private decimal? _shortTakeProfit;
private decimal? _shortStopLoss;
private decimal _shortLow;
/// <summary>
/// Desired take profit distance in price steps.
/// </summary>
public decimal TakeProfitSteps
{
get => _takeProfitSteps.Value;
set => _takeProfitSteps.Value = value;
}
/// <summary>
/// Desired stop loss distance in price steps.
/// </summary>
public decimal StopLossSteps
{
get => _stopLossSteps.Value;
set => _stopLossSteps.Value = value;
}
/// <summary>
/// Trailing stop offset in price steps.
/// </summary>
public decimal TrailingStopSteps
{
get => _trailingStopSteps.Value;
set => _trailingStopSteps.Value = value;
}
/// <summary>
/// Minimum separation between the signal SMA and the slow SMA (in price steps) to validate breakouts.
/// </summary>
public decimal SlopeThresholdSteps
{
get => _slopeThresholdSteps.Value;
set => _slopeThresholdSteps.Value = value;
}
/// <summary>
/// Fast SMA period (originally 5).
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Medium SMA period (originally 20).
/// </summary>
public int MediumPeriod
{
get => _mediumPeriod.Value;
set => _mediumPeriod.Value = value;
}
/// <summary>
/// Signal SMA period that must cross the slow SMA upward or downward (originally 40).
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Slow SMA period defining the background trend (originally 60).
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Candle type for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MovingAverageTradeSystemStrategy"/> class.
/// </summary>
public MovingAverageTradeSystemStrategy()
{
_takeProfitSteps = Param(nameof(TakeProfitSteps), 50m)
.SetGreaterThanZero()
.SetDisplay("Take Profit (steps)", "Distance to take profit in price steps", "Risk Management")
.SetOptimize(10m, 200m, 10m);
_stopLossSteps = Param(nameof(StopLossSteps), 50m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (steps)", "Distance to stop loss in price steps", "Risk Management")
.SetOptimize(10m, 200m, 10m);
_trailingStopSteps = Param(nameof(TrailingStopSteps), 11m)
.SetNotNegative()
.SetDisplay("Trailing Stop (steps)", "Trailing stop offset in price steps", "Risk Management")
.SetOptimize(0m, 100m, 5m);
_slopeThresholdSteps = Param(nameof(SlopeThresholdSteps), 10m)
.SetNotNegative()
.SetDisplay("Slope Threshold", "Minimum SMA40 vs SMA60 distance in steps", "Signals")
.SetOptimize(0m, 10m, 1m);
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Fast SMA length", "Signals")
.SetOptimize(3, 20, 1);
_mediumPeriod = Param(nameof(MediumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Medium SMA", "Medium SMA length", "Signals")
.SetOptimize(10, 60, 1);
_signalPeriod = Param(nameof(SignalPeriod), 40)
.SetGreaterThanZero()
.SetDisplay("Signal SMA", "Crossing SMA length", "Signals")
.SetOptimize(20, 80, 1);
_slowPeriod = Param(nameof(SlowPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Slow SMA length", "Signals")
.SetOptimize(30, 120, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type for calculations", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousSignal = null;
_previousSlow = null;
ResetLongState();
ResetShortState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create the moving averages on median price to match the original indicator setup.
_smaFast = new SMA { Length = FastPeriod };
_smaMedium = new SMA { Length = MediumPeriod };
_smaSignal = new SMA { Length = SignalPeriod };
_smaSlow = new SMA { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_smaFast, _smaMedium, _smaSignal, _smaSlow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _smaFast);
DrawIndicator(area, _smaMedium);
DrawIndicator(area, _smaSignal);
DrawIndicator(area, _smaSlow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaFast, decimal smaMedium, decimal smaSignal, decimal smaSlow)
{
if (candle.State != CandleStates.Finished)
return;
if (!_smaFast.IsFormed || !_smaMedium.IsFormed || !_smaSignal.IsFormed || !_smaSlow.IsFormed)
{
_previousSignal = smaSignal;
_previousSlow = smaSlow;
return;
}
var previousSignal = _previousSignal;
var previousSlow = _previousSlow;
_previousSignal = smaSignal;
_previousSlow = smaSlow;
if (previousSignal is null || previousSlow is null)
return;
var priceStep = GetPriceStep();
var slopeThreshold = SlopeThresholdSteps * priceStep;
var bullishStructure = smaFast > smaMedium && smaMedium > smaSlow;
var bearishStructure = smaFast < smaMedium && smaMedium < smaSlow;
var bullishSlope = (smaSignal - smaSlow) >= slopeThreshold;
var bearishSlope = (smaSlow - smaSignal) >= slopeThreshold;
var bullishCross = previousSignal.Value <= previousSlow.Value && smaSignal > smaSlow;
var bearishCross = previousSignal.Value >= previousSlow.Value && smaSignal < smaSlow;
var buySignal = bullishStructure && bullishSlope && bullishCross;
var sellSignal = bearishStructure && bearishSlope && bearishCross;
if (buySignal && Position <= 0m)
{
// Flip the position by buying enough volume to cover shorts and add the desired long exposure.
var volume = Volume + (Position < 0m ? Math.Abs(Position) : 0m);
if (volume > 0m)
BuyMarket();
}
else if (sellSignal && Position >= 0m)
{
// Flip the position by selling enough volume to cover longs and add the desired short exposure.
var volume = Volume + (Position > 0m ? Position : 0m);
if (volume > 0m)
SellMarket();
}
else
{
// Manage open positions when no new entry is triggered this bar.
if (Position > 0m)
{
if (smaSignal <= smaSlow)
{
SellMarket();
}
else
{
ManageLongPosition(candle, priceStep);
}
}
else if (Position < 0m)
{
if (smaSignal >= smaSlow)
{
BuyMarket();
}
else
{
ManageShortPosition(candle, priceStep);
}
}
}
}
private void ManageLongPosition(ICandleMessage candle, decimal priceStep)
{
if (!_longEntryPrice.HasValue)
return;
_longHigh = Math.Max(_longHigh, candle.HighPrice);
if (_longTakeProfit.HasValue && candle.HighPrice >= _longTakeProfit.Value)
{
SellMarket();
return;
}
if (_longStopLoss.HasValue && candle.LowPrice <= _longStopLoss.Value)
{
SellMarket();
return;
}
if (TrailingStopSteps <= 0m)
return;
var trailingLevel = _longHigh - TrailingStopSteps * priceStep;
if (candle.ClosePrice <= trailingLevel)
SellMarket();
}
private void ManageShortPosition(ICandleMessage candle, decimal priceStep)
{
if (!_shortEntryPrice.HasValue)
return;
_shortLow = Math.Min(_shortLow, candle.LowPrice);
if (_shortTakeProfit.HasValue && candle.LowPrice <= _shortTakeProfit.Value)
{
BuyMarket();
return;
}
if (_shortStopLoss.HasValue && candle.HighPrice >= _shortStopLoss.Value)
{
BuyMarket();
return;
}
if (TrailingStopSteps <= 0m)
return;
var trailingLevel = _shortLow + TrailingStopSteps * priceStep;
if (candle.ClosePrice >= trailingLevel)
BuyMarket();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
var price = trade.Trade?.Price;
if (price is null)
return;
if (Position > 0m)
{
// After switching to long reset the short tracking and configure profit/stop levels.
ResetShortState();
SetupLongState(price.Value);
}
else if (Position < 0m)
{
// After switching to short reset the long tracking and configure profit/stop levels.
ResetLongState();
SetupShortState(price.Value);
}
else
{
// Flat position clears both trackers.
ResetLongState();
ResetShortState();
}
}
private void SetupLongState(decimal entryPrice)
{
var priceStep = GetPriceStep();
_longEntryPrice = entryPrice;
_longHigh = entryPrice;
_longTakeProfit = TakeProfitSteps > 0m ? entryPrice + TakeProfitSteps * priceStep : null;
_longStopLoss = StopLossSteps > 0m ? entryPrice - StopLossSteps * priceStep : null;
}
private void SetupShortState(decimal entryPrice)
{
var priceStep = GetPriceStep();
_shortEntryPrice = entryPrice;
_shortLow = entryPrice;
_shortTakeProfit = TakeProfitSteps > 0m ? entryPrice - TakeProfitSteps * priceStep : null;
_shortStopLoss = StopLossSteps > 0m ? entryPrice + StopLossSteps * priceStep : null;
}
private void ResetLongState()
{
_longEntryPrice = null;
_longTakeProfit = null;
_longStopLoss = null;
_longHigh = 0m;
}
private void ResetShortState()
{
_shortEntryPrice = null;
_shortTakeProfit = null;
_shortStopLoss = null;
_shortLow = 0m;
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep;
return step is null || step == 0m ? 1m : step.Value;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class moving_average_trade_system_strategy(Strategy):
def __init__(self):
super(moving_average_trade_system_strategy, self).__init__()
self._take_profit_steps = self.Param("TakeProfitSteps", 50.0)
self._stop_loss_steps = self.Param("StopLossSteps", 50.0)
self._trailing_stop_steps = self.Param("TrailingStopSteps", 11.0)
self._slope_threshold_steps = self.Param("SlopeThresholdSteps", 10.0)
self._fast_period = self.Param("FastPeriod", 5)
self._medium_period = self.Param("MediumPeriod", 20)
self._signal_period = self.Param("SignalPeriod", 40)
self._slow_period = self.Param("SlowPeriod", 60)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_signal = None
self._prev_slow = None
self._long_entry_price = None
self._long_take_profit = None
self._long_stop_loss = None
self._long_high = 0.0
self._short_entry_price = None
self._short_take_profit = None
self._short_stop_loss = None
self._short_low = 0.0
self._sma_fast = None
self._sma_medium = None
self._sma_signal = None
self._sma_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(moving_average_trade_system_strategy, self).OnReseted()
self._prev_signal = None
self._prev_slow = None
self._reset_long_state()
self._reset_short_state()
def OnStarted2(self, time):
super(moving_average_trade_system_strategy, self).OnStarted2(time)
self._sma_fast = SimpleMovingAverage()
self._sma_fast.Length = self._fast_period.Value
self._sma_medium = SimpleMovingAverage()
self._sma_medium.Length = self._medium_period.Value
self._sma_signal = SimpleMovingAverage()
self._sma_signal.Length = self._signal_period.Value
self._sma_slow = SimpleMovingAverage()
self._sma_slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._sma_fast, self._sma_medium, self._sma_signal, self._sma_slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma_fast)
self.DrawIndicator(area, self._sma_medium)
self.DrawIndicator(area, self._sma_signal)
self.DrawIndicator(area, self._sma_slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, medium_val, signal_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
medium = float(medium_val)
sig = float(signal_val)
slow = float(slow_val)
if not self._sma_fast.IsFormed or not self._sma_medium.IsFormed or not self._sma_signal.IsFormed or not self._sma_slow.IsFormed:
self._prev_signal = sig
self._prev_slow = slow
return
prev_signal = self._prev_signal
prev_slow = self._prev_slow
self._prev_signal = sig
self._prev_slow = slow
if prev_signal is None or prev_slow is None:
return
price_step = self._get_price_step()
slope_threshold = float(self._slope_threshold_steps.Value) * price_step
bullish_structure = fast > medium and medium > slow
bearish_structure = fast < medium and medium < slow
bullish_slope = (sig - slow) >= slope_threshold
bearish_slope = (slow - sig) >= slope_threshold
bullish_cross = prev_signal <= prev_slow and sig > slow
bearish_cross = prev_signal >= prev_slow and sig < slow
buy_signal = bullish_structure and bullish_slope and bullish_cross
sell_signal = bearish_structure and bearish_slope and bearish_cross
if buy_signal and self.Position <= 0:
self.BuyMarket()
elif sell_signal and self.Position >= 0:
self.SellMarket()
else:
if self.Position > 0:
if sig <= slow:
self.SellMarket()
else:
self._manage_long(candle, price_step)
elif self.Position < 0:
if sig >= slow:
self.BuyMarket()
else:
self._manage_short(candle, price_step)
def _manage_long(self, candle, price_step):
if self._long_entry_price is None:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._long_high = max(self._long_high, high)
if self._long_take_profit is not None and high >= self._long_take_profit:
self.SellMarket()
return
if self._long_stop_loss is not None and low <= self._long_stop_loss:
self.SellMarket()
return
trailing = float(self._trailing_stop_steps.Value)
if trailing <= 0:
return
trailing_level = self._long_high - trailing * price_step
if close <= trailing_level:
self.SellMarket()
def _manage_short(self, candle, price_step):
if self._short_entry_price is None:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._short_low = min(self._short_low, low)
if self._short_take_profit is not None and low <= self._short_take_profit:
self.BuyMarket()
return
if self._short_stop_loss is not None and high >= self._short_stop_loss:
self.BuyMarket()
return
trailing = float(self._trailing_stop_steps.Value)
if trailing <= 0:
return
trailing_level = self._short_low + trailing * price_step
if close >= trailing_level:
self.BuyMarket()
def OnOwnTradeReceived(self, trade):
super(moving_average_trade_system_strategy, self).OnOwnTradeReceived(trade)
t = trade.Trade
if t is None:
return
price = float(t.Price)
if self.Position > 0:
self._reset_short_state()
self._setup_long_state(price)
elif self.Position < 0:
self._reset_long_state()
self._setup_short_state(price)
else:
self._reset_long_state()
self._reset_short_state()
def _setup_long_state(self, entry_price):
price_step = self._get_price_step()
self._long_entry_price = entry_price
self._long_high = entry_price
tp = float(self._take_profit_steps.Value)
sl = float(self._stop_loss_steps.Value)
self._long_take_profit = entry_price + tp * price_step if tp > 0 else None
self._long_stop_loss = entry_price - sl * price_step if sl > 0 else None
def _setup_short_state(self, entry_price):
price_step = self._get_price_step()
self._short_entry_price = entry_price
self._short_low = entry_price
tp = float(self._take_profit_steps.Value)
sl = float(self._stop_loss_steps.Value)
self._short_take_profit = entry_price - tp * price_step if tp > 0 else None
self._short_stop_loss = entry_price + sl * price_step if sl > 0 else None
def _reset_long_state(self):
self._long_entry_price = None
self._long_take_profit = None
self._long_stop_loss = None
self._long_high = 0.0
def _reset_short_state(self):
self._short_entry_price = None
self._short_take_profit = None
self._short_stop_loss = None
self._short_low = 0.0
def _get_price_step(self):
sec = self.Security
if sec is not None:
step = sec.PriceStep
if step is not None and float(step) != 0.0:
return float(step)
return 1.0
def CreateClone(self):
return moving_average_trade_system_strategy()