Стратегия Dots
Конвертировано из MQL5 "Exp_Dots". Стратегия торгует развороты при смене цвета индикатора Dots. Открывает покупку, когда цвет меняется с синего на красный, и продажу, когда меняется с красного на синий.
Подробности
- Условия входа:
- Длинная: цвет изменился с синего на красный.
- Короткая: цвет изменился с красного на синий.
- Long/Short: Оба
- Условия выхода: противоположный сигнал
- Стопы: Нет
- Параметры по умолчанию:
Length= 10Filter= 0mCandleType= TimeSpan.FromHours(4).TimeFrame()
- Фильтры:
- Категория: Trend reversal
- Направление: Оба
- Индикаторы: Dots (NonLag Moving Average)
- Стопы: Нет
- Сложность: Средняя
- Таймфрейм: 4H
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()