Estrategia Hedge Average
Esta estrategia reproduce el experto "Hedge Average" de MetaTrader. Compara medias móviles simples de los precios de apertura y cierre en dos períodos de tiempo.
Lógica de trading
- Calcular la SMA del precio de apertura y cierre para
Period1yPeriod2. - Si la media de apertura del período largo está por encima de su media de cierre y la media de apertura del período corto está por debajo de su media de cierre, se abre una posición larga.
- Si la media de apertura del período largo está por debajo de su media de cierre y la media de apertura del período corto está por encima de su media de cierre, se abre una posición corta.
- El trading solo está permitido entre
StartHouryEndHour. - El stop-loss y el take-profit opcionales se establecen en unidades de precio absolutas. El trailing stop mueve el stop protector junto con el precio cuando está habilitado.
Parámetros
Period1– período para las medias rápidas.Period2– período para las medias lentas.StartHour– hora del día en que el trading se activa.EndHour– hora del día en que el trading se detiene.CandleType– marco temporal de velas utilizado para los cálculos.TakeProfit– distancia del take profit en unidades de precio.StopLoss– distancia del stop loss en unidades de precio.UseTrailing– activar trailing stop basado en la distancia del stop-loss.
Notas
La estrategia utiliza un enfoque de posición única y no replica el objetivo de ganancia basado en dinero de la versión MQL original.
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()