View on GitHub

Three White Soldiers Strategy

The Three White Soldiers pattern is a classic bullish reversal consisting of three consecutive strong up candles. After a downtrend, this sequence often marks the start of a sustained move higher as buying pressure overwhelms sellers.

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

The strategy enters long once the third soldier forms, expecting follow-through from the surge in momentum. Short trades are not taken because the setup is purely bullish, but the system does allow exiting short positions initiated by other methods.

Stops are placed a short distance below the pattern to guard against false signals and positions exit if price closes back below that level.

Details

  • Entry Criteria: pattern match
  • Long/Short: Both
  • Exit Criteria: stop-loss or opposite signal
  • Stops: Yes, percent based
  • Default Values:
    • CandleType = 15 minute
    • StopLossPercent = 2 The strategy only buys. The stop lies StopLossPercent below the lowest low of the pattern and the position closes when a candle closes below it.
  • Filters:
    • Category: Pattern
    • Direction: Both
    • Indicators: Candlestick
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Intraday
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Medium
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Three White Soldiers strategy.
/// While flat it buys after three bullish candles in a row, each closing above the previous close.
/// The stop lies StopLossPercent below the pattern's lowest low, and a close beyond it closes the position.
/// </summary>
public class ThreeWhiteSoldiersStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<ICandleMessage> _candles = [];
	private decimal _stopPrice;

	/// <summary>
	/// Distance of the stop beyond the pattern, in percent.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public ThreeWhiteSoldiersStrategy()
	{
		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetNotNegative()
			.SetDisplay("Stop Loss %", "Distance of the stop beyond the pattern, in percent", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
		_candles.Clear();
		_stopPrice = default;
	}

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

		_candles.Clear();
		_stopPrice = default;

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

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

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

		_candles.Add(candle);

		if (_candles.Count > 3)
			_candles.RemoveAt(0);

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position > 0)
		{
			if (candle.ClosePrice <= _stopPrice)
				SellMarket(Position);

			return;
		}

		if (Position != 0 || _candles.Count < 3)
			return;

		var c0 = _candles[0];
		var c1 = _candles[1];
		var c2 = _candles[2];

		if (!(c0.ClosePrice > c0.OpenPrice && c1.ClosePrice > c1.OpenPrice && c2.ClosePrice > c2.OpenPrice && c1.ClosePrice > c0.ClosePrice && c2.ClosePrice > c1.ClosePrice))
			return;

		BuyMarket(Volume);
		_stopPrice = Math.Min(Math.Min(c0.LowPrice, c1.LowPrice), c2.LowPrice) * (1 - StopLossPercent / 100m);
	}
}