Vortex Indicator Duplex Strategy
This strategy converts the MetaTrader expert Exp_VortexIndicator_Duplex to the StockSharp high level API. Two independent Vortex indicator streams are maintained: one governs long trades and the other governs short trades. Each stream can use its own timeframe, indicator length and bar shift, allowing asymmetric behaviour between bullish and bearish setups.
How it works
- Two candle subscriptions are opened according to
LongCandleTypeandShortCandleType. Each feed updates its ownVortexIndicatorinstance. - On every finished candle the strategy records the latest VI+ and VI- values. The
LongSignalBar/ShortSignalBarparameters define how many closed candles back should be used for signal evaluation, matching the MetaTraderSignalBarinput. - Long entry – allowed when
AllowLongEntries = true. A buy order is sent if the current long-stream VI+ value is above VI- while the previous sampled value had VI+ less than or equal to VI-. Any existing short exposure is flattened before the new long position is established. - Long exit – enabled through
AllowLongExits. The long position is closed when the long-stream VI- value rises above VI+. In addition, protective stop-loss and take-profit levels expressed in price steps (LongStopLossSteps,LongTakeProfitSteps) are monitored on every candle; hitting either threshold also closes the trade. - Short entry – governed by
AllowShortEntries. A sell order is placed when the short-stream VI+ drops below VI- after previously being above it. Existing long exposure is flattened during the reversal. - Short exit – controlled by
AllowShortExits. The short position is covered when VI+ climbs back above VI-. Protective distances (ShortStopLossSteps,ShortTakeProfitSteps) close the trade if reached. - Position sizing uses the
TradeVolumeparameter. The strategy relies on the instrumentPriceStepto convert step counts into absolute price distances; setting a step parameter to zero disables the corresponding protective rule.
The stop/take checks are evaluated on every finished candle from both timeframes. If the account has no position, cached entry data is cleared to mirror the MetaTrader implementation.
Parameters
| Parameter | Default | Description |
|---|---|---|
LongCandleType |
H4 | Timeframe used for the long-side Vortex indicator. |
ShortCandleType |
H4 | Timeframe used for the short-side indicator. |
LongLength |
14 | VI period applied to the long stream. |
ShortLength |
14 | VI period applied to the short stream. |
LongSignalBar |
1 | Closed-candle offset for the long evaluation (0 = current finished bar). |
ShortSignalBar |
1 | Closed-candle offset for the short evaluation. |
AllowLongEntries |
true | Enables opening long positions. |
AllowLongExits |
true | Enables closing long positions. |
AllowShortEntries |
true | Enables opening short positions. |
AllowShortExits |
true | Enables closing short positions. |
LongStopLossSteps |
1000 | Stop-loss distance for long trades, expressed in price steps. |
LongTakeProfitSteps |
2000 | Take-profit distance for long trades, expressed in price steps. |
ShortStopLossSteps |
1000 | Stop-loss distance for short trades, expressed in price steps. |
ShortTakeProfitSteps |
2000 | Take-profit distance for short trades, expressed in price steps. |
TradeVolume |
1 | Base market order size used when entering a position. |
Execution notes
- The strategy closes any opposite position before opening a new one, effectively reproducing the MT5 behaviour where separate magic numbers managed long and short signals.
- Protective distances are converted via
distance = steps * Security.PriceStep. Ensure the instrument has a valid price step; otherwise the strategy falls back to 1.0. - Set any stop/take parameter to zero to disable that protection path while keeping signal-based exits active.
- Because both timeframes can trigger risk management, choose
TradeVolumecarefully to avoid repeated reversals on thin markets.
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()