Стратегия переносит эксперт OverHedge V2 из MetaTrader на высокоуровневый API StockSharp. Она строит хеджирующую сетку: определяет направление по быстрой и медленной EMA, затем чередует покупки и продажи внутри динамического туннеля. Объём каждой новой сделки увеличивается геометрически, а весь пул позиций закрывается после достижения заданной совокупной прибыли.
Логика торговли
Фильтр тренда. EMA(8) должна отстоять от EMA(21) не менее чем на MinDistancePips. Это определяет направление первой сделки в каждом цикле.
Туннель. Его ширина равна удвоенному текущему спреду плюс TunnelWidthPips, пересчитанный в цену. Границы туннеля служат триггером для сделки в противоположную сторону.
Чередование. Первые три позиции открываются по тренду, затем стратегия чередует сторону сделок, чтобы захеджировать позицию, сохраняя привязку к тем же уровням.
Рост объёма. Каждая последующая сделка умножает объём на BaseMultiplier, начиная с StartVolume. Значение приводится к допустимому шагу лота инструмента.
Выход из цикла. Когда совокупная нереализованная прибыль в пересчёте на лот превышает MinProfitTargetPips, а общий результат превысил ProfitTargetPips, все позиции закрываются и цикл начинается заново.
Принудительное завершение. Параметр ShutdownGrid = true мгновенно закрывает позиции и блокирует открытие новых, пока флаг не будет сброшен.
Условия входа
Покупка
Фильтр тренда указывает на рост (EMA_short - EMA_long > MinDistancePips).
Текущая цена Ask не ниже уровня покупки.
Стратегия не находится в режиме остановки и целевая прибыль ещё не достигнута.
Продажа
Фильтр тренда указывает на снижение (EMA_long - EMA_short > MinDistancePips).
Цена Ask не выше текущей точки продажи.
Флаг остановки снят, целевая прибыль ещё не получена.
Управление выходом
Фиксация прибыли. Все позиции закрываются по рынку, если совокупный профит корзины превышает ProfitTargetPips, а каждая сторона заработала не менее MinProfitTargetPips на лот.
Аварийный выход. Установка ShutdownGrid = true немедленно ликвидирует позицию.
Индикаторы и данные
EMA(8) и EMA(21), рассчитываемые по выбранному типу свечей.
Подписка Level 1 необходима для отслеживания лучшего Bid/Ask при расчёте туннеля и проверке условий входа.
Параметры
Параметр
Описание
StartVolume
Начальный объём первой сделки в цикле.
BaseMultiplier
Множитель, увеличивающий объём каждой следующей заявки.
TunnelWidthPips
Дополнительная ширина туннеля в пунктах поверх двойного спреда.
ProfitTargetPips
Целевая прибыль корзины в пунктах.
MinProfitTargetPips
Минимальное движение в прибыль по каждой стороне перед закрытием цикла.
ShortEmaPeriod
Период быстрой EMA для определения тренда.
LongEmaPeriod
Период медленной EMA для подтверждения тренда.
MinDistancePips
Минимальное расхождение EMA, необходимое для входа.
CandleType
Таймфрейм свечей, на которых работают индикаторы и торговый цикл.
ShutdownGrid
Флаг остановки: закрывает позиции и запрещает новые сделки.
Практические замечания
По умолчанию используется часовой таймфрейм; при необходимости установите тот же период, что и в оригинальном советнике.
Для корректной работы требуется поток котировок Level 1, обеспечивающий актуальные Bid/Ask.
StockSharp ведёт нетто-позицию по инструменту, поэтому встречные сделки уменьшают или разворачивают общий объём, а не создают отдельные хеджевые ордера, хотя логика закрытия корзины остаётся прежней.
Перед запуском убедитесь, что шаг цены и объёмов инструмента соответствуют заданным параметрам туннеля и шкалы лотов.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid hedging strategy based on EMA crossover direction.
/// Opens positions on EMA trend, reverses on direction change.
/// </summary>
public class OverHedgeV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _shortEmaPeriod;
private readonly StrategyParam<int> _longEmaPeriod;
private int _prevSignal;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int ShortEmaPeriod
{
get => _shortEmaPeriod.Value;
set => _shortEmaPeriod.Value = value;
}
public int LongEmaPeriod
{
get => _longEmaPeriod.Value;
set => _longEmaPeriod.Value = value;
}
public OverHedgeV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Short EMA", "Fast EMA length", "Indicators");
_longEmaPeriod = Param(nameof(LongEmaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Long EMA", "Slow EMA length", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSignal = 0;
var shortEma = new ExponentialMovingAverage { Length = ShortEmaPeriod };
var longEma = new ExponentialMovingAverage { Length = LongEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(shortEma, longEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortEma);
DrawIndicator(area, longEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal shortEma, decimal longEma)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var signal = shortEma > longEma ? 1 : shortEma < longEma ? -1 : _prevSignal;
if (signal == _prevSignal)
return;
var oldSignal = _prevSignal;
_prevSignal = signal;
if (signal == 1 && oldSignal <= 0)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (signal == -1 && oldSignal >= 0)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class over_hedge_v2_strategy(Strategy):
def __init__(self):
super(over_hedge_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._short_ema_period = self.Param("ShortEmaPeriod", 8) \
.SetDisplay("Short EMA", "Fast EMA length", "Indicators")
self._long_ema_period = self.Param("LongEmaPeriod", 21) \
.SetDisplay("Long EMA", "Slow EMA length", "Indicators")
self._prev_signal = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def ShortEmaPeriod(self):
return self._short_ema_period.Value
@property
def LongEmaPeriod(self):
return self._long_ema_period.Value
def OnReseted(self):
super(over_hedge_v2_strategy, self).OnReseted()
self._prev_signal = 0
def OnStarted2(self, time):
super(over_hedge_v2_strategy, self).OnStarted2(time)
self._prev_signal = 0
short_ema = ExponentialMovingAverage()
short_ema.Length = self.ShortEmaPeriod
long_ema = ExponentialMovingAverage()
long_ema.Length = self.LongEmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(short_ema, long_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ema)
self.DrawIndicator(area, long_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, short_value, long_value):
if candle.State != CandleStates.Finished:
return
sv = float(short_value)
lv = float(long_value)
if sv > lv:
signal = 1
elif sv < lv:
signal = -1
else:
signal = self._prev_signal
if signal == self._prev_signal:
return
old_signal = self._prev_signal
self._prev_signal = signal
if signal == 1 and old_signal <= 0:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif signal == -1 and old_signal >= 0:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return over_hedge_v2_strategy()