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>
/// Contrarian strategy based on the XFatl and XSatl cloud crossovers.
/// </summary>
public class XFatlXSatlCloudStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<SmoothMethods> _fastMethod;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _fastPhase;
private readonly StrategyParam<SmoothMethods> _slowMethod;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _slowPhase;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<bool> _allowLongEntry;
private readonly StrategyParam<bool> _allowShortEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<int> _takeProfitTicks;
private readonly StrategyParam<int> _stopLossTicks;
private readonly List<decimal> _fastHistory = new();
private readonly List<decimal> _slowHistory = new();
public XFatlXSatlCloudStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for indicator calculations", "General");
_fastMethod = Param(nameof(FastMethod), SmoothMethods.Ema)
.SetDisplay("Fast Method", "Smoothing algorithm for the fast line", "Indicators");
_fastLength = Param(nameof(FastLength), 3)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Length of the fast filter", "Indicators");
_fastPhase = Param(nameof(FastPhase), 15)
.SetDisplay("Fast Phase", "Phase parameter for Jurik smoothing", "Indicators");
_slowMethod = Param(nameof(SlowMethod), SmoothMethods.Ema)
.SetDisplay("Slow Method", "Smoothing algorithm for the slow line", "Indicators");
_slowLength = Param(nameof(SlowLength), 5)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Length of the slow filter", "Indicators");
_slowPhase = Param(nameof(SlowPhase), 15)
.SetDisplay("Slow Phase", "Phase parameter for Jurik smoothing", "Indicators");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Index of the bar used for signals", "Logic");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size in lots", "Risk");
_allowLongEntry = Param(nameof(AllowLongEntry), true)
.SetDisplay("Allow Long Entry", "Enable contrarian long trades", "Logic");
_allowShortEntry = Param(nameof(AllowShortEntry), true)
.SetDisplay("Allow Short Entry", "Enable contrarian short trades", "Logic");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Allow Long Exit", "Allow indicator to close long trades", "Logic");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Allow Short Exit", "Allow indicator to close short trades", "Logic");
_takeProfitTicks = Param(nameof(TakeProfitTicks), 2000)
.SetNotNegative()
.SetDisplay("Take Profit Ticks", "Distance to take profit in price steps", "Risk");
_stopLossTicks = Param(nameof(StopLossTicks), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss Ticks", "Distance to stop loss in price steps", "Risk");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public SmoothMethods FastMethod
{
get => _fastMethod.Value;
set => _fastMethod.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int FastPhase
{
get => _fastPhase.Value;
set => _fastPhase.Value = value;
}
public SmoothMethods SlowMethod
{
get => _slowMethod.Value;
set => _slowMethod.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int SlowPhase
{
get => _slowPhase.Value;
set => _slowPhase.Value = value;
}
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
public bool AllowLongEntry
{
get => _allowLongEntry.Value;
set => _allowLongEntry.Value = value;
}
public bool AllowShortEntry
{
get => _allowShortEntry.Value;
set => _allowShortEntry.Value = value;
}
public bool AllowLongExit
{
get => _allowLongExit.Value;
set => _allowLongExit.Value = value;
}
public bool AllowShortExit
{
get => _allowShortExit.Value;
set => _allowShortExit.Value = value;
}
public int TakeProfitTicks
{
get => _takeProfitTicks.Value;
set => _takeProfitTicks.Value = value;
}
public int StopLossTicks
{
get => _stopLossTicks.Value;
set => _stopLossTicks.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastHistory.Clear();
_slowHistory.Clear();
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastIndicator = CreateIndicator(FastMethod, FastLength, FastPhase);
var slowIndicator = CreateIndicator(SlowMethod, SlowLength, SlowPhase);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastIndicator, slowIndicator, ProcessCandle).Start();
var step = Security?.PriceStep ?? 1m;
Unit takeProfit = null;
if (TakeProfitTicks > 0)
takeProfit = new Unit(TakeProfitTicks * step, UnitTypes.Absolute);
Unit stopLoss = null;
if (StopLossTicks > 0)
stopLoss = new Unit(StopLossTicks * step, UnitTypes.Absolute);
if (takeProfit != null || stopLoss != null)
StartProtection(takeProfit: takeProfit, stopLoss: stopLoss);
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateHistory(_fastHistory, fastValue);
UpdateHistory(_slowHistory, slowValue);
var required = SignalBar + 2;
if (_fastHistory.Count < required || _slowHistory.Count < required)
return;
var fastCurrent = GetShiftedValue(_fastHistory, SignalBar);
var fastPrevious = GetShiftedValue(_fastHistory, SignalBar + 1);
var slowCurrent = GetShiftedValue(_slowHistory, SignalBar);
var slowPrevious = GetShiftedValue(_slowHistory, SignalBar + 1);
// The cloud is considered bullish when the fast line was above the slow line on the prior bar.
var fastWasAbove = fastPrevious > slowPrevious;
var fastWasBelow = fastPrevious < slowPrevious;
var closeShort = AllowShortExit && fastWasAbove && Position < 0;
if (closeShort)
{
BuyMarket();
}
var closeLong = AllowLongExit && fastWasBelow && Position > 0;
if (closeLong)
{
SellMarket();
}
var enterLong = AllowLongEntry && fastWasAbove && fastCurrent <= slowCurrent;
var enterShort = AllowShortEntry && fastWasBelow && fastCurrent >= slowCurrent;
// Wait for the portfolio to flatten before issuing a new entry order.
if (Position != 0)
return;
if (enterLong)
{
BuyMarket();
}
else if (enterShort)
{
SellMarket();
}
}
private void UpdateHistory(List<decimal> history, decimal value)
{
history.Add(value);
var maxSize = SignalBar + 2;
while (history.Count > maxSize)
history.RemoveAt(0);
}
private static decimal GetShiftedValue(List<decimal> history, int shift)
{
var index = history.Count - shift - 1;
if (index >= 0 && index < history.Count)
return history[index];
return 0m;
}
private static IIndicator CreateIndicator(SmoothMethods method, int length, int phase)
{
return method switch
{
SmoothMethods.Sma => new SimpleMovingAverage { Length = length },
SmoothMethods.Ema => new ExponentialMovingAverage { Length = length },
SmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
SmoothMethods.Wma => new WeightedMovingAverage { Length = length },
_ => CreateJurikIndicator(length, phase),
};
}
private static IIndicator CreateJurikIndicator(int length, int phase)
{
var jurik = new JurikMovingAverage { Length = length };
// Configure the Jurik phase through reflection because the property is optional across versions.
var phaseProperty = jurik.GetType().GetProperty("Phase");
if (phaseProperty != null && phaseProperty.CanWrite)
{
var converted = Convert.ChangeType(phase, phaseProperty.PropertyType);
phaseProperty.SetValue(jurik, converted);
}
return jurik;
}
public enum SmoothMethods
{
Sma = 1,
Ema,
Smma,
Wma,
Jurik
}
}
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, ExponentialMovingAverage,
SmoothedMovingAverage, WeightedMovingAverage, JurikMovingAverage)
from StockSharp.Algo.Strategies import Strategy
SMOOTH_SMA = 1
SMOOTH_EMA = 2
SMOOTH_SMMA = 3
SMOOTH_WMA = 4
SMOOTH_JURIK = 5
class x_fatl_x_satl_cloud_strategy(Strategy):
def __init__(self):
super(x_fatl_x_satl_cloud_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._fast_method = self.Param("FastMethod", SMOOTH_EMA)
self._fast_length = self.Param("FastLength", 3)
self._fast_phase = self.Param("FastPhase", 15)
self._slow_method = self.Param("SlowMethod", SMOOTH_EMA)
self._slow_length = self.Param("SlowLength", 5)
self._slow_phase = self.Param("SlowPhase", 15)
self._signal_bar = self.Param("SignalBar", 1)
self._trade_volume = self.Param("TradeVolume", 1.0)
self._allow_long_entry = self.Param("AllowLongEntry", True)
self._allow_short_entry = self.Param("AllowShortEntry", True)
self._allow_long_exit = self.Param("AllowLongExit", True)
self._allow_short_exit = self.Param("AllowShortExit", True)
self._take_profit_ticks = self.Param("TakeProfitTicks", 2000)
self._stop_loss_ticks = self.Param("StopLossTicks", 1000)
self._fast_history = []
self._slow_history = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastMethod(self):
return self._fast_method.Value
@FastMethod.setter
def FastMethod(self, value):
self._fast_method.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def FastPhase(self):
return self._fast_phase.Value
@FastPhase.setter
def FastPhase(self, value):
self._fast_phase.Value = value
@property
def SlowMethod(self):
return self._slow_method.Value
@SlowMethod.setter
def SlowMethod(self, value):
self._slow_method.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def SlowPhase(self):
return self._slow_phase.Value
@SlowPhase.setter
def SlowPhase(self, value):
self._slow_phase.Value = value
@property
def SignalBar(self):
return self._signal_bar.Value
@SignalBar.setter
def SignalBar(self, value):
self._signal_bar.Value = value
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
@property
def AllowLongEntry(self):
return self._allow_long_entry.Value
@AllowLongEntry.setter
def AllowLongEntry(self, value):
self._allow_long_entry.Value = value
@property
def AllowShortEntry(self):
return self._allow_short_entry.Value
@AllowShortEntry.setter
def AllowShortEntry(self, value):
self._allow_short_entry.Value = value
@property
def AllowLongExit(self):
return self._allow_long_exit.Value
@AllowLongExit.setter
def AllowLongExit(self, value):
self._allow_long_exit.Value = value
@property
def AllowShortExit(self):
return self._allow_short_exit.Value
@AllowShortExit.setter
def AllowShortExit(self, value):
self._allow_short_exit.Value = value
@property
def TakeProfitTicks(self):
return self._take_profit_ticks.Value
@TakeProfitTicks.setter
def TakeProfitTicks(self, value):
self._take_profit_ticks.Value = value
@property
def StopLossTicks(self):
return self._stop_loss_ticks.Value
@StopLossTicks.setter
def StopLossTicks(self, value):
self._stop_loss_ticks.Value = value
def _create_indicator(self, method, length):
m = int(method)
if m == SMOOTH_SMA:
ind = SimpleMovingAverage()
ind.Length = length
return ind
elif m == SMOOTH_EMA:
ind = ExponentialMovingAverage()
ind.Length = length
return ind
elif m == SMOOTH_SMMA:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
elif m == SMOOTH_WMA:
ind = WeightedMovingAverage()
ind.Length = length
return ind
else:
ind = JurikMovingAverage()
ind.Length = length
return ind
def OnStarted2(self, time):
super(x_fatl_x_satl_cloud_strategy, self).OnStarted2(time)
self._fast_history = []
self._slow_history = []
fast_ind = self._create_indicator(self.FastMethod, int(self.FastLength))
slow_ind = self._create_indicator(self.SlowMethod, int(self.SlowLength))
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ind, slow_ind, self.ProcessCandle).Start()
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
tp = int(self.TakeProfitTicks)
sl = int(self.StopLossTicks)
tp_unit = Unit(tp * step, UnitTypes.Absolute) if tp > 0 else None
sl_unit = Unit(sl * step, UnitTypes.Absolute) if sl > 0 else None
if tp_unit is not None or sl_unit is not None:
self.StartProtection(tp_unit, sl_unit)
def ProcessCandle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
self._update_history(self._fast_history, fv)
self._update_history(self._slow_history, sv)
signal_bar = int(self.SignalBar)
required = signal_bar + 2
if len(self._fast_history) < required or len(self._slow_history) < required:
return
fast_current = self._get_shifted(self._fast_history, signal_bar)
fast_previous = self._get_shifted(self._fast_history, signal_bar + 1)
slow_current = self._get_shifted(self._slow_history, signal_bar)
slow_previous = self._get_shifted(self._slow_history, signal_bar + 1)
fast_was_above = fast_previous > slow_previous
fast_was_below = fast_previous < slow_previous
close_short = self.AllowShortExit and fast_was_above and self.Position < 0
if close_short:
self.BuyMarket()
close_long = self.AllowLongExit and fast_was_below and self.Position > 0
if close_long:
self.SellMarket()
enter_long = self.AllowLongEntry and fast_was_above and fast_current <= slow_current
enter_short = self.AllowShortEntry and fast_was_below and fast_current >= slow_current
if self.Position != 0:
return
if enter_long:
self.BuyMarket()
elif enter_short:
self.SellMarket()
def _update_history(self, history, value):
history.append(value)
max_size = int(self.SignalBar) + 2
while len(history) > max_size:
history.pop(0)
def _get_shifted(self, history, shift):
index = len(history) - shift - 1
if 0 <= index < len(history):
return history[index]
return 0.0
def OnReseted(self):
super(x_fatl_x_satl_cloud_strategy, self).OnReseted()
self._fast_history = []
self._slow_history = []
def CreateClone(self):
return x_fatl_x_satl_cloud_strategy()