Vortex Cross with MA Confirmation Strategy
Стратегия использует индикатор Vortex для поиска разворотов и подтверждает входы сглаженной скользящей средней. Покупка совершается при пересечении положительной линии Vortex выше отрицательной и цене выше линии сглаживания. Продажа выполняется при противоположных условиях.
Параметры
- Vortex Length – период расчёта Vortex.
- SMA Length – период базовой SMA.
- Smoothing Length – период сглаживающей средней.
- MA Type – метод сглаживания.
- Candle Type – тип используемых свечей.
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 indicator cross with moving average confirmation.
/// Manual Vortex calculation with SMA trend filter.
/// Buys on VI+ crossing above VI- with price above SMA, sells on opposite.
/// </summary>
public class VortexCrossMaConfirmationStrategy : Strategy
{
private readonly StrategyParam<int> _vortexLength;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<DataType> _candleType;
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;
public int VortexLength { get => _vortexLength.Value; set => _vortexLength.Value = value; }
public int SmaLength { get => _smaLength.Value; set => _smaLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VortexCrossMaConfirmationStrategy()
{
_vortexLength = Param(nameof(VortexLength), 14)
.SetGreaterThanZero()
.SetDisplay("Vortex Length", "Vortex indicator period", "General");
_smaLength = Param(nameof(SmaLength), 9)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "MA confirmation period", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_vmPlus.Clear();
_vmMinus.Clear();
_trueRanges.Clear();
_prevHigh = null;
_prevLow = null;
_prevClose = null;
_prevVip = 0;
_prevVim = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = SmaLength };
_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);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevHigh == null)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevClose = candle.ClosePrice;
return;
}
// Vortex movement
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 > VortexLength)
{
_vmPlus.RemoveAt(0);
_vmMinus.RemoveAt(0);
_trueRanges.RemoveAt(0);
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevClose = candle.ClosePrice;
if (_vmPlus.Count < VortexLength)
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;
}
// Crossover signals with MA confirmation
var longSignal = _prevVip < _prevVim && vip > vim && candle.ClosePrice > smaVal;
var shortSignal = _prevVip > _prevVim && vip < vim && candle.ClosePrice < smaVal;
if (longSignal && Position <= 0)
BuyMarket();
else if (shortSignal && Position >= 0)
SellMarket();
_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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vortex_cross_ma_confirmation_strategy(Strategy):
"""Manual Vortex indicator crossover with SMA trend confirmation."""
def __init__(self):
super(vortex_cross_ma_confirmation_strategy, self).__init__()
self._vortex_len = self.Param("VortexLength", 14).SetGreaterThanZero().SetDisplay("Vortex Length", "Vortex period", "General")
self._sma_len = self.Param("SmaLength", 9).SetGreaterThanZero().SetDisplay("SMA Length", "MA confirmation period", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(vortex_cross_ma_confirmation_strategy, self).OnReseted()
self._vm_plus = []
self._vm_minus = []
self._true_ranges = []
self._prev_high = None
self._prev_low = None
self._prev_close_val = None
self._prev_vip = 0
self._prev_vim = 0
def OnStarted2(self, time):
super(vortex_cross_ma_confirmation_strategy, self).OnStarted2(time)
self._vm_plus = []
self._vm_minus = []
self._true_ranges = []
self._prev_high = None
self._prev_low = None
self._prev_close_val = None
self._prev_vip = 0
self._prev_vim = 0
sma = SimpleMovingAverage()
sma.Length = self._sma_len.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(sma, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
sv = float(sma_val)
vlen = self._vortex_len.Value
if self._prev_high is None:
self._prev_high = high
self._prev_low = low
self._prev_close_val = close
return
# Vortex movement
vmp = abs(high - self._prev_low)
vmm = abs(low - self._prev_high)
tr = max(high - low, max(abs(high - self._prev_close_val), abs(low - self._prev_close_val)))
self._vm_plus.append(vmp)
self._vm_minus.append(vmm)
self._true_ranges.append(tr)
while len(self._vm_plus) > vlen:
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_val = close
if len(self._vm_plus) < vlen:
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
long_signal = self._prev_vip < self._prev_vim and vip > vim and close > sv
short_signal = self._prev_vip > self._prev_vim and vip < vim and close < sv
if long_signal and self.Position <= 0:
self.BuyMarket()
elif short_signal and self.Position >= 0:
self.SellMarket()
self._prev_vip = vip
self._prev_vim = vim
def CreateClone(self):
return vortex_cross_ma_confirmation_strategy()