Volume Profile Strategy
This simplified volume profile strategy tracks session high, low and the point of control defined by the price of the candle with the highest volume. The strategy buys when price is above the point of control and sells when it is below. Positions are closed when price returns to the session mid level.
Parameters
- Candle Type – timeframe of input candles.
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>
/// Simplified volume profile strategy.
/// Tracks session high, low, mid and point of control based on maximum candle volume.
/// Buys when price is above POC and sells when below.
/// </summary>
public class VolumeProfileMakit0Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentSession;
private decimal _sessionHigh;
private decimal _sessionLow;
private decimal _sessionMid;
private decimal _pocPrice;
private decimal _maxVolume;
private bool _sessionTradeDone;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="VolumeProfileMakit0Strategy"/>.
/// </summary>
public VolumeProfileMakit0Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentSession = default;
_sessionHigh = 0;
_sessionLow = 0;
_sessionMid = 0;
_pocPrice = 0;
_maxVolume = 0;
_sessionTradeDone = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var sessionStart = GetSessionStart(candle.OpenTime.Date);
// start new session on session boundary
if (_currentSession != sessionStart)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_currentSession = sessionStart;
_sessionHigh = candle.HighPrice;
_sessionLow = candle.LowPrice;
_sessionMid = candle.ClosePrice;
_pocPrice = candle.ClosePrice;
_maxVolume = candle.TotalVolume;
_sessionTradeDone = false;
return;
}
_sessionHigh = Math.Max(_sessionHigh, candle.HighPrice);
_sessionLow = Math.Min(_sessionLow, candle.LowPrice);
_sessionMid = (_sessionHigh + _sessionLow) / 2m;
if (candle.TotalVolume > _maxVolume)
{
_maxVolume = candle.TotalVolume;
_pocPrice = candle.ClosePrice;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bullishProfile = candle.ClosePrice > _pocPrice && candle.ClosePrice > _sessionMid;
var bearishProfile = candle.ClosePrice < _pocPrice && candle.ClosePrice < _sessionMid;
if (!_sessionTradeDone && Position == 0 && bullishProfile)
{
BuyMarket(Volume);
_sessionTradeDone = true;
}
else if (!_sessionTradeDone && Position == 0 && bearishProfile)
{
SellMarket(Volume);
_sessionTradeDone = true;
}
if (Position > 0 && candle.ClosePrice < _pocPrice && candle.ClosePrice < _sessionMid)
SellMarket(Position);
else if (Position < 0 && candle.ClosePrice > _pocPrice && candle.ClosePrice > _sessionMid)
BuyMarket(Math.Abs(Position));
}
private static DateTime GetSessionStart(DateTime date)
{
return new DateTime(date.Year, date.Month, 1);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DateTime
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class volume_profile_makit0_strategy(Strategy):
def __init__(self):
super(volume_profile_makit0_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._current_session = None
self._session_high = 0.0
self._session_low = 0.0
self._session_mid = 0.0
self._poc_price = 0.0
self._max_volume = 0.0
self._session_trade_done = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_profile_makit0_strategy, self).OnReseted()
self._current_session = None
self._session_high = 0.0
self._session_low = 0.0
self._session_mid = 0.0
self._poc_price = 0.0
self._max_volume = 0.0
self._session_trade_done = False
def OnStarted2(self, time):
super(volume_profile_makit0_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
self.StartProtection(Unit(2, UnitTypes.Percent), Unit(1, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
@staticmethod
def _get_session_start(date):
return DateTime(date.Year, date.Month, 1)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
session_start = self._get_session_start(candle.OpenTime.Date)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
vol = float(candle.TotalVolume)
# start new session on session boundary
if self._current_session is None or self._current_session != session_start:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._current_session = session_start
self._session_high = high
self._session_low = low
self._session_mid = close
self._poc_price = close
self._max_volume = vol
self._session_trade_done = False
return
self._session_high = max(self._session_high, high)
self._session_low = min(self._session_low, low)
self._session_mid = (self._session_high + self._session_low) / 2.0
if vol > self._max_volume:
self._max_volume = vol
self._poc_price = close
bullish_profile = close > self._poc_price and close > self._session_mid
bearish_profile = close < self._poc_price and close < self._session_mid
if not self._session_trade_done and self.Position == 0 and bullish_profile:
self.BuyMarket()
self._session_trade_done = True
elif not self._session_trade_done and self.Position == 0 and bearish_profile:
self.SellMarket()
self._session_trade_done = True
if self.Position > 0 and close < self._poc_price and close < self._session_mid:
self.SellMarket()
elif self.Position < 0 and close > self._poc_price and close > self._session_mid:
self.BuyMarket()
def CreateClone(self):
return volume_profile_makit0_strategy()