Стратегия MACD Waterline Cross Expectator
Стратегия открывает длинные позиции, когда сигнальная линия MACD пересекает нулевой уровень снизу вверх, и короткие позиции, когда пересечение происходит сверху вниз. Управление рисками основано на фиксированном стоп-лоссе и коэффициенте риск/прибыль, определяющем расстояние до тейк-профита.
Логика
- Вычисляется индикатор MACD с настраиваемыми периодами быстрых и медленных EMA и сигнальной линии.
- На каждой завершённой свече отслеживается значение сигнальной линии.
- При переходе сигнальной линии из отрицательной области в положительную и готовности к покупке отправляется рыночная заявка Buy.
- При переходе из положительной области в отрицательную и готовности к продаже отправляется рыночная заявка Sell.
- Для каждой позиции автоматически устанавливаются стоп-лосс и тейк-профит.
Параметры
- FastEmaPeriod – период быстрой EMA.
- SlowEmaPeriod – период медленной EMA.
- SignalPeriod – период EMA сигнальной линии.
- StopLoss – расстояние до стоп-лосса в абсолютных ценовых единицах.
- Volume – объём заявки при открытии позиции.
- RiskBenefitRatios – предопределённые коэффициенты от 1:5 до 1:1 для вычисления тейк-профита.
- CandleType – таймфрейм свечей, используемых стратегией.
Примечания
- Стратегия поочерёдно открывает длинные и короткие позиции, используя внутренний флаг.
- Сделки исполняются по рынку и всегда переворачивают существующую позицию при появлении противоположного сигнала.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades on MACD signal line crossing the zero level.
/// </summary>
public class MacdWaterlineCrossExpectatorStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _rrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private bool _shouldBuy;
private bool _hasPrev;
private decimal _prevSignal;
/// <summary>
/// Fast EMA period for MACD.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Slow EMA period for MACD.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Signal line period for MACD.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
/// <summary>
/// Risk reward multiplier.
/// </summary>
public decimal RRMultiplier
{
get => _rrMultiplier.Value;
set => _rrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="MacdWaterlineCrossExpectatorStrategy"/>.
/// </summary>
public MacdWaterlineCrossExpectatorStrategy()
{
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 9)
.SetDisplay("Signal", "Signal line period", "Indicators");
_stopLossPct = Param(nameof(StopLossPct), 1m)
.SetDisplay("Stop Loss %", "Stop loss percent", "Risk")
.SetGreaterThanZero();
_rrMultiplier = Param(nameof(RRMultiplier), 2m)
.SetDisplay("RR Multiplier", "Risk reward multiplier", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle", "Candle time frame", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_shouldBuy = true;
_hasPrev = false;
_prevSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(
takeProfit: new Unit(StopLossPct * RRMultiplier, UnitTypes.Percent),
stopLoss: new Unit(StopLossPct, UnitTypes.Percent)
);
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastEmaPeriod },
LongMa = { Length = SlowEmaPeriod },
},
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!macdValue.IsFinal || !macdValue.IsFormed)
return;
var typed = (IMovingAverageConvergenceDivergenceSignalValue)macdValue;
var signal = typed.Signal;
if (signal == null)
return;
var signalVal = signal.Value;
if (!_hasPrev)
{
_prevSignal = signalVal;
_hasPrev = true;
return;
}
var crossedAbove = _prevSignal < 0 && signalVal > 0;
var crossedBelow = _prevSignal > 0 && signalVal < 0;
if (crossedAbove && _shouldBuy)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_shouldBuy = false;
}
else if (crossedBelow && !_shouldBuy)
{
if (Position > 0)
SellMarket();
SellMarket();
_shouldBuy = true;
}
_prevSignal = signalVal;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class macd_waterline_cross_expectator_strategy(Strategy):
def __init__(self):
super(macd_waterline_cross_expectator_strategy, self).__init__()
self._fast_ema_period = self.Param("FastEmaPeriod", 12) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_ema_period = self.Param("SlowEmaPeriod", 26) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._signal_period = self.Param("SignalPeriod", 9) \
.SetDisplay("Signal", "Signal line period", "Indicators")
self._stop_loss_pct = self.Param("StopLossPct", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percent", "Risk")
self._rr_multiplier = self.Param("RRMultiplier", 2.0) \
.SetDisplay("RR Multiplier", "Risk reward multiplier", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle", "Candle time frame", "General")
self._should_buy = True
self._has_prev = False
self._prev_signal = 0.0
@property
def fast_ema_period(self):
return self._fast_ema_period.Value
@property
def slow_ema_period(self):
return self._slow_ema_period.Value
@property
def signal_period(self):
return self._signal_period.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def rr_multiplier(self):
return self._rr_multiplier.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_waterline_cross_expectator_strategy, self).OnReseted()
self._should_buy = True
self._has_prev = False
self._prev_signal = 0.0
def OnStarted2(self, time):
super(macd_waterline_cross_expectator_strategy, self).OnStarted2(time)
sl = float(self.stop_loss_pct)
rr = float(self.rr_multiplier)
self.StartProtection(
takeProfit=Unit(sl * rr, UnitTypes.Percent),
stopLoss=Unit(sl, UnitTypes.Percent))
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.fast_ema_period
macd.Macd.LongMa.Length = self.slow_ema_period
macd.SignalMa.Length = self.signal_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
if not macd_value.IsFinal or not macd_value.IsFormed:
return
signal = macd_value.Signal
if signal is None:
return
signal_val = float(signal)
if not self._has_prev:
self._prev_signal = signal_val
self._has_prev = True
return
crossed_above = self._prev_signal < 0 and signal_val > 0
crossed_below = self._prev_signal > 0 and signal_val < 0
if crossed_above and self._should_buy:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._should_buy = False
elif crossed_below and not self._should_buy:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._should_buy = True
self._prev_signal = signal_val
def CreateClone(self):
return macd_waterline_cross_expectator_strategy()