Стратегия является точной конверсией советника MetaTrader «MA MACD Position averaging». Она объединяет фильтр по взвешенной скользящей средней с проверкой отношения линий MACD и включает модуль усреднения с наращиванием позиции при неблагоприятном движении цены на заданное число пунктов. Все рисковые параметры задаются в пунктах и автоматически переводятся в ценовые отступы с учётом параметров инструмента из StockSharp.
Логика работы
Подготовка индикаторов
Настраиваемая скользящая средняя (MaPeriod, MaMethod, MaAppliedPrice) рассчитывается на закрытых свечах. Параметры SignalBar и MaShift имитируют возможность MetaTrader обращаться к данным предыдущих баров и выводить индикатор со сдвигом по горизонтали.
Индикатор MACD (MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod, MacdAppliedPrice) рассчитывается по тем же свечам. Основная и сигнальная линии складываются в небольшой буфер, что позволяет получать исторические значения без прямых вызовов индикатора.
Условия входа
Покупка: обе линии MACD ниже нуля, отношение MACDmain / MACDsignal не меньше MacdRatio, закрытие свечи выше выбранной скользящей средней, а расстояние между ценой и средней не меньше IndentPips пунктов.
Продажа: обе линии MACD выше нуля, отношение превышает MacdRatio, закрытие ниже скользящей средней, а разрыв не меньше IndentPips пунктов.
Новые входы разрешены только при отсутствии открытой позиции. Если цикл усреднения уже запущен, сигнальная часть пропускается и работают только правила усреднения.
Модуль усреднения
При наличии длинной позиции и снижении цены минимум на StepLossingPips пунктов относительно лучшей (самой дешёвой) покупки отправляется дополнительный ордер BUY объёмом, равным объёму последней ноги, умноженному на LotCoefficient (с округлением по шагу объёма инструмента).
При наличии короткой позиции и росте цены минимум на StepLossingPips пунктов относительно лучшей (самой высокой) продажи добавляется новая нога SELL с тем же коэффициентом LotCoefficient.
Если одновременно обнаружены длинные и короткие ноги (в норме этого не происходит), стратегия немедленно закрывает все позиции для восстановления консистентности.
Защитные выходы
Для каждой ноги сохраняются индивидуальные уровни стоп-лосса и тейк-профита (StopLossPips, TakeProfitPips). На каждой завершённой свече проверяется, пересёк ли ценовой диапазон свечи один из уровней; в случае пересечения нога закрывается рыночным ордером.
Трейлинг-стоп (TrailingStopPips, TrailingStepPips) включается по желанию. После движения цены в прибыль на TrailingStopPips + TrailingStepPips пунктов стоп переносится на расстояние TrailingStopPips пунктов от текущего закрытия и двигается дальше только при дополнительном прогрессе не менее TrailingStepPips пунктов.
Служебная логика
Все объёмы приводятся к шагу объёма и ограничиваются допустимым диапазоном. Обработка выполняется только для полностью сформированных свечей (CandleStates.Finished), что исключает двойные срабатывания.
Параметры
Параметр
Тип
Значение по умолчанию
Описание
CandleType
DataType
TimeSpan.FromHours(1).TimeFrame()
Таймфрейм для расчёта индикаторов.
OrderVolume
decimal
0.1
Базовый объём первой сделки.
StopLossPips
int
50
Дистанция стоп-лосса в пунктах (0 отключает стоп).
TakeProfitPips
int
50
Дистанция тейк-профита в пунктах (0 отключает цель).
TrailingStopPips
int
5
Отступ трейлинг-стопа в пунктах. Должен быть > 0 для активации.
TrailingStepPips
int
5
Дополнительное движение в пунктах перед очередным переносом трейлинг-стопа.
StepLossingPips
int
30
Просадка в пунктах, при которой открывается новая нога усреднения.
LotCoefficient
decimal
2.0
Коэффициент умножения объёма для каждой новой ноги.
SignalBar
int
0
Количество полностью завершённых баров, смещающих выборку индикаторов.
MaPeriod
int
15
Длина скользящей средней в барах.
MaShift
int
0
Горизонтальный сдвиг (в барах) значений скользящей средней.
MaMethod
MovingAverageMethod
Weighted
Тип сглаживания (simple, exponential, smoothed, weighted).
MaAppliedPrice
AppliedPriceType
Weighted
Источник цены для скользящей средней.
IndentPips
int
4
Минимальный разрыв между ценой и средней для входа.
MacdFastPeriod
int
12
Период быстрой EMA в MACD.
MacdSlowPeriod
int
26
Период медленной EMA в MACD.
MacdSignalPeriod
int
9
Период сигнальной линии MACD.
MacdAppliedPrice
AppliedPriceType
Weighted
Тип цены, подаваемый на MACD.
MacdRatio
decimal
0.9
Минимальное отношение основной и сигнальной линий MACD для торговли.
Преобразование пунктов
Все параметры в пунктах (StopLossPips, TakeProfitPips, TrailingStopPips, TrailingStepPips, StepLossingPips, IndentPips) умножаются на PriceStep инструмента. Если у инструмента 3 или 5 знаков после запятой, значение дополнительно умножается на 10 для соответствия определению пункта в MetaTrader. При отсутствии данных о шаге цены используется значение 0.0001.
Особенности реализации
Стратегия хранит собственный список ног позиции, поскольку в StockSharp применяется режим неттинга. Каждая нога содержит цену входа, стоп и тейк, что позволяет воспроизвести логику усреднения исходного советника.
Защитные ордера реализуются программно: при достижении уровня стопа или тейка на свечке нога закрывается рыночным ордером на этой же свече.
Усреднение автоматически отключается, если StepLossingPips = 0. В остальных случаях объём новой ноги равен объёму предыдущей, умноженному на LotCoefficient, и округляется вниз до шага объёма.
Перенос трейлинг-стопа использует цену закрытия свечи в качестве текущей цены. Стоп никогда не отодвигается назад и активируется только после прохождения TrailingStopPips + TrailingStepPips пунктов.
Буферы индикаторов учитывают сдвиги SignalBar и MaShift, поэтому логика принимает те же значения, что и в MetaTrader при чтении буферов iMA и iMACD.
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>
/// MA MACD strategy using WMA crossover with SL/TP management.
/// Buys when fast WMA crosses above slow WMA, sells on reverse cross.
/// </summary>
public class MaMacdPositionAveragingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public MaMacdPositionAveragingStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// WMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_macd_position_averaging_strategy(Strategy):
"""
WMA crossover with SL/TP in price steps.
"""
def __init__(self):
super(ma_macd_position_averaging_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15).SetDisplay("Fast WMA", "Fast WMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 100).SetDisplay("Slow WMA", "Slow WMA period", "Indicators")
self._sl_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_macd_position_averaging_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(ma_macd_position_averaging_strategy, self).OnStarted2(time)
fast = WeightedMovingAverage()
fast.Length = self._fast_period.Value
slow = WeightedMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
f = float(fast_val)
s = float(slow_val)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = f
self._prev_slow = s
return
close = float(candle.ClosePrice)
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
return
if self._prev_fast <= self._prev_slow and f > s and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and f < s and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = f
self._prev_slow = s
def CreateClone(self):
return ma_macd_position_averaging_strategy()