Стратегия MACD Volume BBO Reversal
Стратегия использует объемный осциллятор и пересечения MACD с нулевой линией. Вход в лонг происходит когда MACD пересекает ноль снизу вверх, объемный осциллятор положительный и MACD выше сигнальной линии. Шорт выполняется симметрично. Стоп ставится на ближайший минимум/максимум, тейк рассчитывается по коэффициенту риск/прибыль.
Параметры
VolumeShortLength– период короткой EMA объема (по умолчанию 6)VolumeLongLength– период длинной EMA объема (по умолчанию 12)MacdFastLength– быстрый период MACD (по умолчанию 11)MacdSlowLength– медленный период MACD (по умолчанию 21)MacdSignalLength– период сигнальной линии MACD (по умолчанию 10)LookbackPeriod– количество баров для поиска экстремумов (по умолчанию 10)RiskReward– отношение тейк-профита к стоп-лоссу (по умолчанию 1.5)CandleType– таймфрейм свечей (по умолчанию 5 минут)
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD zero-line crossover reversal.
/// </summary>
public class MacdVolumeBboReversalStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _emaFast;
private ExponentialMovingAverage _emaSlow;
private bool _prevFastAbove;
private bool _initialized;
private int _barsFromSignal;
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MacdVolumeBboReversalStrategy()
{
_fastLength = Param(nameof(FastLength), 11).SetDisplay("Fast", "Fast EMA", "MACD");
_slowLength = Param(nameof(SlowLength), 21).SetDisplay("Slow", "Slow EMA", "MACD");
_cooldownBars = Param(nameof(CooldownBars), 10).SetDisplay("Cooldown", "Bars between signals", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaFast = null;
_emaSlow = null;
_prevFastAbove = false;
_initialized = false;
_barsFromSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFastAbove = false;
_initialized = false;
_barsFromSignal = 0;
_emaFast = new ExponentialMovingAverage { Length = FastLength };
_emaSlow = new ExponentialMovingAverage { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_emaFast, _emaSlow, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaFast);
DrawIndicator(area, _emaSlow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (fast == 0 || slow == 0)
return;
var isFastAbove = fast > slow;
if (!_initialized) { _prevFastAbove = isFastAbove; _initialized = true; return; }
if (_barsFromSignal < 10000) _barsFromSignal++;
var crossUp = isFastAbove && !_prevFastAbove;
var crossDown = !isFastAbove && _prevFastAbove;
var canSignal = _barsFromSignal >= CooldownBars;
if (canSignal && crossUp && Position <= 0)
{
BuyMarket();
_barsFromSignal = 0;
}
else if (canSignal && crossDown && Position >= 0)
{
SellMarket();
_barsFromSignal = 0;
}
_prevFastAbove = isFastAbove;
}
}
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_volume_bbo_reversal_strategy(Strategy):
"""
MACD Volume BBO Reversal: EMA crossover with cooldown.
"""
def __init__(self):
super(macd_volume_bbo_reversal_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 11).SetDisplay("Fast", "Fast EMA", "MACD")
self._slow_length = self.Param("SlowLength", 21).SetDisplay("Slow", "Slow EMA", "MACD")
self._cooldown_bars = self.Param("CooldownBars", 10).SetDisplay("Cooldown", "Bars between signals", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast_above = False
self._initialized = False
self._bars_from_signal = 9999
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_volume_bbo_reversal_strategy, self).OnReseted()
self._prev_fast_above = False
self._initialized = False
self._bars_from_signal = 9999
def OnStarted2(self, time):
super(macd_volume_bbo_reversal_strategy, self).OnStarted2(time)
self._prev_fast_above = False
self._initialized = False
self._bars_from_signal = 0
fast = ExponentialMovingAverage()
fast.Length = self._fast_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
if fast == 0 or slow == 0:
return
is_fast_above = fast > slow
if not self._initialized:
self._prev_fast_above = is_fast_above
self._initialized = True
return
self._bars_from_signal += 1
cross_up = is_fast_above and not self._prev_fast_above
cross_down = not is_fast_above and self._prev_fast_above
can_signal = self._bars_from_signal >= self._cooldown_bars.Value
if can_signal and cross_up and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif can_signal and cross_down and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
self._prev_fast_above = is_fast_above
def CreateClone(self):
return macd_volume_bbo_reversal_strategy()