Ver en GitHub

Simple Trade Strategy

Overview

This strategy is a C# conversion of the MetaTrader 5 expert advisor "SimpleTrade (barabashkakvn's edition)". It compares the opening price of the current bar with the opening price three bars ago. If the current open is higher, the strategy goes long; otherwise it goes short. Every position is held for only one completed candle and is secured with a fixed stop-loss distance expressed in pips.

The StockSharp implementation subscribes to the selected candle series through the high-level API and reacts only to finished bars, ensuring that decisions are based on completed price data. Positions are closed at the next bar transition or earlier if the stop level is touched within the bar range.

Trading Logic

  • Entry
    • On each completed bar, store its opening price and maintain a rolling history of the last four opens.
    • When there is no open position and at least four opening prices are available, compare the latest open with the one recorded three bars earlier.
    • Enter a long position if the current open is above the open three bars ago; otherwise enter a short position.
  • Exit
    • Every trade is protected by a stop level calculated as StopLossPips × pip size from the entry open price.
    • On the following bar the position is closed regardless of outcome, replicating the original expert advisor that never holds a trade longer than one candle.
    • If the bar's high (for shorts) or low (for longs) penetrates the stop level, the strategy attempts to close the position immediately at market.

Parameters

Parameter Default Description
StopLossPips 120 Distance from the entry open price to the protective stop, measured in pips. The code reproduces the MetaTrader behaviour by multiplying the price step by 10 for symbols quoted with 3 or 5 decimals.
TradeVolume 1 Order volume used for market entries. Adjust it to align with the contract size of the traded instrument.
CandleType 1 hour time frame Specifies which candle series the strategy subscribes to. Select the timeframe that corresponds to the chart used in MetaTrader.

All parameters are exposed as StrategyParam<T> objects so they can be optimised or changed through the graphical interface.

Implementation Notes

  • The rolling history of four opening prices is maintained without collections to comply with repository guidelines.
  • Stops are not submitted as separate orders; instead the logic checks candle ranges and issues a market exit when the stop level would have been triggered.
  • Because StockSharp processes positions asynchronously, the strategy exits an existing trade before evaluating a new entry signal. In live trading, this mirrors the original "close then reopen" sequence while avoiding overlapping orders.
  • Pip size is derived from Security.PriceStep. For 5-digit or 3-digit symbols the step is multiplied by ten so that a pip matches the MetaTrader definition.

Usage Tips

  • Run the strategy on instruments with consistent tick sizes where pip-based stops are meaningful (for example, major Forex pairs).
  • Optimise the StopLossPips value per instrument; large values widen the protective buffer, while smaller values make the strategy more sensitive to intrabar noise.
  • Ensure the brokerage connection sends candle updates with final states so that the strategy receives the correct open prices.

Risks and Limitations

  • Holding trades for only one bar means the strategy relies heavily on the chosen timeframe. Backtesting different candle durations is essential.
  • Using candle extremes to emulate stop execution introduces slippage in volatile markets compared to native stop orders.
  • The strategy always stays in the market (either long or short) after the first four bars of data, which may generate frequent trades in sideways markets.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple trade strategy converted from MetaTrader that compares the current open with the open three bars ago.
/// The logic holds positions for a single bar and protects the trade with a fixed stop distance.
/// </summary>
public class SimpleTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _openCurrent;
	private decimal _openMinus1;
	private decimal _openMinus2;
	private decimal _openMinus3;
	private int _historyCount;
	private decimal? _stopPrice;

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Trade volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="SimpleTradeStrategy"/> parameters.
	/// </summary>
	public SimpleTradeStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 120)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Fixed protective distance in pips", "Risk Management")
			;

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle source for decisions", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_openCurrent = 0m;
		_openMinus1 = 0m;
		_openMinus2 = 0m;
		_openMinus3 = 0m;
		_historyCount = 0;
		_stopPrice = null;
	}

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

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

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

		// Exit existing trades before evaluating new entries to mimic the original MQL behaviour.
		if (TryCloseExistingPosition(candle))
			return;

		UpdateHistory(candle.OpenPrice);

		// Need at least four opens to compare with the value three bars ago.
		if (_historyCount < 4)
			return;

		ExecuteEntry(candle);
	}

	private bool TryCloseExistingPosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var volume = Position;

			// Close long trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		if (Position < 0)
		{
			var volume = Math.Abs(Position);

			// Close short trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		return false;
	}

	private void UpdateHistory(decimal currentOpen)
	{
		// Shift the stored opens so that _openMinus3 keeps the value three candles back.
		_openMinus3 = _openMinus2;
		_openMinus2 = _openMinus1;
		_openMinus1 = _openCurrent;
		_openCurrent = currentOpen;

		if (_historyCount < 4)
			_historyCount++;
	}

	private void ExecuteEntry(ICandleMessage candle)
	{
		var pipSize = CalculatePipSize();
		var stopOffset = pipSize * StopLossPips;

		// Enter long when the current open is above the open three bars ago, otherwise enter short.
		if (_openCurrent > _openMinus3)
		{
			BuyMarket();
			_stopPrice = candle.OpenPrice - stopOffset;
		}
		else
		{
			SellMarket();
			_stopPrice = candle.OpenPrice + stopOffset;
		}
	}

	private decimal CalculatePipSize()
	{
		// Reproduce the MetaTrader adjustment: multiply by ten for symbols with 3 or 5 decimals.
		var step = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			step *= 10m;

		return step;
	}
}