在 GitHub 上查看

Williams R Ichimoku Strategy

此策略结合Williams %R与一目均衡表。当%R跌破-80且价格位于云层上方并且转折线高于基准线时做多;当%R高于-20且价格在云层下方并且转折线低于基准线时做空。价格穿越云层另一侧时离场。

测试表明年均收益约为 73%,该策略在加密市场表现最佳。

该方法适合偏好明确趋势过滤的交易者,止损围绕基准线设置,随趋势强度调整。

细节

  • 入场条件:
    • 多头: %R < -80 && price above Ichimoku cloud && Tenkan > Kijun
    • 空头: %R > -20 && price below Ichimoku cloud && Tenkan < Kijun
  • 多/空: 双向
  • 离场条件:
    • 多头: 价格跌破云层时平仓
    • 空头: 价格突破云层时平仓
  • 止损: 是
  • 默认值:
    • WilliamsRPeriod = 14
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouSpanBPeriod = 52
    • CandleType = TimeSpan.FromMinutes(15)
  • 过滤器:
    • 类别: Mixed
    • 方向: 双向
    • 指标: 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;
			}
		}
	}
}