Estratégia Hedge Average
Esta estratégia reproduz o especialista "Hedge Average" do MetaTrader. Compara médias móveis simples dos preços de abertura e fechamento em dois períodos de tempo.
Lógica de trading
- Calcular a SMA do preço de abertura e fechamento para
Period1ePeriod2. - Se a média de abertura do período longo estiver acima de sua média de fechamento e a média de abertura do período curto estiver abaixo de sua média de fechamento, uma posição comprada é aberta.
- Se a média de abertura do período longo estiver abaixo de sua média de fechamento e a média de abertura do período curto estiver acima de sua média de fechamento, uma posição vendida é aberta.
- O trading só é permitido entre
StartHoureEndHour. - Stop-loss e take-profit opcionais são definidos em unidades de preço absolutas. O trailing stop move o stop protetor junto com o preço quando habilitado.
Parâmetros
Period1– período para as médias rápidas.Period2– período para as médias lentas.StartHour– hora do dia em que o trading se torna ativo.EndHour– hora do dia em que o trading para.CandleType– período de vela usado para cálculos.TakeProfit– distância do take profit em unidades de preço.StopLoss– distância do stop loss em unidades de preço.UseTrailing– ativar trailing stop baseado na distância do stop-loss.
Notas
A estratégia usa uma abordagem de posição única e não replica o objetivo de lucro baseado em dinheiro da versão 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()