Стратегия Williams R Ichimoku
Эта стратегия сочетает экстремальные значения Williams %R с трендовым фильтром облака Ишимоку. Идея состоит в том, чтобы присоединяться к сильным движениям только тогда, когда цена находится на благоприятной стороне облака, а короткие линии подтверждают направление.
Тестирование показывает среднегодичную доходность около 73%. Стратегию лучше запускать на крипторынке.
Сигнал на покупку появляется, когда осциллятор опускается ниже -80, цена выше облака, а линия Тенкан-сэн пересекает Киджун-сэн снизу вверх. Сигнал на продажу возникает при %R выше -20, цене ниже облака и Тенкан-сэн ниже Киджун-сэн. Позиция удерживается до пересечения цены противоположной стороны облака.
Метод подойдёт трейдерам, которые предпочитают чёткие трендовые фильтры вместо быстрых разворотов. Динамический стоп размещается около Киджун-сэн, что позволяет риску меняться вместе с силой тренда.
Детали
- Условия входа:
- Лонг: %R < -80, цена выше облака Ишимоку и Тенкан-сэн > Киджун-сэн
- Шорт: %R > -20, цена ниже облака Ишимоку и Тенкан-сэн < Киджун-сэн
- Лонг/Шорт: обе стороны.
- Условия выхода:
- Лонг: Выход при пересечении цены ниже облака
- Шорт: Выход при пересечении цены выше облака
- Стопы: да.
- Значения по умолчанию:
WilliamsRPeriod= 14TenkanPeriod= 9KijunPeriod= 26SenkouSpanBPeriod= 52CandleType= TimeSpan.FromMinutes(15)
- Фильтры:
- Категория: Смешанная
- Направление: Оба
- Индикаторы: Williams R Ichimoku
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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 Williams %R and Ichimoku indicators.
/// Enters long when Williams %R is below -80 (oversold) and price is above Ichimoku Cloud with Tenkan-sen > Kijun-sen.
/// Enters short when Williams %R is above -20 (overbought) and price is below Ichimoku Cloud with Tenkan-sen < Kijun-sen.
/// </summary>
public class WilliamsIchimokuStrategy : Strategy
{
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private WilliamsR _williamsR;
private Ichimoku _ichimoku;
private decimal? _lastKijun;
private decimal _prevWilliamsR;
private int _cooldown;
/// <summary>
/// Williams %R indicator period.
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// Tenkan-sen period (Ichimoku).
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen period (Ichimoku).
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B period (Ichimoku).
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type parameter.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public WilliamsIchimokuStrategy()
{
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period for Williams %R calculation", "Indicators")
.SetOptimize(10, 20, 2);
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen line (Ichimoku)", "Indicators")
.SetOptimize(7, 13, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun-sen Period", "Period for Kijun-sen line (Ichimoku)", "Indicators")
.SetOptimize(20, 30, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B line (Ichimoku)", "Indicators")
.SetOptimize(40, 60, 4);
_cooldownBars = Param(nameof(CooldownBars), 60)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_williamsR = null;
_ichimoku = null;
_lastKijun = null;
_prevWilliamsR = -50m;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_williamsR = new WilliamsR
{
Length = WilliamsRPeriod
};
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
// Create candles subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicators to subscription
subscription
.BindEx(_williamsR, _ichimoku, ProcessCandle)
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williamsR);
DrawIndicator(area, _ichimoku);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue williamsRValue, IIndicatorValue ichimokuValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Skip if strategy is not ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Extract Ichimoku values
var ichimokuTyped = (IchimokuValue)ichimokuValue;
if (ichimokuTyped.Tenkan is not decimal tenkan)
return;
if (ichimokuTyped.Kijun is not decimal kijun)
return;
if (ichimokuTyped.SenkouA is not decimal senkouA)
return;
if (ichimokuTyped.SenkouB is not decimal senkouB)
return;
// Determine if price is above or below the Kumo (cloud)
var kumoTop = Math.Max(senkouA, senkouB);
var kumoBottom = Math.Min(senkouA, senkouB);
var isPriceAboveKumo = candle.ClosePrice > kumoTop;
var isPriceBelowKumo = candle.ClosePrice < kumoBottom;
var williamsRDec = williamsRValue.ToDecimal();
var crossedBelow80 = _prevWilliamsR >= -90m && williamsRDec < -90m;
var crossedAbove20 = _prevWilliamsR <= -10m && williamsRDec > -10m;
_prevWilliamsR = williamsRDec;
if (_cooldown > 0)
_cooldown--;
// Save current Kijun for stop-loss
_lastKijun = kijun;
// Trading logic
if (_cooldown == 0 && crossedBelow80 && candle.ClosePrice > kumoTop * 1.002m && isPriceAboveKumo && tenkan > kijun)
{
// Long signal: %R < -80 (oversold), price above Kumo, Tenkan > Kijun
if (Position <= 0)
{
// Close any existing short position and open long
BuyMarket(Volume + Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if (_cooldown == 0 && crossedAbove20 && candle.ClosePrice < kumoBottom * 0.998m && isPriceBelowKumo && tenkan < kijun)
{
// Short signal: %R > -20 (overbought), price below Kumo, Tenkan < Kijun
if (Position >= 0)
{
// Close any existing long position and open short
SellMarket(Volume + Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if ((Position > 0 && candle.ClosePrice < kumoBottom) ||
(Position < 0 && candle.ClosePrice > kumoTop))
{
// Exit positions when price crosses the Kumo
if (Position > 0)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
}
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
from StockSharp.Algo.Indicators import WilliamsR, Ichimoku
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class williams_ichimoku_strategy(Strategy):
"""
Strategy based on Williams %R and Ichimoku indicators.
"""
def __init__(self):
super(williams_ichimoku_strategy, self).__init__()
self._williams_r_period = self.Param("WilliamsRPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Williams %R Period", "Period for Williams %R calculation", "Indicators")
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen line (Ichimoku)", "Indicators")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun-sen Period", "Period for Kijun-sen line (Ichimoku)", "Indicators")
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B line (Ichimoku)", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 60) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", tf(30)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_williams_r = -50.0
self._cooldown = 0
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(williams_ichimoku_strategy, self).OnReseted()
self._prev_williams_r = -50.0
self._cooldown = 0
def OnStarted2(self, time):
super(williams_ichimoku_strategy, self).OnStarted2(time)
self._prev_williams_r = -50.0
self._cooldown = 0
williams_r = WilliamsR()
williams_r.Length = self._williams_r_period.Value
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self._tenkan_period.Value
ichimoku.Kijun.Length = self._kijun_period.Value
ichimoku.SenkouB.Length = self._senkou_span_b_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(williams_r, ichimoku, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, williams_r)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, williams_r_value, ichimoku_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
tenkan = ichimoku_value.Tenkan
kijun = ichimoku_value.Kijun
senkou_a = ichimoku_value.SenkouA
senkou_b = ichimoku_value.SenkouB
if tenkan is None or kijun is None or senkou_a is None or senkou_b is None:
return
tenkan = float(tenkan)
kijun = float(kijun)
senkou_a = float(senkou_a)
senkou_b = float(senkou_b)
kumo_top = max(senkou_a, senkou_b)
kumo_bottom = min(senkou_a, senkou_b)
price = float(candle.ClosePrice)
is_price_above_kumo = price > kumo_top
is_price_below_kumo = price < kumo_bottom
wr = float(williams_r_value)
crossed_below_90 = self._prev_williams_r >= -90 and wr < -90
crossed_above_10 = self._prev_williams_r <= -10 and wr > -10
self._prev_williams_r = wr
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldown_bars.Value)
if self._cooldown == 0 and crossed_below_90 and price > kumo_top * 1.002 and is_price_above_kumo and tenkan > kijun:
if self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
elif self._cooldown == 0 and crossed_above_10 and price < kumo_bottom * 0.998 and is_price_below_kumo and tenkan < kijun:
if self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
elif (self.Position > 0 and price < kumo_bottom) or (self.Position < 0 and price > kumo_top):
if self.Position > 0:
self.SellMarket(abs(self.Position))
self._cooldown = cooldown_val
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._cooldown = cooldown_val
def CreateClone(self):
return williams_ichimoku_strategy()