Estrategia de Cruce Bulls vs Bears
Descripción general
Esta estrategia implementa un sistema de cruce basado en el indicador Bulls vs Bears (BvsB). El indicador mide la distancia entre los precios máximo y mínimo de una vela y una media móvil. Cuando la distancia alcista cae por debajo de la distancia bajista, indica una presión al alza que se desvanece, y la estrategia abre una posición larga. Por el contrario, cuando la distancia alcista sube por encima de la distancia bajista, se abre una posición corta. Las posiciones existentes se cierran ante la señal opuesta o cuando se alcanzan los objetivos de beneficio o pérdida.
El tipo y período de la media móvil son configurables, lo que permite que la estrategia se adapte a diferentes mercados y marcos temporales. La gestión de riesgo se controla mediante niveles fijos de stop-loss y take-profit expresados en pasos de precio.
Parámetros
| Nombre | Descripción |
|---|---|
MaType |
Método de cálculo de la media móvil (SMA, EMA, SMMA, WMA). |
MaLength |
Período de la media móvil. |
StopLoss |
Distancia de stop-loss en pasos de precio. |
TakeProfit |
Distancia de take-profit en pasos de precio. |
OpenLong |
Permitir apertura de posiciones largas en cruce alcista. |
OpenShort |
Permitir apertura de posiciones cortas en cruce bajista. |
CloseLong |
Permitir cierre de posiciones largas en cruce bajista. |
CloseShort |
Permitir cierre de posiciones cortas en cruce alcista. |
CandleType |
Marco temporal de las velas procesadas. |
Cómo funciona
- Suscribirse a la serie de velas especificada y calcular una media móvil.
- Para cada vela finalizada, calcular las distancias alcista y bajista:
- Bull =
(HighPrice - MA) / PriceStep - Bear =
(MA - LowPrice) / PriceStep
- Bull =
- Detectar cruces entre los valores Bull y Bear.
- Abrir o cerrar posiciones según la dirección del cruce y las opciones habilitadas.
- Gestionar el riesgo usando los niveles de stop-loss y take-profit configurados.
Este enfoque simple pero flexible puede aplicarse a muchos instrumentos para medir el equilibrio entre las fuerzas alcistas y bajistas.
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()