This strategy is an adaptation of the original MetaTrader expert from MQL/16214. It uses the Balance of Power (BOP) indicator to detect momentum changes in the market.
Logic
The strategy calculates the Balance of Power for each finished candle:
\(BOP = \frac{Close - Open}{High - Low}\)
Three consecutive BOP values are compared.
When the previous value is lower than the value before it and the current value is higher than the previous one, the BOP turns upward and the strategy enters a long position.
When the previous value is higher than the value before it and the current value is lower than the previous one, the BOP turns downward and the strategy enters a short position.
Position is changed only after a completed candle to avoid false signals.
Parameters
CandleType – timeframe of candles used for calculations. The default is four-hour candles.
Notes
This port focuses on the core behaviour of the original strategy and does not implement the advanced money-management options from the MQL version.
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>
/// Balance of Power histogram strategy that trades confirmed zero-line reversals.
/// </summary>
public class BalanceOfPowerHistogramStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _signalLevel;
private readonly StrategyParam<int> _cooldownCandles;
private int _barsSinceSignal;
private decimal? _prevBop;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum Balance of Power value required for a signal.
/// </summary>
public decimal SignalLevel
{
get => _signalLevel.Value;
set => _signalLevel.Value = value;
}
/// <summary>
/// Minimum number of finished candles between entries.
/// </summary>
public int CooldownCandles
{
get => _cooldownCandles.Value;
set => _cooldownCandles.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="BalanceOfPowerHistogramStrategy"/>.
/// </summary>
public BalanceOfPowerHistogramStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_signalLevel = Param(nameof(SignalLevel), 0.30m)
.SetDisplay("Signal Level", "Minimum BOP value for confirmed reversals", "Signal");
_cooldownCandles = Param(nameof(CooldownCandles), 3)
.SetDisplay("Cooldown Candles", "Minimum finished candles between entries", "Signal");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_barsSinceSignal = CooldownCandles;
_prevBop = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_barsSinceSignal = CooldownCandles;
_prevBop = null;
var bop = new BalanceOfPower();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(bop, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal bop)
{
if (candle.State != CandleStates.Finished)
return;
var prevBop = _prevBop;
_prevBop = bop;
_barsSinceSignal++;
if (!IsFormedAndOnlineAndAllowTrading() || prevBop is null)
return;
if (_barsSinceSignal < CooldownCandles)
return;
var turnedUp = prevBop <= -SignalLevel && bop >= SignalLevel;
var turnedDown = prevBop >= SignalLevel && bop <= -SignalLevel;
if (turnedUp && Position <= 0)
{
BuyMarket();
_barsSinceSignal = 0;
}
else if (turnedDown && Position >= 0)
{
SellMarket();
_barsSinceSignal = 0;
}
}
}
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 BalanceOfPower
from StockSharp.Algo.Strategies import Strategy
class balance_of_power_histogram_strategy(Strategy):
def __init__(self):
super(balance_of_power_histogram_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._signal_level = self.Param("SignalLevel", 0.30) \
.SetDisplay("Signal Level", "Minimum BOP value for confirmed reversals", "Signal")
self._cooldown_candles = self.Param("CooldownCandles", 3) \
.SetDisplay("Cooldown Candles", "Minimum finished candles between entries", "Signal")
self._bars_since_signal = 0
self._prev_bop = None
@property
def candle_type(self):
return self._candle_type.Value
@property
def signal_level(self):
return self._signal_level.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
def OnReseted(self):
super(balance_of_power_histogram_strategy, self).OnReseted()
self._bars_since_signal = int(self.cooldown_candles)
self._prev_bop = None
def OnStarted2(self, time):
super(balance_of_power_histogram_strategy, self).OnStarted2(time)
self._bars_since_signal = int(self.cooldown_candles)
self._prev_bop = None
bop = BalanceOfPower()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(bop, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, bop):
if candle.State != CandleStates.Finished:
return
bop = float(bop)
prev_bop = self._prev_bop
self._prev_bop = bop
self._bars_since_signal += 1
if prev_bop is None:
return
if self._bars_since_signal < int(self.cooldown_candles):
return
sl = float(self.signal_level)
turned_up = prev_bop <= -sl and bop >= sl
turned_down = prev_bop >= sl and bop <= -sl
if turned_up and self.Position <= 0:
self.BuyMarket()
self._bars_since_signal = 0
elif turned_down and self.Position >= 0:
self.SellMarket()
self._bars_since_signal = 0
def CreateClone(self):
return balance_of_power_histogram_strategy()