Vortex Oscillator System 策略
概述
Vortex Oscillator System 是从 MetaTrader 5 专家顾问直接移植的策略,核心是 Vortex 振荡指标。该指标取正向 Vortex 线(VI+)与负向 Vortex 线(VI-)的差值,基于选定的 K 线序列计算。当振荡器数值大幅为负时,说明 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,与原始 EA 相同)。
实现细节
- 仅在蜡烛收盘后处理,避免在同一根蜡烛内重复触发信号。
- 主要阈值提供了优化范围,可在 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()