Стратегия Vortex Oscillator System
Общее описание
Vortex Oscillator System — это прямой порт советника MetaTrader 5, основанного на осцилляторе Vortex. Индикатор вычисляется как разница между положительной линией Vortex (VI+) и отрицательной линией Vortex (VI-) на выбранной серии свечей. Сильные отрицательные значения показывают доминирование VI-, а высокие положительные значения свидетельствуют о превосходстве VI+. Стратегия трактует эти экстремумы как потенциальные точки разворота и реагирует контртрендовыми входами, дополняя их выходами по уровням осциллятора.
Принцип работы стратегии
- Свечи строятся с использованием заданного таймфрейма и передаются во встроенный индикатор
VortexIndicator.
- После формирования индикатора осциллятор на каждом закрытом баре вычисляется как
VI+ - VI-.
- Осциллятор сравнивается с настраиваемыми порогами:
- Если значение падает ниже порога покупок, формируется сигнал на вход в лонг.
- Если значение поднимается выше порога продаж, формируется сигнал на вход в шорт.
- Дополнительные параметры могут ограничивать сигналы диапазоном между торговым порогом и уровнем стоп-лосса (для каждой стороны отдельно).
- При появлении нового сигнала стратегия закрывает противоположную позицию и открывает сделку в нужном направлении заданным объёмом.
- Открытые позиции постоянно контролируются: при достижении уровней стоп-лосса или тейк-профита по осциллятору позиция закрывается немедленно.
Такой алгоритм полностью повторяет логику MT5: расчёт выполняется только на закрытых барах, сигналы не конфликтуют между собой, а защитные правила привязаны к значениям осциллятора.
Правила входа
- Покупка
- Условие выполняется, когда осциллятор меньше либо равен порогу покупок.
- Если включён стоп-лосс для лонгов, значение осциллятора также должно быть выше уровня стоп-лосса.
- Перед открытием лонга существующая короткая позиция закрывается.
- Продажа
- Условие выполняется, когда осциллятор больше либо равен порогу продаж.
- Если включён стоп-лосс для шортов, значение осциллятора также должно быть ниже уровня стоп-лосса.
- Перед открытием шорта существующая длинная позиция закрывается.
- Если значение осциллятора находится между порогами покупок и продаж, сигналы обнуляются и позиция не меняется.
Правила выхода
- Длинные позиции
- Закрываются, если осциллятор опускается до уровня стоп-лосса или ниже (при включённом параметре).
- Закрываются, если осциллятор поднимается до уровня тейк-профита или выше (при включённом параметре).
- Короткие позиции
- Закрываются, если осциллятор поднимается до уровня стоп-лосса или выше (при включённом параметре).
- Закрываются, если осциллятор опускается до уровня тейк-профита или ниже (при включённом параметре).
Проверка условий выхода выполняется после каждого закрытия свечи, что обеспечивает точное соответствие циклу мониторинга оригинального советника.
Параметры
- Vortex Length — период расчёта индикатора Vortex (по умолчанию 14).
- Candle Type — таймфрейм, используемый для построения свечей и расчёта индикатора.
- Use Buy Stop Loss — включает фильтр и выход по стоп-лоссу для длинных позиций.
- Use Buy Take Profit — включает выход по тейк-профиту для длинных позиций.
- Use Sell Stop Loss — включает фильтр и выход по стоп-лоссу для коротких позиций.
- Use Sell Take Profit — включает выход по тейк-профиту для коротких позиций.
- Buy Threshold — значение осциллятора, при котором разрешён вход в лонг (по умолчанию -0.75).
- Buy Stop Loss Level — значение осциллятора, закрывающее длинные позиции при активном стоп-лоссе (по умолчанию -1.00).
- Buy Take Profit Level — значение осциллятора, закрывающее длинные позиции при активном тейк-профите (по умолчанию 0.00).
- Sell Threshold — значение осциллятора, при котором разрешён вход в шорт (по умолчанию 0.75).
- Sell Stop Loss Level — значение осциллятора, закрывающее короткие позиции при активном стоп-лоссе (по умолчанию 1.00).
- Sell Take Profit Level — значение осциллятора, закрывающее короткие позиции при активном тейк-профите (по умолчанию 0.00).
- Volume — размер позиции для новых сделок (по умолчанию 0.1, как в оригинальном советнике).
Особенности реализации
- Обработка ведётся только по закрытым свечам, чтобы исключить повторные сигналы внутри бара.
- Для основных порогов заданы диапазоны оптимизации, что позволяет исследовать параметры в тестере StockSharp.
- При смене направления стратегия автоматически переворачивает позицию, отправляя заявку, достаточную для закрытия противоположного объёма и открытия новой сделки.
- Настройки стоп-лосса и тейк-профита независимы: можно использовать любой набор защитных правил.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Vortex oscillator trading system ported from the MetaTrader implementation.
/// Opens long positions when the oscillator drops below a configured level and
/// shorts when the oscillator rises above the upper threshold.
/// Optional stop-loss and take-profit rules monitor the oscillator value to exit positions.
/// </summary>
public class VortexOscillatorSystemStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _useBuyStopLoss;
private readonly StrategyParam<bool> _useBuyTakeProfit;
private readonly StrategyParam<bool> _useSellStopLoss;
private readonly StrategyParam<bool> _useSellTakeProfit;
private readonly StrategyParam<decimal> _buyThreshold;
private readonly StrategyParam<decimal> _buyStopLossLevel;
private readonly StrategyParam<decimal> _buyTakeProfitLevel;
private readonly StrategyParam<decimal> _sellThreshold;
private readonly StrategyParam<decimal> _sellStopLossLevel;
private readonly StrategyParam<decimal> _sellTakeProfitLevel;
private VortexIndicator _vortexIndicator = null!;
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public VortexOscillatorSystemStrategy()
{
_length = Param(nameof(Length), 14)
.SetGreaterThanZero()
.SetDisplay("Vortex Length", "Period used for the Vortex indicator.", "General")
.SetOptimize(7, 28, 7);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to build candles for calculations.", "General");
_useBuyStopLoss = Param(nameof(UseBuyStopLoss), false)
.SetDisplay("Use Buy Stop Loss", "Enable oscillator-based stop loss for long positions.", "Risk Management");
_useBuyTakeProfit = Param(nameof(UseBuyTakeProfit), false)
.SetDisplay("Use Buy Take Profit", "Enable oscillator-based take profit for long positions.", "Risk Management");
_useSellStopLoss = Param(nameof(UseSellStopLoss), false)
.SetDisplay("Use Sell Stop Loss", "Enable oscillator-based stop loss for short positions.", "Risk Management");
_useSellTakeProfit = Param(nameof(UseSellTakeProfit), false)
.SetDisplay("Use Sell Take Profit", "Enable oscillator-based take profit for short positions.", "Risk Management");
_buyThreshold = Param(nameof(BuyThreshold), -0.1m)
.SetDisplay("Buy Threshold", "Oscillator value that triggers a long setup.", "Signals")
.SetOptimize(-1.5m, -0.25m, 0.25m);
_buyStopLossLevel = Param(nameof(BuyStopLossLevel), -1m)
.SetDisplay("Buy Stop Loss Level", "Oscillator value that closes long trades when stop loss is enabled.", "Signals");
_buyTakeProfitLevel = Param(nameof(BuyTakeProfitLevel), 0m)
.SetDisplay("Buy Take Profit Level", "Oscillator value that closes long trades when take profit is enabled.", "Signals");
_sellThreshold = Param(nameof(SellThreshold), 0.1m)
.SetDisplay("Sell Threshold", "Oscillator value that triggers a short setup.", "Signals")
.SetOptimize(0.25m, 1.5m, 0.25m);
_sellStopLossLevel = Param(nameof(SellStopLossLevel), 1m)
.SetDisplay("Sell Stop Loss Level", "Oscillator value that closes short trades when stop loss is enabled.", "Signals");
_sellTakeProfitLevel = Param(nameof(SellTakeProfitLevel), 0m)
.SetDisplay("Sell Take Profit Level", "Oscillator value that closes short trades when take profit is enabled.", "Signals");
Volume = 0.1m;
}
/// <summary>
/// Vortex indicator lookback length.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Enable oscillator-based stop loss for long positions.
/// </summary>
public bool UseBuyStopLoss
{
get => _useBuyStopLoss.Value;
set => _useBuyStopLoss.Value = value;
}
/// <summary>
/// Enable oscillator-based take profit for long positions.
/// </summary>
public bool UseBuyTakeProfit
{
get => _useBuyTakeProfit.Value;
set => _useBuyTakeProfit.Value = value;
}
/// <summary>
/// Enable oscillator-based stop loss for short positions.
/// </summary>
public bool UseSellStopLoss
{
get => _useSellStopLoss.Value;
set => _useSellStopLoss.Value = value;
}
/// <summary>
/// Enable oscillator-based take profit for short positions.
/// </summary>
public bool UseSellTakeProfit
{
get => _useSellTakeProfit.Value;
set => _useSellTakeProfit.Value = value;
}
/// <summary>
/// Oscillator level used to open long trades.
/// </summary>
public decimal BuyThreshold
{
get => _buyThreshold.Value;
set => _buyThreshold.Value = value;
}
/// <summary>
/// Oscillator level that closes long trades when stop loss is enabled.
/// </summary>
public decimal BuyStopLossLevel
{
get => _buyStopLossLevel.Value;
set => _buyStopLossLevel.Value = value;
}
/// <summary>
/// Oscillator level that closes long trades when take profit is enabled.
/// </summary>
public decimal BuyTakeProfitLevel
{
get => _buyTakeProfitLevel.Value;
set => _buyTakeProfitLevel.Value = value;
}
/// <summary>
/// Oscillator level used to open short trades.
/// </summary>
public decimal SellThreshold
{
get => _sellThreshold.Value;
set => _sellThreshold.Value = value;
}
/// <summary>
/// Oscillator level that closes short trades when stop loss is enabled.
/// </summary>
public decimal SellStopLossLevel
{
get => _sellStopLossLevel.Value;
set => _sellStopLossLevel.Value = value;
}
/// <summary>
/// Oscillator level that closes short trades when take profit is enabled.
/// </summary>
public decimal SellTakeProfitLevel
{
get => _sellTakeProfitLevel.Value;
set => _sellTakeProfitLevel.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_vortexIndicator = new VortexIndicator
{
Length = Length,
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_vortexIndicator, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue vortexValue)
{
// Process only finished candles to mirror bar-close logic from the original script.
if (candle.State != CandleStates.Finished)
return;
if (!_vortexIndicator.IsFormed)
return;
var vortexTyped = (VortexIndicatorValue)vortexValue;
var viPlus = vortexTyped.PlusVi ?? 0m;
var viMinus = vortexTyped.MinusVi ?? 0m;
// Vortex oscillator equals the difference between VI+ and VI- lines.
var oscillator = viPlus - viMinus;
var longSetupExists = false;
var shortSetupExists = false;
// Long setups are considered when the oscillator falls below the buy threshold.
if (UseBuyStopLoss)
{
if (oscillator <= BuyThreshold && oscillator > BuyStopLossLevel)
{
longSetupExists = true;
shortSetupExists = false;
}
}
else if (oscillator <= BuyThreshold)
{
longSetupExists = true;
shortSetupExists = false;
}
// Short setups require the oscillator to rise above the sell threshold.
if (UseSellStopLoss)
{
if (oscillator >= SellThreshold && oscillator < SellStopLossLevel)
{
shortSetupExists = true;
longSetupExists = false;
}
}
else if (oscillator >= SellThreshold)
{
shortSetupExists = true;
longSetupExists = false;
}
// Neutral zone cancels both long and short intentions.
if (oscillator >= BuyThreshold && oscillator <= SellThreshold)
{
longSetupExists = false;
shortSetupExists = false;
}
var currentPosition = Position;
if (longSetupExists && currentPosition <= 0)
{
// Close existing shorts and open a long position when a valid long setup appears.
BuyMarket();
}
else if (shortSetupExists && currentPosition >= 0)
{
// Close existing longs and open a short position when a valid short setup appears.
SellMarket();
}
currentPosition = Position;
if (currentPosition > 0)
{
// Manage long positions with oscillator-based stops and targets.
if (UseBuyStopLoss && oscillator <= BuyStopLossLevel)
{
SellMarket();
return;
}
if (UseBuyTakeProfit && oscillator >= BuyTakeProfitLevel)
{
SellMarket();
return;
}
}
else if (currentPosition < 0)
{
// Manage short positions with oscillator-based stops and targets.
if (UseSellStopLoss && oscillator >= SellStopLossLevel)
{
BuyMarket();
return;
}
if (UseSellTakeProfit && oscillator <= SellTakeProfitLevel)
{
BuyMarket();
}
}
}
}
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
from StockSharp.Algo.Indicators import VortexIndicator, VortexIndicatorValue
class vortex_oscillator_system_strategy(Strategy):
"""Vortex oscillator system: trades when VI+-VI- crosses configurable thresholds."""
def __init__(self):
super(vortex_oscillator_system_strategy, self).__init__()
self._length = self.Param("Length", 14) \
.SetGreaterThanZero() \
.SetDisplay("Vortex Length", "Period used for the Vortex indicator", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used to build candles", "General")
self._use_buy_stop_loss = self.Param("UseBuyStopLoss", False) \
.SetDisplay("Use Buy Stop Loss", "Enable oscillator-based stop loss for longs", "Risk")
self._use_buy_take_profit = self.Param("UseBuyTakeProfit", False) \
.SetDisplay("Use Buy Take Profit", "Enable oscillator-based take profit for longs", "Risk")
self._use_sell_stop_loss = self.Param("UseSellStopLoss", False) \
.SetDisplay("Use Sell Stop Loss", "Enable oscillator-based stop loss for shorts", "Risk")
self._use_sell_take_profit = self.Param("UseSellTakeProfit", False) \
.SetDisplay("Use Sell Take Profit", "Enable oscillator-based take profit for shorts", "Risk")
self._buy_threshold = self.Param("BuyThreshold", -0.1) \
.SetDisplay("Buy Threshold", "Oscillator value that triggers a long setup", "Signals")
self._buy_stop_loss_level = self.Param("BuyStopLossLevel", -1.0) \
.SetDisplay("Buy Stop Loss Level", "Oscillator value that closes long trades", "Signals")
self._buy_take_profit_level = self.Param("BuyTakeProfitLevel", 0.0) \
.SetDisplay("Buy Take Profit Level", "Oscillator value that closes long trades", "Signals")
self._sell_threshold = self.Param("SellThreshold", 0.1) \
.SetDisplay("Sell Threshold", "Oscillator value that triggers a short setup", "Signals")
self._sell_stop_loss_level = self.Param("SellStopLossLevel", 1.0) \
.SetDisplay("Sell Stop Loss Level", "Oscillator value that closes short trades", "Signals")
self._sell_take_profit_level = self.Param("SellTakeProfitLevel", 0.0) \
.SetDisplay("Sell Take Profit Level", "Oscillator value that closes short trades", "Signals")
@property
def Length(self):
return int(self._length.Value)
@property
def CandleType(self):
return self._candle_type.Value
@property
def UseBuyStopLoss(self):
return self._use_buy_stop_loss.Value
@property
def UseBuyTakeProfit(self):
return self._use_buy_take_profit.Value
@property
def UseSellStopLoss(self):
return self._use_sell_stop_loss.Value
@property
def UseSellTakeProfit(self):
return self._use_sell_take_profit.Value
@property
def BuyThreshold(self):
return float(self._buy_threshold.Value)
@property
def BuyStopLossLevel(self):
return float(self._buy_stop_loss_level.Value)
@property
def BuyTakeProfitLevel(self):
return float(self._buy_take_profit_level.Value)
@property
def SellThreshold(self):
return float(self._sell_threshold.Value)
@property
def SellStopLossLevel(self):
return float(self._sell_stop_loss_level.Value)
@property
def SellTakeProfitLevel(self):
return float(self._sell_take_profit_level.Value)
def OnStarted2(self, time):
super(vortex_oscillator_system_strategy, self).OnStarted2(time)
self._vortex = VortexIndicator()
self._vortex.Length = self.Length
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._vortex, self.process_candle).Start()
def process_candle(self, candle, vortex_value):
if candle.State != CandleStates.Finished:
return
if not self._vortex.IsFormed:
return
vi_plus = float(vortex_value.PlusVi) if vortex_value.PlusVi is not None else 0.0
vi_minus = float(vortex_value.MinusVi) if vortex_value.MinusVi is not None else 0.0
oscillator = vi_plus - vi_minus
long_setup = False
short_setup = False
if self.UseBuyStopLoss:
if oscillator <= self.BuyThreshold and oscillator > self.BuyStopLossLevel:
long_setup = True
short_setup = False
elif oscillator <= self.BuyThreshold:
long_setup = True
short_setup = False
if self.UseSellStopLoss:
if oscillator >= self.SellThreshold and oscillator < self.SellStopLossLevel:
short_setup = True
long_setup = False
elif oscillator >= self.SellThreshold:
short_setup = True
long_setup = False
if oscillator >= self.BuyThreshold and oscillator <= self.SellThreshold:
long_setup = False
short_setup = False
if long_setup and self.Position <= 0:
self.BuyMarket()
elif short_setup and self.Position >= 0:
self.SellMarket()
if self.Position > 0:
if self.UseBuyStopLoss and oscillator <= self.BuyStopLossLevel:
self.SellMarket()
return
if self.UseBuyTakeProfit and oscillator >= self.BuyTakeProfitLevel:
self.SellMarket()
return
elif self.Position < 0:
if self.UseSellStopLoss and oscillator >= self.SellStopLossLevel:
self.BuyMarket()
return
if self.UseSellTakeProfit and oscillator <= self.SellTakeProfitLevel:
self.BuyMarket()
def OnReseted(self):
super(vortex_oscillator_system_strategy, self).OnReseted()
def CreateClone(self):
return vortex_oscillator_system_strategy()