AI Volume Strategy
AI Volume Strategy hunts for sudden participation bursts. A volume spike occurs when current volume exceeds its EMA by a given multiplier. If the spike aligns with the 50-period price EMA and the candle color, the strategy enters in that direction. Each trade is closed after a fixed number of bars.
Details
- Entry Criteria: Volume > VolumeEMA * VolumeMultiplier and price above/below 50 EMA with matching candle color.
- Long/Short: Both directions.
- Exit Criteria: Position closed after
ExitBarscandles. - Stops: None.
- Default Values:
VolumeEmaLength= 20VolumeMultiplier= 2.0ExitBars= 5CandleType= TimeSpan.FromMinutes(1)
- Filters:
- Category: Volume breakout
- Direction: Both
- Indicators: EMA, Volume EMA
- Stops: No
- Complexity: Basic
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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>
/// AI Volume Strategy - trades volume spikes in trend direction.
/// Uses EMA for trend and volume EMA for spike detection.
/// </summary>
public class AiVolumeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _priceEmaLength;
private readonly StrategyParam<int> _volumeEmaLength;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _exitBars;
private readonly StrategyParam<int> _cooldownBars;
private SimpleMovingAverage _volumeSma;
private int _barsInPosition;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int PriceEmaLength { get => _priceEmaLength.Value; set => _priceEmaLength.Value = value; }
public int VolumeEmaLength { get => _volumeEmaLength.Value; set => _volumeEmaLength.Value = value; }
public decimal VolumeMultiplier { get => _volumeMultiplier.Value; set => _volumeMultiplier.Value = value; }
public int ExitBars { get => _exitBars.Value; set => _exitBars.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public AiVolumeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_priceEmaLength = Param(nameof(PriceEmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Price EMA Length", "Length for price EMA", "Parameters");
_volumeEmaLength = Param(nameof(VolumeEmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume EMA Length", "Length for volume EMA", "Parameters");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.0m)
.SetDisplay("Volume Multiplier", "Multiplier for volume spike detection", "Parameters");
_exitBars = Param(nameof(ExitBars), 20)
.SetGreaterThanZero()
.SetDisplay("Exit Bars", "Exit position after this many bars", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeSma = null;
_barsInPosition = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var priceEma = new ExponentialMovingAverage { Length = PriceEmaLength };
_volumeSma = new SimpleMovingAverage { Length = VolumeEmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(priceEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, priceEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal priceEmaValue)
{
if (candle.State != CandleStates.Finished)
return;
var volumeResult = _volumeSma.Process(new DecimalIndicatorValue(_volumeSma, candle.TotalVolume, candle.ServerTime));
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Time-based exit
if (Position != 0)
{
_barsInPosition++;
if (_barsInPosition >= ExitBars)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
_barsInPosition = 0;
_cooldownRemaining = CooldownBars;
return;
}
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var avgVolume = _volumeSma.IsFormed ? volumeResult.ToDecimal() : 0m;
var volumeSpike = avgVolume > 0 && candle.TotalVolume > avgVolume * VolumeMultiplier;
// If no volume data, use price action only
var useVolumeFilter = avgVolume > 0;
var trendUp = candle.ClosePrice > priceEmaValue;
var trendDown = candle.ClosePrice < priceEmaValue;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
var longOk = trendUp && isBullish && (!useVolumeFilter || volumeSpike);
var shortOk = trendDown && isBearish && (!useVolumeFilter || volumeSpike);
if (longOk && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_barsInPosition = 0;
_cooldownRemaining = CooldownBars;
}
else if (shortOk && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_barsInPosition = 0;
_cooldownRemaining = CooldownBars;
}
}
}
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 CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class ai_volume_strategy(Strategy):
"""
AI Volume Strategy - trades volume spikes in trend direction.
Uses EMA for trend and volume SMA for spike detection.
"""
def __init__(self):
super(ai_volume_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(30)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._price_ema_length = self.Param("PriceEmaLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Price EMA Length", "Length for price EMA", "Parameters")
self._volume_ema_length = self.Param("VolumeEmaLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Volume EMA Length", "Length for volume EMA", "Parameters")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.0) \
.SetDisplay("Volume Multiplier", "Multiplier for volume spike detection", "Parameters")
self._exit_bars = self.Param("ExitBars", 20) \
.SetGreaterThanZero() \
.SetDisplay("Exit Bars", "Exit position after this many bars", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._volume_sma = None
self._bars_in_position = 0
self._cooldown_remaining = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def PriceEmaLength(self):
return self._price_ema_length.Value
@PriceEmaLength.setter
def PriceEmaLength(self, value):
self._price_ema_length.Value = value
@property
def VolumeEmaLength(self):
return self._volume_ema_length.Value
@VolumeEmaLength.setter
def VolumeEmaLength(self, value):
self._volume_ema_length.Value = value
@property
def VolumeMultiplier(self):
return self._volume_multiplier.Value
@VolumeMultiplier.setter
def VolumeMultiplier(self, value):
self._volume_multiplier.Value = value
@property
def ExitBars(self):
return self._exit_bars.Value
@ExitBars.setter
def ExitBars(self, value):
self._exit_bars.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
def OnReseted(self):
super(ai_volume_strategy, self).OnReseted()
self._volume_sma = None
self._bars_in_position = 0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ai_volume_strategy, self).OnStarted2(time)
price_ema = ExponentialMovingAverage()
price_ema.Length = self.PriceEmaLength
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self.VolumeEmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(price_ema, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, price_ema)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, price_ema_value):
if candle.State != CandleStates.Finished:
return
volume_result = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True)
# Time-based exit
if self.Position != 0:
self._bars_in_position += 1
if self._bars_in_position >= self.ExitBars:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
else:
self.BuyMarket(Math.Abs(self.Position))
self._bars_in_position = 0
self._cooldown_remaining = self.CooldownBars
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
avg_volume = float(to_decimal(volume_result)) if self._volume_sma.IsFormed else 0.0
volume_spike = avg_volume > 0 and float(candle.TotalVolume) > avg_volume * float(self.VolumeMultiplier)
use_volume_filter = avg_volume > 0
close = float(candle.ClosePrice)
ema_val = float(price_ema_value)
trend_up = close > ema_val
trend_down = close < ema_val
is_bullish = close > float(candle.OpenPrice)
is_bearish = close < float(candle.OpenPrice)
long_ok = trend_up and is_bullish and (not use_volume_filter or volume_spike)
short_ok = trend_down and is_bearish and (not use_volume_filter or volume_spike)
if long_ok and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._bars_in_position = 0
self._cooldown_remaining = self.CooldownBars
elif short_ok and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._bars_in_position = 0
self._cooldown_remaining = self.CooldownBars
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return ai_volume_strategy()