Стратегия Bands Price
Стратегия является адаптацией эксперта i-BandsPrice из MetaTrader. Она использует полосы Боллинджера для оценки положения цены внутри канала и реагирует на выход из крайних зон.
Логика
- Строятся полосы Боллинджера с заданными периодом и отклонением.
- Рассчитывается положение цены относительно нижней и верхней полосы, нормированное в диапазоне от -50 до +50.
- Значение сглаживается простой скользящей средней.
- Формируется код цвета:
4— значение выше верхнего уровня.0— значение ниже нижнего уровня.- Остальные числа обозначают промежуточные зоны.
- Длинная позиция открывается при выходе значения из верхней зоны (
4→ не4). - Короткая позиция открывается при выходе из нижней зоны (
0→ положительное значение). - Лонг закрывается, когда значение становится неположительным.
- Шорт закрывается, когда значение становится неотрицательным.
Параметры
| Название | Описание |
|---|---|
| BuyOpen | Разрешить входы в лонг. |
| SellOpen | Разрешить входы в шорт. |
| BuyClose | Разрешить закрытие лонга. |
| SellClose | Разрешить закрытие шорта. |
| BandsPeriod | Период полос Боллинджера. |
| BandsDeviation | Отклонение полос. |
| Smooth | Период сглаживания внутреннего значения. |
| UpLevel | Верхний порог, по умолчанию 25. |
| DnLevel | Нижний порог, по умолчанию -25. |
| CandleType | Таймфрейм используемых свечей. |
Примечания
Пример показывает перенос логики индикатора из MetaTrader в StockSharp с применением высокоуровневого API и методов SubscribeCandles и Bind.
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()