Auf GitHub ansehen

IStochastic Trading Strategy

Overview

IStochastic Trading Strategy is a direct StockSharp port of the "IStochastic_Trading" MetaTrader 5 expert advisor. The bot uses the Stochastic Oscillator to detect oversold and overbought conditions and then builds a martingale-style position ladder while managing every entry with stop loss, take profit and a trailing stop. The implementation operates on finished candles obtained through StockSharp's high-level API and relies on market orders only.

Trading Logic

  1. Calculate a Stochastic Oscillator with configurable %K length, %D smoothing and an additional slowing factor.
  2. When there are no active positions, evaluate the most recent finished candle:
    • Open a long position if %K is above %D and %D is below the configured buy zone.
    • Open a short position if %K is below %D and %D is above the configured sell zone.
  3. When a position exists, monitor the latest fill in the ladder:
    • If the market moves against the trade by at least the configured gap (in pips), open a new position in the same direction with twice the previous volume, as long as the maximum number of positions is not exceeded.
  4. For every entry maintain per-trade stop loss and take profit levels derived from pip distances converted to price points using the security's PriceStep and number of decimals. If the closing price reaches the stop or the target, the strategy exits the specific position with a market order.
  5. Apply a trailing stop after each candle close. When the trade moves far enough in the favourable direction, the stop price is tightened by the specified trailing step, approximating the terminal's per-position trailing behaviour.

Parameters

Name Default Description
OrderVolume 0.1 Initial position size in lots. Additional entries double the previous volume.
TakeProfitPips 50 Take profit distance measured in pips. The value is converted to price points internally.
StopLossPips 50 Stop loss distance in pips for each position.
TrailingStopPips 10 Trailing stop distance in pips. Set to zero to disable trailing.
TrailingStepPips 5 Minimum favourable move (in pips) before the trailing stop is adjusted.
MaxPositions 3 Maximum number of simultaneously open martingale steps. A value of 0 removes the limit.
GapPips 7 Price gap, in pips, required before doubling into the current direction.
KPeriod 5 Number of candles used to build the %K line.
DPeriod 3 Period of the %D smoothing average.
Slowing 3 Additional smoothing applied to %K.
ZoneBuy 30 %D threshold used to validate long entries (oversold zone).
ZoneSell 70 %D threshold used to validate short entries (overbought zone).
CandleType 15-minute time frame Candle series employed for calculations.

Implementation Notes

  • Pip distances are converted to prices with PriceStep. For 3- and 5-digit quotes an additional factor of 10 is used to mimic MetaTrader's adjusted point logic.
  • Stop loss, take profit and trailing stop checks rely on closed candle prices to keep the logic deterministic inside the backtester. Real-time execution can be customised if intrabar management is required.
  • The strategy only opens one directional ladder at a time; all positions must be closed before switching direction.
  • Python implementation is intentionally omitted as requested.
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;
	}
}