Vortex Indicator Duplex 策略
该策略将 MetaTrader 专家顾问 Exp_VortexIndicator_Duplex 迁移到 StockSharp 高级 API。策略维护两个独立的 Vortex 指标流:一个负责多头信号,另一个负责空头信号。每个流都可以配置自己的时间周期、指标周期以及信号偏移,从而实现对多空逻辑的差异化设置。
运行逻辑
- 根据
LongCandleType与ShortCandleType打开两条蜡烛订阅,每条订阅驱动各自的VortexIndicator实例。 - 每根蜡烛收盘后记录最新的 VI+ 与 VI- 数值。参数
LongSignalBar/ShortSignalBar指定在回看多少根已收盘蜡烛时产生信号,行为与原始 EA 的SignalBar输入保持一致。 - 做多开仓(当
AllowLongEntries = true时):若当前长周期 VI+ 高于 VI-,且上一采样点 VI+ 小于等于 VI-,则发送买入市价单。如果账户中存在空头仓位,会先平掉空头再建立多头。 - 做多平仓(当
AllowLongExits = true时):当长周期 VI- 上穿 VI+ 时平掉多头。同时监控以价格步长表示的保护距离LongStopLossSteps与LongTakeProfitSteps;一旦触发也会立即平仓。 - 做空开仓(当
AllowShortEntries = true时):若当前短周期 VI+ 低于 VI-,且上一采样点 VI+ 大于等于 VI-,则发送卖出市价单,并在必要时平掉多头仓位。 - 做空平仓(当
AllowShortExits = true时):当短周期 VI+ 再次上穿 VI- 时回补空头,同时监控ShortStopLossSteps与ShortTakeProfitSteps。 - 交易量由
TradeVolume控制。策略使用品种的Security.PriceStep将“步长”转换成实际价格差,若参数为 0 则关闭相应保护规则。
所有止损/止盈检查都会在两个时间框架的每根收盘蜡烛上执行。当账户没有持仓时,会重置缓存的入场信息,行为与原始 MT5 版本保持一致。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
LongCandleType |
H4 | 多头 Vortex 指标使用的时间周期。 |
ShortCandleType |
H4 | 空头 Vortex 指标使用的时间周期。 |
LongLength |
14 | 多头指标的 VI 周期。 |
ShortLength |
14 | 空头指标的 VI 周期。 |
LongSignalBar |
1 | 多头信号使用的已收盘蜡烛偏移(0 表示最新收盘)。 |
ShortSignalBar |
1 | 空头信号使用的已收盘蜡烛偏移。 |
AllowLongEntries |
true | 是否允许开多。 |
AllowLongExits |
true | 是否允许平多。 |
AllowShortEntries |
true | 是否允许开空。 |
AllowShortExits |
true | 是否允许平空。 |
LongStopLossSteps |
1000 | 多头止损距离,单位为价格步长。 |
LongTakeProfitSteps |
2000 | 多头止盈距离,单位为价格步长。 |
ShortStopLossSteps |
1000 | 空头止损距离,单位为价格步长。 |
ShortTakeProfitSteps |
2000 | 空头止盈距离,单位为价格步长。 |
TradeVolume |
1 | 开仓时使用的基础交易量。 |
使用提示
- 在开立新仓前会先平掉反向头寸,对应了 MT5 中通过不同 Magic 编号管理多空的做法。
- 价格步长转换公式为
distance = steps * Security.PriceStep;若品种未提供步长,则默认使用 1。 - 将任意保护参数设为 0 可以关闭该保护,而信号型出场仍然有效。
- 由于两个时间框架都会触发风控检查,请根据市场流动性合理设置
TradeVolume,避免频繁反复开仓。
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>
/// Dual Vortex indicator strategy converted from the MetaTrader expert Exp_VortexIndicator_Duplex.
/// Maintains independent long and short signal streams with configurable timeframes and risk parameters.
/// </summary>
public class VortexIndicatorDuplexStrategy : Strategy
{
private readonly StrategyParam<DataType> _longCandleType;
private readonly StrategyParam<DataType> _shortCandleType;
private readonly StrategyParam<int> _longLength;
private readonly StrategyParam<int> _shortLength;
private readonly StrategyParam<int> _longSignalBar;
private readonly StrategyParam<int> _shortSignalBar;
private readonly StrategyParam<bool> _allowLongEntries;
private readonly StrategyParam<bool> _allowLongExits;
private readonly StrategyParam<bool> _allowShortEntries;
private readonly StrategyParam<bool> _allowShortExits;
private readonly StrategyParam<decimal> _longStopLossSteps;
private readonly StrategyParam<decimal> _longTakeProfitSteps;
private readonly StrategyParam<decimal> _shortStopLossSteps;
private readonly StrategyParam<decimal> _shortTakeProfitSteps;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<int> _signalCooldownBars;
private VortexIndicator _longVortex = null!;
private VortexIndicator _shortVortex = null!;
private readonly List<(decimal plus, decimal minus)> _longHistory = new();
private readonly List<(decimal plus, decimal minus)> _shortHistory = new();
private decimal _priceStep;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longStopPrice;
private decimal? _shortStopPrice;
private decimal? _longTakeProfitPrice;
private decimal? _shortTakeProfitPrice;
private int _cooldownRemaining;
private readonly StrategyParam<int> _maxHistoryLength;
/// <summary>
/// Initializes parameters for the duplex Vortex strategy.
/// </summary>
public VortexIndicatorDuplexStrategy()
{
_maxHistoryLength = Param(nameof(MaxHistoryLength), 512)
.SetGreaterThanZero()
.SetDisplay("Max History Length", "Maximum stored Vortex samples per direction.", "General");
_longCandleType = Param(nameof(LongCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Long Candle Type", "Timeframe used for long-side Vortex calculations.", "General");
_shortCandleType = Param(nameof(ShortCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Short Candle Type", "Timeframe used for short-side Vortex calculations.", "General");
_longLength = Param(nameof(LongLength), 14)
.SetGreaterThanZero()
.SetDisplay("Long Vortex Length", "VI period applied to the long signal stream.", "Indicator")
.SetOptimize(7, 42, 7);
_shortLength = Param(nameof(ShortLength), 14)
.SetGreaterThanZero()
.SetDisplay("Short Vortex Length", "VI period applied to the short signal stream.", "Indicator")
.SetOptimize(7, 42, 7);
_longSignalBar = Param(nameof(LongSignalBar), 1)
.SetNotNegative()
.SetDisplay("Long Signal Bar", "Closed bar shift used for long evaluations.", "Signals");
_shortSignalBar = Param(nameof(ShortSignalBar), 1)
.SetNotNegative()
.SetDisplay("Short Signal Bar", "Closed bar shift used for short evaluations.", "Signals");
_allowLongEntries = Param(nameof(AllowLongEntries), true)
.SetDisplay("Allow Long Entries", "Enable opening long positions when VI+ crosses above VI-.", "Trading");
_allowLongExits = Param(nameof(AllowLongExits), true)
.SetDisplay("Allow Long Exits", "Enable closing long positions when VI- dominates VI+.", "Trading");
_allowShortEntries = Param(nameof(AllowShortEntries), true)
.SetDisplay("Allow Short Entries", "Enable opening short positions when VI+ crosses below VI-.", "Trading");
_allowShortExits = Param(nameof(AllowShortExits), true)
.SetDisplay("Allow Short Exits", "Enable closing short positions when VI+ recovers above VI-.", "Trading");
_longStopLossSteps = Param(nameof(LongStopLossSteps), 1000m)
.SetNotNegative()
.SetDisplay("Long Stop Loss Steps", "Protective distance below the long entry price in price steps (0 disables).", "Risk");
_longTakeProfitSteps = Param(nameof(LongTakeProfitSteps), 2000m)
.SetNotNegative()
.SetDisplay("Long Take Profit Steps", "Target distance above the long entry price in price steps (0 disables).", "Risk");
_shortStopLossSteps = Param(nameof(ShortStopLossSteps), 1000m)
.SetNotNegative()
.SetDisplay("Short Stop Loss Steps", "Protective distance above the short entry price in price steps (0 disables).", "Risk");
_shortTakeProfitSteps = Param(nameof(ShortTakeProfitSteps), 2000m)
.SetNotNegative()
.SetDisplay("Short Take Profit Steps", "Target distance below the short entry price in price steps (0 disables).", "Risk");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Base order volume used for entries.", "Risk");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trading actions.", "Trading");
}
/// <summary>
/// Candle type for the long-side signal calculations.
/// </summary>
public DataType LongCandleType
{
get => _longCandleType.Value;
set => _longCandleType.Value = value;
}
/// <summary>
/// Candle type for the short-side signal calculations.
/// </summary>
public DataType ShortCandleType
{
get => _shortCandleType.Value;
set => _shortCandleType.Value = value;
}
/// <summary>
/// Vortex period applied to the long signal stream.
/// </summary>
public int LongLength
{
get => _longLength.Value;
set => _longLength.Value = value;
}
/// <summary>
/// Vortex period applied to the short signal stream.
/// </summary>
public int ShortLength
{
get => _shortLength.Value;
set => _shortLength.Value = value;
}
/// <summary>
/// Shift in closed candles for long evaluations.
/// </summary>
public int LongSignalBar
{
get => _longSignalBar.Value;
set => _longSignalBar.Value = value;
}
/// <summary>
/// Shift in closed candles for short evaluations.
/// </summary>
public int ShortSignalBar
{
get => _shortSignalBar.Value;
set => _shortSignalBar.Value = value;
}
/// <summary>
/// Enables long entries.
/// </summary>
public bool AllowLongEntries
{
get => _allowLongEntries.Value;
set => _allowLongEntries.Value = value;
}
/// <summary>
/// Enables long exits.
/// </summary>
public bool AllowLongExits
{
get => _allowLongExits.Value;
set => _allowLongExits.Value = value;
}
/// <summary>
/// Enables short entries.
/// </summary>
public bool AllowShortEntries
{
get => _allowShortEntries.Value;
set => _allowShortEntries.Value = value;
}
/// <summary>
/// Enables short exits.
/// </summary>
public bool AllowShortExits
{
get => _allowShortExits.Value;
set => _allowShortExits.Value = value;
}
/// <summary>
/// Stop-loss distance for long trades in price steps.
/// </summary>
public decimal LongStopLossSteps
{
get => _longStopLossSteps.Value;
set => _longStopLossSteps.Value = value;
}
/// <summary>
/// Take-profit distance for long trades in price steps.
/// </summary>
public decimal LongTakeProfitSteps
{
get => _longTakeProfitSteps.Value;
set => _longTakeProfitSteps.Value = value;
}
/// <summary>
/// Stop-loss distance for short trades in price steps.
/// </summary>
public decimal ShortStopLossSteps
{
get => _shortStopLossSteps.Value;
set => _shortStopLossSteps.Value = value;
}
/// <summary>
/// Take-profit distance for short trades in price steps.
/// </summary>
public decimal ShortTakeProfitSteps
{
get => _shortTakeProfitSteps.Value;
set => _shortTakeProfitSteps.Value = value;
}
/// <summary>
/// Volume used when sending market orders.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Bars to wait after each position change.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Maximum number of stored Vortex samples per signal stream.
/// </summary>
public int MaxHistoryLength
{
get => _maxHistoryLength.Value;
set => _maxHistoryLength.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
var result = new List<(Security, DataType)> { (Security, LongCandleType) };
if (!ShortCandleType.Equals(LongCandleType))
{
result.Add((Security, ShortCandleType));
}
return result;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longHistory.Clear();
_shortHistory.Clear();
ResetLongState();
ResetShortState();
_priceStep = 0m;
_cooldownRemaining = 0;
_longVortex = null!;
_shortVortex = null!;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security.PriceStep ?? 0m;
if (_priceStep <= 0m)
{
_priceStep = 1m;
}
Volume = TradeVolume;
_longVortex = new VortexIndicator { Length = LongLength };
var longSubscription = SubscribeCandles(LongCandleType);
longSubscription
.Bind(ProcessLongCandle)
.Start();
_shortVortex = new VortexIndicator { Length = ShortLength };
var shortSubscription = SubscribeCandles(ShortCandleType);
shortSubscription
.Bind(ProcessShortCandle)
.Start();
StartProtection(null, null);
}
private void ProcessLongCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
{
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
}
if (CheckRiskManagement(candle.ClosePrice))
{
return;
}
var value = _longVortex.Process(candle);
if (value is not IVortexIndicatorValue vortexValue ||
vortexValue.PlusVi is not decimal viPlus ||
vortexValue.MinusVi is not decimal viMinus)
{
return;
}
AppendHistory(_longHistory, (viPlus, viMinus));
if (!_longVortex.IsFormed)
{
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
return;
}
if (!TryGetHistoryPair(_longHistory, LongSignalBar, out var previous, out var current))
{
return;
}
var crossUp = previous.plus <= previous.minus && current.plus > current.minus;
var longExit = current.minus > current.plus;
if (longExit && AllowLongExits && Position > 0m)
{
SellMarket(Position);
ResetLongState();
_cooldownRemaining = SignalCooldownBars;
}
if (_cooldownRemaining == 0 && crossUp && AllowLongEntries)
{
TryOpenLong(candle.ClosePrice);
}
}
private void ProcessShortCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
{
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
}
if (CheckRiskManagement(candle.ClosePrice))
{
return;
}
var value = _shortVortex.Process(candle);
if (value is not IVortexIndicatorValue vortexValue ||
vortexValue.PlusVi is not decimal viPlus ||
vortexValue.MinusVi is not decimal viMinus)
{
return;
}
AppendHistory(_shortHistory, (viPlus, viMinus));
if (!_shortVortex.IsFormed)
{
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
return;
}
if (!TryGetHistoryPair(_shortHistory, ShortSignalBar, out var previous, out var current))
{
return;
}
var crossDown = previous.plus >= previous.minus && current.plus < current.minus;
var shortExit = current.plus > current.minus;
if (shortExit && AllowShortExits && Position < 0m)
{
BuyMarket(-Position);
ResetShortState();
_cooldownRemaining = SignalCooldownBars;
}
if (_cooldownRemaining == 0 && crossDown && AllowShortEntries)
{
TryOpenShort(candle.ClosePrice);
}
}
private void TryOpenLong(decimal price)
{
if (Position > 0m)
{
return;
}
var volume = Volume;
if (volume <= 0m)
{
return;
}
var buyVolume = volume;
if (Position < 0m)
{
buyVolume += Math.Abs(Position);
}
if (buyVolume <= 0m)
{
return;
}
BuyMarket(buyVolume);
_longEntryPrice = price;
_longStopPrice = LongStopLossSteps > 0m ? price - GetStepValue(LongStopLossSteps) : null;
_longTakeProfitPrice = LongTakeProfitSteps > 0m ? price + GetStepValue(LongTakeProfitSteps) : null;
_cooldownRemaining = SignalCooldownBars;
ResetShortState();
}
private void TryOpenShort(decimal price)
{
if (Position < 0m)
{
return;
}
var volume = Volume;
if (volume <= 0m)
{
return;
}
var sellVolume = volume;
if (Position > 0m)
{
sellVolume += Position;
}
if (sellVolume <= 0m)
{
return;
}
SellMarket(sellVolume);
_shortEntryPrice = price;
_shortStopPrice = ShortStopLossSteps > 0m ? price + GetStepValue(ShortStopLossSteps) : null;
_shortTakeProfitPrice = ShortTakeProfitSteps > 0m ? price - GetStepValue(ShortTakeProfitSteps) : null;
_cooldownRemaining = SignalCooldownBars;
ResetLongState();
}
private bool CheckRiskManagement(decimal price)
{
if (Position > 0m)
{
if (_longStopPrice is decimal stop && price <= stop)
{
SellMarket(Position);
ResetLongState();
_cooldownRemaining = SignalCooldownBars;
return true;
}
if (_longTakeProfitPrice is decimal take && price >= take)
{
SellMarket(Position);
ResetLongState();
_cooldownRemaining = SignalCooldownBars;
return true;
}
}
else if (Position < 0m)
{
if (_shortStopPrice is decimal stop && price >= stop)
{
BuyMarket(-Position);
ResetShortState();
_cooldownRemaining = SignalCooldownBars;
return true;
}
if (_shortTakeProfitPrice is decimal take && price <= take)
{
BuyMarket(-Position);
ResetShortState();
_cooldownRemaining = SignalCooldownBars;
return true;
}
}
else
{
ResetLongState();
ResetShortState();
}
return false;
}
private void AppendHistory(List<(decimal plus, decimal minus)> history, (decimal plus, decimal minus) value)
{
history.Add(value);
if (history.Count > MaxHistoryLength)
{
history.RemoveAt(0);
}
}
private bool TryGetHistoryPair(List<(decimal plus, decimal minus)> history, int signalBar, out (decimal plus, decimal minus) previous, out (decimal plus, decimal minus) current)
{
previous = default;
current = default;
var currentIndex = history.Count - 1 - signalBar;
var previousIndex = currentIndex - 1;
if (currentIndex < 0 || previousIndex < 0)
{
return false;
}
current = history[currentIndex];
previous = history[previousIndex];
return true;
}
private decimal GetStepValue(decimal steps)
{
return steps * _priceStep;
}
private void ResetLongState()
{
_longEntryPrice = null;
_longStopPrice = null;
_longTakeProfitPrice = null;
}
private void ResetShortState()
{
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakeProfitPrice = null;
}
}
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 VortexIndicator, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class vortex_indicator_duplex_strategy(Strategy):
def __init__(self):
super(vortex_indicator_duplex_strategy, self).__init__()
self._long_candle_type = self.Param("LongCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Long Candle Type", "Timeframe used for long-side Vortex calculations", "General")
self._short_candle_type = self.Param("ShortCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Short Candle Type", "Timeframe used for short-side Vortex calculations", "General")
self._long_length = self.Param("LongLength", 14) \
.SetDisplay("Long Vortex Length", "VI period applied to the long signal stream", "Indicator")
self._short_length = self.Param("ShortLength", 14) \
.SetDisplay("Short Vortex Length", "VI period applied to the short signal stream", "Indicator")
self._long_signal_bar = self.Param("LongSignalBar", 1) \
.SetDisplay("Long Signal Bar", "Closed bar shift used for long evaluations", "Signals")
self._short_signal_bar = self.Param("ShortSignalBar", 1) \
.SetDisplay("Short Signal Bar", "Closed bar shift used for short evaluations", "Signals")
self._long_stop_loss_steps = self.Param("LongStopLossSteps", 1000.0) \
.SetDisplay("Long Stop Loss Steps", "Protective distance below long entry in price steps", "Risk")
self._long_take_profit_steps = self.Param("LongTakeProfitSteps", 2000.0) \
.SetDisplay("Long Take Profit Steps", "Target distance above long entry in price steps", "Risk")
self._short_stop_loss_steps = self.Param("ShortStopLossSteps", 1000.0) \
.SetDisplay("Short Stop Loss Steps", "Protective distance above short entry in price steps", "Risk")
self._short_take_profit_steps = self.Param("ShortTakeProfitSteps", 2000.0) \
.SetDisplay("Short Take Profit Steps", "Target distance below short entry in price steps", "Risk")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 4) \
.SetDisplay("Signal Cooldown", "Bars to wait between trading actions", "Trading")
self._long_vortex = None
self._short_vortex = None
self._long_history = []
self._short_history = []
self._price_step = 1.0
self._long_entry_price = None
self._short_entry_price = None
self._long_stop_price = None
self._short_stop_price = None
self._long_take_profit_price = None
self._short_take_profit_price = None
self._cooldown_remaining = 0
@property
def LongCandleType(self):
return self._long_candle_type.Value
@property
def ShortCandleType(self):
return self._short_candle_type.Value
@property
def LongLength(self):
return self._long_length.Value
@property
def ShortLength(self):
return self._short_length.Value
@property
def LongSignalBar(self):
return self._long_signal_bar.Value
@property
def ShortSignalBar(self):
return self._short_signal_bar.Value
@property
def LongStopLossSteps(self):
return self._long_stop_loss_steps.Value
@property
def LongTakeProfitSteps(self):
return self._long_take_profit_steps.Value
@property
def ShortStopLossSteps(self):
return self._short_stop_loss_steps.Value
@property
def ShortTakeProfitSteps(self):
return self._short_take_profit_steps.Value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(vortex_indicator_duplex_strategy, self).OnReseted()
self._long_history = []
self._short_history = []
self._reset_long_state()
self._reset_short_state()
self._price_step = 1.0
self._cooldown_remaining = 0
self._long_vortex = None
self._short_vortex = None
def OnStarted2(self, time):
super(vortex_indicator_duplex_strategy, self).OnStarted2(time)
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self._price_step <= 0.0:
self._price_step = 1.0
self._long_history = []
self._short_history = []
self._reset_long_state()
self._reset_short_state()
self._cooldown_remaining = 0
self._long_vortex = VortexIndicator()
self._long_vortex.Length = self.LongLength
long_subscription = self.SubscribeCandles(self.LongCandleType)
long_subscription.Bind(self._on_long_candle).Start()
self._short_vortex = VortexIndicator()
self._short_vortex.Length = self.ShortLength
short_subscription = self.SubscribeCandles(self.ShortCandleType)
short_subscription.Bind(self._on_short_candle).Start()
def _on_long_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if self._check_risk_management(float(candle.ClosePrice)):
return
value = self._long_vortex.Process(CandleIndicatorValue(self._long_vortex, candle))
try:
vi_plus = float(value.PlusVi)
vi_minus = float(value.MinusVi)
except:
return
self._long_history.append((vi_plus, vi_minus))
max_history = 512
if len(self._long_history) > max_history:
self._long_history.pop(0)
if not self._long_vortex.IsFormed:
return
pair = self._try_get_history_pair(self._long_history, self.LongSignalBar)
if pair is None:
return
previous, current = pair
cross_up = previous[0] <= previous[1] and current[0] > current[1]
long_exit = current[1] > current[0]
if long_exit and self.Position > 0:
self.SellMarket()
self._reset_long_state()
self._cooldown_remaining = self.SignalCooldownBars
if self._cooldown_remaining == 0 and cross_up:
self._try_open_long(float(candle.ClosePrice))
def _on_short_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if self._check_risk_management(float(candle.ClosePrice)):
return
value = self._short_vortex.Process(CandleIndicatorValue(self._short_vortex, candle))
try:
vi_plus = float(value.PlusVi)
vi_minus = float(value.MinusVi)
except:
return
self._short_history.append((vi_plus, vi_minus))
max_history = 512
if len(self._short_history) > max_history:
self._short_history.pop(0)
if not self._short_vortex.IsFormed:
return
pair = self._try_get_history_pair(self._short_history, self.ShortSignalBar)
if pair is None:
return
previous, current = pair
cross_down = previous[0] >= previous[1] and current[0] < current[1]
short_exit = current[0] > current[1]
if short_exit and self.Position < 0:
self.BuyMarket()
self._reset_short_state()
self._cooldown_remaining = self.SignalCooldownBars
if self._cooldown_remaining == 0 and cross_down:
self._try_open_short(float(candle.ClosePrice))
def _try_open_long(self, price):
if self.Position > 0:
return
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._long_entry_price = price
sl = float(self.LongStopLossSteps)
tp = float(self.LongTakeProfitSteps)
self._long_stop_price = price - sl * self._price_step if sl > 0.0 else None
self._long_take_profit_price = price + tp * self._price_step if tp > 0.0 else None
self._cooldown_remaining = self.SignalCooldownBars
self._reset_short_state()
def _try_open_short(self, price):
if self.Position < 0:
return
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._short_entry_price = price
sl = float(self.ShortStopLossSteps)
tp = float(self.ShortTakeProfitSteps)
self._short_stop_price = price + sl * self._price_step if sl > 0.0 else None
self._short_take_profit_price = price - tp * self._price_step if tp > 0.0 else None
self._cooldown_remaining = self.SignalCooldownBars
self._reset_long_state()
def _check_risk_management(self, price):
if self.Position > 0:
if self._long_stop_price is not None and price <= self._long_stop_price:
self.SellMarket()
self._reset_long_state()
self._cooldown_remaining = self.SignalCooldownBars
return True
if self._long_take_profit_price is not None and price >= self._long_take_profit_price:
self.SellMarket()
self._reset_long_state()
self._cooldown_remaining = self.SignalCooldownBars
return True
elif self.Position < 0:
if self._short_stop_price is not None and price >= self._short_stop_price:
self.BuyMarket()
self._reset_short_state()
self._cooldown_remaining = self.SignalCooldownBars
return True
if self._short_take_profit_price is not None and price <= self._short_take_profit_price:
self.BuyMarket()
self._reset_short_state()
self._cooldown_remaining = self.SignalCooldownBars
return True
else:
self._reset_long_state()
self._reset_short_state()
return False
def _try_get_history_pair(self, history, signal_bar):
current_index = len(history) - 1 - signal_bar
previous_index = current_index - 1
if current_index < 0 or previous_index < 0:
return None
return (history[previous_index], history[current_index])
def _reset_long_state(self):
self._long_entry_price = None
self._long_stop_price = None
self._long_take_profit_price = None
def _reset_short_state(self):
self._short_entry_price = None
self._short_stop_price = None
self._short_take_profit_price = None
def CreateClone(self):
return vortex_indicator_duplex_strategy()