MA Volume Strategy
MA Volume combines a moving average trend filter with volume surges to time entries. Rising volume alongside price above the average signals strong accumulation; falling volume below the average indicates distribution.
Testing indicates an average annual return of about 136%. It performs best in the stocks market.
The strategy trades in the direction of the moving average when volume expands, exiting once volume dries up or the average reverses.
A percent stop protects against sudden shifts in trend.
Details
- Entry Criteria: indicator signal
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLoss= 2%
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Moving Average, Volume
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy that combines moving average and volume indicators.
/// Buys on MA crossover with volume confirmation, sells on reverse crossover.
/// </summary>
public class MaVolumeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _volumeThreshold;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevClose;
private decimal _prevSma;
private bool _hasPrev;
private decimal _prevVolume;
private int _cooldown;
/// <summary>
/// Data type for candles.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period for moving average calculation.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Volume threshold multiplier for volume confirmation.
/// </summary>
public decimal VolumeThreshold
{
get => _volumeThreshold.Value;
set => _volumeThreshold.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MaVolumeStrategy"/>.
/// </summary>
public MaVolumeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetDisplay("MA Period", "Period for moving average calculation", "MA Settings");
_volumeThreshold = Param(nameof(VolumeThreshold), 1.2m)
.SetDisplay("Volume Threshold", "Volume threshold multiplier", "Volume Settings");
_cooldownBars = Param(nameof(CooldownBars), 150)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevSma = 0;
_hasPrev = false;
_prevVolume = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var volumeSma = new SimpleMovingAverage { Length = 20 };
var priceSma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
// Use volumeSma to track volume average via separate bind
subscription.Bind(priceSma, OnProcess);
subscription
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, priceSma);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var vol = candle.TotalVolume;
if (_cooldown > 0)
{
_cooldown--;
_prevClose = close;
_prevSma = smaValue;
_prevVolume = vol;
_hasPrev = true;
return;
}
// Volume confirmation: current volume is above threshold * previous volume
var volumeOk = _prevVolume > 0 && vol > _prevVolume * VolumeThreshold;
if (_hasPrev)
{
// Price crosses above MA with volume - buy
if (_prevClose <= _prevSma && close > smaValue && volumeOk && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Price crosses below MA with volume - sell
else if (_prevClose >= _prevSma && close < smaValue && volumeOk && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long on MA cross down
else if (_prevClose >= _prevSma && close < smaValue && Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short on MA cross up
else if (_prevClose <= _prevSma && close > smaValue && Position < 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
_prevClose = close;
_prevSma = smaValue;
_prevVolume = vol;
_hasPrev = true;
}
}
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 ma_volume_strategy(Strategy):
"""
MA Volume strategy.
Buys on MA crossover with volume confirmation, sells on reverse crossover.
"""
def __init__(self):
super(ma_volume_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Period for moving average calculation", "MA Settings")
self._volume_threshold = self.Param("VolumeThreshold", 1.2).SetDisplay("Volume Threshold", "Volume threshold multiplier", "Volume Settings")
self._cooldown_bars = self.Param("CooldownBars", 150).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
self._prev_volume = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_volume_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
self._prev_volume = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(ma_volume_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_sma = 0.0
self._has_prev = False
self._prev_volume = 0.0
self._cooldown = 0
price_sma = SimpleMovingAverage()
price_sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(price_sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, price_sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sma = float(sma_val)
vol = float(candle.TotalVolume)
cd = self._cooldown_bars.Value
threshold = float(self._volume_threshold.Value)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_close = close
self._prev_sma = sma
self._prev_volume = vol
self._has_prev = True
return
# Volume confirmation: current volume is above threshold * previous volume
volume_ok = self._prev_volume > 0 and vol > self._prev_volume * threshold
if self._has_prev:
# Price crosses above MA with volume - buy
if self._prev_close <= self._prev_sma and close > sma and volume_ok and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
# Price crosses below MA with volume - sell
elif self._prev_close >= self._prev_sma and close < sma and volume_ok and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit long on MA cross down
elif self._prev_close >= self._prev_sma and close < sma and self.Position > 0:
self.SellMarket()
self._cooldown = cd
# Exit short on MA cross up
elif self._prev_close <= self._prev_sma and close > sma and self.Position < 0:
self.BuyMarket()
self._cooldown = cd
self._prev_close = close
self._prev_sma = sma
self._prev_volume = vol
self._has_prev = True
def CreateClone(self):
return ma_volume_strategy()