GitHub で見る

80-20 Strategy

The strategy detects candles where price closes in the top or bottom 20% of the session. A bullish signal occurs when the close is within the upper fifth and the open is within the lower fifth of the range. A bearish signal occurs when the open is within the upper fifth and the close is within the lower fifth. The approach aims to capture rapid reversals from extreme candle closes.

Details

  • Entry Criteria:
    • Close in top 20% and open in bottom 20% → long.
    • Open in top 20% and close in bottom 20% → short.
  • Long/Short: Both.
  • Exit Criteria:
    • Opposite signal reverses the position.
  • Stops: None.
  • Default Values:
    • Range percent = 0.2.
  • Filters:
    • Category: Pattern recognition
    • Direction: Both
    • Indicators: None
    • Stops: None
    • Complexity: Low
    • Timeframe: Any
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Medium
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// 80-20 strategy - trades when price closes near extremes of the candle.
/// Buys on strong bullish candles (close near high, open near low).
/// Sells on strong bearish candles (open near high, close near low).
/// Uses EMA as trend filter.
/// </summary>
public class EightyTwentyStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _rangePercent;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _cooldownBars;

	private ExponentialMovingAverage _ema;
	private int _cooldownRemaining;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public decimal RangePercent { get => _rangePercent.Value; set => _rangePercent.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }

	public EightyTwentyStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_rangePercent = Param(nameof(RangePercent), 0.2m)
			.SetDisplay("Range Percent", "Fraction of candle range for trigger zone", "General");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA trend filter period", "Indicators");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
	}

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

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

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

		_ema = new ExponentialMovingAverage { Length = EmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_ema, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _ema);
			DrawOwnTrades(area);
		}
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

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

		var range = candle.HighPrice - candle.LowPrice;
		if (range <= 0)
			return;

		var offset = RangePercent * range;

		var triggerGreen = candle.ClosePrice >= candle.HighPrice - offset &&
			candle.OpenPrice <= candle.LowPrice + offset;

		var triggerRed = candle.OpenPrice >= candle.HighPrice - offset &&
			candle.ClosePrice <= candle.LowPrice + offset;

		if (triggerGreen && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		else if (triggerRed && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			SellMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
	}
}