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>
/// MARE5.1 strategy that trades shifted SMA crossovers with time filtering.
/// </summary>
public class Mare51Strategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _movingAverageShift;
private readonly StrategyParam<int> _sessionOpenHour;
private readonly StrategyParam<int> _sessionCloseHour;
private readonly StrategyParam<DataType> _candleType;
private SMA _fastSma;
private SMA _slowSma;
private decimal?[] _fastBuffer;
private decimal?[] _slowBuffer;
private ICandleMessage _previousCandle;
private decimal _pipSize;
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int MovingAverageShift
{
get => _movingAverageShift.Value;
set => _movingAverageShift.Value = value;
}
public int SessionOpenHour
{
get => _sessionOpenHour.Value;
set => _sessionOpenHour.Value = value;
}
public int SessionCloseHour
{
get => _sessionCloseHour.Value;
set => _sessionCloseHour.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public Mare51Strategy()
{
_tradeVolume = Param(nameof(TradeVolume), 0.01m)
.SetDisplay("Volume", "Default order volume", "Trading")
.SetGreaterThanZero();
_takeProfitPips = Param(nameof(TakeProfitPips), 35m)
.SetDisplay("Take Profit (pips)", "Take profit distance in adjusted pips", "Risk")
.SetNotNegative();
_stopLossPips = Param(nameof(StopLossPips), 55m)
.SetDisplay("Stop Loss (pips)", "Stop loss distance in adjusted pips", "Risk")
.SetNotNegative();
_fastPeriod = Param(nameof(FastPeriod), 14)
.SetDisplay("Fast Period", "Fast SMA period", "Indicators")
.SetGreaterThanZero();
_slowPeriod = Param(nameof(SlowPeriod), 20)
.SetDisplay("Slow Period", "Slow SMA period", "Indicators")
.SetGreaterThanZero();
_movingAverageShift = Param(nameof(MovingAverageShift), 1)
.SetDisplay("MA Shift", "Forward shift applied to both SMAs", "Indicators")
.SetNotNegative();
_sessionOpenHour = Param(nameof(SessionOpenHour), 0)
.SetDisplay("Session Open Hour", "Inclusive start hour for trading", "Session")
.SetRange(0, 23);
_sessionCloseHour = Param(nameof(SessionCloseHour), 23)
.SetDisplay("Session Close Hour", "Inclusive end hour for trading", "Session")
.SetRange(0, 23);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle data type", "Data");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_fastSma = null;
_slowSma = null;
_fastBuffer = null;
_slowBuffer = null;
_previousCandle = null;
_pipSize = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (SessionOpenHour >= SessionCloseHour)
throw new InvalidOperationException("SessionOpenHour must be less than SessionCloseHour.");
Volume = TradeVolume;
_fastSma = new SMA { Length = FastPeriod };
_slowSma = new SMA { Length = SlowPeriod };
_fastBuffer = new decimal?[MovingAverageShift + 6];
_slowBuffer = new decimal?[MovingAverageShift + 6];
_pipSize = CalculatePipSize();
var takeProfitUnit = TakeProfitPips > 0m
? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
: new Unit(0m);
var stopLossUnit = StopLossPips > 0m
? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
: new Unit(0m);
StartProtection(stopLossUnit, takeProfitUnit);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastSma, _slowSma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastSma);
DrawIndicator(area, _slowSma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_fastBuffer == null || _slowBuffer == null)
return;
// Shift raw SMA values so we can later access shifted indexes.
for (var i = _fastBuffer.Length - 1; i > 0; i--)
{
_fastBuffer[i] = _fastBuffer[i - 1];
_slowBuffer[i] = _slowBuffer[i - 1];
}
_fastBuffer[0] = fastValue;
_slowBuffer[0] = slowValue;
var previousCandle = _previousCandle;
_previousCandle = candle;
//if (!IsFormedAndOnlineAndAllowTrading())
// return;
if (previousCandle == null)
return;
if (_fastSma == null || _slowSma == null)
return;
if (!_fastSma.IsFormed || !_slowSma.IsFormed)
return;
var fast0 = GetShiftedValue(_fastBuffer, 0);
var fast2 = GetShiftedValue(_fastBuffer, 2);
var fast5 = GetShiftedValue(_fastBuffer, 5);
var slow0 = GetShiftedValue(_slowBuffer, 0);
var slow2 = GetShiftedValue(_slowBuffer, 2);
var slow5 = GetShiftedValue(_slowBuffer, 5);
if (fast0 is not decimal f0 || fast2 is not decimal f2 || fast5 is not decimal f5 ||
slow0 is not decimal s0 || slow2 is not decimal s2 || slow5 is not decimal s5)
{
return;
}
if (!IsWithinSession(candle.OpenTime))
return;
var bearishPrevious = previousCandle.ClosePrice < previousCandle.OpenPrice;
var bullishPrevious = previousCandle.ClosePrice > previousCandle.OpenPrice;
var sellSignal = f5 >= s5 && f2 < s2 && f0 < s0 && bearishPrevious;
var buySignal = f5 <= s5 && f2 > s2 && f0 > s0 && bullishPrevious;
if (Position != 0)
return;
if (sellSignal)
{
// Enter short when slow SMA overtakes the fast SMA and previous bars confirm the reversal.
SellMarket();
}
else if (buySignal)
{
// Enter long when fast SMA overtakes the slow SMA and previous bars confirm the reversal.
BuyMarket();
}
}
private decimal? GetShiftedValue(decimal?[] buffer, int index)
{
var targetIndex = index + MovingAverageShift;
if (buffer == null)
return null;
if (targetIndex < 0 || targetIndex >= buffer.Length)
return null;
return buffer[targetIndex];
}
private bool IsWithinSession(DateTimeOffset time)
{
var hour = time.Hour;
return hour >= SessionOpenHour && hour <= SessionCloseHour;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
var scale = GetDecimalScale(step);
return (scale == 3 || scale == 5) ? step * 10m : step;
}
private static int GetDecimalScale(decimal value)
{
var bits = decimal.GetBits(value);
return (bits[3] >> 16) & 0xFF;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class mare51_strategy(Strategy):
def __init__(self):
super(mare51_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 0.01)
self._take_profit_pips = self.Param("TakeProfitPips", 35.0)
self._stop_loss_pips = self.Param("StopLossPips", 55.0)
self._fast_period = self.Param("FastPeriod", 14)
self._slow_period = self.Param("SlowPeriod", 20)
self._ma_shift = self.Param("MovingAverageShift", 1)
self._session_open_hour = self.Param("SessionOpenHour", 0)
self._session_close_hour = self.Param("SessionCloseHour", 23)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._fast_buffer = None
self._slow_buffer = None
self._previous_candle_close = None
self._previous_candle_open = None
self._buffer_size = 0
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
@property
def MovingAverageShift(self):
return self._ma_shift.Value
@MovingAverageShift.setter
def MovingAverageShift(self, value):
self._ma_shift.Value = value
@property
def SessionOpenHour(self):
return self._session_open_hour.Value
@SessionOpenHour.setter
def SessionOpenHour(self, value):
self._session_open_hour.Value = value
@property
def SessionCloseHour(self):
return self._session_close_hour.Value
@SessionCloseHour.setter
def SessionCloseHour(self, value):
self._session_close_hour.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(mare51_strategy, self).OnStarted2(time)
self._buffer_size = int(self.MovingAverageShift) + 6
self._fast_buffer = [None] * self._buffer_size
self._slow_buffer = [None] * self._buffer_size
self._previous_candle_close = None
self._previous_candle_open = None
fast_sma = SimpleMovingAverage()
fast_sma.Length = self.FastPeriod
slow_sma = SimpleMovingAverage()
slow_sma.Length = self.SlowPeriod
self._fast_sma = fast_sma
self._slow_sma = slow_sma
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_sma, slow_sma, self.ProcessCandle).Start()
self.Volume = self._trade_volume.Value
pip_size = self._calculate_pip_size()
tp = float(self._take_profit_pips.Value)
sl = float(self._stop_loss_pips.Value)
take_unit = Unit(tp * pip_size, UnitTypes.Absolute) if tp > 0 else Unit(0)
stop_unit = Unit(sl * pip_size, UnitTypes.Absolute) if sl > 0 else Unit(0)
self.StartProtection(
stopLoss=stop_unit,
takeProfit=take_unit)
def ProcessCandle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
for i in range(self._buffer_size - 1, 0, -1):
self._fast_buffer[i] = self._fast_buffer[i - 1]
self._slow_buffer[i] = self._slow_buffer[i - 1]
self._fast_buffer[0] = fast_val
self._slow_buffer[0] = slow_val
prev_close = self._previous_candle_close
prev_open = self._previous_candle_open
self._previous_candle_close = float(candle.ClosePrice)
self._previous_candle_open = float(candle.OpenPrice)
if prev_close is None or prev_open is None:
return
if not self._fast_sma.IsFormed or not self._slow_sma.IsFormed:
return
shift = int(self.MovingAverageShift)
f0 = self._get_shifted(self._fast_buffer, 0, shift)
f2 = self._get_shifted(self._fast_buffer, 2, shift)
f5 = self._get_shifted(self._fast_buffer, 5, shift)
s0 = self._get_shifted(self._slow_buffer, 0, shift)
s2 = self._get_shifted(self._slow_buffer, 2, shift)
s5 = self._get_shifted(self._slow_buffer, 5, shift)
if f0 is None or f2 is None or f5 is None or s0 is None or s2 is None or s5 is None:
return
hour = candle.OpenTime.Hour
if hour < int(self.SessionOpenHour) or hour > int(self.SessionCloseHour):
return
bearish_prev = prev_close < prev_open
bullish_prev = prev_close > prev_open
sell_signal = f5 >= s5 and f2 < s2 and f0 < s0 and bearish_prev
buy_signal = f5 <= s5 and f2 > s2 and f0 > s0 and bullish_prev
if self.Position != 0:
return
if sell_signal:
self.SellMarket()
elif buy_signal:
self.BuyMarket()
def _get_shifted(self, buffer, index, shift):
target = index + shift
if target < 0 or target >= self._buffer_size:
return None
return buffer[target]
def _calculate_pip_size(self):
sec = self.Security
if sec is None:
return 1.0
ps = sec.PriceStep
if ps is None:
return 1.0
step = float(ps)
if step <= 0:
return 1.0
# count decimal places
import System
bits = System.Decimal.GetBits(ps)
scale = (bits[3] >> 16) & 0xFF
if scale == 3 or scale == 5:
return step * 10.0
return step
def OnReseted(self):
super(mare51_strategy, self).OnReseted()
self._fast_buffer = None
self._slow_buffer = None
self._previous_candle_close = None
self._previous_candle_open = None
def CreateClone(self):
return mare51_strategy()