Bulls vs Bears Crossover 策略
概述
该策略基于 Bulls vs Bears (BvsB) 指标实现。该指标衡量蜡烛高价和低价与移动平均线之间的距离。当多头距离下降到空头距离之下,表明上行动能减弱,策略开多单。相反,当多头距离上升到空头距离之上时,策略开空单。当前持仓在出现反向信号或达到止盈止损时关闭。
移动平均类型和周期可配置,以适应不同市场和时间框架。风险控制通过以价格步长表示的固定止损和止盈水平来实现。
参数
| 名称 | 描述 |
|---|---|
MaType |
移动平均算法类型(SMA、EMA、SMMA、WMA)。 |
MaLength |
移动平均周期。 |
StopLoss |
止损距离(价格步长)。 |
TakeProfit |
止盈距离(价格步长)。 |
OpenLong |
在多头交叉时允许开多单。 |
OpenShort |
在空头交叉时允许开空单。 |
CloseLong |
在空头交叉时允许平多单。 |
CloseShort |
在多头交叉时允许平空单。 |
CandleType |
处理的蜡烛时间框架。 |
工作原理
- 订阅指定的蜡烛序列并计算移动平均线。
- 对于每根完成的蜡烛,计算多头和空头距离:
- Bull =
(HighPrice - MA) / PriceStep - Bear =
(MA - LowPrice) / PriceStep
- Bull =
- 检测 Bull 与 Bear 的交叉。
- 根据交叉方向和启用的选项开仓或平仓。
- 通过设置的止损和止盈水平进行风险管理。
这种简洁而灵活的方法可用于评估买卖双方力量的变化。
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()