Открыть на GitHub

Стратегия I4 DRF

Стратегия основана на пользовательском индикаторе I4 DRF. Он сравнивает направление последних максимумов и минимумов свечей и формирует значение от -100 до +100. Торговые действия зависят от переходов цвета индикатора и выбранного режима.

Детали

  • Условия входа:
    • Режим Direct: вход в лонг при смене индикатора с положительного на отрицательный, вход в шорт при смене с отрицательного на положительный.
    • Режим NotDirect: вход в лонг при смене с отрицательного на положительный, вход в шорт при смене с положительного на отрицательный.
  • Направление: Оба
  • Условия выхода:
    • Позиции закрываются при появлении противоположного сигнала.
  • Стопы: Нет
  • Значения по умолчанию:
    • Period = 11
    • SignalBar = 1
    • TrendMode = Direct
    • CandleType = TimeSpan.FromHours(4).TimeFrame()
  • Фильтры:
    • Категория: Тренд
    • Направление: Оба
    • Индикаторы: I4 DRF
    • Стопы: Нет
    • Сложность: Базовая
    • Таймфрейм: Среднесрочный
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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 I4 DRF indicator.
/// The indicator compares changes in highs and lows and returns a value between -100 and 100.
/// Depending on <see cref="TrendMode"/> signals are interpreted with or against the trend.
/// </summary>
public class I4DrfStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<Modes> _trendMode;
	private readonly StrategyParam<bool> _buyPosOpen;
	private readonly StrategyParam<bool> _sellPosOpen;
	private readonly StrategyParam<bool> _buyPosClose;
	private readonly StrategyParam<bool> _sellPosClose;

	private I4Drf _indicator;
	private decimal _prevColor;
	private decimal _prevPrevColor;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }
	public int SignalBar { get => _signalBar.Value; set => _signalBar.Value = value; }
	public Modes TrendMode { get => _trendMode.Value; set => _trendMode.Value = value; }
	public bool BuyPosOpen { get => _buyPosOpen.Value; set => _buyPosOpen.Value = value; }
	public bool SellPosOpen { get => _sellPosOpen.Value; set => _sellPosOpen.Value = value; }
	public bool BuyPosClose { get => _buyPosClose.Value; set => _buyPosClose.Value = value; }
	public bool SellPosClose { get => _sellPosClose.Value; set => _sellPosClose.Value = value; }

	public I4DrfStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe of candles", "General");
		_period = Param(nameof(Period), 11)
		.SetGreaterThanZero()
		.SetDisplay("Period", "Indicator period", "Parameters");
		_signalBar = Param(nameof(SignalBar), 1)
		.SetGreaterThanZero()
		.SetDisplay("Signal Bar", "Shift for signal", "Parameters");
		_trendMode = Param(nameof(TrendMode), Modes.Direct)
		.SetDisplay("Trend Modes", "Trading mode", "Parameters");
		_buyPosOpen = Param(nameof(BuyPosOpen), true)
		.SetDisplay("Open Long", "Allow opening long positions", "Switches");
		_sellPosOpen = Param(nameof(SellPosOpen), true)
		.SetDisplay("Open Short", "Allow opening short positions", "Switches");
		_buyPosClose = Param(nameof(BuyPosClose), true)
		.SetDisplay("Close Long", "Allow closing long positions", "Switches");
		_sellPosClose = Param(nameof(SellPosClose), true)
		.SetDisplay("Close Short", "Allow closing short positions", "Switches");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevColor = 0m;
		_prevPrevColor = 0m;
		_indicator?.Reset();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_indicator = new I4Drf { Length = Period };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_indicator, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _indicator);
			DrawOwnTrades(area);
		}

		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle, decimal value)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var color = value > 0m ? 1m : 0m;

		if (!_indicator.IsFormed)
		{
			_prevPrevColor = _prevColor;
			_prevColor = color;
			return;
		}

		var buyOpen = false;
		var sellOpen = false;
		var buyClose = false;
		var sellClose = false;

		if (TrendMode == Modes.Direct)
		{
			if (_prevPrevColor == 1m)
			{
				if (BuyPosOpen && _prevColor < 1m)
				buyOpen = true;
				if (SellPosClose)
				sellClose = true;
			}
			if (_prevPrevColor == 0m)
			{
				if (SellPosOpen && _prevColor > 0m)
				sellOpen = true;
				if (BuyPosClose)
				buyClose = true;
			}
		}
		else
		{
			if (_prevPrevColor == 0m)
			{
				if (BuyPosOpen && _prevColor > 0m)
				buyOpen = true;
				if (SellPosClose)
				sellClose = true;
			}
			if (_prevPrevColor == 1m)
			{
				if (SellPosOpen && _prevColor < 1m)
				sellOpen = true;
				if (BuyPosClose)
				buyClose = true;
			}
		}

		if (buyClose && Position > 0)
		SellMarket();
		if (sellClose && Position < 0)
		BuyMarket();
		if (buyOpen && Position <= 0)
			BuyMarket();
		if (sellOpen && Position >= 0)
			SellMarket();

		_prevPrevColor = _prevColor;
		_prevColor = color;
	}

	public enum Modes
	{
		Direct,
		NotDirect
	}

	private class I4Drf : BaseIndicator
	{
		public int Length { get; set; } = 11;

		private readonly Queue<int> _diffs = new();
		private int _sum;
		private decimal? _prevPrice;

		protected override IIndicatorValue OnProcess(IIndicatorValue input)
		{
			var price = input.GetValue<decimal>();

			if (_prevPrice is null)
			{
				_prevPrice = price;
				IsFormed = false;
				return new DecimalIndicatorValue(this, 0m, input.Time);
			}

			var diff = price > _prevPrice.Value ? 1 : (price < _prevPrice.Value ? -1 : 0);
			_prevPrice = price;

			_sum += diff;
			_diffs.Enqueue(diff);
			if (_diffs.Count > Length)
				_sum -= _diffs.Dequeue();

			if (_diffs.Count < Length)
			{
				IsFormed = false;
				return new DecimalIndicatorValue(this, 0m, input.Time);
			}

			IsFormed = true;
			var value = (decimal)_sum / Length * 100m;
			return new DecimalIndicatorValue(this, value, input.Time);
		}

		public override void Reset()
		{
			base.Reset();
			_diffs.Clear();
			_sum = 0;
			_prevPrice = null;
		}
	}
}