DC2008 布林带突破策略
将 Sergey Pavlov (DC2008) 的 MetaTrader 布林带突破专家顾问移植到 StockSharp 高级策略 API。策略在选定的周期上等待完整蜡烛,依据指定的价格源计算布林带,并且只有在当前持仓不亏损时才开仓或反向。
概览
- 在设定的
CandleType上计算布林带(支持收盘价、开盘价、最高价、最低价、中位价、典型价、加权价或 OHLC 平均价)。 - 做多 条件:蜡烛最低价跌破下轨,同时最高价仍低于中轨,表示价格被压制在均线下方后可能反弹。
- 做空 条件:蜡烛最高价突破上轨,同时最低价仍高于中轨,表示价格被推升到均线上方后可能回落。
- 原始 MQL 策略在每个 tick 上运行;移植版本在蜡烛完成后判断,保证指标输出稳定且避免未完成数据。
- 仅当当前仓位的浮动盈亏不为负时才允许开新仓或反向,保持与原始过滤规则一致。
交易流程
指标处理
- 订阅所选的蜡烛类型(默认 1 小时)。
- 将
AppliedPrice指定的价格输入布林带指标 (Length = BandsPeriod,Width = BandsDeviation)。 - 仅在指标给出有效的上轨、中轨、下轨后才继续判断信号。
信号判定
- 买入:
Low < LowerBand且High < MiddleBand。 - 卖出:
High > UpperBand且Low > MiddleBand。
仓位管理
- 无持仓时,在信号出现后按
Volume下达市价单建仓。 - 已有持仓时:
- 计算蜡烛收盘价下的浮动盈亏
Position * (Close - PositionPrice)。 - 如果浮亏为负,则跳过本根蜡烛的所有操作。
- 如果浮盈不为负且信号与当前方向相反,则按
Volume + |Position|发送市价单,实现平仓并反向建仓。 - 与当前方向相同的信号不会加仓。
- 计算蜡烛收盘价下的浮动盈亏
- 策略不设置固定止损或止盈,退出完全依赖满足条件的反向信号。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
BandsPeriod |
80 | 布林带均线与标准差的计算周期,可优化,必须大于 0。 |
BandsDeviation |
3.0 | 布林带标准差系数,控制带宽,可优化。 |
AppliedPrice |
Close | 指定指标使用的价格:Close、Open、High、Low、Median、Typical、Weighted 或 Average (OHLC/4)。对应 MetaTrader 的 ENUM_APPLIED_PRICE。 |
CandleType |
1 小时 | 用于分析和下单的蜡烛类型,可替换为 StockSharp 支持的其他数据类型。 |
Volume(继承) |
取决于经纪商 | 新开仓的数量,反向时会自动加上当前持仓的绝对值。 |
与原始 MQL EA 的差异
- Tick 级触发 → 仅在蜡烛收盘后触发。
- 原代码中的布林带移位参数固定为 0,因此在此实现中保持隐式。
- MQL 中直接读取持仓浮盈;此版本通过
PositionPrice与收盘价估算,足以判断正负号。 - 去除了订单文本注释,仅保留核心交易逻辑。
实现说明
- 使用 StockSharp 的高层 API:
SubscribeCandles().Bind(...)、BuyMarket、SellMarket等。 - 如果 UI 中可创建图表区域,将自动绘制蜡烛、布林带以及策略成交。
- 每次启动都会重新创建指标,因此修改参数后在下次运行即可生效。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger breakout strategy inspired by DC2008 implementation.
/// </summary>
public class BollingerBreakoutDc2008Strategy : Strategy
{
public enum AppliedPriceTypes
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted,
Average
}
private readonly StrategyParam<int> _bandsPeriod;
private readonly StrategyParam<decimal> _bandsDeviation;
private readonly StrategyParam<AppliedPriceTypes> _appliedPrice;
private readonly StrategyParam<DataType> _candleType;
private BollingerBands _bollinger;
private decimal _entryPrice;
public int BandsPeriod
{
get => _bandsPeriod.Value;
set => _bandsPeriod.Value = value;
}
public decimal BandsDeviation
{
get => _bandsDeviation.Value;
set => _bandsDeviation.Value = value;
}
public AppliedPriceTypes AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public BollingerBreakoutDc2008Strategy()
{
_bandsPeriod = Param(nameof(BandsPeriod), 80)
.SetDisplay("Bands Period", "Number of candles for Bollinger Bands", "Indicators")
.SetGreaterThanZero()
.SetOptimize(10, 200, 10);
_bandsDeviation = Param(nameof(BandsDeviation), 3m)
.SetDisplay("Deviation", "Standard deviation multiplier", "Indicators")
.SetGreaterThanZero()
.SetOptimize(1m, 5m, 0.5m);
_appliedPrice = Param(nameof(AppliedPrice), AppliedPriceTypes.Close)
.SetDisplay("Applied Price", "Candle price source for Bollinger Bands", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to analyze", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bollinger = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create Bollinger Bands indicator with the configured parameters.
_bollinger = new BollingerBands
{
Length = BandsPeriod,
Width = BandsDeviation
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || _bollinger is null)
return;
// Calculate Bollinger Bands for the selected price source.
var indicatorValue = _bollinger.Process(new DecimalIndicatorValue(_bollinger, GetAppliedPrice(candle), candle.OpenTime) { IsFinal = true });
if (!indicatorValue.IsFinal)
return;
if (indicatorValue is not BollingerBandsValue bands)
return;
if (bands.UpBand is not decimal upper || bands.LowBand is not decimal lower || bands.MovingAverage is not decimal middle)
return;
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
// Determine breakout conditions based on Bollinger structure.
var buySignal = low < lower && high < middle;
var sellSignal = high > upper && low > middle;
if (!buySignal && !sellSignal)
return;
// Compute unrealized profit to mimic original position filter.
var unrealizedPnL = Position == 0 ? 0m : Position * (close - _entryPrice);
if (buySignal)
{
if (Position == 0)
{
// No position open, start a new long.
BuyMarket();
_entryPrice = close;
}
else
{
if (unrealizedPnL < 0m)
return;
if (Position < 0)
{
// Reverse from short to long while preserving target volume.
BuyMarket();
_entryPrice = close;
}
}
return;
}
if (sellSignal)
{
if (Position == 0)
{
// No position open, start a new short.
SellMarket();
_entryPrice = close;
}
else
{
if (unrealizedPnL < 0m)
return;
if (Position > 0)
{
// Reverse from long to short while preserving target volume.
SellMarket();
_entryPrice = close;
}
}
}
}
private decimal GetAppliedPrice(ICandleMessage candle)
{
return AppliedPrice switch
{
AppliedPriceTypes.Close => candle.ClosePrice,
AppliedPriceTypes.Open => candle.OpenPrice,
AppliedPriceTypes.High => candle.HighPrice,
AppliedPriceTypes.Low => candle.LowPrice,
AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + (2m * candle.ClosePrice)) / 4m,
AppliedPriceTypes.Average => (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m,
_ => candle.ClosePrice
};
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
PRICE_CLOSE = 0
PRICE_OPEN = 1
PRICE_HIGH = 2
PRICE_LOW = 3
PRICE_MEDIAN = 4
PRICE_TYPICAL = 5
PRICE_WEIGHTED = 6
PRICE_AVERAGE = 7
class bollinger_breakout_dc2008_strategy(Strategy):
def __init__(self):
super(bollinger_breakout_dc2008_strategy, self).__init__()
self._bands_period = self.Param("BandsPeriod", 80)
self._bands_deviation = self.Param("BandsDeviation", 3.0)
self._applied_price = self.Param("AppliedPrice", PRICE_CLOSE)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._entry_price = 0.0
@property
def BandsPeriod(self):
return self._bands_period.Value
@BandsPeriod.setter
def BandsPeriod(self, value):
self._bands_period.Value = value
@property
def BandsDeviation(self):
return self._bands_deviation.Value
@BandsDeviation.setter
def BandsDeviation(self, value):
self._bands_deviation.Value = value
@property
def AppliedPrice(self):
return self._applied_price.Value
@AppliedPrice.setter
def AppliedPrice(self, value):
self._applied_price.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _get_applied_price(self, candle):
ap = int(self.AppliedPrice)
if ap == PRICE_OPEN:
return candle.OpenPrice
elif ap == PRICE_HIGH:
return candle.HighPrice
elif ap == PRICE_LOW:
return candle.LowPrice
elif ap == PRICE_MEDIAN:
return (candle.HighPrice + candle.LowPrice) / 2
elif ap == PRICE_TYPICAL:
return (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3
elif ap == PRICE_WEIGHTED:
return (candle.HighPrice + candle.LowPrice + 2 * candle.ClosePrice) / 4
elif ap == PRICE_AVERAGE:
return (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4
else:
return candle.ClosePrice
def OnStarted2(self, time):
super(bollinger_breakout_dc2008_strategy, self).OnStarted2(time)
self._bollinger = BollingerBands()
self._bollinger.Length = self.BandsPeriod
self._bollinger.Width = self.BandsDeviation
self._entry_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price_value = self._get_applied_price(candle)
indicator_value = process_float(self._bollinger, price_value, candle.OpenTime, True)
if not indicator_value.IsFinal:
return
up_band = indicator_value.UpBand
low_band = indicator_value.LowBand
moving_average = indicator_value.MovingAverage
if up_band is None or low_band is None or moving_average is None:
return
upper = float(up_band)
lower = float(low_band)
middle = float(moving_average)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
buy_signal = low < lower and high < middle
sell_signal = high > upper and low > middle
if not buy_signal and not sell_signal:
return
unrealized_pnl = 0.0
if self.Position != 0:
unrealized_pnl = self.Position * (close - self._entry_price)
if buy_signal:
if self.Position == 0:
self.BuyMarket()
self._entry_price = close
else:
if unrealized_pnl < 0.0:
return
if self.Position < 0:
self.BuyMarket()
self._entry_price = close
return
if sell_signal:
if self.Position == 0:
self.SellMarket()
self._entry_price = close
else:
if unrealized_pnl < 0.0:
return
if self.Position > 0:
self.SellMarket()
self._entry_price = close
def OnReseted(self):
super(bollinger_breakout_dc2008_strategy, self).OnReseted()
self._entry_price = 0.0
def CreateClone(self):
return bollinger_breakout_dc2008_strategy()