Стратегия представляет собой порт на StockSharp классического советника Moving Average из MetaTrader 4. Система анализирует завершённые свечи и сравнивает их с сдвинутой простой скользящей средней (SMA), чтобы определить смену направления. Сделки совершаются рыночными заявками, в рынке одновременно может находиться только одна позиция.
Логика торговли
Подписка на свечи выбранного таймфрейма (по умолчанию 5 минут) и расчёт SMA с заданным периодом.
Сдвиг SMA на указанное количество завершённых свечей для имитации поведения функции iMA из MetaTrader.
Анализ предыдущей завершённой свечи:
Бычий пробой (открытие ниже SMA и закрытие выше) открывает длинную позицию, если активных позиций нет.
Медвежий пробой (открытие выше и закрытие ниже SMA) открывает короткую позицию при отсутствии открытых позиций.
Управление выходом на тех же условиях:
Длинная позиция закрывается, когда последняя свеча пересекает SMA сверху вниз.
Короткая позиция закрывается, когда свеча пересекает SMA снизу вверх.
Одновременно поддерживается только одна позиция, что повторяет логику исходного советника, который последовательно чередовал покупки и продажи.
Параметры
Имя
Описание
Значение по умолчанию
CandleType
Свечная серия для расчётов, выбирается любой таймфрейм DataType.
5-минутный таймфрейм
MovingPeriod
Количество свечей в периоде SMA.
12
MovingShift
Сдвиг значения SMA в завершённых свечах, аналог аргумента shift у iMA.
6
BaseVolume
Базовый объём заявок для входа в позицию. Используется одинаково для длинных и коротких сделок.
1
Работа с индикатором
Индикатор SimpleMovingAverage создаётся в OnStarted и связывается со свечами через высокоуровневый метод Bind.
Выходные значения SMA попадают в небольшую очередь FIFO, что позволяет получить значение MovingShift свечей назад без ручного пересчёта индикатора.
Очередь хранит только MovingShift + 1 значений, поэтому объём памяти остаётся постоянным даже при большом сдвиге.
Управление ордерами и рисками
Заявки отправляются методами BuyMarket/SellMarket, размер задаётся параметром BaseVolume. При закрытии используется текущий модуль позиции, чтобы полностью выйти из сделки.
В оригинале MetaTrader лоты менялись в зависимости от свободной маржи и серии убытков. В версии для StockSharp логика упрощена: пользователь самостоятельно задаёт объём через параметр, что избавляет от зависимости от брокерских метрик и сохраняет главные правила входа/выхода.
Особенности конверсии
Сигналы оцениваются по предыдущей свече, повторяя проверку Volume[0] == 1 из MetaTrader, которая ждала появления нового бара.
Обрабатываются только завершённые свечи (CandleStates.Finished), чтобы не реагировать на незакрытые бары.
Стратегия использует вспомогательные методы StockSharp для отображения свечей, индикатора и сделок на графике при наличии области графика.
Использование
Скомпилируйте стратегию в Designer, Shell или Runner.
Выберите инструмент и портфель.
Настройте параметры, если требуется другой таймфрейм, период или объём.
Запустите стратегию — она подпишется на свечи, будет отслеживать пересечения SMA и торговать по описанным правилам.
Дополнительные идеи
При необходимости добавить защитные стопы и цели через StartProtection.
Заменить простую SMA на другой индикатор (EMA, LWMA и т.д.), сохранив текущую схему подписки.
Реализовать масштабирование позиции, изменяя метод GetEntryVolume.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class MovingAverageShiftStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevEma;
private bool _hasPrev;
private int _cooldownRemaining;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MovingAverageShiftStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = default;
_prevEma = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_prevEma = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevClose = close;
_prevEma = ema;
return;
}
if (_prevClose <= _prevEma && close > ema && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevClose >= _prevEma && close < ema && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevClose = close;
_prevEma = ema;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class moving_average_shift_strategy(Strategy):
def __init__(self):
super(moving_average_shift_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 200) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(moving_average_shift_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(moving_average_shift_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.process_candle).Start()
def process_candle(self, candle, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_close = close
self._prev_ema = ema_val
return
if self._prev_close <= self._prev_ema and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_close >= self._prev_ema and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_close = close
self._prev_ema = ema_val
def CreateClone(self):
return moving_average_shift_strategy()