Конверсия эксперта MetaTrader «VQ_EA», работающего по индикатору Volatility Quality.
В версии StockSharp линия VQ аппроксимируется сглаженной медианной ценой, что позволяет использовать высокоуровневый API.
Сделки открываются при смене направления сглаженной кривой, защита позиции настраивается через стоп-параметры.
Поведение оригинального MQL-советника
Получает сигналы покупки и продажи из пользовательского индикатора VQ (буферы 3 и 4).
Открывает рыночную позицию при появлении нового сигнала, если по этому направлению ещё нет сделки.
При противоположном сигнале немедленно закрывает обратную позицию.
Дополнительно реализованы фиксированные/дробные лоты, перевод в безубыток, трейлинг, логирование и системы оповещений.
Реализация в StockSharp
Вместо оригинального индикатора применяется простое скользящее среднее медианной цены с дополнительным сглаживанием.
Угол наклона этой кривой имитирует смену цвета линии VQ.
Фильтр в пунктах подавляет незначительные колебания.
Входы и выходы выполняются рыночными ордерами, как и в MetaTrader-версии.
Формирование сигналов
Подписка на выбранный тип свечей и расчёт медианной цены для каждой завершённой свечи.
Применение базового сглаживания (Length) и, при необходимости, второго сглаживания (Smoothing).
Сравнение текущего сглаженного значения с предыдущим. Если абсолютное изменение превышает FilterPoints (в ценовых единицах), определяется направление.
Переход направления из «вниз» в «вверх» даёт сигнал на покупку, из «вверх» в «вниз» — на продажу. При открытии добавляется объём противоположной позиции для реверса.
Управление рисками
StopLossPoints, TakeProfitPoints и TrailingStopPoints переводятся в абсолютные цены с учётом шага цены инструмента (PriceStep).
При включении любой защиты вызывается StartProtection с использованием рыночных ордеров, что повторяет механику исходного советника.
Трейлинг активен только при UseTrailing = true и положительном значении шага.
Параметры
Length – базовый период сглаживания медианной цены. Значение по умолчанию: 5.
Smoothing – дополнительный период сглаживания. Значение по умолчанию: 1 (выключено).
FilterPoints – минимальное изменение в пунктах для подтверждения смены направления. Значение по умолчанию: 5.
StopLossPoints – стоп-лосс в пунктах. Значение по умолчанию: 60 (0 отключает).
TakeProfitPoints – тейк-профит в пунктах. Значение по умолчанию: 0 (отключён).
UseTrailing – включение трейлинг-стопа. Значение по умолчанию: false.
TrailingStopPoints – расстояние трейлинг-стопа в пунктах. Значение по умолчанию: 0 (игнорируется, если трейлинг выключен).
CandleType – используемый таймфрейм. Значение по умолчанию: свечи 1 часа.
Volume – наследуется от Strategy.Volume, по умолчанию 1 контракт и применяется к каждому входу.
Отличия от оригинала
Линия VQ не переносилась напрямую, используется приближение на основе медианной цены.
Не реализованы перевод в безубыток, система оповещений, продвинутое управление лотом и ведение логов.
Трейлинг-стоп использует стандартный механизм StockSharp без шага подстройки.
Рекомендации по использованию
Сигналы формируются только по закрытым свечам, что соответствует режиму «trade at close» исходного эксперта.
Убедитесь, что инструмент имеет корректный PriceStep; при отсутствии данных используется значение 1.0 для перевода пунктов.
Стратегия служит демонстрационным примером и может быть расширена дополнительными правилами управления капиталом.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class VqEaStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMom; private bool _hasPrev;
private int _cooldown;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VqEaStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA filter", "Indicators");
_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMom = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var mom = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, mom, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal mom)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevMom = mom; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevMom = mom;
return;
}
if (close > ema && _prevMom <= 0 && mom > 0 && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 2;
}
else if (close < ema && _prevMom >= 0 && mom < 0 && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 2;
}
_prevMom = mom;
}
}
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, Momentum
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class vq_ea_strategy(Strategy):
"""EMA trend filter with Momentum zero-cross for entries and cooldown."""
def __init__(self):
super(vq_ea_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "EMA filter", "Indicators")
self._mom_period = self.Param("MomentumPeriod", 14).SetDisplay("Momentum", "Momentum period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(vq_ea_strategy, self).OnReseted()
self._prev_mom = 0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(vq_ea_strategy, self).OnStarted2(time)
self._prev_mom = 0
self._has_prev = False
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
mom = Momentum()
mom.Length = self._mom_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, mom, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ema_val, mom_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ev = float(ema_val)
mv = float(mom_val)
close = float(candle.ClosePrice)
if not self._has_prev:
self._prev_mom = mv
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_mom = mv
return
if close > ev and self._prev_mom <= 0 and mv > 0 and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 2
elif close < ev and self._prev_mom >= 0 and mv < 0 and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = 2
self._prev_mom = mv
def CreateClone(self):
return vq_ea_strategy()