Hedge Average-Strategie
Diese Strategie reproduziert den "Hedge Average"-Experten aus MetaTrader. Sie vergleicht einfache gleitende Durchschnitte der Eröffnungs- und Schlusskurse über zwei Zeiträume.
Handelslogik
- Berechnung des SMA des Eröffnungs- und Schlusskurses für
Period1undPeriod2. - Wenn der langperiodische Eröffnungsdurchschnitt über seinem Schlussdurchschnitt liegt und der kurzperiodische Eröffnungsdurchschnitt unter seinem Schlussdurchschnitt liegt, wird eine Long-Position eröffnet.
- Wenn der langperiodische Eröffnungsdurchschnitt unter seinem Schlussdurchschnitt liegt und der kurzperiodische Eröffnungsdurchschnitt über seinem Schlussdurchschnitt liegt, wird eine Short-Position eröffnet.
- Der Handel ist nur zwischen
StartHourundEndHourerlaubt. - Optionaler Stop-Loss und Take-Profit werden in absoluten Preiseinheiten gesetzt. Der Trailing Stop bewegt den Schutz-Stop mit dem Preis, wenn er aktiviert ist.
Parameter
Period1– Periode für die schnellen Durchschnitte.Period2– Periode für die langsamen Durchschnitte.StartHour– Tagesstunde, zu der der Handel beginnt.EndHour– Tagesstunde, zu der der Handel endet.CandleType– Kerzen-Zeitrahmen für Berechnungen.TakeProfit– Take-Profit-Abstand in Preiseinheiten.StopLoss– Stop-Loss-Abstand in Preiseinheiten.UseTrailing– Trailing Stop auf Basis des Stop-Loss-Abstands aktivieren.
Hinweise
Die Strategie verwendet einen Einzelpositionsansatz und repliziert nicht das geldbasierte Gewinnziel aus der ursprünglichen MQL-Version.
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hedge Average strategy comparing open/close SMAs over a fast and a slow period.
/// </summary>
public class HedgeAverageStrategy : Strategy
{
private readonly StrategyParam<int> _period1;
private readonly StrategyParam<int> _period2;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<bool> _useTrailing;
private readonly List<decimal> _opens = [];
private readonly List<decimal> _closes = [];
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal? _bestPrice;
private DateTimeOffset? _entryCandleTime;
public int Period1 { get => _period1.Value; set => _period1.Value = value; }
public int Period2 { get => _period2.Value; set => _period2.Value = value; }
public int StartHour { get => _startHour.Value; set => _startHour.Value = value; }
public int EndHour { get => _endHour.Value; set => _endHour.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public bool UseTrailing { get => _useTrailing.Value; set => _useTrailing.Value = value; }
public HedgeAverageStrategy()
{
_period1 = Param(nameof(Period1), 5).SetGreaterThanZero();
_period2 = Param(nameof(Period2), 20).SetGreaterThanZero();
_startHour = Param(nameof(StartHour), 0).SetRange(0, 23);
_endHour = Param(nameof(EndHour), 23).SetRange(0, 23);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame());
_takeProfit = Param(nameof(TakeProfit), 0m).SetNotNegative();
_stopLoss = Param(nameof(StopLoss), 0m).SetNotNegative();
_useTrailing = Param(nameof(UseTrailing), false);
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_opens.Clear();
_closes.Clear();
ResetProtection();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_opens.Add(candle.OpenPrice);
_closes.Add(candle.ClosePrice);
var keep = Math.Max(Period1, Period2);
if (_opens.Count > keep)
{
_opens.RemoveRange(0, _opens.Count - keep);
_closes.RemoveRange(0, _closes.Count - keep);
}
if (Position != 0 && ApplyProtection(candle))
return;
if (_opens.Count < keep || Position != 0 || !IsTradingHour(candle.OpenTime.Hour))
return;
var fastOpen = AverageTail(_opens, Period1);
var fastClose = AverageTail(_closes, Period1);
var slowOpen = AverageTail(_opens, Period2);
var slowClose = AverageTail(_closes, Period2);
if (slowOpen > slowClose && fastOpen < fastClose)
Enter(Sides.Buy, candle.ClosePrice, candle.OpenTime);
else if (slowOpen < slowClose && fastOpen > fastClose)
Enter(Sides.Sell, candle.ClosePrice, candle.OpenTime);
}
private void Enter(Sides side, decimal price, DateTimeOffset candleTime)
{
if (side == Sides.Buy)
BuyMarket();
else
SellMarket();
_entryPrice = price;
_entryCandleTime = candleTime;
_bestPrice = price;
_stopPrice = StopLoss > 0m ? (side == Sides.Buy ? price - StopLoss : price + StopLoss) : null;
_takePrice = TakeProfit > 0m ? (side == Sides.Buy ? price + TakeProfit : price - TakeProfit) : null;
}
private bool ApplyProtection(ICandleMessage candle)
{
if (_entryCandleTime is DateTimeOffset entryTime && candle.OpenTime <= entryTime)
return false;
if (Position > 0)
{
_bestPrice = _bestPrice is decimal best ? Math.Max(best, candle.HighPrice) : candle.HighPrice;
if (UseTrailing && StopLoss > 0m)
{
var candidate = _bestPrice.Value - StopLoss;
if (_stopPrice is null || candidate > _stopPrice)
_stopPrice = candidate;
}
if ((_stopPrice is decimal stop && candle.LowPrice <= stop) ||
(_takePrice is decimal take && candle.HighPrice >= take))
{
SellMarket(Math.Abs(Position));
ResetProtection();
return true;
}
}
else if (Position < 0)
{
_bestPrice = _bestPrice is decimal best ? Math.Min(best, candle.LowPrice) : candle.LowPrice;
if (UseTrailing && StopLoss > 0m)
{
var candidate = _bestPrice.Value + StopLoss;
if (_stopPrice is null || candidate < _stopPrice)
_stopPrice = candidate;
}
if ((_stopPrice is decimal stop && candle.HighPrice >= stop) ||
(_takePrice is decimal take && candle.LowPrice <= take))
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return true;
}
}
return false;
}
private bool IsTradingHour(int hour)
=> StartHour <= EndHour
? hour >= StartHour && hour <= EndHour
: hour >= StartHour || hour <= EndHour;
private static decimal AverageTail(List<decimal> values, int length)
=> values.Skip(values.Count - length).Average();
private void ResetProtection()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_bestPrice = null;
_entryCandleTime = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Strategies import Strategy
class hedge_average_strategy(Strategy):
def __init__(self):
super(hedge_average_strategy, self).__init__()
self._period1 = self.Param("Period1", 5).SetGreaterThanZero()
self._period2 = self.Param("Period2", 20).SetGreaterThanZero()
self._start_hour = self.Param("StartHour", 0).SetRange(0, 23)
self._end_hour = self.Param("EndHour", 23).SetRange(0, 23)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._take_profit = self.Param("TakeProfit", 0.0).SetNotNegative()
self._stop_loss = self.Param("StopLoss", 0.0).SetNotNegative()
self._use_trailing = self.Param("UseTrailing", False)
self._opens = []
self._closes = []
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._best_price = None
self._entry_candle_time = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(hedge_average_strategy, self).OnReseted()
self._opens = []
self._closes = []
self._reset_protection()
def OnStarted2(self, time):
super(hedge_average_strategy, self).OnStarted2(time)
self.SubscribeCandles(self._candle_type.Value).Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._opens.append(float(candle.OpenPrice))
self._closes.append(float(candle.ClosePrice))
p1 = int(self._period1.Value)
p2 = int(self._period2.Value)
keep = max(p1, p2)
if len(self._opens) > keep:
del self._opens[:-keep]
del self._closes[:-keep]
if self.Position != 0 and self._apply_protection(candle):
return
if len(self._opens) < keep or self.Position != 0 or not self._is_trading_hour(candle.OpenTime.Hour):
return
fast_open = sum(self._opens[-p1:]) / p1
fast_close = sum(self._closes[-p1:]) / p1
slow_open = sum(self._opens[-p2:]) / p2
slow_close = sum(self._closes[-p2:]) / p2
if slow_open > slow_close and fast_open < fast_close:
self._enter(Sides.Buy, float(candle.ClosePrice), candle.OpenTime)
elif slow_open < slow_close and fast_open > fast_close:
self._enter(Sides.Sell, float(candle.ClosePrice), candle.OpenTime)
def _enter(self, side, price, candle_time):
if side == Sides.Buy:
self.BuyMarket()
else:
self.SellMarket()
sl = float(self._stop_loss.Value)
tp = float(self._take_profit.Value)
self._entry_price = price
self._entry_candle_time = candle_time
self._best_price = price
self._stop_price = (price - sl if side == Sides.Buy else price + sl) if sl > 0 else None
self._take_price = (price + tp if side == Sides.Buy else price - tp) if tp > 0 else None
def _apply_protection(self, candle):
if self._entry_candle_time is not None and candle.OpenTime <= self._entry_candle_time:
return False
sl = float(self._stop_loss.Value)
if self.Position > 0:
high = float(candle.HighPrice)
self._best_price = high if self._best_price is None else max(self._best_price, high)
if bool(self._use_trailing.Value) and sl > 0:
candidate = self._best_price - sl
if self._stop_price is None or candidate > self._stop_price:
self._stop_price = candidate
if ((self._stop_price is not None and float(candle.LowPrice) <= self._stop_price) or
(self._take_price is not None and high >= self._take_price)):
self.SellMarket(Math.Abs(self.Position))
self._reset_protection()
return True
elif self.Position < 0:
low = float(candle.LowPrice)
self._best_price = low if self._best_price is None else min(self._best_price, low)
if bool(self._use_trailing.Value) and sl > 0:
candidate = self._best_price + sl
if self._stop_price is None or candidate < self._stop_price:
self._stop_price = candidate
if ((self._stop_price is not None and float(candle.HighPrice) >= self._stop_price) or
(self._take_price is not None and low <= self._take_price)):
self.BuyMarket(Math.Abs(self.Position))
self._reset_protection()
return True
return False
def _is_trading_hour(self, hour):
start = int(self._start_hour.Value)
end = int(self._end_hour.Value)
return start <= hour <= end if start <= end else hour >= start or hour <= end
def _reset_protection(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._best_price = None
self._entry_candle_time = None
def CreateClone(self):
return hedge_average_strategy()