Стратегия EES Hedger повторяет логику одноимённого советника для MetaTrader и автоматически хеджирует позиции, открытые другим алгоритмом или вручную. Когда на счёте появляется сделка, удовлетворяющая заданному фильтру, стратегия немедленно открывает противоположную позицию со своими параметрами. Это позволяет свести рыночную экспозицию к нулю, сохраняя исходную сделку в рынке.
Реализация использует высокоуровневый API StockSharp: стратегия отслеживает сделки счёта, выставляет ордера для хеджа и управляет защитой через стоп-лосс, тейк-профит и трейлинг-стоп. Алгоритм трейлинга копирует оригинал: стоп переносится только после того, как цена прошла расстояние, превышающее сумму TrailingStopPips и TrailingStepPips.
Параметры
Параметр
Назначение
HedgeVolume
Фиксированный объём встречного ордера. Не зависит от объёма исходной сделки.
StopLossPips
Расстояние до начального стоп-лосса. Значение 0 отключает стартовый стоп.
TakeProfitPips
Расстояние до тейк-профита. Значение 0 означает отсутствие цели.
TrailingStopPips
Расстояние трейлинг-стопа от текущей цены.
TrailingStepPips
Минимальный шаг для очередного переноса трейлинг-стопа. При включённом трейлинге должен быть больше нуля.
OriginalOrderComment
Необязательный фильтр по комментарию. Хеджируются только сделки с совпадающим комментарием (без учёта регистра). Пустое значение — все сделки.
HedgerOrderComment
Необязательный комментарий для идентификации собственных хеджевых сделок. Совпадающие комментарии игнорируются, чтобы исключить повторный хедж.
Логика работы
Отслеживание сделок. Стратегия подписывается на событие NewMyTrade коннектора и анализирует каждую сделку по выбранному инструменту.
Выставление хеджа. При появлении подходящей сделки открывается рыночный ордер противоположного направления объёмом HedgeVolume.
Настройка защиты. После каждого собственного исполнения отменяются старые защитные ордера и выставляются новые стоп-лосс и тейк-профит, рассчитанные от средней цены позиции.
Трейлинг-стоп. Каждый новый тик проверяет условия трейлинга. Когда цена прошла в прибыльную сторону не меньше TrailingStopPips + TrailingStepPips, стоп подтягивается ближе к рынку (под цену для лонга и над ценой для шорта).
Завершение цикла. При полном закрытии хеджевой позиции оставшиеся ордера отменяются, и стратегия ожидает следующую исходную сделку.
Рекомендации по использованию
Убедитесь, что коннектор передаёт все сделки счёта, включая операции других систем.
Расчёт величины пункта выполняется на основе PriceStep; для инструментов с 3 или 5 знаками после запятой добавляется множитель 10, как в версии MQL.
Чтобы хеджировать только определённый алгоритм, укажите его комментарий в OriginalOrderComment. Для ручных операций оставьте поле пустым.
При активном трейлинге параметр TrailingStepPips должен быть положительным, иначе стратегия завершит работу при запуске.
Поскольку объём хеджа постоянный, настройте HedgeVolume в соответствии со средней позицией исходной системы, чтобы достичь требуемого уровня защиты.
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;
using System.Globalization;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mirrors trades by opening an opposite hedge position with trailing stop management.
/// Simplified from the EES Hedger expert advisor.
/// </summary>
public class EesHedgerStrategy : Strategy
{
private readonly StrategyParam<decimal> _hedgeVolume;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal _pipSize;
/// <summary>
/// Hedge position volume.
/// </summary>
public decimal HedgeVolume
{
get => _hedgeVolume.Value;
set => _hedgeVolume.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimum step between trailing stop updates in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public EesHedgerStrategy()
{
_hedgeVolume = Param(nameof(HedgeVolume), 0.1m)
.SetDisplay("Hedge Volume", "Volume used for hedge orders", "General");
_stopLossPips = Param(nameof(StopLossPips), 50)
.SetDisplay("Stop Loss (pips)", "Stop-loss distance per hedge", "Risk Management");
_takeProfitPips = Param(nameof(TakeProfitPips), 50)
.SetDisplay("Take Profit (pips)", "Take-profit distance per hedge", "Risk Management");
_trailingStopPips = Param(nameof(TrailingStopPips), 25)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk Management");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Minimum trailing stop increment", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for processing", "General");
}
/// <summary>
/// Candle type used for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_stopPrice = null;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
if (price <= 0m)
return;
// Entry: if no position, open based on tick direction
if (Position == 0 && _entryPrice == 0m)
{
var volume = HedgeVolume > 0m ? HedgeVolume : Volume;
if (volume <= 0m)
return;
BuyMarket();
_entryPrice = price;
_stopPrice = null;
return;
}
if (Position != 0 && _entryPrice == 0m)
_entryPrice = price;
// Check stop loss
if (Position != 0 && StopLossPips > 0 && _pipSize > 0m)
{
var stopDistance = StopLossPips * _pipSize;
if (Position > 0 && price <= _entryPrice - stopDistance)
{
SellMarket();
_entryPrice = 0m;
_stopPrice = null;
return;
}
else if (Position < 0 && price >= _entryPrice + stopDistance)
{
BuyMarket();
_entryPrice = 0m;
_stopPrice = null;
return;
}
}
// Check take profit
if (Position != 0 && TakeProfitPips > 0 && _pipSize > 0m)
{
var takeDistance = TakeProfitPips * _pipSize;
if (Position > 0 && price >= _entryPrice + takeDistance)
{
SellMarket();
_entryPrice = 0m;
_stopPrice = null;
return;
}
else if (Position < 0 && price <= _entryPrice - takeDistance)
{
BuyMarket();
_entryPrice = 0m;
_stopPrice = null;
return;
}
}
// Trailing stop
if (Position != 0 && TrailingStopPips > 0 && _pipSize > 0m)
{
var trailingDistance = TrailingStopPips * _pipSize;
var trailingStep = TrailingStepPips * _pipSize;
if (Position > 0)
{
var newStop = price - trailingDistance;
if (newStop > _entryPrice && (!_stopPrice.HasValue || newStop > _stopPrice.Value + trailingStep))
_stopPrice = newStop;
if (_stopPrice.HasValue && price <= _stopPrice.Value)
{
SellMarket();
_entryPrice = 0m;
_stopPrice = null;
}
}
else if (Position < 0)
{
var newStop = price + trailingDistance;
if (newStop < _entryPrice && (!_stopPrice.HasValue || newStop < _stopPrice.Value - trailingStep))
_stopPrice = newStop;
if (_stopPrice.HasValue && price >= _stopPrice.Value)
{
BuyMarket();
_entryPrice = 0m;
_stopPrice = null;
}
}
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
var decimals = GetDecimalPlaces(step);
return decimals == 3 || decimals == 5 ? step * 10m : step;
}
private static int GetDecimalPlaces(decimal value)
{
var text = Math.Abs(value).ToString(CultureInfo.InvariantCulture);
var index = text.IndexOf('.');
return index >= 0 ? text.Length - index - 1 : 0;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class ees_hedger_strategy(Strategy):
"""Hedge strategy with trailing stop management."""
def __init__(self):
super(ees_hedger_strategy, self).__init__()
self._hedge_volume = self.Param("HedgeVolume", 0.1) \
.SetDisplay("Hedge Volume", "Volume used for hedge orders", "General")
self._stop_loss_pips = self.Param("StopLossPips", 50) \
.SetDisplay("Stop Loss (pips)", "Stop-loss distance per hedge", "Risk Management")
self._take_profit_pips = self.Param("TakeProfitPips", 50) \
.SetDisplay("Take Profit (pips)", "Take-profit distance per hedge", "Risk Management")
self._trailing_stop_pips = self.Param("TrailingStopPips", 25) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk Management")
self._trailing_step_pips = self.Param("TrailingStepPips", 5) \
.SetDisplay("Trailing Step (pips)", "Minimum trailing stop increment", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for processing", "General")
self._entry_price = 0.0
self._stop_price = None
self._pip_size = 1.0
@property
def HedgeVolume(self):
return self._hedge_volume.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def CandleType(self):
return self._candle_type.Value
def _calc_pip_size(self):
sec = self.Security
if sec is None or sec.PriceStep is None or float(sec.PriceStep) <= 0:
return 1.0
step = float(sec.PriceStep)
decimals = sec.Decimals if sec.Decimals is not None else 0
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def OnStarted2(self, time):
super(ees_hedger_strategy, self).OnStarted2(time)
self._pip_size = self._calc_pip_size()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
if price <= 0:
return
# Entry: if no position, open buy
if self.Position == 0 and self._entry_price == 0:
self.BuyMarket()
self._entry_price = price
self._stop_price = None
return
if self.Position != 0 and self._entry_price == 0:
self._entry_price = price
# Stop loss check
if self.Position != 0 and self.StopLossPips > 0 and self._pip_size > 0:
stop_dist = self.StopLossPips * self._pip_size
if self.Position > 0 and price <= self._entry_price - stop_dist:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = None
return
elif self.Position < 0 and price >= self._entry_price + stop_dist:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = None
return
# Take profit check
if self.Position != 0 and self.TakeProfitPips > 0 and self._pip_size > 0:
take_dist = self.TakeProfitPips * self._pip_size
if self.Position > 0 and price >= self._entry_price + take_dist:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = None
return
elif self.Position < 0 and price <= self._entry_price - take_dist:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = None
return
# Trailing stop
if self.Position != 0 and self.TrailingStopPips > 0 and self._pip_size > 0:
trail_dist = self.TrailingStopPips * self._pip_size
trail_step = self.TrailingStepPips * self._pip_size
if self.Position > 0:
new_stop = price - trail_dist
if new_stop > self._entry_price and (self._stop_price is None or new_stop > self._stop_price + trail_step):
self._stop_price = new_stop
if self._stop_price is not None and price <= self._stop_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = None
elif self.Position < 0:
new_stop = price + trail_dist
if new_stop < self._entry_price and (self._stop_price is None or new_stop < self._stop_price - trail_step):
self._stop_price = new_stop
if self._stop_price is not None and price >= self._stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = None
def OnReseted(self):
super(ees_hedger_strategy, self).OnReseted()
self._entry_price = 0.0
self._stop_price = None
self._pip_size = 1.0
def CreateClone(self):
return ees_hedger_strategy()