BnB 策略
该策略来源于 MetaTrader 5 EA “Exp_BnB”。它使用自定义的 BnB(多空力量)指标,在每根 K 线内部评估多头和空头压力,并用指数移动平均进行平滑。
工作原理
- 对每根已完成的 K 线计算 bulls 和 bears 值。
- 两个序列都使用 EMA 平滑。
- 当 bulls 线向上穿越 bears 线时:
- 平掉所有空头仓位;
- 开立多头仓位。
- 当 bears 线向上穿越 bulls 线时:
- 平掉所有多头仓位;
- 开立空头仓位。
- 止损和止盈以绝对价格点设置。
参数
Candle Type– 计算所用的 K 线周期;EMA Length– 平滑周期;Stop Loss– 止损距离;Take Profit– 止盈距离;Allow Long Entry– 允许开多;Allow Short Entry– 允许开空;Allow Long Exit– 允许平多;Allow Short Exit– 允许平空。
说明
原始指标支持多种平滑方法,本策略使用标准指数移动平均进行近似。
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()