using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// XDPO candle strategy that trades on color changes of double smoothed candles.
/// </summary>
public class XdpoCandleStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _emaOpen1;
private decimal? _emaOpen2;
private decimal? _emaClose1;
private decimal? _emaClose2;
private int? _previousColor;
/// <summary>
/// Length of the first exponential moving average.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Length of the second exponential moving average.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="XdpoCandleStrategy"/>.
/// </summary>
public XdpoCandleStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Length of the first EMA", "Parameters")
;
_slowLength = Param(nameof(SlowLength), 5)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Length of the second EMA", "Parameters")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return new[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaOpen1 = _emaOpen2 = _emaClose1 = _emaClose2 = null;
_previousColor = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var warmup = new StockSharp.Algo.Indicators.ExponentialMovingAverage { Length = FastLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(warmup, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal _warmupVal)
{
if (candle.State != CandleStates.Finished)
return;
var open1 = CalcEma(candle.OpenPrice, ref _emaOpen1, FastLength);
var open2 = CalcEma(open1, ref _emaOpen2, SlowLength);
var close1 = CalcEma(candle.ClosePrice, ref _emaClose1, FastLength);
var close2 = CalcEma(close1, ref _emaClose2, SlowLength);
var color = open2 < close2 ? 2 : open2 > close2 ? 0 : 1;
var goLong = color == 2 && _previousColor != 2;
var goShort = color == 0 && _previousColor != 0;
if (IsFormedAndOnlineAndAllowTrading())
{
if (goLong && Position <= 0)
BuyMarket();
else if (goShort && Position >= 0)
SellMarket();
}
_previousColor = color;
}
private static decimal CalcEma(decimal price, ref decimal? prev, int length)
{
var k = 2m / (length + 1m);
var result = prev.HasValue ? price * k + prev.Value * (1m - k) : price;
prev = result;
return result;
}
}
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 xdpo_candle_strategy(Strategy):
def __init__(self):
super(xdpo_candle_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12) \
.SetDisplay("Fast Length", "Length of the first EMA", "Parameters")
self._slow_length = self.Param("SlowLength", 5) \
.SetDisplay("Slow Length", "Length of the second EMA", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ema_open1 = None
self._ema_open2 = None
self._ema_close1 = None
self._ema_close2 = None
self._previous_color = None
@property
def fast_length(self):
return self._fast_length.Value
@property
def slow_length(self):
return self._slow_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(xdpo_candle_strategy, self).OnReseted()
self._ema_open1 = None
self._ema_open2 = None
self._ema_close1 = None
self._ema_close2 = None
self._previous_color = None
def _calc_ema(self, price, prev, length):
k = 2.0 / (length + 1.0)
if prev is not None:
result = price * k + prev * (1.0 - k)
else:
result = price
return result
def OnStarted2(self, time):
super(xdpo_candle_strategy, self).OnStarted2(time)
warmup = ExponentialMovingAverage()
warmup.Length = self.fast_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(warmup, self.process_candle).Start()
def process_candle(self, candle, warmup_val):
if candle.State != CandleStates.Finished:
return
fast_len = int(self.fast_length)
slow_len = int(self.slow_length)
open_price = float(candle.OpenPrice)
close_price = float(candle.ClosePrice)
open1 = self._calc_ema(open_price, self._ema_open1, fast_len)
self._ema_open1 = open1
open2 = self._calc_ema(open1, self._ema_open2, slow_len)
self._ema_open2 = open2
close1 = self._calc_ema(close_price, self._ema_close1, fast_len)
self._ema_close1 = close1
close2 = self._calc_ema(close1, self._ema_close2, slow_len)
self._ema_close2 = close2
if open2 < close2:
color = 2
elif open2 > close2:
color = 0
else:
color = 1
go_long = color == 2 and self._previous_color != 2
go_short = color == 0 and self._previous_color != 0
if go_long and self.Position <= 0:
self.BuyMarket()
elif go_short and self.Position >= 0:
self.SellMarket()
self._previous_color = color
def CreateClone(self):
return xdpo_candle_strategy()