Bollinger Williams R Strategy 策略 (中文)
该文档的中文版本尚未完成,详细说明请参阅英文版 README.md。
测试表明年均收益约为 103%,该策略在股票市场表现最佳。
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>
/// Strategy based on Bollinger Bands and Williams %R indicators.
/// Enters long when price is at lower band and Williams %R is oversold (< -80)
/// Enters short when price is at upper band and Williams %R is overbought (> -20)
/// </summary>
public class BollingerWilliamsRStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private int _cooldown;
private bool _wasBelowLower;
private bool _wasAboveUpper;
/// <summary>
/// Bollinger Bands period
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands deviation
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Williams %R period
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// ATR period for stop-loss calculation
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for stop-loss
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type for strategy calculation
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor
/// </summary>
public BollingerWilliamsRStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Indicators")
.SetOptimize(15, 30, 5);
_bollingerDeviation = Param(nameof(BollingerDeviation), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Deviation multiplier for Bollinger Bands", "Indicators")
.SetOptimize(1.5m, 2.5m, 0.5m);
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators")
.SetOptimize(10, 20, 2);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR indicator for stop-loss", "Risk Management")
.SetOptimize(10, 20, 2);
_atrMultiplier = Param(nameof(AtrMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop-loss", "Risk Management")
.SetOptimize(1.5m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = 0;
_wasBelowLower = false;
_wasAboveUpper = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var bollinger = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation
};
var williamsR = new WilliamsR { Length = WilliamsRPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
// Subscribe to candles and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, williamsR, atr, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
// Create a separate area for Williams %R
var williamsArea = CreateChartArea();
if (williamsArea != null)
{
DrawIndicator(williamsArea, williamsR);
}
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue williamsRValue, IIndicatorValue atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Get additional values from Bollinger Bands
var bollingerTyped = (BollingerBandsValue)bollingerValue;
var middleBand = bollingerTyped.MovingAverage; // Middle band is returned by default
var upperBand = bollingerTyped.UpBand;
var lowerBand = bollingerTyped.LowBand;
// Current price (close of the candle)
var price = candle.ClosePrice;
// Stop-loss size based on ATR
var stopSize = atrValue.ToDecimal() * AtrMultiplier;
var williamsRValueDec = williamsRValue.ToDecimal();
var isBelowLower = price <= lowerBand * 1.001m;
var isAboveUpper = price >= upperBand * 0.999m;
if (_cooldown > 0)
{
_cooldown--;
_wasBelowLower = isBelowLower;
_wasAboveUpper = isAboveUpper;
return;
}
// Trading logic
if (!_wasBelowLower && isBelowLower && williamsRValueDec < -45 && Position <= 0)
{
// Buy signal: price at/below lower band and Williams %R oversold
BuyMarket(Volume + Math.Abs(Position));
_cooldown = 6;
// Set stop-loss
var stopPrice = price - stopSize;
RegisterOrder(CreateOrder(Sides.Sell, stopPrice, Math.Abs(Position + Volume).Max(Volume)));
}
else if (!_wasAboveUpper && isAboveUpper && williamsRValueDec > -55 && Position >= 0)
{
// Sell signal: price at/above upper band and Williams %R overbought
SellMarket(Volume + Math.Abs(Position));
_cooldown = 6;
// Set stop-loss
var stopPrice = price + stopSize;
RegisterOrder(CreateOrder(Sides.Buy, stopPrice, Math.Abs(Position + Volume).Max(Volume)));
}
// Exit conditions
else if (price >= middleBand && Position < 0)
{
// Exit short position when price returns to middle band
BuyMarket(Math.Abs(Position));
}
else if (price <= middleBand && Position > 0)
{
// Exit long position when price returns to middle band
SellMarket(Position);
}
_wasBelowLower = isBelowLower;
_wasAboveUpper = isAboveUpper;
}
}
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, Sides
from StockSharp.Algo.Indicators import BollingerBands, WilliamsR, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class bollinger_williams_r_strategy(Strategy):
"""
Bollinger Bands + Williams %R strategy.
Enters long when price is at lower band and Williams %R is oversold.
Enters short when price is at upper band and Williams %R is overbought.
"""
def __init__(self):
super(bollinger_williams_r_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0) \
.SetDisplay("Bollinger Deviation", "Deviation multiplier for Bollinger Bands", "Indicators")
self._williams_r_period = self.Param("WilliamsRPeriod", 14) \
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR indicator for stop-loss", "Risk Management")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop-loss", "Risk Management")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Timeframe for strategy", "General")
self._cooldown = 0
self._was_below_lower = False
self._was_above_upper = False
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(bollinger_williams_r_strategy, self).OnStarted2(time)
self._cooldown = 0
self._was_below_lower = False
self._was_above_upper = False
bollinger = BollingerBands()
bollinger.Length = self._bollinger_period.Value
bollinger.Width = self._bollinger_deviation.Value
williams_r = WilliamsR()
williams_r.Length = self._williams_r_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, williams_r, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
wr_area = self.CreateChartArea()
if wr_area is not None:
self.DrawIndicator(wr_area, williams_r)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, bb_value, wr_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
upper_band = float(bb_value.UpBand)
lower_band = float(bb_value.LowBand)
middle_band = float(bb_value.MovingAverage)
price = float(candle.ClosePrice)
wr_dec = float(wr_value)
atr_dec = float(atr_value)
atr_mult = float(self._atr_multiplier.Value)
stop_size = atr_dec * atr_mult
is_below_lower = price <= lower_band * 1.001
is_above_upper = price >= upper_band * 0.999
if self._cooldown > 0:
self._cooldown -= 1
self._was_below_lower = is_below_lower
self._was_above_upper = is_above_upper
return
if not self._was_below_lower and is_below_lower and wr_dec < -45 and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 6
# Set stop-loss
stop_price = price - stop_size
stop_vol = max(abs(self.Position + self.Volume), self.Volume)
self.RegisterOrder(self.CreateOrder(Sides.Sell, stop_price, stop_vol))
elif not self._was_above_upper and is_above_upper and wr_dec > -55 and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = 6
# Set stop-loss
stop_price = price + stop_size
stop_vol = max(abs(self.Position + self.Volume), self.Volume)
self.RegisterOrder(self.CreateOrder(Sides.Buy, stop_price, stop_vol))
elif price >= middle_band and self.Position < 0:
self.BuyMarket(abs(self.Position))
elif price <= middle_band and self.Position > 0:
self.SellMarket(self.Position)
self._was_below_lower = is_below_lower
self._was_above_upper = is_above_upper
def OnReseted(self):
super(bollinger_williams_r_strategy, self).OnReseted()
self._cooldown = 0
self._was_below_lower = False
self._was_above_upper = False
def CreateClone(self):
return bollinger_williams_r_strategy()