BnB Strategy
This strategy is a port of the MetaTrader 5 Expert Advisor "Exp_BnB". It operates on the custom BnB (Bulls and Bears) indicator that measures bullish and bearish pressure inside each candle and smooths them with an exponential moving average.
How it works
- For each finished candle the strategy calculates bulls and bears values.
- Both series are smoothed with EMA.
- When the bulls line crosses above the bears line:
- Any short position is closed.
- A long position is opened.
- When the bears line crosses above the bulls line:
- Any long position is closed.
- A short position is opened.
- Stop loss and take profit levels are managed in absolute price points.
Parameters
Candle Type– time frame of the candles used for calculations.EMA Length– smoothing period for bulls and bears.Stop Loss– distance to the protective stop in price points.Take Profit– distance to the profit target in price points.Allow Long Entry– enable long position opening.Allow Short Entry– enable short position opening.Allow Long Exit– enable long position closing.Allow Short Exit– enable short position closing.
Notes
The original indicator supports multiple smoothing methods. In this port the universal filter is approximated with a standard exponential moving average.
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>
/// Strategy based on bull/bear power comparison using EMA smoothing.
/// </summary>
public class BnBStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _minNetPower;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevBull;
private decimal _prevBear;
private bool _initialized;
private decimal _bullEma;
private decimal _bearEma;
private decimal _k;
private int _count;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Length { get => _length.Value; set => _length.Value = value; }
public decimal MinNetPower { get => _minNetPower.Value; set => _minNetPower.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public BnBStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candles used for calculations", "General");
_length = Param(nameof(Length), 14)
.SetDisplay("EMA Length", "Length of smoothing for bulls and bears", "Parameters");
_minNetPower = Param(nameof(MinNetPower), 20m)
.SetDisplay("Minimum Net Power", "Minimum absolute net bull/bear power for entries", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevBull = 0m;
_prevBear = 0m;
_initialized = false;
_bullEma = 0m;
_bearEma = 0m;
_k = 0m;
_count = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_k = 2m / (Length + 1m);
_count = 0;
var sma = new SimpleMovingAverage { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var bullPower = candle.HighPrice - smaValue;
var bearPower = candle.LowPrice - smaValue;
_count++;
if (_count == 1)
{
_bullEma = bullPower;
_bearEma = bearPower;
}
else
{
_bullEma = bullPower * _k + _bullEma * (1m - _k);
_bearEma = bearPower * _k + _bearEma * (1m - _k);
}
if (_count < Length)
return;
if (!_initialized)
{
_prevBull = _bullEma;
_prevBear = _bearEma;
_initialized = true;
return;
}
var netPower = _bullEma + _bearEma;
var prevNet = _prevBull + _prevBear;
var crossUp = prevNet <= 0m && netPower > 0m && Math.Abs(netPower) >= MinNetPower;
var crossDown = prevNet >= 0m && netPower < 0m && Math.Abs(netPower) >= MinNetPower;
if (_cooldownRemaining == 0)
{
if (crossUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (crossDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevBull = _bullEma;
_prevBear = _bearEma;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bnb_strategy(Strategy):
def __init__(self):
super(bnb_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candles used for calculations", "General")
self._length = self.Param("Length", 14) \
.SetDisplay("EMA Length", "Length of smoothing for bulls and bears", "Parameters")
self._min_net_power = self.Param("MinNetPower", 20.0) \
.SetDisplay("Minimum Net Power", "Minimum absolute net bull/bear power for entries", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._prev_bull = 0.0
self._prev_bear = 0.0
self._initialized = False
self._bull_ema = 0.0
self._bear_ema = 0.0
self._k = 0.0
self._count = 0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
@property
def length(self):
return self._length.Value
@property
def min_net_power(self):
return self._min_net_power.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(bnb_strategy, self).OnReseted()
self._prev_bull = 0.0
self._prev_bear = 0.0
self._initialized = False
self._bull_ema = 0.0
self._bear_ema = 0.0
self._k = 0.0
self._count = 0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(bnb_strategy, self).OnStarted2(time)
self._k = 2.0 / (self.length + 1.0)
self._count = 0
sma = SimpleMovingAverage()
sma.Length = self.length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
sma_value = float(sma_value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
bull_power = float(candle.HighPrice) - sma_value
bear_power = float(candle.LowPrice) - sma_value
self._count += 1
if self._count == 1:
self._bull_ema = bull_power
self._bear_ema = bear_power
else:
self._bull_ema = bull_power * self._k + self._bull_ema * (1.0 - self._k)
self._bear_ema = bear_power * self._k + self._bear_ema * (1.0 - self._k)
if self._count < self.length:
return
if not self._initialized:
self._prev_bull = self._bull_ema
self._prev_bear = self._bear_ema
self._initialized = True
return
net_power = self._bull_ema + self._bear_ema
prev_net = self._prev_bull + self._prev_bear
min_np = float(self.min_net_power)
cross_up = prev_net <= 0 and net_power > 0 and abs(net_power) >= min_np
cross_down = prev_net >= 0 and net_power < 0 and abs(net_power) >= min_np
if self._cooldown_remaining == 0:
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_bull = self._bull_ema
self._prev_bear = self._bear_ema
def CreateClone(self):
return bnb_strategy()