Macd Secrets — это порт оригинального советника "Macd Secrets I" на платформу StockSharp. Перенос выполнен на высокоуровневом API и строится на сочетании трёх временных горизонтов MACD, фильтрации через линейно-взвешенные скользящие средние (LWMA) и проверки отклонения осциллятора Momentum. В отличие от исходного советника, версия для StockSharp ведёт только одну суммарную позицию, что упрощает контроль над рисками и делает поведение стратегии прозрачнее.
Правила входа
Лонг
На рабочем таймфрейме быстрая LWMA располагается ниже медленной LWMA, что сигнализирует о нахождении цены в нижней части трендового канала (тот же фильтр присутствует в MQL-версии).
Линия MACD выше сигнальной на всех трёх таймфреймах: рабочем, подтверждающем и долгосрочном (месячном).
На подтверждающем таймфрейме хотя бы одно из трёх последних значений Momentum отклоняется от уровня 100 минимум на заданный порог (по умолчанию 0.3), как и в исходном коде (MathAbs(100 - Momentum)).
Открытых позиций нет.
При выполнении условий отправляется рыночная заявка на покупку с заданным объёмом.
Шорт
Линия MACD ниже сигнальной на рабочем, подтверждающем и месячном таймфреймах.
В одной из трёх последних свечей подтверждающего таймфрейма зафиксировано отклонение Momentum от 100, превышающее порог для коротких сделок.
Текущая позиция отсутствует (стратегия не хеджирует и не наращивает пирамиду).
При выполнении всех пунктов размещается рыночная заявка на продажу.
Управление сделкой
В начале работы стратегия может включить защиту через StartProtection, переводя заданные дистанции стоп-лосса и тейк-профита из пунктов в цену с помощью шага цены инструмента.
Перенос не включает исходные механизмы трейлинг-стопа, перевода в безубыток, монетарных ограничений по прибыли/убытку или глобального контроля по капиталу.
Сигналы анализируются только после закрытия свечей, чтобы исключить шум внутри бара.
Временные горизонты
Основной таймфрейм (по умолчанию 15 минут) — рассчитываются MACD и пара LWMA.
Подтверждающий таймфрейм (по умолчанию 1 час) — здесь работают MACD и Momentum, а также собираются три последних отклонения Momentum.
Месячный таймфрейм (по умолчанию 30 дней) — обеспечивает долгосрочное подтверждение направления MACD.
Переопределение GetWorkingSecurities гарантирует запрос всех трёх подписок у коннектора до запуска расчётов.
Параметры
Имя
Описание
Значение по умолчанию
OrderVolume
Объём сделки в лотах.
0.1
TakeProfitPoints
Дистанция тейк-профита в пунктах (0 — отключить).
50
StopLossPoints
Дистанция стоп-лосса в пунктах (0 — отключить).
20
FastMaPeriod
Период быстрой LWMA на рабочем таймфрейме.
6
SlowMaPeriod
Период медленной LWMA на рабочем таймфрейме.
85
MacdFastPeriod
Период быстрой EMA в MACD.
12
MacdSlowPeriod
Период медленной EMA в MACD.
26
MacdSignalPeriod
Период сигнальной EMA в MACD.
9
MomentumPeriod
Длина окна Momentum на подтверждающем таймфрейме.
14
MomentumBuyThreshold
Минимальное отклонение Momentum для лонгов.
0.3
MomentumSellThreshold
Минимальное отклонение Momentum для шортов.
0.3
PrimaryCandleType
Таймфрейм исполнения (по умолчанию 15 минут).
15m
TrendCandleType
Таймфрейм подтверждения (по умолчанию 1 час).
1h
MonthlyCandleType
Долгосрочный таймфрейм (по умолчанию 30 дней).
30d
Практические рекомендации
Фильтр LWMA намеренно асимметричен: он применяется только к лонгам, что соответствует поведению исходного советника.
В портированной версии отсутствует функция LotsOptimized; объём сделок постоянен. Для реализации ступенчатого набора позиций потребуется собственный учёт уже исполненного объёма.
Перед запуском убедитесь, что провайдер данных поддерживает все используемые таймфреймы, иначе индикаторы не смогут сформироваться.
Если месячные свечи недоступны, укажите альтернативный DataType с нужной периодичностью.
Стратегия работает только с завершёнными свечами и не обращается к индикаторам напрямую через буферы, что соответствует рекомендациям StockSharp.
Отличия от оригинала
Нет трейлинг-стопа, перевода в безубыток, денежных лимитов и уведомлений (e-mail, push и т.д.).
Нет пирамидинга и мартингейла — стратегия всегда открывает одну позицию фиксированного объёма.
Предупреждение
Торговля на финансовых рынках связана с повышенным риском. Перед использованием стратегии на реальном счёте протестируйте её на исторических данных и в демо-режиме.
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;
public class MacdSecretsStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public MacdSecretsStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { 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;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class macd_secrets_strategy(Strategy):
"""
MACD Secrets: EMA crossover with manual SL/TP via price steps.
"""
def __init__(self):
super(macd_secrets_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12).SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50).SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400).SetDisplay("Take Profit", "Take-profit 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(macd_secrets_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(macd_secrets_strategy, self).OnStarted2(time)
self._fast_ind = ExponentialMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = ExponentialMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
if not self._fast_ind.IsFormed or not self._slow_ind.IsFormed:
self._prev_fast = fast
self._prev_slow = slow
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast
self._prev_slow = slow
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close <= self._entry_price - self._stop_loss_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
if self._take_profit_points.Value > 0 and close >= self._entry_price + self._take_profit_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
elif self.Position < 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close >= self._entry_price + self._stop_loss_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
if self._take_profit_points.Value > 0 and close <= self._entry_price - self._take_profit_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
return
if self._prev_fast <= self._prev_slow and fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return macd_secrets_strategy()