Macd Vwap Strategy
Strategy based on MACD and VWAP indicators. Enters long when MACD > Signal and price > VWAP Enters short when MACD < Signal and price < VWAP
Testing indicates an average annual return of about 109%. It performs best in the crypto market.
MACD momentum is gauged relative to the VWAP line. Long trades look for MACD strength below VWAP, whereas shorts take form above it.
Ideal for intraday momentum players using volume-weighted references. ATR-based stops manage risk.
Details
- Entry Criteria:
- Long:
MACD > Signal && Close > VWAP - Short:
MACD < Signal && Close < VWAP
- Long:
- Long/Short: Both
- Exit Criteria: MACD cross opposite
- Stops: Percent-based using
StopLossPercent - Default Values:
MacdFast= 12MacdSlow= 26MacdSignal= 9StopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: MACD, VWAP
- 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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on MACD and VWAP indicators.
/// Enters long when MACD > Signal and price > VWAP
/// Enters short when MACD < Signal and price < VWAP
/// </summary>
public class MacdVwapStrategy : Strategy
{
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private int _cooldown;
private bool _hasPrevDiff;
private decimal _prevDiff;
/// <summary>
/// MACD fast period
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// MACD slow period
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// MACD signal period
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop-loss percentage
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type for strategy calculation
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor
/// </summary>
public MacdVwapStrategy()
{
_macdFast = Param(nameof(MacdFast), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators")
.SetOptimize(8, 16, 2);
_macdSlow = Param(nameof(MacdSlow), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators")
.SetOptimize(20, 30, 2);
_macdSignal = Param(nameof(MacdSignal), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators")
.SetOptimize(7, 12, 1);
_cooldownBars = Param(nameof(CooldownBars), 35)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between new entries", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = 0;
_hasPrevDiff = false;
_prevDiff = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
var vwap = new VolumeWeightedMovingAverage();
// Subscribe to candles and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, vwap, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawIndicator(area, vwap);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue vwapValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Get additional values from MACD (signal line)
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
var macd = macdTyped.Macd ?? 0m;
var signal = macdTyped.Signal ?? 0m;
var vwap = vwapValue.ToDecimal();
// Current price (close of the candle)
var price = candle.ClosePrice;
var diff = macd - signal;
if (!_hasPrevDiff)
{
_hasPrevDiff = true;
_prevDiff = diff;
return;
}
var crossUp = _prevDiff <= 0m && diff > 0m;
var crossDown = _prevDiff >= 0m && diff < 0m;
if (_cooldown > 0)
{
_cooldown--;
_prevDiff = diff;
return;
}
// Trading logic
if (crossUp && price > vwap * 1.001m && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (crossDown && price < vwap * 0.999m && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (crossDown && Position > 0)
{
SellMarket(Position);
_cooldown = CooldownBars;
}
else if (crossUp && Position < 0)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
_prevDiff = diff;
}
}
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
class macd_vwap_strategy(Strategy):
"""
MACD + VWAP: enters long when MACD crosses above signal and price above VWAP.
"""
def __init__(self):
super(macd_vwap_strategy, self).__init__()
self._macd_fast = self.Param("MacdFast", 12).SetDisplay("MACD Fast", "Fast EMA period", "Indicators")
self._macd_slow = self.Param("MacdSlow", 26).SetDisplay("MACD Slow", "Slow EMA period", "Indicators")
self._macd_signal = self.Param("MacdSignal", 9).SetDisplay("MACD Signal", "Signal line period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 35).SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetDisplay("Stop Loss %", "Stop loss pct", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._cooldown = 0
self._has_prev_diff = False
self._prev_diff = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_vwap_strategy, self).OnReseted()
self._cooldown = 0
self._has_prev_diff = False
self._prev_diff = 0.0
def OnStarted2(self, time):
super(macd_vwap_strategy, self).OnStarted2(time)
self._cooldown = 0
self._has_prev_diff = False
self._prev_diff = 0.0
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macd_fast.Value
macd.Macd.LongMa.Length = self._macd_slow.Value
macd.SignalMa.Length = self._macd_signal.Value
vwap = VolumeWeightedMovingAverage()
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, vwap, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawIndicator(area, vwap)
self.DrawOwnTrades(area)
def _process_candle(self, candle, macd_value, vwap_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
macd_line = macd_value.Macd
signal_line = macd_value.Signal
if macd_line is None or signal_line is None:
return
macd_f = float(macd_line)
signal_f = float(signal_line)
vwap_f = float(vwap_value)
price = float(candle.ClosePrice)
diff = macd_f - signal_f
if not self._has_prev_diff:
self._has_prev_diff = True
self._prev_diff = diff
return
cross_up = self._prev_diff <= 0 and diff > 0
cross_down = self._prev_diff >= 0 and diff < 0
if self._cooldown > 0:
self._cooldown -= 1
self._prev_diff = diff
return
if cross_up and price > vwap_f * 1.001 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown = self._cooldown_bars.Value
elif cross_down and price < vwap_f * 0.999 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown = self._cooldown_bars.Value
elif cross_down and self.Position > 0:
self.SellMarket(self.Position)
self._cooldown = self._cooldown_bars.Value
elif cross_up and self.Position < 0:
self.BuyMarket(abs(self.Position))
self._cooldown = self._cooldown_bars.Value
self._prev_diff = diff
def CreateClone(self):
return macd_vwap_strategy()