GitHub で見る

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 = 10
    • Filter = 0m
    • CandleType = 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;
		}
	}
}