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>
/// Fast and slow moving average crossover strategy with intraday time filter and pip-based risk controls.
/// </summary>
public class FastSlowMaCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<TimeSpan> _startTime;
private readonly StrategyParam<TimeSpan> _stopTime;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _tradeVolume;
private decimal _pipSize;
private decimal? _previousFast;
private decimal? _previousSlow;
private DateTimeOffset? _lastSignalTime;
private bool _hasActivePosition;
private bool _isLongPosition;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _targetPrice;
/// <summary>
/// Initializes a new instance of the <see cref="FastSlowMaCrossoverStrategy"/> class.
/// </summary>
public FastSlowMaCrossoverStrategy()
{
_fastMaPeriod = Param(nameof(FastMaPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Fast MA Period", "Length of the fast moving average", "Parameters")
;
_slowMaPeriod = Param(nameof(SlowMaPeriod), 80)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Length of the slow moving average", "Parameters")
;
_takeProfitPips = Param(nameof(TakeProfitPips), 80)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Distance in pips for profit taking", "Risk Management")
;
_stopLossPips = Param(nameof(StopLossPips), 80)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Distance in pips for protective stop", "Risk Management")
;
_startTime = Param(nameof(StartTime), new TimeSpan(8, 0, 0))
.SetDisplay("Start Time", "Start of the allowed trading window", "Schedule");
_stopTime = Param(nameof(StopTime), new TimeSpan(18, 0, 0))
.SetDisplay("Stop Time", "End of the allowed trading window", "Schedule");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for calculations", "General");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Volume of each market order", "Trading")
;
}
/// <summary>
/// Period of the fast moving average.
/// </summary>
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
/// <summary>
/// Period of the slow moving average.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// Profit target distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Start time of the allowed trading window.
/// </summary>
public TimeSpan StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// Stop time of the allowed trading window.
/// </summary>
public TimeSpan StopTime
{
get => _stopTime.Value;
set => _stopTime.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Volume used for each market order.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set
{
_tradeVolume.Value = value;
Volume = value;
}
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_previousFast = null;
_previousSlow = null;
_lastSignalTime = null;
ResetPositionState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
_pipSize = CalculatePipSize();
var fastMa = new EMA { Length = FastMaPeriod };
var slowMa = new EMA { Length = SlowMaPeriod };
SubscribeCandles(CandleType)
.Bind(fastMa, slowMa, (candle, fastValue, slowValue) => ProcessCandle(candle, fastValue, slowValue, fastMa.IsFormed && slowMa.IsFormed))
.Start();
}
private decimal CalculatePipSize()
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
return 0.0001m;
var decimals = Security?.Decimals ?? 0;
var factor = (decimals == 3 || decimals == 5) ? 10m : 1m;
return priceStep * factor;
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, bool indicatorsFormed)
{
if (candle.State != CandleStates.Finished)
return;
var timeOfDay = candle.CloseTime.TimeOfDay;
if (!IsWithinTradingWindow(timeOfDay))
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
ManageExistingPosition(candle);
if (!indicatorsFormed)
{
_previousFast = fastValue;
_previousSlow = slowValue;
return;
}
if (_previousFast is null || _previousSlow is null)
{
_previousFast = fastValue;
_previousSlow = slowValue;
return;
}
var crossUp = _previousFast <= _previousSlow && fastValue > slowValue;
var crossDown = _previousFast >= _previousSlow && fastValue < slowValue;
if (_pipSize <= 0m)
{
_previousFast = fastValue;
_previousSlow = slowValue;
return;
}
var currentCandleTime = candle.CloseTime;
if (crossUp && Position <= 0m && _lastSignalTime != currentCandleTime)
{
var volume = TradeVolume;
if (Position < 0m)
volume += -Position;
if (volume > 0m)
{
BuyMarket(volume);
RecordEntryState(candle.ClosePrice, true);
_lastSignalTime = currentCandleTime;
}
}
else if (crossDown && Position >= 0m && _lastSignalTime != currentCandleTime)
{
var volume = TradeVolume;
if (Position > 0m)
volume += Position;
if (volume > 0m)
{
SellMarket(volume);
RecordEntryState(candle.ClosePrice, false);
_lastSignalTime = currentCandleTime;
}
}
_previousFast = fastValue;
_previousSlow = slowValue;
}
private void ManageExistingPosition(ICandleMessage candle)
{
if (!_hasActivePosition || _pipSize <= 0m)
return;
var high = candle.HighPrice;
var low = candle.LowPrice;
if (_isLongPosition)
{
if (StopLossPips > 0 && low <= _stopPrice)
{
SellMarket(Position);
ResetPositionState();
return;
}
if (TakeProfitPips > 0 && high >= _targetPrice)
{
SellMarket(Position);
ResetPositionState();
}
}
else
{
if (StopLossPips > 0 && high >= _stopPrice)
{
BuyMarket(-Position);
ResetPositionState();
return;
}
if (TakeProfitPips > 0 && low <= _targetPrice)
{
BuyMarket(-Position);
ResetPositionState();
}
}
}
private void RecordEntryState(decimal closePrice, bool isLong)
{
_hasActivePosition = true;
_isLongPosition = isLong;
_entryPrice = closePrice;
var takeOffset = TakeProfitPips > 0 ? TakeProfitPips * _pipSize : 0m;
var stopOffset = StopLossPips > 0 ? StopLossPips * _pipSize : 0m;
if (isLong)
{
_targetPrice = takeOffset > 0m ? (_entryPrice + takeOffset) : 0m;
_stopPrice = stopOffset > 0m ? (_entryPrice - stopOffset) : 0m;
}
else
{
_targetPrice = takeOffset > 0m ? (_entryPrice - takeOffset) : 0m;
_stopPrice = stopOffset > 0m ? (_entryPrice + stopOffset) : 0m;
}
}
private bool IsWithinTradingWindow(TimeSpan current)
{
var start = StartTime;
var stop = StopTime;
if (start == stop)
return true;
if (start < stop)
return current >= start && current <= stop;
return current >= start || current <= stop;
}
private void ResetPositionState()
{
_hasActivePosition = false;
_isLongPosition = false;
_entryPrice = 0m;
_stopPrice = 0m;
_targetPrice = 0m;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position == 0m)
{
ResetPositionState();
}
else
{
_hasActivePosition = true;
_isLongPosition = Position > 0m;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fast_slow_ma_crossover_strategy(Strategy):
def __init__(self):
super(fast_slow_ma_crossover_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(120)))
self._fast_ma_period = self.Param("FastMaPeriod", 30)
self._slow_ma_period = self.Param("SlowMaPeriod", 80)
self._stop_loss_pct = self.Param("StopLossPct", 2.0)
self._take_profit_pct = self.Param("TakeProfitPct", 3.0)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@FastMaPeriod.setter
def FastMaPeriod(self, value):
self._fast_ma_period.Value = value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
@SlowMaPeriod.setter
def SlowMaPeriod(self, value):
self._slow_ma_period.Value = value
@property
def StopLossPct(self):
return self._stop_loss_pct.Value
@StopLossPct.setter
def StopLossPct(self, value):
self._stop_loss_pct.Value = value
@property
def TakeProfitPct(self):
return self._take_profit_pct.Value
@TakeProfitPct.setter
def TakeProfitPct(self, value):
self._take_profit_pct.Value = value
def OnReseted(self):
super(fast_slow_ma_crossover_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._entry_price = 0.0
def OnStarted2(self, time):
super(fast_slow_ma_crossover_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
self._entry_price = 0.0
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.FastMaPeriod
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.SlowMaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ma, slow_ma, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
close = float(candle.ClosePrice)
# Check SL/TP on existing position
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
pnl_pct = (close - self._entry_price) / self._entry_price * 100.0
if pnl_pct <= -float(self.StopLossPct) or pnl_pct >= float(self.TakeProfitPct):
self.SellMarket()
self._entry_price = 0.0
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
return
elif self.Position < 0:
pnl_pct = (self._entry_price - close) / self._entry_price * 100.0
if pnl_pct <= -float(self.StopLossPct) or pnl_pct >= float(self.TakeProfitPct):
self.BuyMarket()
self._entry_price = 0.0
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
return
if self._has_prev:
cross_up = self._prev_fast <= self._prev_slow and fast_val > slow_val
cross_down = self._prev_fast >= self._prev_slow and fast_val < slow_val
if cross_up and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
elif cross_down and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
def CreateClone(self):
return fast_slow_ma_crossover_strategy()