Bands Price 策略
该策略是 MetaTrader 平台 i-BandsPrice 专家的移植。它利用布林带衡量价格在通道中的相对位置,并在值离开极端区域时做出反应。
逻辑
- 根据设置的周期和偏差构建布林带。
- 计算价格在带内的位置,范围在 -50 到 +50 之间。
- 使用简单移动平均对该值进行平滑。
- 生成颜色代码:
- 当平滑值高于上限时为
4。 - 当平滑值低于下限时为
0。 - 其他数值表示中间区域。
- 当平滑值高于上限时为
- 当指标从上方区域离开时(
4→ 非4)做多。 - 当指标从下方区域离开时(
0→ 正值)做空。 - 当值变为非正时平多仓。
- 当值变为非负时平空仓。
参数
| 名称 | 描述 |
|---|---|
| BuyOpen | 是否允许做多。 |
| SellOpen | 是否允许做空。 |
| BuyClose | 是否允许平多。 |
| SellClose | 是否允许平空。 |
| BandsPeriod | 布林带周期。 |
| BandsDeviation | 带宽偏差。 |
| Smooth | 内部值的平滑长度。 |
| UpLevel | 上阈值,默认 25。 |
| DnLevel | 下阈值,默认 -25。 |
| CandleType | 计算所用的K线时间框架。 |
说明
该策略展示了如何使用 SubscribeCandles 与 Bind 将 MetaTrader 指标逻辑迁移至 StockSharp 高级 API。
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>
/// Mean reversion strategy based on the Bands Price indicator.
/// Computes price position within Bollinger Bands as an oscillator.
/// Buys when leaving overbought zone, sells when leaving oversold zone.
/// </summary>
public class BandsPriceStrategy : Strategy
{
private readonly StrategyParam<int> _bandsPeriod;
private readonly StrategyParam<decimal> _bandsDeviation;
private readonly StrategyParam<int> _smooth;
private readonly StrategyParam<int> _upLevel;
private readonly StrategyParam<int> _dnLevel;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _smoother;
private int _prevColor;
private int _prevPrevColor;
public int BandsPeriod { get => _bandsPeriod.Value; set => _bandsPeriod.Value = value; }
public decimal BandsDeviation { get => _bandsDeviation.Value; set => _bandsDeviation.Value = value; }
public int Smooth { get => _smooth.Value; set => _smooth.Value = value; }
public int UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
public int DnLevel { get => _dnLevel.Value; set => _dnLevel.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BandsPriceStrategy()
{
_bandsPeriod = Param(nameof(BandsPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Bands Period", "Bollinger Bands period", "Indicator");
_bandsDeviation = Param(nameof(BandsDeviation), 2m)
.SetDisplay("Bands Deviation", "Width of Bollinger Bands", "Indicator");
_smooth = Param(nameof(Smooth), 5)
.SetGreaterThanZero()
.SetDisplay("Smoothing", "Length of smoothing EMA", "Indicator");
_upLevel = Param(nameof(UpLevel), 25)
.SetDisplay("Upper Level", "Threshold for overbought zone", "Indicator");
_dnLevel = Param(nameof(DnLevel), -25)
.SetDisplay("Lower Level", "Threshold for oversold zone", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_smoother = default;
_prevColor = -1;
_prevPrevColor = -1;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevColor = -1;
_prevPrevColor = -1;
_smoother = new ExponentialMovingAverage { Length = Smooth };
Indicators.Add(_smoother);
var bands = new BollingerBands { Length = BandsPeriod, Width = BandsDeviation };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bands, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFormed)
return;
var bb = (BollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
var width = upper - lower;
if (width == 0)
return;
// Normalize price position within bands: -50 to +50
var res = 100m * (candle.ClosePrice - lower) / width - 50m;
var smoothResult = _smoother.Process(res, candle.OpenTime, true);
if (!smoothResult.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var jres = smoothResult.ToDecimal();
// Determine color zone
int color;
if (jres > UpLevel)
color = 4; // overbought
else if (jres > 0)
color = 3; // above zero
else if (jres < DnLevel)
color = 0; // oversold
else if (jres < 0)
color = 1; // below zero
else
color = 2; // neutral
if (_prevPrevColor != -1 && _prevColor != -1)
{
// Buy: was overbought (4), now leaving -> mean reversion buy
if (_prevPrevColor == 4 && _prevColor < 4 && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Sell: was oversold (0), now leaving -> mean reversion sell
else if (_prevPrevColor == 0 && _prevColor > 0 && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
_prevPrevColor = _prevColor;
_prevColor = color;
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class bands_price_strategy(Strategy):
def __init__(self):
super(bands_price_strategy, self).__init__()
self._bands_period = self.Param("BandsPeriod", 100) \
.SetDisplay("Bands Period", "Bollinger Bands period", "Indicator")
self._bands_deviation = self.Param("BandsDeviation", 2.0) \
.SetDisplay("Bands Deviation", "Width of Bollinger Bands", "Indicator")
self._smooth = self.Param("Smooth", 5) \
.SetDisplay("Smoothing", "Length of smoothing EMA", "Indicator")
self._up_level = self.Param("UpLevel", 25) \
.SetDisplay("Upper Level", "Threshold for overbought zone", "Indicator")
self._dn_level = self.Param("DnLevel", -25) \
.SetDisplay("Lower Level", "Threshold for oversold zone", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._smoother = None
self._prev_color = -1
self._prev_prev_color = -1
@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 Smooth(self):
return self._smooth.Value
@Smooth.setter
def Smooth(self, value):
self._smooth.Value = value
@property
def UpLevel(self):
return self._up_level.Value
@UpLevel.setter
def UpLevel(self, value):
self._up_level.Value = value
@property
def DnLevel(self):
return self._dn_level.Value
@DnLevel.setter
def DnLevel(self, value):
self._dn_level.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(bands_price_strategy, self).OnStarted2(time)
self._prev_color = -1
self._prev_prev_color = -1
self._smoother = ExponentialMovingAverage()
self._smoother.Length = self.Smooth
bands = BollingerBands()
bands.Length = self.BandsPeriod
bands.Width = self.BandsDeviation
self.SubscribeCandles(self.CandleType) \
.BindEx(bands, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if not bb_value.IsFormed:
return
upper = bb_value.UpBand
lower = bb_value.LowBand
if upper is None or lower is None:
return
upper_f = float(upper)
lower_f = float(lower)
width = upper_f - lower_f
if width == 0:
return
close = float(candle.ClosePrice)
res = 100.0 * (close - lower_f) / width - 50.0
t = candle.OpenTime
smooth_result = process_float(self._smoother, res, t, True)
if not smooth_result.IsFormed:
return
jres = float(smooth_result)
up = float(self.UpLevel)
dn = float(self.DnLevel)
if jres > up:
color = 4
elif jres > 0:
color = 3
elif jres < dn:
color = 0
elif jres < 0:
color = 1
else:
color = 2
if self._prev_prev_color != -1 and self._prev_color != -1:
if self._prev_prev_color == 4 and self._prev_color < 4 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_prev_color == 0 and self._prev_color > 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_prev_color = self._prev_color
self._prev_color = color
def OnReseted(self):
super(bands_price_strategy, self).OnReseted()
self._smoother = None
self._prev_color = -1
self._prev_prev_color = -1
def CreateClone(self):
return bands_price_strategy()