Ver en GitHub

Return Strategy

This strategy replicates the classic "Return Strategy" expert advisor. It prepares a grid of paired buy-limit and sell-limit orders at the start of a configured trading window. The grid is symmetric around the market price, uses fixed spacing in pips, and can be sized either by a fixed volume or a percentage risk model. Once orders are filled the strategy supervises the position with static and trailing stop-loss logic, monitors cumulative open profit, and forces a full flattening at the daily cut-off time or every Friday.

The original system was designed for netting accounts and focused on capturing mean-reversion moves after scheduled times. The conversion keeps that structure while adapting order management, trailing, and capital controls to the StockSharp high-level API.

Trading Rules

  • Daily preparation – At the StartHour the strategy checks that no grid orders are active and places PendingOrderCount buy limits below and sell limits above the current price. The first level is offset by DistancePips and each subsequent level adds StepPips of spacing.
  • Risk control – Each pending order can use either a fixed OrderVolume or a risk-based size derived from RiskPercent. When risk sizing is used the available capital and stop-loss distance determine the per-order volume so that the total grid risk equals the configured percentage.
  • Stop management – Every filled position receives an initial stop loss based on StopLossPips. If TrailingStopPips is greater than zero, once price advances beyond the trailing threshold the stop is ratcheted in steps of TrailingStepPips.
  • Profit target and session exit – The net open profit is tracked in pips. When it reaches TotalProfitPips the strategy marks all positions and orders for closure. It also performs the same flush at the configured EndHour and on every Friday regardless of profit.
  • Order expiration – Pending orders can automatically expire after ExpirationHours. Expired or manually cancelled orders are removed from the tracking list to allow a new grid to be placed the next day.

Parameters

Parameter Description
StopLossPips Initial stop distance for any filled position (in adjusted pips).
StartHour Hour (0–23) when the pending-order grid is created.
EndHour Hour (0–23) that triggers a complete exit of positions and orders.
TotalProfitPips Net open profit target (in pips) that forces all trades to be closed.
TrailingStopPips Distance of the trailing stop from price once activated. Set to zero to disable trailing.
TrailingStepPips Additional advance required before moving the trailing stop. Must be positive when trailing is enabled.
DistancePips Initial offset for the first pending order on each side of the market.
StepPips Incremental spacing between consecutive pending orders.
PendingOrderCount Number of buy limits and sell limits to register at StartHour.
ExpirationHours Lifetime of pending orders in hours. Zero disables expiration.
OrderVolume Fixed volume per pending order. Leave at zero to enable risk-based sizing.
RiskPercent Portfolio percentage allocated to the entire grid. Per-order size is derived from this value when OrderVolume is zero.
CandleType Candle series used to drive timing and stop management logic.

Additional Notes

  • The pip conversion mirrors the original MetaTrader logic by adjusting the step size for three- and five-decimal instruments.
  • When RiskPercent is used, the percentage applies to the combined grid and is divided equally across all pending orders.
  • The strategy enforces validation rules identical to the source EA: hours must be inside the daily range, trailing requires a non-zero step, and only one of OrderVolume/RiskPercent may be active at a time.
  • All public comments in the code are provided in English for consistency with repository guidelines.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Mean reversion strategy using Bollinger Bands.
/// Buys when price drops below lower band and sells when price rises above upper band.
/// </summary>
public class ReturnStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<decimal> _width;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	public decimal Width
	{
		get => _width.Value;
		set => _width.Value = value;
	}

	public ReturnStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 20)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Bollinger Bands period", "Indicators");

		_width = Param(nameof(Width), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Width", "Bollinger Bands width", "Indicators");
	}

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

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var ma = new SimpleMovingAverage { Length = Period };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var bandWidth = Width / 100m;
		var upper = middle * (1m + bandWidth);
		var lower = middle * (1m - bandWidth);
		var close = candle.ClosePrice;

		// Buy when price drops below lower band (mean reversion)
		if (close < lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Sell when price rises above upper band
		else if (close > upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit long at middle band
		else if (Position > 0 && close >= middle)
		{
			SellMarket();
		}
		// Exit short at middle band
		else if (Position < 0 && close <= middle)
		{
			BuyMarket();
		}
	}
}