Открыть на GitHub

Стратегия IStochastic Trading

Обзор

Стратегия IStochastic Trading представляет собой перенос эксперта MetaTrader 5 "IStochastic_Trading" на платформу StockSharp. Алгоритм использует осциллятор Стохастик для определения зон перепроданности и перекупленности, после чего строит лесенку позиций по принципу мартингейла и сопровождает каждое входящую позицию стоп-лоссом, тейк-профитом и трейлинг-стопом. Реализация основана на свечах, предоставляемых высокоуровневым API StockSharp, и работает исключительно с рыночными приказами.

Логика торговли

  1. Рассчитывается осциллятор Стохастик с настраиваемыми параметрами %K, %D и дополнительным сглаживанием.
  2. При отсутствии позиций анализируется последняя закрытая свеча:
    • Открывается длинная позиция, если %K выше %D и %D находится ниже порога покупок.
    • Открывается короткая позиция, если %K ниже %D и %D выше порога продаж.
  3. Если позиции уже есть, отслеживается последняя добавленная ступень:
    • Когда цена движется против позиции на величину, равную настроенному зазору (в пунктах), открывается новая позиция в том же направлении с удвоенным объёмом, если лимит по количеству ступеней ещё не достигнут.
  4. Для каждой ступени рассчитываются индивидуальные уровни стоп-лосса и тейк-профита. Конвертация из пунктов в цену выполняется на основе PriceStep инструмента и количества знаков после запятой, что повторяет логику "adjusted point" оригинального советника. При достижении закрытия цены уровня стопа или цели соответствующая позиция закрывается рыночным приказом.
  5. После закрытия каждой свечи выполняется подтягивание трейлинг-стопа. При достаточном движении цены в прибыльную сторону стоп переносится на величину заданного шага, имитируя поведение MetaTrader для каждой позиции.

Параметры

Имя Значение по умолчанию Описание
OrderVolume 0.1 Объём первой сделки в лотах. Каждое последующее добавление удваивает объём предыдущей позиции.
TakeProfitPips 50 Расстояние до тейк-профита в пунктах для каждой ступени.
StopLossPips 50 Расстояние до стоп-лосса в пунктах для каждой ступени.
TrailingStopPips 10 Дистанция трейлинг-стопа в пунктах. Значение 0 отключает сопровождение.
TrailingStepPips 5 Минимальное благоприятное движение (в пунктах), после которого стоп подтягивается.
MaxPositions 3 Максимальное количество одновременно открытых ступеней. Значение 0 снимает ограничение.
GapPips 7 Обратное движение цены (в пунктах), необходимое для удвоения позиции.
KPeriod 5 Количество свечей при расчёте линии %K.
DPeriod 3 Период сглаживания для линии %D.
Slowing 3 Дополнительное сглаживание линии %K.
ZoneBuy 30 Порог %D для подтверждения длинного сигнала (зона перепроданности).
ZoneSell 70 Порог %D для подтверждения короткого сигнала (зона перекупленности).
CandleType 15 минут Таймфрейм свечей, используемый в расчётах.

Особенности реализации

  • Перевод расстояний в пунктах в цены выполняется с помощью PriceStep. Для инструментов с 3 или 5 знаками после запятой дополнительно применяется множитель 10, что соответствует подходу MetaTrader.
  • Контроль стопов и целей выполняется по закрытию свечи. Это обеспечивает детерминированность в тестах; при необходимости можно расширить логику для внутрисвечного сопровождения.
  • В каждый момент времени стратегия сопровождает только одну лесенку в заданном направлении. Для смены направления все позиции должны быть закрыты.
  • Python-версия и соответствующая папка намеренно не создавались по условиям задания.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Stochastic Oscillator-based strategy with zone filtering.
/// Goes long when %K crosses above %D in the oversold zone, short when %K crosses below %D in overbought zone.
/// </summary>
public class IStochasticTradingStrategy : Strategy
{
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<decimal> _zoneBuy;
	private readonly StrategyParam<decimal> _zoneSell;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevK;
	private decimal? _prevD;

	/// <summary>
	/// %K period.
	/// </summary>
	public int KPeriod
	{
		get => _kPeriod.Value;
		set => _kPeriod.Value = value;
	}

	/// <summary>
	/// %D smoothing period.
	/// </summary>
	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	/// <summary>
	/// Buy zone threshold (oversold).
	/// </summary>
	public decimal ZoneBuy
	{
		get => _zoneBuy.Value;
		set => _zoneBuy.Value = value;
	}

	/// <summary>
	/// Sell zone threshold (overbought).
	/// </summary>
	public decimal ZoneSell
	{
		get => _zoneSell.Value;
		set => _zoneSell.Value = value;
	}

	/// <summary>
	/// Candle type used for analysis.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public IStochasticTradingStrategy()
	{
		_kPeriod = Param(nameof(KPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("K Period", "Number of bars for %K", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("D Period", "Smoothing period for %D", "Indicators");

		_zoneBuy = Param(nameof(ZoneBuy), 30m)
			.SetRange(0m, 100m)
			.SetDisplay("Buy Zone", "Upper boundary for bullish confirmation", "Signals");

		_zoneSell = Param(nameof(ZoneSell), 70m)
			.SetRange(0m, 100m)
			.SetDisplay("Sell Zone", "Lower boundary for bearish confirmation", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevK = null;
		_prevD = null;
	}

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

		_prevK = null;
		_prevD = null;

		var stochastic = new StochasticOscillator();
		stochastic.K.Length = KPeriod;
		stochastic.D.Length = DPeriod;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(stochastic, ProcessCandle)
			.Start();

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

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

		if (!stochasticValue.IsFinal)
			return;

		var stoch = (StochasticOscillatorValue)stochasticValue;

		if (stoch.K is not decimal kValue || stoch.D is not decimal dValue)
			return;

		if (_prevK is decimal prevK && _prevD is decimal prevD)
		{
			var crossedUp = prevK <= prevD && kValue > dValue;
			var crossedDown = prevK >= prevD && kValue < dValue;

			if (crossedUp && dValue < ZoneBuy && Position <= 0)
			{
				BuyMarket();
			}
			else if (crossedDown && dValue > ZoneSell && Position >= 0)
			{
				SellMarket();
			}
		}

		_prevK = kValue;
		_prevD = dValue;
	}
}