Конвертация советника MetaTrader 5 «Moving Average with Frames». Оригинальная система оценивает взаимное положение цены открытия и закрытия бара относительно смещённой простой скользящей средней (SMA) и выводит несколько линий баланса («фреймы») после оптимизации. В версии для StockSharp реализована торговая логика: обработка только завершённых баров, работа в неттинговом режиме с одной позицией и сохранение правил управления капиталом исходника.
Логика торговли
Источник данных – стратегия подписывается на выбранный таймфрейм (CandleType) и обрабатывает только финальные свечи, повторяя условие MetaTrader if(rt[1].tick_volume>1) return;.
Индикатор – простая скользящая средняя с периодом MovingPeriod. Выход SMA сдвигается вперёд на MovingShift закрытых баров за счёт внутреннего буфера значений.
Прогрев – до накопления минимум 100 завершённых свечей входы запрещены, как и в оригинале (Bars(_Symbol,_Period)>100).
Условия входа
Покупка – бар открывается ниже смещённой SMA и закрывается выше неё.
Продажа – бар открывается выше смещённой SMA и закрывается ниже неё.
Стратегия удерживает единственную позицию: при смене направления противоположная позиция закрывается перед открытием новой.
Выходы – лонг закрывается, когда цена открытия выше, а закрытия ниже смещённой SMA; шорт закрывается на обратном пересечении. После закрытия на том же баре новая сделка не открывается, что соответствует поведению MQL5.
Управление рисками и объёмом
MaximumRisk – рассчитывает базовый объём как Portfolio.CurrentValue * MaximumRisk / price, если брокер предоставляет стоимость портфеля. При отсутствии данных используется свойство Volume.
DecreaseFactor – после более чем одного подряд убыточного выхода объём уменьшается на volume * losses / DecreaseFactor, полностью копируя алгоритм MetaTrader. Любая прибыльная сделка сбрасывает счётчик.
Нормализация объёма – итоговый объём приводится к шагу VolumeStep, ограничивается диапазоном [MinVolume, MaxVolume] и округляется до двух знаков, если биржа не публикует шаг.
Дополнительные замечания
Отрисовка оптимизационных «фреймов» не переносилась, так как StockSharp предоставляет собственные инструменты визуализации результатов. Торговые правила и тайминг сигналов сохранены.
Значения индикатора берутся напрямую из обработчика Bind, вызовов GetValue нет.
Учёт серии убыточных сделок реализован в OnOwnTradeReceived, что корректно обрабатывает частичные исполнения и неттинговую модель.
Параметры
Параметр
Значение по умолчанию
Описание
MaximumRisk
0.02
Доля капитала, используемая при открытии позиции.
DecreaseFactor
3
Делитель, уменьшающий объём после двух и более подряд убыточных сделок.
MovingPeriod
12
Период простой скользящей средней по ценам закрытия.
MovingShift
6
Количество завершённых свечей, на которое сдвигается SMA вперёд по времени.
CandleType
Таймфрейм 1 час
Основной поток свечей для расчётов.
Рекомендации по использованию
Привяжите стратегию к нужному инструменту и портфелю в Designer либо в коде.
Подберите таймфрейм, соответствующий графику MetaTrader, на котором использовался советник.
Настройте MaximumRisk и DecreaseFactor под размер депозита и допустимый риск.
Проведите тестирование, чтобы убедиться, что сигналы пересечений совпадают с результатами оригинального советника.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving Average With Frames: SMA crossover with candle body confirmation.
/// Buys when close crosses above shifted SMA, sells when crosses below.
/// </summary>
public class MovingAverageWithFramesStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private decimal _prevClose;
public MovingAverageWithFramesStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 12)
.SetDisplay("SMA Length", "SMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevClose = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
if (_prevClose == 0)
{
_prevClose = close;
return;
}
if (Position > 0)
{
if (close < smaVal && _prevClose >= smaVal)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close > smaVal && _prevClose <= smaVal)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > smaVal && _prevClose <= smaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (close < smaVal && _prevClose >= smaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevClose = close;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
class moving_average_with_frames_strategy(Strategy):
def __init__(self):
super(moving_average_with_frames_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._sma_length = self.Param("SmaLength", 12) \
.SetDisplay("SMA Length", "SMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._prev_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmaLength(self):
return self._sma_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(moving_average_with_frames_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._prev_close = 0.0
self._sma = SimpleMovingAverage()
self._sma.Length = self.SmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
sv = float(sma_val)
av = float(atr_val)
if av <= 0:
return
close = float(candle.ClosePrice)
if self._prev_close == 0:
self._prev_close = close
return
if self.Position > 0:
if close < sv and self._prev_close >= sv:
self.SellMarket()
self._entry_price = 0.0
elif close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close > sv and self._prev_close <= sv:
self.BuyMarket()
self._entry_price = 0.0
elif close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > sv and self._prev_close <= sv:
self._entry_price = close
self.BuyMarket()
elif close < sv and self._prev_close >= sv:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(moving_average_with_frames_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_close = 0.0
def CreateClone(self):
return moving_average_with_frames_strategy()