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;
public class ExpXPeriodCandleStrategy : Strategy
{
public enum SmoothingMethods
{
Simple,
Exponential,
Smoothed,
LinearWeighted,
JurikLike,
JurxLike,
ParabolicLike,
TillsonT3Like,
VidyaLike,
AdaptiveLike
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<int> _smoothingPhase;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<bool> _enableLongEntry;
private readonly StrategyParam<bool> _enableShortEntry;
private readonly StrategyParam<bool> _enableLongExit;
private readonly StrategyParam<bool> _enableShortExit;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _slippagePoints;
private Smoother _openSmoother;
private Smoother _highSmoother;
private Smoother _lowSmoother;
private Smoother _closeSmoother;
private readonly List<int> _colorHistory = new();
private readonly Queue<decimal> _smoothedHighs = new();
private readonly Queue<decimal> _smoothedLows = new();
public ExpXPeriodCandleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for calculations", "General");
_period = Param(nameof(Period), 5)
.SetGreaterThanZero()
.SetDisplay("Smoothing Window", "Depth of the price smoothing window", "Indicator")
;
_smoothingMethod = Param(nameof(SmoothingMethods), SmoothingMethods.JurikLike)
.SetDisplay("Smoothing Method", "Type of moving average approximation", "Indicator");
_smoothingLength = Param(nameof(SmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "Length used by the smoother", "Indicator")
;
_smoothingPhase = Param(nameof(SmoothingPhase), 100)
.SetDisplay("Smoothing Phase", "Phase parameter for adaptive smoothers", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Shift", "Which completed candle to evaluate", "Trading");
_enableLongEntry = Param(nameof(EnableLongEntry), true)
.SetDisplay("Enable Long Entry", "Allow opening buy positions", "Trading");
_enableShortEntry = Param(nameof(EnableShortEntry), true)
.SetDisplay("Enable Short Entry", "Allow opening sell positions", "Trading");
_enableLongExit = Param(nameof(EnableLongExit), true)
.SetDisplay("Close Longs On Opposite", "Close long positions on opposite signals", "Trading");
_enableShortExit = Param(nameof(EnableShortExit), true)
.SetDisplay("Close Shorts On Opposite", "Close short positions on opposite signals", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Protective stop loss in price points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Protective take profit in price points", "Risk");
_slippagePoints = Param(nameof(SlippagePoints), 10)
.SetNotNegative()
.SetDisplay("Slippage (pts)", "Allowed slippage in price points", "Trading");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
public SmoothingMethods Smoothing
{
get => _smoothingMethod.Value;
set => _smoothingMethod.Value = value;
}
public int SmoothingLength
{
get => _smoothingLength.Value;
set => _smoothingLength.Value = value;
}
public int SmoothingPhase
{
get => _smoothingPhase.Value;
set => _smoothingPhase.Value = value;
}
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
public bool EnableLongEntry
{
get => _enableLongEntry.Value;
set => _enableLongEntry.Value = value;
}
public bool EnableShortEntry
{
get => _enableShortEntry.Value;
set => _enableShortEntry.Value = value;
}
public bool EnableLongExit
{
get => _enableLongExit.Value;
set => _enableLongExit.Value = value;
}
public bool EnableShortExit
{
get => _enableShortExit.Value;
set => _enableShortExit.Value = value;
}
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public int SlippagePoints
{
get => _slippagePoints.Value;
set => _slippagePoints.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_colorHistory.Clear();
_smoothedHighs.Clear();
_smoothedLows.Clear();
_openSmoother = null;
_highSmoother = null;
_lowSmoother = null;
_closeSmoother = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_openSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
_highSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
_lowSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
_closeSmoother = CreateSmoother(Smoothing, SmoothingLength, SmoothingPhase);
_colorHistory.Clear();
_smoothedHighs.Clear();
_smoothedLows.Clear();
// Protection and slippage removed (forbidden APIs)
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var openValue = _openSmoother?.Process(candle.OpenPrice);
var highValue = _highSmoother?.Process(candle.HighPrice);
var lowValue = _lowSmoother?.Process(candle.LowPrice);
var closeValue = _closeSmoother?.Process(candle.ClosePrice);
if (openValue is null || highValue is null || lowValue is null || closeValue is null)
return;
UpdateQueue(_smoothedHighs, highValue.Value, Period);
UpdateQueue(_smoothedLows, lowValue.Value, Period);
if (_smoothedHighs.Count < Period || _smoothedLows.Count < Period)
return;
var color = openValue.Value <= closeValue.Value ? 0 : 2;
_colorHistory.Add(color);
var maxHistory = Math.Max(Period * 4, SignalBar + 4);
if (_colorHistory.Count > maxHistory)
_colorHistory.RemoveAt(0);
if (_colorHistory.Count < SignalBar + 1)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_colorHistory.Count <= SignalBar)
return;
var index0 = _colorHistory.Count - SignalBar;
if (index0 >= _colorHistory.Count)
index0 = _colorHistory.Count - 1;
var index1 = index0 - 1;
if (index1 < 0)
return;
var value0 = _colorHistory[index0];
var value1 = _colorHistory[index1];
var baseLongCondition = value1 < 1;
var baseShortCondition = value1 > 1;
var openLong = EnableLongEntry && baseLongCondition && value0 > 0;
var openShort = EnableShortEntry && baseShortCondition && value0 < 2;
var closeShort = EnableShortExit && baseLongCondition;
var closeLong = EnableLongExit && baseShortCondition;
if (closeLong && Position > 0)
SellMarket(Position);
if (closeShort && Position < 0)
BuyMarket(-Position);
if (openLong && Position <= 0)
{
var volume = Volume + (Position < 0 ? -Position : 0m);
BuyMarket(volume);
}
else if (openShort && Position >= 0)
{
var volume = Volume + (Position > 0 ? Position : 0m);
SellMarket(volume);
}
}
private static void UpdateQueue(Queue<decimal> queue, decimal value, int maxCount)
{
queue.Enqueue(value);
if (queue.Count > maxCount)
queue.Dequeue();
}
private static decimal GetMax(IEnumerable<decimal> source)
{
var max = decimal.MinValue;
foreach (var value in source)
{
if (value > max)
max = value;
}
return max;
}
private static decimal GetMin(IEnumerable<decimal> source)
{
var min = decimal.MaxValue;
foreach (var value in source)
{
if (value < min)
min = value;
}
return min;
}
private static Smoother CreateSmoother(SmoothingMethods method, int length, int phase)
{
switch (method)
{
case SmoothingMethods.Simple:
return new SmaSmoother(length);
case SmoothingMethods.Exponential:
return new EmaSmoother(length);
case SmoothingMethods.Smoothed:
return new SmmaSmoother(length);
case SmoothingMethods.LinearWeighted:
return new LwmaSmoother(length);
default:
// Approximate advanced smoothing modes (JJMA, JurX, Parabolic, T3, VIDYA, AMA) with EMA.
return new EmaSmoother(length);
}
}
private abstract class Smoother
{
protected Smoother(int length)
{
Length = Math.Max(1, length);
}
protected int Length { get; }
public abstract decimal? Process(decimal value);
}
private sealed class SmaSmoother : Smoother
{
private readonly Queue<decimal> _values = new();
private decimal _sum;
public SmaSmoother(int length)
: base(length)
{
}
public override decimal? Process(decimal value)
{
_values.Enqueue(value);
_sum += value;
if (_values.Count > Length)
{
_sum -= _values.Dequeue();
}
if (_values.Count < Length)
return null;
return _sum / _values.Count;
}
}
private sealed class EmaSmoother : Smoother
{
private decimal? _ema;
private readonly decimal _alpha;
public EmaSmoother(int length)
: base(length)
{
_alpha = 2m / (Length + 1m);
}
public override decimal? Process(decimal value)
{
if (_ema is null)
_ema = value;
else
_ema += _alpha * (value - _ema.Value);
return _ema;
}
}
private sealed class SmmaSmoother : Smoother
{
private decimal? _smma;
public SmmaSmoother(int length)
: base(length)
{
}
public override decimal? Process(decimal value)
{
if (_smma is null)
_smma = value;
else
_smma = ((_smma.Value * (Length - 1)) + value) / Length;
return _smma;
}
}
private sealed class LwmaSmoother : Smoother
{
private readonly Queue<decimal> _values = new();
public LwmaSmoother(int length)
: base(length)
{
}
public override decimal? Process(decimal value)
{
_values.Enqueue(value);
if (_values.Count > Length)
_values.Dequeue();
if (_values.Count < Length)
return null;
var weightSum = 0m;
var weightedTotal = 0m;
var weight = 1m;
foreach (var item in _values)
{
weightedTotal += item * weight;
weightSum += weight;
weight += 1m;
}
return weightedTotal / weightSum;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from collections import deque
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class exp_x_period_candle_strategy(Strategy):
"""
ExpXPeriodCandle: Smoothed OHLC candle coloring strategy.
Smooths price data with configurable MA types, trades on color changes.
"""
def __init__(self):
super(exp_x_period_candle_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame", "General")
self._period = self.Param("Period", 5) \
.SetDisplay("Smoothing Window", "Depth of smoothing window", "Indicator")
self._smoothing_length = self.Param("SmoothingLength", 3) \
.SetDisplay("Smoothing Length", "Length used by smoother", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Shift", "Which completed candle to evaluate", "Trading")
self._open_smoother = None
self._high_smoother = None
self._low_smoother = None
self._close_smoother = None
self._color_history = []
self._smoothed_highs = deque()
self._smoothed_lows = deque()
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_x_period_candle_strategy, self).OnReseted()
self._color_history = []
self._smoothed_highs = deque()
self._smoothed_lows = deque()
self._open_smoother = None
self._high_smoother = None
self._low_smoother = None
self._close_smoother = None
def OnStarted2(self, time):
super(exp_x_period_candle_strategy, self).OnStarted2(time)
length = self._smoothing_length.Value
self._open_smoother = _EmaSmoother(length)
self._high_smoother = _EmaSmoother(length)
self._low_smoother = _EmaSmoother(length)
self._close_smoother = _EmaSmoother(length)
self._color_history = []
self._smoothed_highs = deque()
self._smoothed_lows = deque()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
open_val = self._open_smoother.process(float(candle.OpenPrice))
high_val = self._high_smoother.process(float(candle.HighPrice))
low_val = self._low_smoother.process(float(candle.LowPrice))
close_val = self._close_smoother.process(float(candle.ClosePrice))
if open_val is None or high_val is None or low_val is None or close_val is None:
return
period = self._period.Value
self._smoothed_highs.append(high_val)
self._smoothed_lows.append(low_val)
while len(self._smoothed_highs) > period:
self._smoothed_highs.popleft()
while len(self._smoothed_lows) > period:
self._smoothed_lows.popleft()
if len(self._smoothed_highs) < period or len(self._smoothed_lows) < period:
return
color = 0 if open_val <= close_val else 2
self._color_history.append(color)
sig_bar = self._signal_bar.Value
max_hist = max(period * 4, sig_bar + 4)
while len(self._color_history) > max_hist:
self._color_history.pop(0)
if len(self._color_history) < sig_bar + 1:
return
if len(self._color_history) <= sig_bar:
return
index0 = len(self._color_history) - sig_bar
if index0 >= len(self._color_history):
index0 = len(self._color_history) - 1
index1 = index0 - 1
if index1 < 0:
return
val0 = self._color_history[index0]
val1 = self._color_history[index1]
base_long = val1 < 1
base_short = val1 > 1
open_long = base_long and val0 > 0
open_short = base_short and val0 < 2
close_long = base_short
close_short = base_long
if close_long and self.Position > 0:
self.SellMarket(self.Position)
if close_short and self.Position < 0:
self.BuyMarket(abs(self.Position))
if open_long and self.Position <= 0:
vol = self.Volume + (abs(self.Position) if self.Position < 0 else 0)
self.BuyMarket(vol)
elif open_short and self.Position >= 0:
vol = self.Volume + (self.Position if self.Position > 0 else 0)
self.SellMarket(vol)
def CreateClone(self):
return exp_x_period_candle_strategy()
class _EmaSmoother:
def __init__(self, length):
self._length = max(1, length)
self._alpha = 2.0 / (self._length + 1.0)
self._ema = None
def process(self, value):
if self._ema is None:
self._ema = value
else:
self._ema += self._alpha * (value - self._ema)
return self._ema