Auf GitHub ansehen

Ma Williams R Strategy

Implementation of strategy - MA + Williams %R. Buy when price is above MA and Williams %R is below -80 (oversold). Sell when price is below MA and Williams %R is above -20 (overbought).

Testing indicates an average annual return of about 79%. It performs best in the stocks market.

The moving average shows the prevailing trend direction. Williams %R looks for overbought or oversold points relative to that trend.

Fits swing traders waiting for pullbacks toward the average. Stop-loss distance comes from ATR.

Details

  • Entry Criteria:
    • Long: Close > MA && WilliamsR < WilliamsROversold
    • Short: Close < MA && WilliamsR > WilliamsROverbought
  • Long/Short: Both
  • Exit Criteria:
    • Williams %R returns to middle
  • Stops: Percent-based using StopLoss
  • Default Values:
    • MaPeriod = 20
    • MaType = MovingAverageTypeEnum.Simple
    • WilliamsRPeriod = 14
    • WilliamsROversold = -80m
    • WilliamsROverbought = -20m
    • StopLoss = new Unit(2, UnitTypes.Percent)
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
  • Filters:
    • Category: Mean reversion
    • Direction: Both
    • Indicators: Moving Average, Williams %R, R
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Mid-term
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
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;

using StockSharp.Algo;
using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Implementation of strategy - MA + Williams %R.
/// Buy when price is above MA and Williams %R is below -80 (oversold).
/// Sell when price is below MA and Williams %R is above -20 (overbought).
/// </summary>
public class MaWilliamsRStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<MovingAverageTypes> _maType;
	private readonly StrategyParam<int> _williamsRPeriod;
	private readonly StrategyParam<decimal> _williamsROversold;
	private readonly StrategyParam<decimal> _williamsROverbought;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<Unit> _stopLoss;
	private readonly StrategyParam<DataType> _candleType;

	private int _cooldown;

	/// <summary>
	/// Moving Average period.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Moving Average type.
	/// </summary>
	public MovingAverageTypes MaType
	{
		get => _maType.Value;
		set => _maType.Value = value;
	}

	/// <summary>
	/// Williams %R period.
	/// </summary>
	public int WilliamsRPeriod
	{
		get => _williamsRPeriod.Value;
		set => _williamsRPeriod.Value = value;
	}

	/// <summary>
	/// Williams %R oversold level (usually below -80).
	/// </summary>
	public decimal WilliamsROversold
	{
		get => _williamsROversold.Value;
		set => _williamsROversold.Value = value;
	}

	/// <summary>
	/// Williams %R overbought level (usually above -20).
	/// </summary>
	public decimal WilliamsROverbought
	{
		get => _williamsROverbought.Value;
		set => _williamsROverbought.Value = value;
	}

	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Stop-loss value.
	/// </summary>
	public Unit StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Candle type used for strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="MaWilliamsRStrategy"/>.
	/// </summary>
	public MaWilliamsRStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Period for Moving Average", "MA Parameters");

		_maType = Param(nameof(MaType), MovingAverageTypes.Simple)
			.SetDisplay("MA Type", "Type of Moving Average", "MA Parameters");

		_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Williams %R Period", "Period for Williams %R", "Williams %R Parameters");

		_williamsROversold = Param(nameof(WilliamsROversold), -70m)
			.SetRange(-100, 0)
			.SetDisplay("Williams %R Oversold", "Williams %R level to consider market oversold", "Williams %R Parameters");

		_williamsROverbought = Param(nameof(WilliamsROverbought), -30m)
			.SetRange(-100, 0)
			.SetDisplay("Williams %R Overbought", "Williams %R level to consider market overbought", "Williams %R Parameters");

		_cooldownBars = Param(nameof(CooldownBars), 120)
			.SetRange(5, 500)
			.SetDisplay("Cooldown Bars", "Bars between trades", "General");

		_stopLoss = Param(nameof(StopLoss), new Unit(2, UnitTypes.Percent))
			.SetDisplay("Stop Loss", "Stop loss percent or value", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for strategy", "General");
	}

	/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
	return [(Security, CandleType)];
}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_cooldown = 0;
	}

/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
	base.OnStarted2(time);

		// Create indicators
		DecimalLengthIndicator ma;
		
		// Create MA based on selected type
		switch (MaType)
		{
			case MovingAverageTypes.Exponential:
				ma = new EMA { Length = MaPeriod };
				break;
			case MovingAverageTypes.Weighted:
				ma = new WeightedMovingAverage { Length = MaPeriod };
				break;
			case MovingAverageTypes.Smoothed:
				ma = new SmoothedMovingAverage { Length = MaPeriod };
				break;
			case MovingAverageTypes.HullMA:
				ma = new HullMovingAverage { Length = MaPeriod };
				break;
			case MovingAverageTypes.Simple:
			default:
				ma = new SMA { Length = MaPeriod };
				break;
		}
		
		var williamsR = new WilliamsR { Length = WilliamsRPeriod };

		// Setup candle subscription
		var subscription = SubscribeCandles(CandleType);
		
		// Bind indicators to candles
		subscription
			.Bind(ma, williamsR, ProcessCandle)
			.Start();

		// Setup chart visualization if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ma);
			
			// Create separate area for Williams %R
			var oscillatorArea = CreateChartArea();
			if (oscillatorArea != null)
			{
				DrawIndicator(oscillatorArea, williamsR);
			}
			
			DrawOwnTrades(area);
		}

	}

	private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal williamsRValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Current price
		var price = candle.ClosePrice;
		
		// Determine if price is above or below MA
		var isPriceAboveMA = price > maValue;

		LogInfo($"Candle: {candle.OpenTime}, Close: {price}, " +
			$"MA: {maValue}, Price > MA: {isPriceAboveMA}, " +
			$"Williams %R: {williamsRValue}");

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		// Trading rules
		if (isPriceAboveMA && williamsRValue <= WilliamsROversold && Position == 0)
		{
			// Buy signal - price above MA and Williams %R oversold
			BuyMarket();
			_cooldown = CooldownBars;
			
			LogInfo($"Buy signal: Price above MA and Williams %R oversold ({williamsRValue} <= {WilliamsROversold}).");
		}
		else if (!isPriceAboveMA && williamsRValue >= WilliamsROverbought && Position == 0)
		{
			// Sell signal - price below MA and Williams %R overbought
			SellMarket();
			_cooldown = CooldownBars;
			
			LogInfo($"Sell signal: Price below MA and Williams %R overbought ({williamsRValue} >= {WilliamsROverbought}).");
		}
		// Exit conditions
		else if (!isPriceAboveMA && Position > 0)
		{
			// Exit long position when price falls below MA
			SellMarket();
			_cooldown = CooldownBars;
			LogInfo($"Exit long: Price fell below MA. Position: {Position}");
		}
		else if (isPriceAboveMA && Position < 0)
		{
			// Exit short position when price rises above MA
			BuyMarket();
			_cooldown = CooldownBars;
			LogInfo($"Exit short: Price rose above MA. Position: {Position}");
		}
	}
	
	/// <summary>
	/// Enum for Moving Average types.
	/// </summary>
	public enum MovingAverageTypes
	{
		/// <summary>
		/// Simple Moving Average
		/// </summary>
		Simple,
		
		/// <summary>
		/// Exponential Moving Average
		/// </summary>
		Exponential,
		
		/// <summary>
		/// Weighted Moving Average
		/// </summary>
		Weighted,
		
		/// <summary>
		/// Smoothed Moving Average
		/// </summary>
		Smoothed,
		
		/// <summary>
		/// Hull Moving Average
		/// </summary>
		HullMA
	}
}