Bulls vs Bears Crossover Strategy
Overview
This strategy implements a crossover system based on the Bulls vs Bears (BvsB) indicator. The indicator measures the distance between a candle's high and low prices and a moving average. When the bullish distance drops below the bearish distance, it indicates fading upward pressure, and the strategy opens a long position. Conversely, when the bullish distance rises above the bearish distance, a short position is opened. Existing positions are closed on the opposite signal or when profit or loss targets are reached.
The moving average type and length are configurable, allowing the strategy to adapt to different markets and timeframes. Risk management is controlled through fixed stop-loss and take-profit levels expressed in price steps.
Parameters
| Name | Description |
|---|---|
MaType |
Moving average calculation method (SMA, EMA, SMMA, WMA). |
MaLength |
Period of the moving average. |
StopLoss |
Stop-loss distance in price steps. |
TakeProfit |
Take-profit distance in price steps. |
OpenLong |
Allow opening long positions on bullish crossover. |
OpenShort |
Allow opening short positions on bearish crossover. |
CloseLong |
Allow closing long positions on bearish crossover. |
CloseShort |
Allow closing short positions on bullish crossover. |
CandleType |
Timeframe of processed candles. |
How It Works
- Subscribe to the specified candle series and calculate a moving average.
- For each finished candle, compute the bullish and bearish distances:
- Bull =
(HighPrice - MA) / PriceStep - Bear =
(MA - LowPrice) / PriceStep
- Bull =
- Detect crossovers between the Bull and Bear values.
- Open or close positions according to crossover direction and enabled options.
- Manage risk using the configured stop-loss and take-profit levels.
This simple yet flexible approach can be applied to many instruments to gauge the balance between bullish and bearish forces.
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>
/// Bulls vs Bears crossover strategy.
/// Opens long or short positions when the distance from high and low to a moving average crosses.
/// </summary>
public class BullsVsBearsCrossoverStrategy : Strategy
{
/// <summary>
/// Moving average types.
/// </summary>
public enum MovingAverageTypes
{
/// <summary>
/// Simple moving average.
/// </summary>
SMA,
/// <summary>
/// Exponential moving average.
/// </summary>
EMA,
/// <summary>
/// Smoothed moving average.
/// </summary>
SMMA,
/// <summary>
/// Weighted moving average.
/// </summary>
WMA
}
private readonly StrategyParam<MovingAverageTypes> _maType;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<bool> _openLong;
private readonly StrategyParam<bool> _openShort;
private readonly StrategyParam<bool> _closeLong;
private readonly StrategyParam<bool> _closeShort;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minSpreadSteps;
private readonly StrategyParam<int> _cooldownBars;
private IIndicator _ma = null!;
private decimal _prevBull;
private decimal _prevBear;
private decimal _entryPrice;
private int _cooldownRemaining;
public MovingAverageTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public bool OpenLong
{
get => _openLong.Value;
set => _openLong.Value = value;
}
public bool OpenShort
{
get => _openShort.Value;
set => _openShort.Value = value;
}
public bool CloseLong
{
get => _closeLong.Value;
set => _closeLong.Value = value;
}
public bool CloseShort
{
get => _closeShort.Value;
set => _closeShort.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal MinSpreadSteps
{
get => _minSpreadSteps.Value;
set => _minSpreadSteps.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public BullsVsBearsCrossoverStrategy()
{
_maType = Param(nameof(MaType), MovingAverageTypes.SMA)
.SetDisplay("MA Type", "Moving average type", "General");
_maLength = Param(nameof(MaLength), 12)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average period", "General");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetDisplay("Stop Loss", "Loss in price steps", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetDisplay("Take Profit", "Profit in price steps", "Risk");
_openLong = Param(nameof(OpenLong), true)
.SetDisplay("Open Long", "Allow long entries", "General");
_openShort = Param(nameof(OpenShort), true)
.SetDisplay("Open Short", "Allow short entries", "General");
_closeLong = Param(nameof(CloseLong), true)
.SetDisplay("Close Long", "Allow closing long positions", "General");
_closeShort = Param(nameof(CloseShort), true)
.SetDisplay("Close Short", "Allow closing short positions", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe to process", "General");
_minSpreadSteps = Param(nameof(MinSpreadSteps), 60m)
.SetDisplay("Minimum Spread", "Minimum spread between bull and bear power in price steps", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 6)
.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();
_ma = null!;
_prevBull = 0m;
_prevBear = 0m;
_entryPrice = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = CreateMovingAverage(MaType, MaLength);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_ma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
var step = Security.PriceStep ?? 1m;
var bull = (candle.HighPrice - maValue) / step;
var bear = (maValue - candle.LowPrice) / step;
if (candle.State != CandleStates.Finished || !_ma.IsFormed)
{
_prevBull = bull;
_prevBear = bear;
return;
}
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var spread = Math.Abs(bull - bear);
var crossDown = _prevBull > _prevBear && bull <= bear && spread >= MinSpreadSteps;
var crossUp = _prevBull < _prevBear && bull >= bear && spread >= MinSpreadSteps;
if (_cooldownRemaining == 0)
{
if (crossDown)
{
if (CloseShort && Position < 0)
BuyMarket();
if (OpenLong && Position <= 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_cooldownRemaining = CooldownBars;
}
}
else if (crossUp)
{
if (CloseLong && Position > 0)
SellMarket();
if (OpenShort && Position >= 0)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_cooldownRemaining = CooldownBars;
}
}
}
if (Position > 0)
{
var tp = _entryPrice + TakeProfit * step;
var sl = _entryPrice - StopLoss * step;
if (candle.ClosePrice >= tp || candle.ClosePrice <= sl)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (Position < 0)
{
var tp = _entryPrice - TakeProfit * step;
var sl = _entryPrice + StopLoss * step;
if (candle.ClosePrice <= tp || candle.ClosePrice >= sl)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevBull = bull;
_prevBear = bear;
}
private static IIndicator CreateMovingAverage(MovingAverageTypes type, int length)
{
return type switch
{
MovingAverageTypes.SMA => new SimpleMovingAverage { Length = length },
MovingAverageTypes.EMA => new ExponentialMovingAverage { Length = length },
MovingAverageTypes.SMMA => new SmoothedMovingAverage { Length = length },
MovingAverageTypes.WMA => new WeightedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length },
};
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (
SimpleMovingAverage, ExponentialMovingAverage,
SmoothedMovingAverage, WeightedMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
class bulls_vs_bears_crossover_strategy(Strategy):
"""
Bulls vs Bears crossover strategy.
Opens long or short positions when the distance from high and low to a moving average crosses.
"""
def __init__(self):
super(bulls_vs_bears_crossover_strategy, self).__init__()
self._ma_type = self.Param("MaType", 0) \
.SetDisplay("MA Type", "0=SMA,1=EMA,2=SMMA,3=WMA", "General")
self._ma_length = self.Param("MaLength", 12) \
.SetDisplay("MA Length", "Moving average period", "General")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Loss in price steps", "Risk")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Profit in price steps", "Risk")
self._open_long = self.Param("OpenLong", True) \
.SetDisplay("Open Long", "Allow long entries", "General")
self._open_short = self.Param("OpenShort", True) \
.SetDisplay("Open Short", "Allow short entries", "General")
self._close_long = self.Param("CloseLong", True) \
.SetDisplay("Close Long", "Allow closing long positions", "General")
self._close_short = self.Param("CloseShort", True) \
.SetDisplay("Close Short", "Allow closing short positions", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe to process", "General")
self._min_spread_steps = self.Param("MinSpreadSteps", 60.0) \
.SetDisplay("Minimum Spread", "Minimum spread between bull and bear power in price steps", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._ma = None
self._prev_bull = 0.0
self._prev_bear = 0.0
self._entry_price = 0.0
self._cooldown_remaining = 0
@property
def ma_type(self):
return self._ma_type.Value
@ma_type.setter
def ma_type(self, value):
self._ma_type.Value = value
@property
def ma_length(self):
return self._ma_length.Value
@ma_length.setter
def ma_length(self, value):
self._ma_length.Value = value
@property
def stop_loss(self):
return self._stop_loss.Value
@stop_loss.setter
def stop_loss(self, value):
self._stop_loss.Value = value
@property
def take_profit(self):
return self._take_profit.Value
@take_profit.setter
def take_profit(self, value):
self._take_profit.Value = value
@property
def open_long(self):
return self._open_long.Value
@open_long.setter
def open_long(self, value):
self._open_long.Value = value
@property
def open_short(self):
return self._open_short.Value
@open_short.setter
def open_short(self, value):
self._open_short.Value = value
@property
def close_long(self):
return self._close_long.Value
@close_long.setter
def close_long(self, value):
self._close_long.Value = value
@property
def close_short(self):
return self._close_short.Value
@close_short.setter
def close_short(self, value):
self._close_short.Value = value
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def min_spread_steps(self):
return self._min_spread_steps.Value
@min_spread_steps.setter
def min_spread_steps(self, value):
self._min_spread_steps.Value = value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@cooldown_bars.setter
def cooldown_bars(self, value):
self._cooldown_bars.Value = value
def OnReseted(self):
super(bulls_vs_bears_crossover_strategy, self).OnReseted()
self._ma = None
self._prev_bull = 0.0
self._prev_bear = 0.0
self._entry_price = 0.0
self._cooldown_remaining = 0
def _create_ma(self, ma_type, length):
if ma_type == 1:
ma = ExponentialMovingAverage()
elif ma_type == 2:
ma = SmoothedMovingAverage()
elif ma_type == 3:
ma = WeightedMovingAverage()
else:
ma = SimpleMovingAverage()
ma.Length = length
return ma
def OnStarted2(self, time):
super(bulls_vs_bears_crossover_strategy, self).OnStarted2(time)
self._ma = self._create_ma(self.ma_type, self.ma_length)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ma)
self.DrawOwnTrades(area)
self.StartProtection(None, None)
def on_process(self, candle, ma_value):
step = self.Security.PriceStep if self.Security.PriceStep is not None else 1.0
bull = (candle.HighPrice - ma_value) / step
bear = (ma_value - candle.LowPrice) / step
if candle.State != CandleStates.Finished or not self._ma.IsFormed:
self._prev_bull = bull
self._prev_bear = bear
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
spread = abs(bull - bear)
cross_down = self._prev_bull > self._prev_bear and bull <= bear and spread >= self.min_spread_steps
cross_up = self._prev_bull < self._prev_bear and bull >= bear and spread >= self.min_spread_steps
if self._cooldown_remaining == 0:
if cross_down:
if self.close_short and self.Position < 0:
self.BuyMarket()
if self.open_long and self.Position <= 0:
self.BuyMarket()
self._entry_price = candle.ClosePrice
self._cooldown_remaining = self.cooldown_bars
elif cross_up:
if self.close_long and self.Position > 0:
self.SellMarket()
if self.open_short and self.Position >= 0:
self.SellMarket()
self._entry_price = candle.ClosePrice
self._cooldown_remaining = self.cooldown_bars
if self.Position > 0:
tp = self._entry_price + self.take_profit * step
sl = self._entry_price - self.stop_loss * step
if candle.ClosePrice >= tp or candle.ClosePrice <= sl:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
elif self.Position < 0:
tp = self._entry_price - self.take_profit * step
sl = self._entry_price + self.stop_loss * step
if candle.ClosePrice <= tp or candle.ClosePrice >= sl:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_bull = bull
self._prev_bear = bear
def CreateClone(self):
return bulls_vs_bears_crossover_strategy()