Эта реализация повторяет советник «Return Strategy». Стратегия ежедневно в заданный час строит симметричную сетку отложенных заявок на покупку и продажу вокруг текущей цены. Расстояние между уровнями задаётся в пунктах, а объём каждой заявки может фиксироваться напрямую либо рассчитываться из процента риска. После активации ордера позиция сопровождается фиксированным и трейлинговым стопом, контролируется суммарная плавающая прибыль и при достижении целевых условий все позиции и заявки закрываются.
Изначально алгоритм предназначался для неттинговых счетов и поиска возвратного движения после определённых часов. Перенос в StockSharp сохраняет структуру исходного робота и переводит управление заявками, защитой и капиталом на высокоуровневый API платформы.
Логика работы
Подготовка сетки – В момент StartHour проверяется отсутствие активных лимитных заявок и создаётся по PendingOrderCount заявок Buy Limit ниже и Sell Limit выше рынка. Первый уровень смещается на DistancePips, каждый следующий добавляет StepPips пунктов.
Управление риском – Для каждой заявки можно задать фиксированный OrderVolume либо использовать расчёт на основе RiskPercent. Во втором случае объём выводится из доступного капитала и величины стопа так, чтобы суммарный риск сетки соответствовал выбранному проценту.
Стопы – Сформированная позиция получает исходный стоп на расстоянии StopLossPips. Если TrailingStopPips больше нуля, после прохождения цены порога стоп подтягивается шагом TrailingStepPips.
Цель и выход из сессии – Открытая прибыль пересчитывается в пунктах. При достижении TotalProfitPips стратегия инициирует закрытие всех позиций и ордеров. То же самое происходит в момент EndHour, а также каждый пятничный день вне зависимости от результата.
Истечение – Отложенные заявки могут автоматически сниматься через ExpirationHours. Просроченные или отменённые ордера удаляются из списка, чтобы на следующий день можно было выставить новую сетку.
Параметры
Параметр
Описание
StopLossPips
Расстояние до стартового стоп-лосса в пунктах.
StartHour
Час (0–23), когда выставляется сетка ордеров.
EndHour
Час (0–23), в который закрываются все позиции и заявки.
TotalProfitPips
Цель по суммарной плавающей прибыли в пунктах.
TrailingStopPips
Расстояние трейлинг-стопа от цены. Ноль отключает трейлинг.
TrailingStepPips
Требуемое дополнительное движение цены для подтяжки трейлинг-стопа. Должно быть > 0 при включённом трейлинге.
DistancePips
Смещение первого лимитного ордера от текущей цены.
StepPips
Дополнительный шаг между соседними ордерами.
PendingOrderCount
Количество заявок Buy Limit и Sell Limit.
ExpirationHours
Время жизни отложенных ордеров в часах. Ноль — без истечения.
OrderVolume
Фиксированный объём для каждой заявки. Ноль включает риск-менеджмент по проценту.
RiskPercent
Процент капитала, распределяемый между всеми ордерами сетки, когда OrderVolume равен нулю.
CandleType
Тип свечей, используемых для тайминга и расчётов.
Дополнительная информация
Пересчёт пункта повторяет логику MetaTrader: для инструментов с 3 и 5 знаками минимальный шаг умножается на 10.
При расчёте по RiskPercent процент применяется ко всей сетке и делится между ордерами поровну.
Встроена проверка параметров, совпадающая с исходным экспертом: часы должны лежать в диапазоне суток, трейлинг требует положительного шага, одновременно можно использовать либо фиксированный объём, либо процент риска.
Комментарии в исходном коде даны на английском языке в соответствии с требованиями репозитория.
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()