Vwap Macd Strategy
Strategy based on VWAP and MACD. Enters long when price is above VWAP and MACD > Signal. Enters short when price is below VWAP and MACD < Signal. Exits when MACD crosses its signal line in the opposite direction.
Testing indicates an average annual return of about 181%. It performs best in the crypto market.
VWAP guides intraday value, and MACD crossovers reveal momentum shifts. Trades are launched as MACD turns near the VWAP level.
Suitable for short-term momentum traders. ATR stop rules prevent excessive risk.
Details
- Entry Criteria:
- Long:
Close > VWAP && MACD > Signal - Short:
Close < VWAP && MACD < Signal
- Long:
- Long/Short: Both
- Exit Criteria: MACD cross opposite
- Stops: Percent-based using
StopLossPercent - Default Values:
MacdFastPeriod= 12MacdSlowPeriod= 26MacdSignalPeriod= 9StopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: VWAP, MACD
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on VWAP and MACD.
/// Enters long when price is above VWAP and MACD > Signal.
/// Enters short when price is below VWAP and MACD < Signal.
/// Exits when MACD crosses its signal line in the opposite direction.
/// </summary>
public class VwapMacdStrategy : Strategy
{
private readonly StrategyParam<int> _macdFastPeriod;
private readonly StrategyParam<int> _macdSlowPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
private VolumeWeightedMovingAverage _vwap;
private decimal _prevMacd;
private decimal _prevSignal;
private int _cooldown;
/// <summary>
/// MACD fast EMA period.
/// </summary>
public int MacdFastPeriod
{
get => _macdFastPeriod.Value;
set => _macdFastPeriod.Value = value;
}
/// <summary>
/// MACD slow EMA period.
/// </summary>
public int MacdSlowPeriod
{
get => _macdSlowPeriod.Value;
set => _macdSlowPeriod.Value = value;
}
/// <summary>
/// MACD signal line period.
/// </summary>
public int MacdSignalPeriod
{
get => _macdSignalPeriod.Value;
set => _macdSignalPeriod.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop loss percentage value.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="VwapMacdStrategy"/>.
/// </summary>
public VwapMacdStrategy()
{
_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD calculation", "Indicators")
;
_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD calculation", "Indicators")
;
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
.SetDisplay("MACD Signal Period", "Signal line period for MACD calculation", "Indicators")
;
_cooldownBars = Param(nameof(CooldownBars), 30)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between entries", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetDisplay("Stop Loss (%)", "Stop loss percentage from entry price", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe of data for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security, DataType)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd?.Reset();
_vwap?.Reset();
_prevMacd = 0;
_prevSignal = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create MACD indicator
_macd = new()
{
Macd =
{
ShortMa = { Length = MacdFastPeriod },
LongMa = { Length = MacdSlowPeriod },
},
SignalMa = { Length = MacdSignalPeriod }
};
_vwap = new() { Length = MacdSignalPeriod };
// Create subscription
var subscription = SubscribeCandles(CandleType);
// Process candles with MACD
subscription
.BindEx(_macd, ProcessCandle)
.Start();
// Setup chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
// MACD in separate area
var macdArea = CreateChartArea();
if (macdArea != null)
{
DrawIndicator(macdArea, _macd);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Get VWAP value (calculated per day)
var vwap = _vwap.Process(candle).ToDecimal();
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
// Extract MACD and Signal values
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
{
return;
}
// Detect MACD crosses
bool macdCrossedAboveSignal = _prevMacd <= _prevSignal && macd > signal;
bool macdCrossedBelowSignal = _prevMacd >= _prevSignal && macd < signal;
// Check if strategy is ready for trading
if (!IsFormedAndOnlineAndAllowTrading())
{
// Store current values for next candle
_prevMacd = macd;
_prevSignal = signal;
return;
}
// Trading logic
if (_cooldown > 0)
_cooldown--;
if (_cooldown == 0 && candle.ClosePrice > vwap * 1.001m && macdCrossedAboveSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (_cooldown == 0 && candle.ClosePrice < vwap * 0.999m && macdCrossedBelowSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldown = CooldownBars;
}
// Exit logic based on MACD crosses
if (Position > 0 && macdCrossedBelowSignal)
{
ClosePosition();
_cooldown = CooldownBars;
}
else if (Position < 0 && macdCrossedAboveSignal)
{
ClosePosition();
_cooldown = CooldownBars;
}
// Store current values for next candle
_prevMacd = macd;
_prevSignal = signal;
}
}
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 MovingAverageConvergenceDivergenceSignal, VolumeWeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class vwap_macd_strategy(Strategy):
"""
Strategy based on VWAP and MACD.
Enters long when price is above VWAP and MACD crosses above Signal.
Enters short when price is below VWAP and MACD crosses below Signal.
Exits when MACD crosses its signal line in the opposite direction.
"""
def __init__(self):
super(vwap_macd_strategy, self).__init__()
self._macd_fast_period = self.Param("MacdFastPeriod", 12) \
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD calculation", "Indicators")
self._macd_slow_period = self.Param("MacdSlowPeriod", 26) \
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD calculation", "Indicators")
self._macd_signal_period = self.Param("MacdSignalPeriod", 9) \
.SetDisplay("MACD Signal Period", "Signal line period for MACD calculation", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 30) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss (%)", "Stop loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Timeframe of data for strategy", "General")
self._vwap = None
self._prev_macd = 0.0
self._prev_signal = 0.0
self._cooldown = 0
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(vwap_macd_strategy, self).OnReseted()
self._vwap = None
self._prev_macd = 0.0
self._prev_signal = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(vwap_macd_strategy, self).OnStarted2(time)
self._prev_macd = 0.0
self._prev_signal = 0.0
self._cooldown = 0
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macd_fast_period.Value
macd.Macd.LongMa.Length = self._macd_slow_period.Value
macd.SignalMa.Length = self._macd_signal_period.Value
self._vwap = VolumeWeightedMovingAverage()
self._vwap.Length = self._macd_signal_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(macd, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
macd_area = self.CreateChartArea()
if macd_area is not None:
self.DrawIndicator(macd_area, macd)
def ProcessCandle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
vwap = float(process_candle(self._vwap, candle))
if macd_value.Macd is None or macd_value.Signal is None:
return
macd = float(macd_value.Macd)
signal = float(macd_value.Signal)
macd_crossed_above = self._prev_macd <= self._prev_signal and macd > signal
macd_crossed_below = self._prev_macd >= self._prev_signal and macd < signal
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_macd = macd
self._prev_signal = signal
return
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldown_bars.Value)
if self._cooldown == 0 and float(candle.ClosePrice) > vwap * 1.001 and macd_crossed_above and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
elif self._cooldown == 0 and float(candle.ClosePrice) < vwap * 0.999 and macd_crossed_below and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
if self.Position > 0 and macd_crossed_below:
self.ClosePosition()
self._cooldown = cooldown_val
elif self.Position < 0 and macd_crossed_above:
self.ClosePosition()
self._cooldown = cooldown_val
self._prev_macd = macd
self._prev_signal = signal
def CreateClone(self):
return vwap_macd_strategy()