Пересечение MA по объёму
В этой стратегии объём анализируется с помощью быстрых и медленных скользящих средних. Когда быстрая средняя объёма пересекает сверху медленную, это говорит о росте участия и служит сигналом на вход в лонг. Пересечение сверху вниз указывает на слабость и инициирует шорт.
Тестирование показывает среднегодичную доходность около 46%. Стратегию лучше запускать на фондовом рынке.
Позиции закрываются при обратном пересечении. Для фильтрации сделок отслеживается также собственная скользящая средняя цены.
Сигналы, основанные на объёме, часто опережают движение цены, позволяя входить раньше.
Детали
- Условия входа: Быстрая MA объёма пересекает медленную MA объёма.
- Длинные/короткие позиции: Оба направления.
- Условия выхода: Обратное пересечение или стоп.
- Стопы: Да.
- Значения по умолчанию:
FastVolumeMALength= 10SlowVolumeMALength= 50CandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Momentum
- Направление: Оба
- Индикаторы: Volume MA
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Volume MA Cross strategy.
/// Uses fast/slow volume MA crossover with price MA for direction.
/// Long: Volume expanding and price above SMA.
/// Short: Volume expanding and price below SMA.
/// </summary>
public class VolumeMAXrossStrategy : Strategy
{
private readonly StrategyParam<int> _priceMaPeriod;
private readonly StrategyParam<int> _fastVolPeriod;
private readonly StrategyParam<int> _slowVolPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _fastVolMA;
private SimpleMovingAverage _slowVolMA;
private decimal _prevClose;
private decimal _prevMa;
private int _cooldown;
/// <summary>
/// Price MA Period.
/// </summary>
public int PriceMaPeriod
{
get => _priceMaPeriod.Value;
set => _priceMaPeriod.Value = value;
}
/// <summary>
/// Fast Volume MA Period.
/// </summary>
public int FastVolPeriod
{
get => _fastVolPeriod.Value;
set => _fastVolPeriod.Value = value;
}
/// <summary>
/// Slow Volume MA Period.
/// </summary>
public int SlowVolPeriod
{
get => _slowVolPeriod.Value;
set => _slowVolPeriod.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize <see cref="VolumeMAXrossStrategy"/>.
/// </summary>
public VolumeMAXrossStrategy()
{
_priceMaPeriod = Param(nameof(PriceMaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Price MA Period", "Period for price SMA", "Indicators");
_fastVolPeriod = Param(nameof(FastVolPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast Vol Period", "Period for fast volume MA", "Indicators");
_slowVolPeriod = Param(nameof(SlowVolPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow Vol Period", "Period for slow volume MA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastVolMA = null;
_slowVolMA = null;
_prevClose = default;
_prevMa = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_prevMa = 0;
_cooldown = 0;
_fastVolMA = new SimpleMovingAverage { Length = FastVolPeriod };
_slowVolMA = new SimpleMovingAverage { Length = SlowVolPeriod };
var sma = new SimpleMovingAverage { Length = PriceMaPeriod };
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 smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Process volume through manual MAs
var fastVol = _fastVolMA.Process(new DecimalIndicatorValue(_fastVolMA, candle.TotalVolume, candle.ServerTime)).ToDecimal();
var slowVol = _slowVolMA.Process(new DecimalIndicatorValue(_slowVolMA, candle.TotalVolume, candle.ServerTime)).ToDecimal();
if (_prevClose == 0)
{
_prevClose = candle.ClosePrice;
_prevMa = smaValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevClose = candle.ClosePrice;
_prevMa = smaValue;
return;
}
var crossUp = _prevClose <= _prevMa && candle.ClosePrice > smaValue;
var crossDown = _prevClose >= _prevMa && candle.ClosePrice < smaValue;
var volumeExpanding = _slowVolMA.IsFormed && fastVol > slowVol;
if (Position == 0 && crossUp)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && crossDown)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && (crossDown || (volumeExpanding && candle.ClosePrice < smaValue)))
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && (crossUp || (volumeExpanding && candle.ClosePrice > smaValue)))
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevClose = candle.ClosePrice;
_prevMa = smaValue;
}
}
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
from indicator_extensions import *
class volume_ma_cross_strategy(Strategy):
"""
Volume MA Cross strategy.
Uses fast/slow volume MA crossover with price MA for direction.
Long: Price crosses above SMA.
Short: Price crosses below SMA.
"""
def __init__(self):
super(volume_ma_cross_strategy, self).__init__()
self._price_ma_period = self.Param("PriceMaPeriod", 20).SetDisplay("Price MA Period", "Period for price SMA", "Indicators")
self._fast_vol_period = self.Param("FastVolPeriod", 10).SetDisplay("Fast Vol Period", "Period for fast volume MA", "Indicators")
self._slow_vol_period = self.Param("SlowVolPeriod", 30).SetDisplay("Slow Vol Period", "Period for slow volume MA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._fast_vol_ma = None
self._slow_vol_ma = None
self._prev_close = 0.0
self._prev_ma = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_ma_cross_strategy, self).OnReseted()
self._fast_vol_ma = None
self._slow_vol_ma = None
self._prev_close = 0.0
self._prev_ma = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(volume_ma_cross_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ma = 0.0
self._cooldown = 0
self._fast_vol_ma = SimpleMovingAverage()
self._fast_vol_ma.Length = self._fast_vol_period.Value
self._slow_vol_ma = SimpleMovingAverage()
self._slow_vol_ma.Length = self._slow_vol_period.Value
sma = SimpleMovingAverage()
sma.Length = self._price_ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
# Process volume through manual MAs
fast_result = process_float(self._fast_vol_ma, candle.TotalVolume, candle.ServerTime, True)
slow_result = process_float(self._slow_vol_ma, candle.TotalVolume, candle.ServerTime, True)
fast_vol = float(fast_result)
slow_vol = float(slow_result)
close = float(candle.ClosePrice)
sv = float(sma_val)
if self._prev_close == 0:
self._prev_close = close
self._prev_ma = sv
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_close = close
self._prev_ma = sv
return
cd = self._cooldown_bars.Value
cross_up = self._prev_close <= self._prev_ma and close > sv
cross_down = self._prev_close >= self._prev_ma and close < sv
volume_expanding = self._slow_vol_ma.IsFormed and fast_vol > slow_vol
if self.Position == 0 and cross_up:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and cross_down:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and (cross_down or (volume_expanding and close < sv)):
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and (cross_up or (volume_expanding and close > sv)):
self.BuyMarket()
self._cooldown = cd
self._prev_close = close
self._prev_ma = sv
def CreateClone(self):
return volume_ma_cross_strategy()