EES Hedger (расширенная версия)
Обзор
Стратегия воспроизводит логику классического советника MetaTrader «EES Hedger». Каждый раз, когда на том же счёте внешний советник, трейдер или ручная операция открывает позицию, стратегия немедленно создаёт противоположный хедж с настраиваемым объёмом. Далее хедж сопровождается стоп-лоссом, тейк-профитом, переносом в безубыток и трейлинг-стопом, что позволяет нейтрализовать риск и сохранить прибыль, полученную на встречной сделке.
Стратегия не генерирует собственных сигналов: она только наблюдает за сделками счёта, реагирует на подходящие операции и управляет хеджем до тех пор, пока позиция не будет закрыта защитными заявками либо вручную.
Логика работы
- Отслеживание внешних сделок. Поток сделок счёта поступает через коннектор. Если заполнен параметр
OriginalOrderComment, учитываются только операции с подходящим комментарием; при пустом значении хеджируются все сделки по инструменту. Сделки самой стратегии исключаются по идентификаторам транзакций.
- Создание хеджа. После обнаружения подходящей сделки стратегия выставляет рыночный ордер противоположного направления и объёма
HedgeVolume. Параметр HedgerOrderComment помогает отделить хеджевые заявки в отчётах.
- Управление рисками. После исполнения хеджа выставляются защитные стоп- и тейк-ордеры на расстояниях, заданных в пипсах. Когда достигается условие безубытка, стоп переносится к цене входа плюс один пип. При включённом трейлинге стоп двигается дальше по мере развития благоприятного движения.
- Сброс состояния. Когда позиция обнуляется (например, при ручном закрытии), все защитные ордера снимаются, а внутренние флаги очищаются — стратегия готова к следующему внешнему сигналу.
Параметры
| Параметр |
Описание |
HedgeVolume |
Объём встречной позиции. |
StopLossPips |
Расстояние от цены входа до стоп-ордера. |
TakeProfitPips |
Расстояние от цены входа до тейк-профита. |
TrailingStopPips |
Дистанция трейлинг-стопа; ноль отключает функцию. |
TrailingActivationPips |
Минимальная прибыль в пипсах, после которой трейлинг-стоп начинает двигаться. |
BreakEvenPips |
Прибыль в пипсах, необходимая для переноса стопа в безубыток. |
OriginalOrderComment |
Фильтр по комментарию исходных сделок; пустое значение — хеджировать все сделки. |
HedgerOrderComment |
Комментарий, присваиваемый хеджевым и защитным ордерам стратегии. |
Практические рекомендации
- Запускайте стратегию на том же счёте, где работают исходные сделки. Только так она сможет увидеть внешние позиции и отреагировать на них.
- При использовании мостов MetaTrader убедитесь, что комментарий исходной заявки передаётся в StockSharp, иначе фильтр по комментарию работать не будет.
- Размер пипса автоматически вычисляется по шагу цены инструмента. Для пятизнаковых валютных котировок указанные значения переводятся в корректные ценовые приращения.
- Безубыток и трейлинг никогда не ухудшают позицию: стоп только приближается к текущей цене и не возвращается в убыточную область.
- Управление исходной позицией (закрытие, переворот и т. п.) остаётся за основным торговым решением.
Порядок работы
- Настройте параметры стратегии, особенно фильтры по комментариям и объём хеджа.
- Запустите стратегию и убедитесь, что соединение с брокером активно. До появления внешней сделки стратегия находится в режиме ожидания.
- Как только поступит подходящая сделка, проследите, как создаётся встречный ордер и выставляются защитные заявки в стакане.
- Контролируйте работу безубытка и трейлинга, чтобы выбранные расстояния соответствовали спецификациям брокера.
- По окончании работы остановите стратегию — все активные защитные ордера будут сняты автоматически.
Ограничения
- Стратегия может хеджировать только те сделки, которые доступны в потоке коннектора. Невидимые операции остаются без реакции.
- Требования к минимальному лоту у разных брокеров различаются. Убедитесь, что
HedgeVolume соответствует шагу объёма инструмента.
- Из-за мгновенной отправки рыночных ордеров в волатильный момент возможен проскальзывание. При необходимости увеличьте защитные расстояния, чтобы учесть этот риск.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Adapted from the MetaTrader "EES Hedger" expert advisor.
/// Uses EMA crossover signals with break-even and trailing stop risk management.
/// </summary>
public class EesHedgerAdvancedStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes default parameters.
/// </summary>
public EesHedgerAdvancedStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_stopLossPips = Param(nameof(StopLossPips), 500)
.SetDisplay("Stop Loss", "Stop-loss distance", "Risk Management");
_takeProfitPips = Param(nameof(TakeProfitPips), 500)
.SetDisplay("Take Profit", "Take-profit distance", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candles used for calculations", "Market Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return new[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
var fastEma = new EMA { Length = FastPeriod };
var slowEma = new EMA { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
// Use StartProtection for SL/TP
var tp = TakeProfitPips > 0 ? new Unit(TakeProfitPips, UnitTypes.Absolute) : null;
var sl = StopLossPips > 0 ? new Unit(StopLossPips, UnitTypes.Absolute) : null;
StartProtection(tp, sl);
base.OnStarted2(time);
}
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fastValue;
_prevSlow = slowValue;
_hasPrev = true;
return;
}
if (!_hasPrev)
{
_prevFast = fastValue;
_prevSlow = slowValue;
_hasPrev = true;
return;
}
// EMA crossover detection
var crossedUp = _prevFast <= _prevSlow && fastValue > slowValue;
var crossedDown = _prevFast >= _prevSlow && fastValue < slowValue;
if (crossedUp)
{
// Close short if any, then go long
if (Position < 0)
BuyMarket(Math.Abs(Position));
if (Position <= 0)
BuyMarket(Volume);
}
else if (crossedDown)
{
// Close long if any, then go short
if (Position > 0)
SellMarket(Position);
if (Position >= 0)
SellMarket(Volume);
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage
class ees_hedger_advanced_strategy(Strategy):
def __init__(self):
super(ees_hedger_advanced_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._stop_loss_pips = self.Param("StopLossPips", 500) \
.SetDisplay("Stop Loss", "Stop-loss distance", "Risk Management")
self._take_profit_pips = self.Param("TakeProfitPips", 500) \
.SetDisplay("Take Profit", "Take-profit distance", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candles used for calculations", "Market Data")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ees_hedger_advanced_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self.ProcessCandle).Start()
tp = Unit(self.TakeProfitPips, UnitTypes.Absolute) if self.TakeProfitPips > 0 else None
sl = Unit(self.StopLossPips, UnitTypes.Absolute) if self.StopLossPips > 0 else None
self.StartProtection(tp, sl)
def ProcessCandle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_value = float(fast_value)
slow_value = float(slow_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = fast_value
self._prev_slow = slow_value
self._has_prev = True
return
if not self._has_prev:
self._prev_fast = fast_value
self._prev_slow = slow_value
self._has_prev = True
return
crossed_up = self._prev_fast <= self._prev_slow and fast_value > slow_value
crossed_down = self._prev_fast >= self._prev_slow and fast_value < slow_value
if crossed_up:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
if self.Position <= 0:
self.BuyMarket(self.Volume)
elif crossed_down:
if self.Position > 0:
self.SellMarket(self.Position)
if self.Position >= 0:
self.SellMarket(self.Volume)
self._prev_fast = fast_value
self._prev_slow = slow_value
def OnReseted(self):
super(ees_hedger_advanced_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def CreateClone(self):
return ees_hedger_advanced_strategy()