在 GitHub 上查看
布林带会话反转策略
该策略是 MetaTrader 专家顾问 BollingerBandsEA (ver. 3.0) 的 C# 版本。它在交易时段内捕捉价格突破布林带后的均值回归机会。
交易逻辑
- 订阅主图的日内 K 线(默认 15 分钟)以及用于趋势过滤的日线数据。
- 在日内数据上计算布林带(周期 20,倍数 2.0),并在日线上计算 100 周期简单移动平均线。
- 实时记录当日和前一日的最高价/最低价,并保存上一根柱子的布林带数值。
- 仅在交易时段窗口内开仓:从开盘后的
SessionStartOffsetMinutes 分钟开始,到收盘前 SessionEndOffsetMinutes 分钟结束。
- 当日收益(含浮动盈亏)转为正值后停止交易,模拟 EA 的日盈利保护逻辑。
- 做空条件:上一根 K 线收阴、收盘价在上轨之上、当前收盘价仍在上轨之上、带宽足够大、价格位于日线均线下方,并且价格突破当日或上一日高点。
- 做多条件:上一根 K 线收阳、收盘价在下轨之下、当前收盘价仍在下轨之下、带宽足够大、价格位于日线均线上方,并且价格跌破当日或上一日低点。
- 仓位大小可采用固定手数,或根据止损距离(点数)计算的风险百分比手数。
- 平仓逻辑包含:止损、止盈、可选的中轨反向离场、追踪止损、保本调整,以及在亏损持续超过设定时间后强制离场。
参数说明
| 参数 |
含义 |
CandleType |
用于计算的日内 K 线类型。 |
BollingerLength |
布林带移动平均线周期。 |
BollingerWidth |
布林带宽度倍数。 |
DailyMaLength |
日线趋势过滤 SMA 的周期。 |
StopLossPoints |
止损距离(点)。 |
UseRiskVolume |
是否启用风险百分比手数。 |
RiskPercent |
单笔交易承担的账户风险百分比。 |
FixedVolume |
未启用风险手数时使用的固定手数。 |
SessionStartOffsetMinutes |
开盘后允许开仓的延迟分钟数。 |
SessionEndOffsetMinutes |
收盘前禁止开仓的提前分钟数。 |
CloseOnMiddleBand |
价格触及中轨时是否平仓。 |
EnableTrailing |
是否启用追踪止损。 |
TrailingFactor |
激活追踪止损所需的盈利倍数。 |
EnableBreakEven |
是否启用保本止损。 |
BreakEvenFactor |
将止损移动到入场价所需的盈利倍数。 |
CloseLosingAfterMinutes |
亏损持仓持续多少分钟后强制离场。 |
注意事项
- 策略在每根 K 线结束时通过最高价/最低价模拟止损与止盈。如需交易所端保护单,可自行扩展相关逻辑。
- 风险手数依赖于
Security.Step 和 Security.StepPrice。当市场资料缺失时会退回到固定手数。
- 日内收益限制基于策略 PnL,确保投资组合的盈亏计算单位与风险参数一致。
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>
/// Trades reversals from Bollinger Bands.
/// Buys when price closes below lower band and sells when price closes above upper band.
/// </summary>
public class BollingerBandsSessionReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerLength;
private readonly StrategyParam<decimal> _bollingerWidth;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Length of the Bollinger Bands moving average.
/// </summary>
public int BollingerLength
{
get => _bollingerLength.Value;
set => _bollingerLength.Value = value;
}
/// <summary>
/// Width multiplier for Bollinger Bands.
/// </summary>
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public BollingerBandsSessionReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
_bollingerLength = Param(nameof(BollingerLength), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Length", "MA period for Bollinger Bands", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Width", "Band width multiplier", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bollinger = new BollingerBands
{
Length = BollingerLength,
Width = BollingerWidth
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (bbValue is not IBollingerBandsValue bb)
return;
var middle = bb.MovingAverage ?? 0m;
var upper = bb.UpBand ?? 0m;
var lower = bb.LowBand ?? 0m;
if (middle == 0m || upper == 0m || lower == 0m)
return;
var price = candle.ClosePrice;
// Reversal: buy when price falls below lower band
if (price < lower && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Reversal: sell when price rises above upper band
else if (price > upper && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit at middle band
else if (Position > 0 && price >= middle)
{
SellMarket();
}
else if (Position < 0 && price <= middle)
{
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class bollinger_bands_session_reversal_strategy(Strategy):
def __init__(self):
super(bollinger_bands_session_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary candle series", "General")
self._bollinger_length = self.Param("BollingerLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Length", "MA period for Bollinger Bands", "Indicators")
self._bollinger_width = self.Param("BollingerWidth", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Bollinger Width", "Band width multiplier", "Indicators")
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerLength(self):
return self._bollinger_length.Value
@BollingerLength.setter
def BollingerLength(self, value):
self._bollinger_length.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
def OnStarted2(self, time):
super(bollinger_bands_session_reversal_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self.BollingerLength
bollinger.Width = self.BollingerWidth
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bollinger, self._process_candle).Start()
def _process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
upper = bb_value.UpBand
lower = bb_value.LowBand
middle = bb_value.MovingAverage
if upper is None or lower is None or middle is None:
return
up = float(upper)
lo = float(lower)
mid = float(middle)
if up == 0 or lo == 0 or mid == 0:
return
price = float(candle.ClosePrice)
if price < lo and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif price > up and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position > 0 and price >= mid:
self.SellMarket()
elif self.Position < 0 and price <= mid:
self.BuyMarket()
def CreateClone(self):
return bollinger_bands_session_reversal_strategy()