Dots Strategy
Converted from MQL5 "Exp_Dots". The strategy trades reversals when the Dots indicator changes color. It goes long when the indicator switches from blue to red and short when it switches from red to blue.
Details
- Entry Criteria:
- Long: Indicator color changes from blue to red.
- Short: Indicator color changes from red to blue.
- Long/Short: Both
- Exit Criteria: Opposite signal
- Stops: No
- Default Values:
Length= 10Filter= 0mCandleType= TimeSpan.FromHours(4).TimeFrame()
- Filters:
- Category: Trend reversal
- Direction: Both
- Indicators: Dots (NonLag Moving Average)
- Stops: No
- Complexity: Intermediate
- Timeframe: 4H
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Strategy based on the Dots indicator which trades reversals on color changes.
/// </summary>
public class DotsStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _filter;
private readonly StrategyParam<double> _coefficient;
private readonly StrategyParam<DataType> _candleType;
private DotsIndicator _dots;
private decimal? _prevColor;
/// <summary>
/// Dots indicator calculation length.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Minimal change required to flip color.
/// </summary>
public decimal Filter
{
get => _filter.Value;
set => _filter.Value = value;
}
/// <summary>
/// Coefficient applied inside the internal weighting formula.
/// </summary>
public double Coefficient
{
get => _coefficient.Value;
set => _coefficient.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 the strategy.
/// </summary>
public DotsStrategy()
{
_length = Param(nameof(Length), 10)
.SetDisplay("Length", "Dots calculation length", "Parameters");
_filter = Param(nameof(Filter), 0m)
.SetDisplay("Filter", "Minimal delta to change color", "Parameters");
_coefficient = Param(nameof(Coefficient), 3.0 * Math.PI)
.SetGreaterThanZero()
.SetDisplay("Coefficient", "Weighting coefficient inside the filter", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_dots = null;
_prevColor = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_dots = new DotsIndicator
{
Length = Length,
Filter = Filter,
Coefficient = Coefficient
};
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_dots, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _dots);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal color)
{
if (candle.State != CandleStates.Finished)
return;
var curr = color;
if (_prevColor is null)
{
_prevColor = curr;
return;
}
if (_prevColor == 0m && curr == 1m && Position <= 0)
{
BuyMarket();
}
else if (_prevColor == 1m && curr == 0m && Position >= 0)
{
SellMarket();
}
_prevColor = curr;
}
private class DotsIndicator : BaseIndicator
{
public int Length { get; set; } = 10;
public decimal Filter { get; set; } = 0m;
public double Coefficient { get; set; } = 3.0 * Math.PI;
private readonly List<decimal> _prices = new();
private decimal? _prevMa;
private decimal _prevColor;
protected override bool CalcIsFormed() => _prices.Count >= Len;
private int Len => (int)(Length * 4 + (Length - 1));
private double Res1 => 1.0 / Math.Max(1.0, Length - 2);
private double Res2 => (2.0 * 4 - 1.0) / (4 * Length - 1.0);
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var price = input.ToDecimal();
_prices.Insert(0, price);
if (_prices.Count > Len)
_prices.RemoveAt(_prices.Count - 1);
if (_prices.Count < Len)
return new DecimalIndicatorValue(this, _prevColor, input.Time);
double t = 0, sum = 0, weight = 0;
for (var i = 0; i < Len; i++)
{
var g = 1.0 / (Coefficient * t + 1.0);
if (t <= 0.5)
g = 1.0;
var beta = Math.Cos(Math.PI * t);
var alfa = g * beta;
sum += alfa * (double)_prices[i];
weight += alfa;
if (t < 1.0)
t += Res1;
else if (t < Len - 1)
t += Res2;
}
var maPrev = (double)(_prevMa ?? _prices[1]);
var ma = weight != 0 ? sum / Math.Abs(weight) : maPrev;
if (Filter > 0m && Math.Abs(ma - maPrev) < (double)Filter)
ma = maPrev;
decimal color;
if (ma - maPrev > (double)Filter)
color = 0m;
else if (maPrev - ma > (double)Filter)
color = 1m;
else
color = _prevColor;
_prevMa = (decimal)ma;
_prevColor = color;
return new DecimalIndicatorValue(this, color, input.Time);
}
public override void Reset()
{
base.Reset();
_prices.Clear();
_prevMa = null;
_prevColor = 0m;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class dots_strategy(Strategy):
"""Strategy based on Dots indicator - weighted cosine filter with color changes."""
def __init__(self):
super(dots_strategy, self).__init__()
self._length = self.Param("Length", 10) \
.SetDisplay("Length", "Dots calculation length", "Parameters")
self._filter = self.Param("Filter", 0.0) \
.SetDisplay("Filter", "Minimal delta to change color", "Parameters")
self._coefficient = self.Param("Coefficient", 3.0 * math.pi) \
.SetDisplay("Coefficient", "Weighting coefficient inside the filter", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prices = []
self._prev_ma = None
self._prev_color = 0.0
self._prev_out_color = None
@property
def length(self):
return self._length.Value
@property
def filter_val(self):
return self._filter.Value
@property
def coefficient(self):
return self._coefficient.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dots_strategy, self).OnReseted()
self._prices = []
self._prev_ma = None
self._prev_color = 0.0
self._prev_out_color = None
def OnStarted2(self, time):
super(dots_strategy, self).OnStarted2(time)
self._prices = []
self._prev_ma = None
self._prev_color = 0.0
self._prev_out_color = None
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
price = float(candle.ClosePrice)
length = int(self.length)
filt = float(self.filter_val)
coeff = float(self.coefficient)
total_len = length * 4 + (length - 1)
res1 = 1.0 / max(1.0, length - 2)
res2 = (2.0 * 4 - 1.0) / (4 * length - 1.0)
self._prices.insert(0, price)
if len(self._prices) > total_len:
self._prices.pop()
if len(self._prices) < total_len:
return
t = 0.0
sum_val = 0.0
weight = 0.0
for i in range(total_len):
g = 1.0 / (coeff * t + 1.0)
if t <= 0.5:
g = 1.0
beta = math.cos(math.pi * t)
alfa = g * beta
sum_val += alfa * self._prices[i]
weight += alfa
if t < 1.0:
t += res1
elif t < total_len - 1:
t += res2
ma_prev = self._prev_ma if self._prev_ma is not None else self._prices[1]
if weight != 0:
ma = sum_val / abs(weight)
else:
ma = ma_prev
if filt > 0 and abs(ma - ma_prev) < filt:
ma = ma_prev
if ma - ma_prev > filt:
color = 0.0
elif ma_prev - ma > filt:
color = 1.0
else:
color = self._prev_color
self._prev_ma = ma
self._prev_color = color
if self._prev_out_color is None:
self._prev_out_color = color
return
if self._prev_out_color == 0.0 and color == 1.0 and self.Position >= 0:
self.SellMarket()
elif self._prev_out_color == 1.0 and color == 0.0 and self.Position <= 0:
self.BuyMarket()
self._prev_out_color = color
def CreateClone(self):
return dots_strategy()