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();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class return_strategy(Strategy):
def __init__(self):
super(return_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 20) \
.SetDisplay("Period", "Bollinger Bands period", "Indicators")
self._width = self.Param("Width", 2.0) \
.SetDisplay("Width", "Bollinger Bands width", "Indicators")
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
@property
def Width(self):
return self._width.Value
def OnStarted2(self, time):
super(return_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ma, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _on_process(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
middle = float(ma_value)
band_width = float(self.Width) / 100.0
upper = middle * (1.0 + band_width)
lower = middle * (1.0 - band_width)
close = float(candle.ClosePrice)
# Buy when price drops below lower band (mean reversion)
if close < lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell when price rises above upper band
elif close > upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
# Exit long at middle band
elif self.Position > 0 and close >= middle:
self.SellMarket()
# Exit short at middle band
elif self.Position < 0 and close <= middle:
self.BuyMarket()
def CreateClone(self):
return return_strategy()