Стратегия повторяет эксперта MetaTrader «Gridtrading_at_volatile_market.mq4» на высокоуровневом API StockSharp. Она отслеживает касания каналов Дончиана на старшем таймфрейме и подтверждает входы поглощениями на рабочем таймфрейме. После запуска сетки стратегия усредняет позицию при движении цены на кратные значения ATR и закрывает все сделки по целевому профиту либо при превышении допустимой просадки портфеля.
Логика работы
Используются два потока свечей: выбранный торговый таймфрейм и автоматически подобранный старший таймфрейм (M1→M5→M15→M30→H1→H4→D1).
На старшем таймфрейме рассчитываются:
ATR(20) — шаг между уровнями сетки;
SMA(SlowMaLength) — трендовый фильтр в паре с RSI;
DonchianChannels(20) — динамические уровни поддержки и сопротивления.
На рабочем таймфрейме анализируются две последние завершённые свечи для поиска бычьего или медвежьего поглощения.
Лонговая сетка стартует, когда предыдущая свеча касается нижней границы Дончиана, формирует бычье поглощение, а RSI указывает на перепроданность (RSI < 35 при закрытии выше старшего SMA). Шортовая сетка строится зеркально относительно верхней границы и условия RSI > 65.
После первой рыночной сделки стратегия фиксирует цену запуска и при движении против позиции на 2 * ATR (для текущего шага) выставляет следующий ордер с объёмом, умноженным на GridMultiplier.
Все позиции закрываются и заявки отменяются, если выполняется одно из условий:
суммарный (реализованный + нереализованный) результат превышает TakeProfitFactor * общий объём сетки;
просадка опускается ниже -MaxDrawdownFraction * стартовая стоимость портфеля.
Параметры
TakeProfitFactor — множитель целевого профита относительно суммарного объёма сетки (по умолчанию 0.1).
SlowMaLength — период SMA на старшем таймфрейме для фильтрации (по умолчанию 50).
GridMultiplier — геометрический коэффициент для каждого дополнительного ордера (по умолчанию 1.5).
BaseOrderVolume — объём первого ордера сетки (по умолчанию 0.1).
MaxDrawdownFraction — допустимая доля просадки от начальной стоимости портфеля (по умолчанию 0.8).
CandleType — рабочий таймфрейм. Старший таймфрейм определяется автоматически.
Примечания
Обрабатываются только закрытые свечи, что исключает перерисовку.
Для оценки открытой прибыли используются котировки bid/ask; при отсутствии стакана расчёт основан на цене последней сделки и может быть менее точным.
Если данные по портфелю недоступны, защита по просадке игнорируется, и сетка будет работать до достижения целевого профита либо ручного вмешательства.
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>
/// Simplified from "Grid Trading at Volatile Market" MetaTrader expert.
/// Uses RSI + SMA trend filter for initial entry then manages a simple
/// grid of averaging orders based on ATR distance.
/// </summary>
public class GridTradingAtVolatileMarketStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _maxGridLevels;
private RelativeStrengthIndex _rsi;
private readonly Queue<decimal> _smaQueue = new();
private decimal _smaSum;
private Sides? _gridDirection;
private int _gridLevel;
private decimal _lastEntryPrice;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
public int MaxGridLevels
{
get => _maxGridLevels.Value;
set => _maxGridLevels.Value = value;
}
public GridTradingAtVolatileMarketStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal detection", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period for entry signals", "Indicators");
_smaPeriod = Param(nameof(SmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators");
_maxGridLevels = Param(nameof(MaxGridLevels), 3)
.SetGreaterThanZero()
.SetDisplay("Max Grid Levels", "Maximum averaging levels", "Grid");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_smaQueue.Clear();
_smaSum = 0;
_gridDirection = null;
_gridLevel = 0;
_lastEntryPrice = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
// Manual SMA
_smaQueue.Enqueue(close);
_smaSum += close;
while (_smaQueue.Count > SmaPeriod)
_smaSum -= _smaQueue.Dequeue();
if (!_rsi.IsFormed || _smaQueue.Count < SmaPeriod)
return;
var smaValue = _smaSum / _smaQueue.Count;
var volume = Volume;
if (volume <= 0)
volume = 1;
// If no grid active, look for entry signals
if (_gridDirection is null)
{
// Buy: RSI oversold + price below SMA (mean reversion)
if (rsiValue < 35 && close < smaValue)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
_gridDirection = Sides.Buy;
_gridLevel = 1;
_lastEntryPrice = close;
}
// Sell: RSI overbought + price above SMA
else if (rsiValue > 65 && close > smaValue)
{
if (Position > 0)
SellMarket(Position);
SellMarket(volume);
_gridDirection = Sides.Sell;
_gridLevel = 1;
_lastEntryPrice = close;
}
}
else
{
// Grid management - add levels on adverse moves
var distanceThreshold = _lastEntryPrice * 0.005m; // 0.5% grid step
if (_gridDirection == Sides.Buy)
{
// Price moved further down - add to grid
if (_gridLevel < MaxGridLevels && close < _lastEntryPrice - distanceThreshold)
{
BuyMarket(volume);
_gridLevel++;
_lastEntryPrice = close;
}
// Take profit - price recovered above SMA
else if (close > smaValue && rsiValue > 50)
{
if (Position > 0)
SellMarket(Position);
_gridDirection = null;
_gridLevel = 0;
}
}
else if (_gridDirection == Sides.Sell)
{
// Price moved further up - add to grid
if (_gridLevel < MaxGridLevels && close > _lastEntryPrice + distanceThreshold)
{
SellMarket(volume);
_gridLevel++;
_lastEntryPrice = close;
}
// Take profit - price fell below SMA
else if (close < smaValue && rsiValue < 50)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
_gridDirection = null;
_gridLevel = 0;
}
}
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_smaQueue.Clear();
_smaSum = 0;
_gridDirection = null;
_gridLevel = 0;
_lastEntryPrice = 0;
base.OnReseted();
}
}
import clr
from collections import deque
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class grid_trading_at_volatile_market_strategy(Strategy):
"""
Grid Trading at Volatile Market: RSI + SMA trend filter for initial entry
then manages a grid of averaging orders based on distance threshold.
"""
def __init__(self):
super(grid_trading_at_volatile_market_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period for entry signals", "Indicators")
self._sma_period = self.Param("SmaPeriod", 50) \
.SetDisplay("SMA Period", "SMA period for trend filter", "Indicators")
self._max_grid_levels = self.Param("MaxGridLevels", 3) \
.SetDisplay("Max Grid Levels", "Maximum averaging levels", "Grid")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe for signal detection", "General")
self._sma_queue = deque()
self._sma_sum = 0.0
self._grid_direction = None # 'buy' or 'sell'
self._grid_level = 0
self._last_entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(grid_trading_at_volatile_market_strategy, self).OnReseted()
self._sma_queue = deque()
self._sma_sum = 0.0
self._grid_direction = None
self._grid_level = 0
self._last_entry_price = 0.0
def OnStarted2(self, time):
super(grid_trading_at_volatile_market_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi = float(rsi_val)
self._sma_queue.append(close)
self._sma_sum += close
sma_period = self._sma_period.Value
while len(self._sma_queue) > sma_period:
self._sma_sum -= self._sma_queue.popleft()
if len(self._sma_queue) < sma_period:
return
sma_value = self._sma_sum / len(self._sma_queue)
if self._grid_direction is None:
if rsi < 35 and close < sma_value:
self.BuyMarket()
self._grid_direction = 'buy'
self._grid_level = 1
self._last_entry_price = close
elif rsi > 65 and close > sma_value:
self.SellMarket()
self._grid_direction = 'sell'
self._grid_level = 1
self._last_entry_price = close
else:
distance_threshold = self._last_entry_price * 0.005
if self._grid_direction == 'buy':
if self._grid_level < self._max_grid_levels.Value and close < self._last_entry_price - distance_threshold:
self.BuyMarket()
self._grid_level += 1
self._last_entry_price = close
elif close > sma_value and rsi > 50:
if self.Position > 0:
self.SellMarket()
self._grid_direction = None
self._grid_level = 0
elif self._grid_direction == 'sell':
if self._grid_level < self._max_grid_levels.Value and close > self._last_entry_price + distance_threshold:
self.SellMarket()
self._grid_level += 1
self._last_entry_price = close
elif close < sma_value and rsi < 50:
if self.Position < 0:
self.BuyMarket()
self._grid_direction = None
self._grid_level = 0
def CreateClone(self):
return grid_trading_at_volatile_market_strategy()