2692 MACD Stochastic Strategy
Обзор
Эта стратегия представляет собой перенос советника MetaTrader 5 «MACD Stochastic» на платформу StockSharp. Логика сочетает пересечение линий MACD с дополнительной проверкой стохастика и допускает сделки только внутри трёх задаваемых внутридневных торговых окон. Для каждой позиции используются стоп-лосс и тейк-профит, выраженные в пунктах, а также опциональное трейлинг-сопровождение, которое переводит стоп в безубыток после достижения заданной прибыли.
Индикаторы
- MACD – основная система сигналов, фиксирующая пересечения быстрой и медленной экспоненциальных средних с их сигнальной линией.
- Стохастический осциллятор – необязательный фильтр, который подтверждает сигналы MACD, требуя свежего пересечения линий %K и %D в сторону предполагаемой сделки.
Торговая логика
Условия для длинной позиции
- Главная линия MACD пересекает сигнальную снизу вверх, и обе линии находятся ниже нулевой отметки.
- На текущем баре ещё не открывалась позиция (стратегия открывает не более одной сделки на свечу).
- Локальное время инструмента находится внутри любого из разрешённых торговых интервалов.
- При включённом фильтре стохастика текущая %K выше %D, а значение StochasticBarsToCheck баров назад демонстрировало обратную ситуацию, что подтверждает новое бычье пересечение.
Условия для короткой позиции
- Линия MACD пересекает сигнальную сверху вниз, при этом обе находятся выше нуля.
- Позиция отсутствует и текущая свеча ещё не использовалась для входа.
- Время попадает в одну из активных сессий.
- При активном фильтре стохастика текущая %K ниже %D, а значения несколько баров назад показывали противоположное соотношение.
Сопровождение позиции
- Стоп-лосс / тейк-профит – рассчитываются в пунктах с учётом шага цены инструмента. Для трёх- и пятизначных котировок шаг увеличивается в десять раз, чтобы приблизиться к стандартному пункту.
- Трейлинг-стоп – начинает работать после достижения прибыли не менее WhenSetNoLossStopPips пунктов:
- Для длинных позиций требуется исходный стоп-лосс. Стоп повышается на TrailingStopPips, если он остаётся минимум на TrailingStepPips + TrailingStopPips пунктов ниже текущей цены закрытия и выше уровня безубытка (NoLossStopPips).
- Для коротких позиций стоп смещается вниз при аналогичных ограничениях. Если исходный стоп отсутствует, алгоритм может выставить безубыточный уровень на расстоянии NoLossStopPips после достаточного движения цены.
- Фиксация прибыли или убытка – при достижении свечой уровней стопа или тейка позиция закрывается рыночным ордером, внутренние переменные сбрасываются.
Параметры
- MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod – настройки MACD.
- UseStochastic – включает фильтр стохастика.
- StochasticBarsToCheck, StochasticLength, StochasticKPeriod, StochasticDPeriod – параметры осциллятора.
- Volume – объём сделки в лотах.
- StopLossPips, TakeProfitPips – начальные расстояния стопа и тейка.
- TrailingStopPips, TrailingStepPips – параметры трейлинг-стопа.
- NoLossStopPips, WhenSetNoLossStopPips – смещение уровня безубытка и порог активации трейлинга.
- MaxPositions – сохранён для совместимости; в StockSharp стратегия оперирует одной нетто-позицией.
- Session1/2/3 Start-End – временные окна, в пределах которых разрешена торговля. Чтобы отключить окно, установите начало и конец в
00:00. - CandleType – тип свечей, используемых для генерации сигналов.
Дополнительные замечания
- Все решения принимаются по завершённым свечам, вход возможен только один раз на бар.
- Точность расчёта пунктов зависит от корректного шага цены (
PriceStep) в данных по инструменту. - Хранение истории стохастика реализовано через небольшую очередь, что позволяет работать на высокоуровневом API без прямого доступа к буферам индикатора.
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>
/// MACD strategy with optional stochastic confirmation and timed trading sessions.
/// </summary>
public class MacdStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _macdFastPeriod;
private readonly StrategyParam<int> _macdSlowPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<bool> _useStochastic;
private readonly StrategyParam<int> _stochasticBarsToCheck;
private readonly StrategyParam<int> _stochasticLength;
private readonly StrategyParam<int> _stochasticKPeriod;
private readonly StrategyParam<int> _stochasticDPeriod;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<int> _noLossStopPips;
private readonly StrategyParam<int> _whenSetNoLossStopPips;
private readonly StrategyParam<TimeSpan> _session1Start;
private readonly StrategyParam<TimeSpan> _session1End;
private readonly StrategyParam<TimeSpan> _session2Start;
private readonly StrategyParam<TimeSpan> _session2End;
private readonly StrategyParam<TimeSpan> _session3Start;
private readonly StrategyParam<TimeSpan> _session3End;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd = null!;
private StochasticOscillator _stochastic = null!;
private readonly List<(decimal K, decimal D)> _stochasticHistory = new();
private decimal _prevMacd;
private decimal _prevSignal;
private bool _hasPrevMacd;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takePrice;
private decimal _pipSize;
private DateTimeOffset _lastEntryBarTime;
/// <summary>
/// Fast EMA period used inside MACD.
/// </summary>
public int MacdFastPeriod
{
get => _macdFastPeriod.Value;
set => _macdFastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period used inside MACD.
/// </summary>
public int MacdSlowPeriod
{
get => _macdSlowPeriod.Value;
set => _macdSlowPeriod.Value = value;
}
/// <summary>
/// Signal line period of MACD.
/// </summary>
public int MacdSignalPeriod
{
get => _macdSignalPeriod.Value;
set => _macdSignalPeriod.Value = value;
}
/// <summary>
/// Use stochastic oscillator as additional confirmation.
/// </summary>
public bool UseStochastic
{
get => _useStochastic.Value;
set => _useStochastic.Value = value;
}
/// <summary>
/// Number of historical bars used for stochastic crossover validation.
/// </summary>
public int StochasticBarsToCheck
{
get => _stochasticBarsToCheck.Value;
set => _stochasticBarsToCheck.Value = value;
}
/// <summary>
/// Base length for the stochastic oscillator.
/// </summary>
public int StochasticLength
{
get => _stochasticLength.Value;
set => _stochasticLength.Value = value;
}
/// <summary>
/// Smoothing applied to %K line.
/// </summary>
public int StochasticKPeriod
{
get => _stochasticKPeriod.Value;
set => _stochasticKPeriod.Value = value;
}
/// <summary>
/// Period used to calculate %D line.
/// </summary>
public int StochasticDPeriod
{
get => _stochasticDPeriod.Value;
set => _stochasticDPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum price move required before updating trailing stop.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Maximum number of simultaneous positions.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Offset applied to break-even stop in pips.
/// </summary>
public int NoLossStopPips
{
get => _noLossStopPips.Value;
set => _noLossStopPips.Value = value;
}
/// <summary>
/// Profit required before activating break-even stop in pips.
/// </summary>
public int WhenSetNoLossStopPips
{
get => _whenSetNoLossStopPips.Value;
set => _whenSetNoLossStopPips.Value = value;
}
/// <summary>
/// Start time for the first trading session.
/// </summary>
public TimeSpan Session1Start
{
get => _session1Start.Value;
set => _session1Start.Value = value;
}
/// <summary>
/// End time for the first trading session.
/// </summary>
public TimeSpan Session1End
{
get => _session1End.Value;
set => _session1End.Value = value;
}
/// <summary>
/// Start time for the second trading session.
/// </summary>
public TimeSpan Session2Start
{
get => _session2Start.Value;
set => _session2Start.Value = value;
}
/// <summary>
/// End time for the second trading session.
/// </summary>
public TimeSpan Session2End
{
get => _session2End.Value;
set => _session2End.Value = value;
}
/// <summary>
/// Start time for the third trading session.
/// </summary>
public TimeSpan Session3Start
{
get => _session3Start.Value;
set => _session3Start.Value = value;
}
/// <summary>
/// End time for the third trading session.
/// </summary>
public TimeSpan Session3End
{
get => _session3End.Value;
set => _session3End.Value = value;
}
/// <summary>
/// Candle type used for generating signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="MacdStochasticStrategy"/>.
/// </summary>
public MacdStochasticStrategy()
{
_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
.SetDisplay("MACD Fast Period", "Fast EMA length for MACD", "MACD")
.SetOptimize(6, 18, 1);
_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
.SetDisplay("MACD Slow Period", "Slow EMA length for MACD", "MACD")
.SetOptimize(20, 40, 1);
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
.SetDisplay("MACD Signal Period", "Signal line length for MACD", "MACD")
.SetOptimize(5, 15, 1);
_useStochastic = Param(nameof(UseStochastic), false)
.SetDisplay("Use Stochastic Filter", "Enable stochastic confirmation", "Stochastic");
_stochasticBarsToCheck = Param(nameof(StochasticBarsToCheck), 5)
.SetDisplay("Stochastic Bars", "History depth for stochastic confirmation", "Stochastic")
.SetGreaterThanZero()
.SetOptimize(2, 8, 1);
_stochasticLength = Param(nameof(StochasticLength), 5)
.SetDisplay("Stochastic Length", "Number of bars for %K calculation", "Stochastic")
.SetGreaterThanZero()
.SetOptimize(5, 14, 1);
_stochasticKPeriod = Param(nameof(StochasticKPeriod), 3)
.SetDisplay("Stochastic %K Smoothing", "Smoothing period for %K line", "Stochastic")
.SetGreaterThanZero()
.SetOptimize(2, 5, 1);
_stochasticDPeriod = Param(nameof(StochasticDPeriod), 3)
.SetDisplay("Stochastic %D Period", "Smoothing period for %D line", "Stochastic")
.SetGreaterThanZero()
.SetOptimize(2, 5, 1);
_stopLossPips = Param(nameof(StopLossPips), 100)
.SetDisplay("Stop Loss (pips)", "Initial stop-loss distance", "Risk")
.SetOptimize(50, 200, 10);
_takeProfitPips = Param(nameof(TakeProfitPips), 100)
.SetDisplay("Take Profit (pips)", "Initial take-profit distance", "Risk")
.SetOptimize(50, 200, 10);
_trailingStopPips = Param(nameof(TrailingStopPips), 0)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Minimum move before trailing", "Risk");
_maxPositions = Param(nameof(MaxPositions), 1)
.SetDisplay("Max Positions", "Maximum simultaneous positions", "Trading")
.SetGreaterThanZero();
_noLossStopPips = Param(nameof(NoLossStopPips), 1)
.SetDisplay("No Loss Stop (pips)", "Break-even offset for trailing", "Risk");
_whenSetNoLossStopPips = Param(nameof(WhenSetNoLossStopPips), 25)
.SetDisplay("Activation Profit (pips)", "Profit before enabling trailing", "Risk");
_session1Start = Param(nameof(Session1Start), new TimeSpan(0, 0, 0))
.SetDisplay("Session 1 Start", "Start time of first window", "Sessions");
_session1End = Param(nameof(Session1End), new TimeSpan(23, 59, 59))
.SetDisplay("Session 1 End", "End time of first window", "Sessions");
_session2Start = Param(nameof(Session2Start), new TimeSpan(0, 0, 0))
.SetDisplay("Session 2 Start", "Start time of second window", "Sessions");
_session2End = Param(nameof(Session2End), new TimeSpan(0, 0, 0))
.SetDisplay("Session 2 End", "End time of second window", "Sessions");
_session3Start = Param(nameof(Session3Start), new TimeSpan(0, 0, 0))
.SetDisplay("Session 3 Start", "Start time of third window", "Sessions");
_session3End = Param(nameof(Session3End), new TimeSpan(0, 0, 0))
.SetDisplay("Session 3 End", "End time of third window", "Sessions");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles used for analysis", "General");
ResetState();
}
/// <summary>
/// Securities required by the strategy.
/// </summary>
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <summary>
/// Reset cached state when strategy is reset.
/// </summary>
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <summary>
/// Start indicator subscriptions and chart visualization.
/// </summary>
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastPeriod },
LongMa = { Length = MacdSlowPeriod }
},
SignalMa = { Length = MacdSignalPeriod }
};
_stochastic = new StochasticOscillator();
_stochastic.K.Length = StochasticLength;
_stochastic.D.Length = StochasticDPeriod;
UpdatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(_macd, _stochastic, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue stochasticValue)
{
if (candle.State != CandleStates.Finished)
return;
if (Position == 0 && _entryPrice != 0m)
ResetPositionState();
if (_pipSize == 0m)
UpdatePipSize();
ManagePosition(candle);
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
return;
decimal? currentK = null;
decimal? currentD = null;
var stochasticTyped = (StochasticOscillatorValue)stochasticValue;
if (stochasticTyped.K is decimal kValue && stochasticTyped.D is decimal dValue)
{
currentK = kValue;
currentD = dValue;
UpdateStochasticHistory(kValue, dValue);
}
var allowTrading = _macd.IsFormed && Volume > 0m && MaxPositions > 0;
var macdCrossUp = _hasPrevMacd && _prevMacd <= _prevSignal && macd > signal && macd < 0m && _prevMacd < 0m;
var macdCrossDown = _hasPrevMacd && _prevMacd >= _prevSignal && macd < signal && macd > 0m && _prevMacd > 0m;
if (allowTrading && Position == 0 && candle.OpenTime > _lastEntryBarTime && IsWithinTradingSession(candle.OpenTime))
{
if (macdCrossUp && PassesStochasticFilter(true, currentK, currentD))
{
EnterLong(candle);
}
else if (macdCrossDown && PassesStochasticFilter(false, currentK, currentD))
{
EnterShort(candle);
}
}
_prevMacd = macd;
_prevSignal = signal;
_hasPrevMacd = true;
}
private void EnterLong(ICandleMessage candle)
{
// Open long position using close price of finished candle.
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = StopLossPips > 0 && _pipSize > 0m ? _entryPrice - StopLossPips * _pipSize : 0m;
_takePrice = TakeProfitPips > 0 && _pipSize > 0m ? _entryPrice + TakeProfitPips * _pipSize : 0m;
_lastEntryBarTime = candle.OpenTime;
}
private void EnterShort(ICandleMessage candle)
{
// Open short position using close price of finished candle.
SellMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = StopLossPips > 0 && _pipSize > 0m ? _entryPrice + StopLossPips * _pipSize : 0m;
_takePrice = TakeProfitPips > 0 && _pipSize > 0m ? _entryPrice - TakeProfitPips * _pipSize : 0m;
_lastEntryBarTime = candle.OpenTime;
}
private void ManagePosition(ICandleMessage candle)
{
if (Position > 0)
{
UpdateLongTrailing(candle);
CheckLongExits(candle);
}
else if (Position < 0)
{
UpdateShortTrailing(candle);
CheckShortExits(candle);
}
}
private void CheckLongExits(ICandleMessage candle)
{
// Close long position if stop or take profit levels are reached.
if (_stopPrice > 0m && candle.LowPrice <= _stopPrice)
{
SellMarket();
ResetPositionState();
return;
}
if (_takePrice > 0m && candle.HighPrice >= _takePrice)
{
SellMarket();
ResetPositionState();
}
}
private void CheckShortExits(ICandleMessage candle)
{
// Close short position if stop or take profit levels are reached.
if (_stopPrice > 0m && candle.HighPrice >= _stopPrice)
{
BuyMarket();
ResetPositionState();
return;
}
if (_takePrice > 0m && candle.LowPrice <= _takePrice)
{
BuyMarket();
ResetPositionState();
}
}
private void UpdateLongTrailing(ICandleMessage candle)
{
// Move long stop towards break-even based on trailing parameters.
if (TrailingStopPips <= 0 || _pipSize <= 0m || _stopPrice <= 0m)
return;
var profit = candle.ClosePrice - _entryPrice;
if (profit <= WhenSetNoLossStopPips * _pipSize)
return;
var newStop = _stopPrice + TrailingStopPips * _pipSize;
var minStop = _entryPrice + NoLossStopPips * _pipSize;
var maxStop = candle.ClosePrice - (TrailingStepPips + TrailingStopPips) * _pipSize;
if (newStop <= _stopPrice)
return;
if (newStop <= minStop)
return;
if (newStop >= maxStop)
return;
_stopPrice = newStop;
}
private void UpdateShortTrailing(ICandleMessage candle)
{
// Move short stop towards break-even based on trailing parameters.
if (TrailingStopPips <= 0 || _pipSize <= 0m)
return;
var profit = _entryPrice - candle.ClosePrice;
if (profit <= WhenSetNoLossStopPips * _pipSize)
return;
if (_stopPrice > 0m)
{
var newStop = _stopPrice - TrailingStopPips * _pipSize;
var maxStop = _entryPrice - NoLossStopPips * _pipSize;
var minStop = candle.ClosePrice + (TrailingStepPips + TrailingStopPips) * _pipSize;
if (newStop >= _stopPrice)
return;
if (newStop >= maxStop)
return;
if (newStop <= minStop)
return;
_stopPrice = newStop;
}
else
{
var candidate = _entryPrice - NoLossStopPips * _pipSize;
var threshold = candle.ClosePrice + WhenSetNoLossStopPips * _pipSize;
if (candidate <= 0m)
return;
if (candidate <= threshold)
return;
_stopPrice = candidate;
}
}
private bool PassesStochasticFilter(bool isBuy, decimal? currentK, decimal? currentD)
{
// Validate stochastic crossover when the filter is enabled.
if (!UseStochastic)
return true;
if (currentK is null || currentD is null)
return false;
var bars = Math.Max(1, StochasticBarsToCheck);
if (_stochasticHistory.Count < bars)
return false;
if (bars <= 1)
return isBuy ? currentD < currentK : currentD > currentK;
var (oldK, oldD) = _stochasticHistory[0];
return isBuy ? currentD < currentK && oldD > oldK : currentD > currentK && oldD < oldK;
}
private void UpdateStochasticHistory(decimal k, decimal d)
{
// Maintain rolling history for stochastic confirmation.
var max = Math.Max(1, StochasticBarsToCheck);
_stochasticHistory.Add((k, d));
while (_stochasticHistory.Count > max)
_stochasticHistory.RemoveAt(0);
}
private bool IsWithinTradingSession(DateTimeOffset time)
{
// Check whether local time is inside any allowed window.
var tod = time.TimeOfDay;
return IsWithinSession(tod, Session1Start, Session1End)
|| IsWithinSession(tod, Session2Start, Session2End)
|| IsWithinSession(tod, Session3Start, Session3End);
}
private static bool IsWithinSession(TimeSpan time, TimeSpan start, TimeSpan end)
{
if (start == end && start == TimeSpan.Zero)
return false;
return start <= end
? time >= start && time <= end
: time >= start || time <= end;
}
private void UpdatePipSize()
{
// Convert pip-based settings to price values using security price step.
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
{
_pipSize = 0m;
return;
}
var ratio = 1m / priceStep;
var digits = (int)Math.Round(Math.Log10((double)ratio));
_pipSize = digits == 3 || digits == 5 ? priceStep * 10m : priceStep;
}
private void ResetState()
{
// Clear cached values when strategy is reset or initialized.
_stochasticHistory.Clear();
_prevMacd = 0m;
_prevSignal = 0m;
_hasPrevMacd = false;
ResetPositionState();
_pipSize = 0m;
_lastEntryBarTime = DateTimeOffset.MinValue;
}
private void ResetPositionState()
{
// Reset position-specific tracking variables.
_entryPrice = 0m;
_stopPrice = 0m;
_takePrice = 0m;
}
}
import clr
import math
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 MovingAverageConvergenceDivergenceSignal, StochasticOscillator
class macd_stochastic_strategy(Strategy):
"""MACD crossover with optional stochastic confirmation, trailing stop, and session windows."""
def __init__(self):
super(macd_stochastic_strategy, self).__init__()
self._macd_fast_period = self.Param("MacdFastPeriod", 12) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast Period", "Fast EMA length for MACD", "MACD")
self._macd_slow_period = self.Param("MacdSlowPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow Period", "Slow EMA length for MACD", "MACD")
self._macd_signal_period = self.Param("MacdSignalPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal Period", "Signal line length for MACD", "MACD")
self._use_stochastic = self.Param("UseStochastic", False) \
.SetDisplay("Use Stochastic Filter", "Enable stochastic confirmation", "Stochastic")
self._stochastic_bars_to_check = self.Param("StochasticBarsToCheck", 5) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Bars", "History depth for stochastic confirmation", "Stochastic")
self._stochastic_length = self.Param("StochasticLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Length", "Number of bars for K calculation", "Stochastic")
self._stochastic_k_period = self.Param("StochasticKPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic K Smoothing", "Smoothing period for K line", "Stochastic")
self._stochastic_d_period = self.Param("StochasticDPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic D Period", "Smoothing period for D line", "Stochastic")
self._stop_loss_pips = self.Param("StopLossPips", 100) \
.SetDisplay("Stop Loss (pips)", "Initial stop-loss distance", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 100) \
.SetDisplay("Take Profit (pips)", "Initial take-profit distance", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 0) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 5) \
.SetDisplay("Trailing Step (pips)", "Minimum move before trailing", "Risk")
self._max_positions = self.Param("MaxPositions", 1) \
.SetGreaterThanZero() \
.SetDisplay("Max Positions", "Maximum simultaneous positions", "Trading")
self._no_loss_stop_pips = self.Param("NoLossStopPips", 1) \
.SetDisplay("No Loss Stop (pips)", "Break-even offset for trailing", "Risk")
self._when_set_no_loss_stop_pips = self.Param("WhenSetNoLossStopPips", 25) \
.SetDisplay("Activation Profit (pips)", "Profit before enabling trailing", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles used for analysis", "General")
self._stochastic_history = []
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev_macd = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
self._pip_size = 0.0
self._last_entry_bar_time = None
@property
def MacdFastPeriod(self):
return int(self._macd_fast_period.Value)
@property
def MacdSlowPeriod(self):
return int(self._macd_slow_period.Value)
@property
def MacdSignalPeriod(self):
return int(self._macd_signal_period.Value)
@property
def UseStochastic(self):
return self._use_stochastic.Value
@property
def StochasticBarsToCheck(self):
return int(self._stochastic_bars_to_check.Value)
@property
def StochasticLength(self):
return int(self._stochastic_length.Value)
@property
def StochasticKPeriod(self):
return int(self._stochastic_k_period.Value)
@property
def StochasticDPeriod(self):
return int(self._stochastic_d_period.Value)
@property
def StopLossPips(self):
return int(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return int(self._take_profit_pips.Value)
@property
def TrailingStopPips(self):
return int(self._trailing_stop_pips.Value)
@property
def TrailingStepPips(self):
return int(self._trailing_step_pips.Value)
@property
def MaxPositions(self):
return int(self._max_positions.Value)
@property
def NoLossStopPips(self):
return int(self._no_loss_stop_pips.Value)
@property
def WhenSetNoLossStopPips(self):
return int(self._when_set_no_loss_stop_pips.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _update_pip_size(self):
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0
if price_step <= 0:
self._pip_size = 0.0
return
ratio = 1.0 / price_step
digits = int(round(math.log10(ratio)))
self._pip_size = price_step * 10.0 if (digits == 3 or digits == 5) else price_step
def OnStarted2(self, time):
super(macd_stochastic_strategy, self).OnStarted2(time)
self._stochastic_history = []
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev_macd = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
self._last_entry_bar_time = None
self._macd_ind = MovingAverageConvergenceDivergenceSignal()
self._macd_ind.Macd.ShortMa.Length = self.MacdFastPeriod
self._macd_ind.Macd.LongMa.Length = self.MacdSlowPeriod
self._macd_ind.SignalMa.Length = self.MacdSignalPeriod
self._stoch_ind = StochasticOscillator()
self._stoch_ind.K.Length = self.StochasticLength
self._stoch_ind.D.Length = self.StochasticDPeriod
self._update_pip_size()
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._macd_ind, self._stoch_ind, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._macd_ind)
self.DrawIndicator(area, self._stoch_ind)
self.DrawOwnTrades(area)
def process_candle(self, candle, macd_value, stochastic_value):
if candle.State != CandleStates.Finished:
return
if self.Position == 0 and self._entry_price != 0:
self._reset_position_state()
if self._pip_size == 0:
self._update_pip_size()
self._manage_position(candle)
macd_main_n = macd_value.Macd
signal_n = macd_value.Signal
if macd_main_n is None or signal_n is None:
return
macd_main = float(macd_main_n)
signal = float(signal_n)
current_k = None
current_d = None
stoch_k_n = stochastic_value.K
stoch_d_n = stochastic_value.D
if stoch_k_n is not None and stoch_d_n is not None:
current_k = float(stoch_k_n)
current_d = float(stoch_d_n)
self._update_stochastic_history(current_k, current_d)
allow_trading = self._macd_ind.IsFormed and self.MaxPositions > 0
macd_cross_up = (self._has_prev_macd and self._prev_macd <= self._prev_signal
and macd_main > signal and macd_main < 0 and self._prev_macd < 0)
macd_cross_down = (self._has_prev_macd and self._prev_macd >= self._prev_signal
and macd_main < signal and macd_main > 0 and self._prev_macd > 0)
if (allow_trading and self.Position == 0
and (self._last_entry_bar_time is None or candle.OpenTime > self._last_entry_bar_time)):
if macd_cross_up and self._passes_stochastic_filter(True, current_k, current_d):
self._enter_long(candle)
elif macd_cross_down and self._passes_stochastic_filter(False, current_k, current_d):
self._enter_short(candle)
self._prev_macd = macd_main
self._prev_signal = signal
self._has_prev_macd = True
def _enter_long(self, candle):
self.BuyMarket()
close = float(candle.ClosePrice)
self._entry_price = close
self._stop_price = close - self.StopLossPips * self._pip_size if self.StopLossPips > 0 and self._pip_size > 0 else 0.0
self._take_price = close + self.TakeProfitPips * self._pip_size if self.TakeProfitPips > 0 and self._pip_size > 0 else 0.0
self._last_entry_bar_time = candle.OpenTime
def _enter_short(self, candle):
self.SellMarket()
close = float(candle.ClosePrice)
self._entry_price = close
self._stop_price = close + self.StopLossPips * self._pip_size if self.StopLossPips > 0 and self._pip_size > 0 else 0.0
self._take_price = close - self.TakeProfitPips * self._pip_size if self.TakeProfitPips > 0 and self._pip_size > 0 else 0.0
self._last_entry_bar_time = candle.OpenTime
def _manage_position(self, candle):
if self.Position > 0:
self._update_long_trailing(candle)
self._check_long_exits(candle)
elif self.Position < 0:
self._update_short_trailing(candle)
self._check_short_exits(candle)
def _check_long_exits(self, candle):
lo = float(candle.LowPrice)
h = float(candle.HighPrice)
if self._stop_price > 0 and lo <= self._stop_price:
self.SellMarket()
self._reset_position_state()
return
if self._take_price > 0 and h >= self._take_price:
self.SellMarket()
self._reset_position_state()
def _check_short_exits(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self._stop_price > 0 and h >= self._stop_price:
self.BuyMarket()
self._reset_position_state()
return
if self._take_price > 0 and lo <= self._take_price:
self.BuyMarket()
self._reset_position_state()
def _update_long_trailing(self, candle):
if self.TrailingStopPips <= 0 or self._pip_size <= 0 or self._stop_price <= 0:
return
close = float(candle.ClosePrice)
profit = close - self._entry_price
if profit <= self.WhenSetNoLossStopPips * self._pip_size:
return
new_stop = self._stop_price + self.TrailingStopPips * self._pip_size
min_stop = self._entry_price + self.NoLossStopPips * self._pip_size
max_stop = close - (self.TrailingStepPips + self.TrailingStopPips) * self._pip_size
if new_stop <= self._stop_price:
return
if new_stop <= min_stop:
return
if new_stop >= max_stop:
return
self._stop_price = new_stop
def _update_short_trailing(self, candle):
if self.TrailingStopPips <= 0 or self._pip_size <= 0:
return
close = float(candle.ClosePrice)
profit = self._entry_price - close
if profit <= self.WhenSetNoLossStopPips * self._pip_size:
return
if self._stop_price > 0:
new_stop = self._stop_price - self.TrailingStopPips * self._pip_size
max_stop = self._entry_price - self.NoLossStopPips * self._pip_size
min_stop = close + (self.TrailingStepPips + self.TrailingStopPips) * self._pip_size
if new_stop >= self._stop_price:
return
if new_stop >= max_stop:
return
if new_stop <= min_stop:
return
self._stop_price = new_stop
else:
candidate = self._entry_price - self.NoLossStopPips * self._pip_size
threshold = close + self.WhenSetNoLossStopPips * self._pip_size
if candidate <= 0:
return
if candidate <= threshold:
return
self._stop_price = candidate
def _passes_stochastic_filter(self, is_buy, current_k, current_d):
if not self.UseStochastic:
return True
if current_k is None or current_d is None:
return False
bars = max(1, self.StochasticBarsToCheck)
if len(self._stochastic_history) < bars:
return False
if bars <= 1:
return current_d < current_k if is_buy else current_d > current_k
old_k, old_d = self._stochastic_history[0]
if is_buy:
return current_d < current_k and old_d > old_k
else:
return current_d > current_k and old_d < old_k
def _update_stochastic_history(self, k, d):
mx = max(1, self.StochasticBarsToCheck)
self._stochastic_history.append((k, d))
while len(self._stochastic_history) > mx:
self._stochastic_history.pop(0)
def _reset_position_state(self):
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
def OnReseted(self):
super(macd_stochastic_strategy, self).OnReseted()
self._stochastic_history = []
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev_macd = False
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
self._pip_size = 0.0
self._last_entry_bar_time = None
def CreateClone(self):
return macd_stochastic_strategy()