Estrategia Vortex MTF
Esta estrategia calcula el indicador Vortex en un marco temporal configurable. Las posiciones largas se abren cuando la línea Vortex positiva cruza por encima de la negativa. Las posiciones cortas se abren en el cruce opuesto.
Detalles
- Indicadores: Vortex
- Dirección: Largo y corto
- Marco temporal: Configurable
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 multi-timeframe strategy.
/// Goes long when VI+ crosses above VI- and short on the opposite signal.
/// Manual Vortex calculation with SMA dummy for Bind.
/// </summary>
public class VortexMtfStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly List<decimal> _vmPlus = new();
private readonly List<decimal> _vmMinus = new();
private readonly List<decimal> _trueRanges = new();
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _prevClose;
private decimal _prevVip;
private decimal _prevVim;
private int _cooldownRemaining;
public int Length { get => _length.Value; set => _length.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public VortexMtfStrategy()
{
_length = Param(nameof(Length), 14)
.SetGreaterThanZero()
.SetDisplay("Vortex Length", "Period of the Vortex indicator", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for Vortex calculation", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new Vortex crossover entry", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_vmPlus.Clear();
_vmMinus.Clear();
_trueRanges.Clear();
_prevHigh = null;
_prevLow = null;
_prevClose = null;
_prevVip = 0;
_prevVim = 0;
_cooldownRemaining = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
_vmPlus.Clear();
_vmMinus.Clear();
_trueRanges.Clear();
_prevHigh = null;
_prevLow = null;
_prevClose = null;
_prevVip = 0;
_prevVim = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal _dummy)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
if (_prevHigh == null)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevClose = candle.ClosePrice;
return;
}
var vmp = Math.Abs(candle.HighPrice - _prevLow.Value);
var vmm = Math.Abs(candle.LowPrice - _prevHigh.Value);
var tr = Math.Max(candle.HighPrice - candle.LowPrice,
Math.Max(Math.Abs(candle.HighPrice - _prevClose.Value),
Math.Abs(candle.LowPrice - _prevClose.Value)));
_vmPlus.Add(vmp);
_vmMinus.Add(vmm);
_trueRanges.Add(tr);
while (_vmPlus.Count > Length)
{
_vmPlus.RemoveAt(0);
_vmMinus.RemoveAt(0);
_trueRanges.RemoveAt(0);
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevClose = candle.ClosePrice;
if (_vmPlus.Count < Length)
return;
var sumTr = _trueRanges.Sum();
if (sumTr == 0)
return;
var vip = _vmPlus.Sum() / sumTr;
var vim = _vmMinus.Sum() / sumTr;
if (_prevVip == 0 && _prevVim == 0)
{
_prevVip = vip;
_prevVim = vim;
return;
}
if (_cooldownRemaining == 0 && _prevVip <= _prevVim && vip > vim && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? -Position : 0m));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && _prevVip >= _prevVim && vip < vim && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Position : 0m));
_cooldownRemaining = SignalCooldownBars;
}
_prevVip = vip;
_prevVim = vim;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vortex_mtf_strategy(Strategy):
def __init__(self):
super(vortex_mtf_strategy, self).__init__()
self._length = self.Param("Length", 14) \
.SetDisplay("Vortex Length", "Period of the Vortex indicator", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe for Vortex calculation", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 4) \
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new Vortex crossover entry", "General")
self._vm_plus = []
self._vm_minus = []
self._true_ranges = []
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._prev_vip = 0.0
self._prev_vim = 0.0
self._cooldown_remaining = 0
@property
def length(self):
return self._length.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def signal_cooldown_bars(self):
return self._signal_cooldown_bars.Value
def OnReseted(self):
super(vortex_mtf_strategy, self).OnReseted()
self._vm_plus = []
self._vm_minus = []
self._true_ranges = []
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._prev_vip = 0.0
self._prev_vim = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(vortex_mtf_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = 2
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, _dummy):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if self._prev_high is None:
self._prev_high = high
self._prev_low = low
self._prev_close = close
return
vmp = abs(high - self._prev_low)
vmm = abs(low - self._prev_high)
tr = max(high - low,
max(abs(high - self._prev_close),
abs(low - self._prev_close)))
self._vm_plus.append(vmp)
self._vm_minus.append(vmm)
self._true_ranges.append(tr)
while len(self._vm_plus) > self.length:
self._vm_plus.pop(0)
self._vm_minus.pop(0)
self._true_ranges.pop(0)
self._prev_high = high
self._prev_low = low
self._prev_close = close
if len(self._vm_plus) < self.length:
return
sum_tr = sum(self._true_ranges)
if sum_tr == 0:
return
vip = sum(self._vm_plus) / sum_tr
vim = sum(self._vm_minus) / sum_tr
if self._prev_vip == 0 and self._prev_vim == 0:
self._prev_vip = vip
self._prev_vim = vim
return
if self._cooldown_remaining == 0 and self._prev_vip <= self._prev_vim and vip > vim and self.Position <= 0:
self.BuyMarket()
self._cooldown_remaining = self.signal_cooldown_bars
elif self._cooldown_remaining == 0 and self._prev_vip >= self._prev_vim and vip < vim and self.Position >= 0:
self.SellMarket()
self._cooldown_remaining = self.signal_cooldown_bars
self._prev_vip = vip
self._prev_vim = vim
def CreateClone(self):
return vortex_mtf_strategy()