HarVesteR-Strategie
Die HarVesteR-Strategie kombiniert MACD-Momentum mit zwei einfachen gleitenden Durchschnitten und einem optionalen ADX-Trendstärkefilter. Sie sucht nach Situationen, in denen der Kurs an den gleitenden Durchschnitten entlangläuft, während der MACD kürzlich die Nulllinie gekreuzt hat, was einen potenziellen Ausbruch aus der Konsolidierung signalisiert. Stops werden an Swing-Hochs oder -Tiefs gesetzt, die Hälfte der Position wird bei einem festen Gewinnmultiplikator geschlossen, und der Rest wird mit einem Break-even-Ausstieg geschützt, der vom schnellen gleitenden Durchschnitt gesteuert wird.
Details
- Einstiegskriterien:
- Long:
MACD > 0 && MACD history contains negative value && Close < SlowSMA && Close + Indentation > FastSMA && Close + Indentation > SlowSMA && ADX ≥ AdxBuyLevel (if enabled) - Short:
MACD < 0 && MACD history contains positive value && Close > SlowSMA && Close - Indentation < FastSMA && Close - Indentation < SlowSMA && ADX ≥ AdxSellLevel (if enabled)
- Long:
- Stop Loss: Letztes Swing-Tief/-Hoch über
StopLookbackabgeschlossene Kerzen. - Teilausstieg: Schließt die Hälfte der Position, wenn der Kurs sich
HalfCloseRatiomal den Abstand zwischen Einstieg und Stop bewegt, und verschiebt dann den Stop auf Break-even. - Endgültiger Ausstieg:
- Long: schließt den Rest, wenn der Kurs unter
FastSMA + Indentationfällt, nachdem der Stop auf Break-even liegt. - Short: schließt den Rest, wenn der Kurs über
FastSMA + Indentationsteigt, nachdem der Stop auf Break-even liegt.
- Long: schließt den Rest, wenn der Kurs unter
- Long/Short: Beide Richtungen unterstützt.
- Filter: Optionaler ADX-Trendstärkefilter; setzen Sie
UseAdxFilterauffalse, um ihn zu deaktivieren. - Positionsmanagement: Kehrt die Position um, indem das entgegengesetzte Signalvolumen zuzüglich des aktuellen Engagements verrechnet wird.
Parameter
| Name | Standard | Beschreibung |
|---|---|---|
MacdFast |
12 | Schnelle EMA-Periode für die MACD-Differenzlinie. |
MacdSlow |
24 | Langsame EMA-Periode für die MACD-Differenzlinie. |
MacdSignal |
9 | Signal-EMA-Periode für die MACD-Glättung. |
MacdLookback |
6 | Anzahl der zuletzt abgeschlossenen Kerzen, die auf einen MACD-Vorzeichenwechsel geprüft werden. |
SmaFastLength |
50 | Länge des schnellen einfachen gleitenden Durchschnitts. |
SmaSlowLength |
100 | Länge des langsamen einfachen gleitenden Durchschnitts. |
MinIndentation |
10 | Versatz in Pips, der um die gleitenden Durchschnitte beim Ein- oder Ausstieg angewendet wird. |
StopLookback |
6 | Swing-Hoch/Tief-Rückblick zur Initialisierung des anfänglichen Stop-Niveaus. |
UseAdxFilter |
false | Aktiviert den ADX-Stärkefilter für beide Richtungen. |
AdxBuyLevel |
50 | Minimales ADX-Niveau, das bei aktiviertem Filter für Long-Einstiege erforderlich ist. |
AdxSellLevel |
50 | Minimales ADX-Niveau, das bei aktiviertem Filter für Short-Einstiege erforderlich ist. |
AdxPeriod |
14 | Periode für die ADX-Berechnung. |
HalfCloseRatio |
2 | Multiplikator für den Einstieg-zu-Stop-Abstand vor der Gewinnmitnahme. |
Volume |
1 | Ordervolumen für neue Einstiege (mit Verrechnung gegenläufiger Positionen). |
CandleType |
1 hour | Primärer Zeitrahmen für den Aufbau von Kerzen und Indikatoren. |
Hinweise
MinIndentationwird über die Tick-Größe des Instruments in Kursabstand umgerechnet. Instrumente mit drei oder fünf Nachkommastellen erhalten eine zehnfache Anpassung zur Annäherung an Pip-Einheiten.- Wenn
UseAdxFilterdeaktiviert ist, akzeptiert die Strategie Signale in beide Richtungen ohne Prüfung des ADX-Werts. - Teilgewinnmitnahme und Break-even-Ausstiege werden bei jeder abgeschlossenen Kerze ausgeführt, um offene Positionen zu schützen, auch wenn keine neuen Trades erlaubt sind.
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>
/// Trend strategy that combines MACD momentum, moving average proximity and ADX filter with partial profit taking.
/// </summary>
public class HarVesteRStrategy : Strategy
{
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _macdLookback;
private readonly StrategyParam<int> _smaFastLength;
private readonly StrategyParam<int> _smaSlowLength;
private readonly StrategyParam<decimal> _minIndentation;
private readonly StrategyParam<int> _stopLookback;
private readonly StrategyParam<bool> _useAdx;
private readonly StrategyParam<decimal> _adxBuyLevel;
private readonly StrategyParam<decimal> _adxSellLevel;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _halfCloseRatio;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd = null!;
private SimpleMovingAverage _smaFast = null!;
private SimpleMovingAverage _smaSlow = null!;
private AverageDirectionalIndex _adx = null!;
private Lowest _lowest = null!;
private Highest _highest = null!;
private readonly List<decimal> _macdHistory = new();
private decimal? _lastLowest;
private decimal? _lastHighest;
private decimal? _longEntry;
private decimal? _longStop;
private bool _longStopMoved;
private decimal? _shortEntry;
private decimal? _shortStop;
private bool _shortStopMoved;
/// <summary>
/// Fast period for MACD.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// Slow period for MACD.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Signal line period for MACD.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Number of bars used to confirm MACD sign change.
/// </summary>
public int MacdLookback
{
get => _macdLookback.Value;
set => _macdLookback.Value = value;
}
/// <summary>
/// Fast simple moving average length.
/// </summary>
public int SmaFastLength
{
get => _smaFastLength.Value;
set => _smaFastLength.Value = value;
}
/// <summary>
/// Slow simple moving average length.
/// </summary>
public int SmaSlowLength
{
get => _smaSlowLength.Value;
set => _smaSlowLength.Value = value;
}
/// <summary>
/// Minimum indentation measured in pips.
/// </summary>
public decimal MinIndentation
{
get => _minIndentation.Value;
set => _minIndentation.Value = value;
}
/// <summary>
/// Bars used to compute stop loss levels.
/// </summary>
public int StopLookback
{
get => _stopLookback.Value;
set => _stopLookback.Value = value;
}
/// <summary>
/// Enable ADX filter for entries.
/// </summary>
public bool UseAdxFilter
{
get => _useAdx.Value;
set => _useAdx.Value = value;
}
/// <summary>
/// Minimum ADX strength required to buy.
/// </summary>
public decimal AdxBuyLevel
{
get => _adxBuyLevel.Value;
set => _adxBuyLevel.Value = value;
}
/// <summary>
/// Minimum ADX strength required to sell.
/// </summary>
public decimal AdxSellLevel
{
get => _adxSellLevel.Value;
set => _adxSellLevel.Value = value;
}
/// <summary>
/// ADX indicator period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Ratio used to trigger half position exit.
/// </summary>
public int HalfCloseRatio
{
get => _halfCloseRatio.Value;
set => _halfCloseRatio.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="HarVesteRStrategy"/>.
/// </summary>
public HarVesteRStrategy()
{
_macdFast = Param(nameof(MacdFast), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast EMA", "Short EMA period for MACD", "MACD")
;
_macdSlow = Param(nameof(MacdSlow), 24)
.SetGreaterThanZero()
.SetDisplay("MACD Slow EMA", "Long EMA period for MACD", "MACD")
;
_macdSignal = Param(nameof(MacdSignal), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal averaging period", "MACD")
;
_macdLookback = Param(nameof(MacdLookback), 6)
.SetGreaterThanZero()
.SetDisplay("MACD Lookback", "Bars to confirm MACD sign change", "MACD")
;
_smaFastLength = Param(nameof(SmaFastLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "First moving average length", "Moving Averages")
;
_smaSlowLength = Param(nameof(SmaSlowLength), 20)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Second moving average length", "Moving Averages")
;
_minIndentation = Param(nameof(MinIndentation), 500m)
.SetGreaterThanZero()
.SetDisplay("Indentation", "Distance from moving averages in pips", "Trading")
;
_stopLookback = Param(nameof(StopLookback), 6)
.SetGreaterThanZero()
.SetDisplay("Stop Lookback", "Bars for stop loss calculation", "Risk")
;
_useAdx = Param(nameof(UseAdxFilter), false)
.SetDisplay("Use ADX", "Enable ADX trend filter", "ADX");
_adxBuyLevel = Param(nameof(AdxBuyLevel), 50m)
.SetGreaterThanZero()
.SetDisplay("ADX Buy Level", "Minimum ADX strength for longs", "ADX");
_adxSellLevel = Param(nameof(AdxSellLevel), 50m)
.SetGreaterThanZero()
.SetDisplay("ADX Sell Level", "Minimum ADX strength for shorts", "ADX");
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "ADX calculation length", "ADX")
;
_halfCloseRatio = Param(nameof(HalfCloseRatio), 2)
.SetGreaterThanZero()
.SetDisplay("Half Close Ratio", "Multiplier applied to stop distance", "Risk")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macdHistory.Clear();
_lastLowest = null;
_lastHighest = null;
ResetLongState();
ResetShortState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Configure indicators used by the strategy.
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
_smaFast = new SMA { Length = SmaFastLength };
_smaSlow = new SMA { Length = SmaSlowLength };
_adx = new AverageDirectionalIndex { Length = AdxPeriod };
_lowest = new Lowest { Length = StopLookback };
_highest = new Highest { Length = StopLookback };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, _smaFast, _smaSlow, _adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _smaFast);
DrawIndicator(area, _smaSlow);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue smaFastValue, IIndicatorValue smaSlowValue, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
// Update trailing stop helpers from recent highs and lows.
var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.ServerTime) { IsFinal = true });
if (lowValue.IsFormed)
_lastLowest = lowValue.ToDecimal();
var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.ServerTime) { IsFinal = true });
if (highValue.IsFormed)
_lastHighest = highValue.ToDecimal();
if (!macdValue.IsFinal || !smaFastValue.IsFinal || !smaSlowValue.IsFinal)
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdTyped.Macd is not decimal macdMain)
return;
var smaFast = smaFastValue.ToDecimal();
var smaSlow = smaSlowValue.ToDecimal();
decimal? adxStrength = null;
if (UseAdxFilter)
{
if (!adxValue.IsFinal)
return;
var adxTyped = (AverageDirectionalIndexValue)adxValue;
adxStrength = adxTyped.MovingAverage;
if (adxStrength is not decimal)
return;
}
_macdHistory.Add(macdMain);
while (_macdHistory.Count > MacdLookback)
try { _macdHistory.RemoveAt(0); } catch { break; }
var indentation = GetIndentation();
var close = candle.ClosePrice;
if (macdMain == 0m || smaFast == 0m || smaSlow == 0m || close <= 0m)
return;
// Manage partial exits and break-even logic for open positions.
ManageOpenPositions(close, smaFast, indentation);
if (!_macd.IsFormed || !_smaFast.IsFormed || !_smaSlow.IsFormed)
return;
if (_macdHistory.Count < MacdLookback)
return;
var hadNegative = HasNegativeMacd();
var hadPositive = HasPositiveMacd();
var adxBuyOk = !UseAdxFilter;
var adxSellOk = !UseAdxFilter;
if (UseAdxFilter && adxStrength is decimal adxValueDecimal)
{
adxBuyOk = adxValueDecimal >= AdxBuyLevel;
adxSellOk = adxValueDecimal >= AdxSellLevel;
}
var okBuy = close < smaSlow;
var okSell = close > smaSlow;
if (macdMain > 0m && hadNegative && adxBuyOk && okBuy && close + indentation > smaFast && close + indentation > smaSlow && Position <= 0m && _lastLowest is decimal longStop)
{
var volume = Volume + Math.Abs(Position);
if (volume > 0m)
{
BuyMarket();
_longEntry = close;
_longStop = longStop;
_longStopMoved = false;
ResetShortState();
}
}
else if (macdMain < 0m && hadPositive && adxSellOk && okSell && close - indentation < smaFast && close - indentation < smaSlow && Position >= 0m && _lastHighest is decimal shortStop)
{
var volume = Volume + Math.Abs(Position);
if (volume > 0m)
{
SellMarket();
_shortEntry = close;
_shortStop = shortStop;
_shortStopMoved = false;
ResetLongState();
}
}
}
private void ManageOpenPositions(decimal close, decimal smaFast, decimal indentation)
{
if (Position > 0m && _longEntry is decimal entry && _longStop is decimal stop)
{
var distance = Math.Abs(entry - stop);
if (distance > 0m)
{
var target = entry + distance * HalfCloseRatio;
if (!_longStopMoved && close > target)
{
var half = Position / 2m;
if (half > 0m)
{
SellMarket();
_longStop = entry;
_longStopMoved = true;
}
}
else if (_longStopMoved && smaFast > close - indentation)
{
SellMarket();
ResetLongState();
}
}
}
else if (Position <= 0m)
{
ResetLongState();
}
if (Position < 0m && _shortEntry is decimal entryShort && _shortStop is decimal stopShort)
{
var distance = Math.Abs(entryShort - stopShort);
if (distance > 0m)
{
var target = entryShort - distance * HalfCloseRatio;
if (!_shortStopMoved && close < target)
{
var half = -Position / 2m;
if (half > 0m)
{
BuyMarket();
_shortStop = entryShort;
_shortStopMoved = true;
}
}
else if (_shortStopMoved && smaFast < close - indentation)
{
BuyMarket();
ResetShortState();
}
}
}
else if (Position >= 0m)
{
ResetShortState();
}
}
private bool HasNegativeMacd()
{
foreach (var value in _macdHistory)
{
if (value < 0m)
return true;
}
return false;
}
private bool HasPositiveMacd()
{
foreach (var value in _macdHistory)
{
if (value > 0m)
return true;
}
return false;
}
private decimal GetIndentation()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return MinIndentation;
var decimals = Security?.Decimals ?? 0;
var factor = (decimals == 3 || decimals == 5) ? 10m : 1m;
return MinIndentation * step * factor;
}
private void ResetLongState()
{
_longEntry = null;
_longStop = null;
_longStopMoved = false;
}
private void ResetShortState()
{
_shortEntry = null;
_shortStop = null;
_shortStopMoved = false;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
MovingAverageConvergenceDivergenceSignal,
SimpleMovingAverage,
AverageDirectionalIndex,
Lowest,
Highest,
)
from indicator_extensions import *
class har_veste_r_strategy(Strategy):
"""Trend strategy combining MACD momentum, MA proximity, ADX filter with partial profit taking."""
def __init__(self):
super(har_veste_r_strategy, self).__init__()
self._macd_fast = self.Param("MacdFast", 12) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast EMA", "Short EMA period for MACD", "MACD")
self._macd_slow = self.Param("MacdSlow", 24) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow EMA", "Long EMA period for MACD", "MACD")
self._macd_signal_param = self.Param("MacdSignal", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal", "Signal averaging period", "MACD")
self._macd_lookback = self.Param("MacdLookback", 6) \
.SetGreaterThanZero() \
.SetDisplay("MACD Lookback", "Bars to confirm MACD sign change", "MACD")
self._sma_fast_length = self.Param("SmaFastLength", 10) \
.SetGreaterThanZero() \
.SetDisplay("Fast SMA", "First moving average length", "Moving Averages")
self._sma_slow_length = self.Param("SmaSlowLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Slow SMA", "Second moving average length", "Moving Averages")
self._min_indentation = self.Param("MinIndentation", 500.0) \
.SetGreaterThanZero() \
.SetDisplay("Indentation", "Distance from moving averages in pips", "Trading")
self._stop_lookback = self.Param("StopLookback", 6) \
.SetGreaterThanZero() \
.SetDisplay("Stop Lookback", "Bars for stop loss calculation", "Risk")
self._use_adx = self.Param("UseAdxFilter", False) \
.SetDisplay("Use ADX", "Enable ADX trend filter", "ADX")
self._adx_buy_level = self.Param("AdxBuyLevel", 50.0) \
.SetGreaterThanZero() \
.SetDisplay("ADX Buy Level", "Minimum ADX strength for longs", "ADX")
self._adx_sell_level = self.Param("AdxSellLevel", 50.0) \
.SetGreaterThanZero() \
.SetDisplay("ADX Sell Level", "Minimum ADX strength for shorts", "ADX")
self._adx_period = self.Param("AdxPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ADX Period", "ADX calculation length", "ADX")
self._half_close_ratio = self.Param("HalfCloseRatio", 2) \
.SetGreaterThanZero() \
.SetDisplay("Half Close Ratio", "Multiplier applied to stop distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Primary timeframe", "General")
self._macd_history = []
self._last_lowest = None
self._last_highest = None
self._long_entry = None
self._long_stop = None
self._long_stop_moved = False
self._short_entry = None
self._short_stop = None
self._short_stop_moved = False
@property
def MacdFast(self):
return int(self._macd_fast.Value)
@property
def MacdSlow(self):
return int(self._macd_slow.Value)
@property
def MacdSignal(self):
return int(self._macd_signal_param.Value)
@property
def MacdLookback(self):
return int(self._macd_lookback.Value)
@property
def SmaFastLength(self):
return int(self._sma_fast_length.Value)
@property
def SmaSlowLength(self):
return int(self._sma_slow_length.Value)
@property
def MinIndentation(self):
return float(self._min_indentation.Value)
@property
def StopLookback(self):
return int(self._stop_lookback.Value)
@property
def UseAdxFilter(self):
return self._use_adx.Value
@property
def AdxBuyLevel(self):
return float(self._adx_buy_level.Value)
@property
def AdxSellLevel(self):
return float(self._adx_sell_level.Value)
@property
def AdxPeriod(self):
return int(self._adx_period.Value)
@property
def HalfCloseRatio(self):
return int(self._half_close_ratio.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(har_veste_r_strategy, self).OnStarted2(time)
self._macd_history = []
self._last_lowest = None
self._last_highest = None
self._reset_long_state()
self._reset_short_state()
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = self.MacdFast
self._macd.Macd.LongMa.Length = self.MacdSlow
self._macd.SignalMa.Length = self.MacdSignal
self._sma_fast_ind = SimpleMovingAverage()
self._sma_fast_ind.Length = self.SmaFastLength
self._sma_slow_ind = SimpleMovingAverage()
self._sma_slow_ind.Length = self.SmaSlowLength
self._adx_ind = AverageDirectionalIndex()
self._adx_ind.Length = self.AdxPeriod
self._lowest_ind = Lowest()
self._lowest_ind.Length = self.StopLookback
self._highest_ind = Highest()
self._highest_ind.Length = self.StopLookback
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._macd, self._sma_fast_ind, self._sma_slow_ind, self._adx_ind, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma_fast_ind)
self.DrawIndicator(area, self._sma_slow_ind)
self.DrawIndicator(area, self._macd)
self.DrawOwnTrades(area)
def process_candle(self, candle, macd_value, sma_fast_value, sma_slow_value, adx_value):
if candle.State != CandleStates.Finished:
return
# Update lowest/highest for stop calculation
low_val = process_float(self._lowest_ind, candle.LowPrice, candle.ServerTime, True)
if low_val.IsFormed:
self._last_lowest = float(low_val.Value)
high_val = process_float(self._highest_ind, candle.HighPrice, candle.ServerTime, True)
if high_val.IsFormed:
self._last_highest = float(high_val.Value)
if not macd_value.IsFinal or not sma_fast_value.IsFinal or not sma_slow_value.IsFinal:
return
macd_main_n = macd_value.Macd
if macd_main_n is None:
return
macd_main = float(macd_main_n)
sma_fast = float(sma_fast_value.Value)
sma_slow = float(sma_slow_value.Value)
adx_strength = None
if self.UseAdxFilter:
if not adx_value.IsFinal:
return
adx_ma = adx_value.MovingAverage
if adx_ma is None:
return
adx_strength = float(adx_ma)
self._macd_history.append(macd_main)
while len(self._macd_history) > self.MacdLookback:
self._macd_history.pop(0)
indentation = self._get_indentation()
close = float(candle.ClosePrice)
if macd_main == 0.0 or sma_fast == 0.0 or sma_slow == 0.0 or close <= 0.0:
return
# Manage partial exits and break-even logic
self._manage_open_positions(close, sma_fast, indentation)
if not self._macd.IsFormed or not self._sma_fast_ind.IsFormed or not self._sma_slow_ind.IsFormed:
return
if len(self._macd_history) < self.MacdLookback:
return
had_negative = self._has_negative_macd()
had_positive = self._has_positive_macd()
adx_buy_ok = not self.UseAdxFilter
adx_sell_ok = not self.UseAdxFilter
if self.UseAdxFilter and adx_strength is not None:
adx_buy_ok = adx_strength >= self.AdxBuyLevel
adx_sell_ok = adx_strength >= self.AdxSellLevel
ok_buy = close < sma_slow
ok_sell = close > sma_slow
if (macd_main > 0 and had_negative and adx_buy_ok and ok_buy
and close + indentation > sma_fast and close + indentation > sma_slow
and self.Position <= 0 and self._last_lowest is not None):
self.BuyMarket()
self._long_entry = close
self._long_stop = self._last_lowest
self._long_stop_moved = False
self._reset_short_state()
elif (macd_main < 0 and had_positive and adx_sell_ok and ok_sell
and close - indentation < sma_fast and close - indentation < sma_slow
and self.Position >= 0 and self._last_highest is not None):
self.SellMarket()
self._short_entry = close
self._short_stop = self._last_highest
self._short_stop_moved = False
self._reset_long_state()
def _manage_open_positions(self, close, sma_fast, indentation):
if self.Position > 0 and self._long_entry is not None and self._long_stop is not None:
distance = abs(self._long_entry - self._long_stop)
if distance > 0:
target = self._long_entry + distance * self.HalfCloseRatio
if not self._long_stop_moved and close > target:
self.SellMarket()
self._long_stop = self._long_entry
self._long_stop_moved = True
elif self._long_stop_moved and sma_fast > close - indentation:
self.SellMarket()
self._reset_long_state()
elif self.Position <= 0:
self._reset_long_state()
if self.Position < 0 and self._short_entry is not None and self._short_stop is not None:
distance = abs(self._short_entry - self._short_stop)
if distance > 0:
target = self._short_entry - distance * self.HalfCloseRatio
if not self._short_stop_moved and close < target:
self.BuyMarket()
self._short_stop = self._short_entry
self._short_stop_moved = True
elif self._short_stop_moved and sma_fast < close - indentation:
self.BuyMarket()
self._reset_short_state()
elif self.Position >= 0:
self._reset_short_state()
def _has_negative_macd(self):
for v in self._macd_history:
if v < 0:
return True
return False
def _has_positive_macd(self):
for v in self._macd_history:
if v > 0:
return True
return False
def _get_indentation(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0
if step <= 0:
return self.MinIndentation
decimals = int(sec.Decimals) if sec is not None and sec.Decimals is not None else 0
factor = 10.0 if (decimals == 3 or decimals == 5) else 1.0
return self.MinIndentation * step * factor
def _reset_long_state(self):
self._long_entry = None
self._long_stop = None
self._long_stop_moved = False
def _reset_short_state(self):
self._short_entry = None
self._short_stop = None
self._short_stop_moved = False
def OnReseted(self):
super(har_veste_r_strategy, self).OnReseted()
self._macd_history = []
self._last_lowest = None
self._last_highest = None
self._reset_long_state()
self._reset_short_state()
def CreateClone(self):
return har_veste_r_strategy()