在 GitHub 上查看

3-Bar Low 策略

3-Bar Low 策略在收盘价跌破前一根K线的三根最低收盘价时做多,并在收盘价突破前一根K线的七根最高收盘价时平仓。可选的 EMA 过滤器要求价格在进场前位于长期均线上方。

细节

  • 入场条件:
    • 收盘价低于前三根K线最低收盘价的前值。
    • 可选:开启过滤器时,收盘价需高于 EMA。
  • 方向:仅做多。
  • 出场条件:
    • 收盘价高于前七根K线最高收盘价的前值。
  • 止损:无。
  • 默认参数:
    • MaPeriod = 200
    • LowestLength = 3
    • HighestLength = 7
    • UseEmaFilter = false
  • 过滤器:
    • 类型:突破
    • 方向:多头
    • 指标:EMA, Highest/Lowest
    • 止损:否
    • 复杂度:低
    • 时间框架:任意
    • 季节性:否
    • 神经网络:否
    • 背离:否
    • 风险等级:低
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>
/// Three Bar Low Strategy.
/// A long opens when the close falls below the lowest close of the previous LowestLength candles and, with UseEmaFilter,
/// the close is above the MaPeriod EMA. The long closes when the close rises above the highest close of the previous
/// HighestLength candles.
/// </summary>
public class ThreeBarLowStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _lowestLength;
	private readonly StrategyParam<int> _highestLength;
	private readonly StrategyParam<bool> _useEmaFilter;
	private readonly StrategyParam<DataType> _candleType;

	private Lowest _lowestClose;
	private Highest _highestClose;
	private decimal? _prevLowest;
	private decimal? _prevHighest;

	/// <summary>
	/// EMA period of the filter.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Previous candles of the lowest close.
	/// </summary>
	public int LowestLength
	{
		get => _lowestLength.Value;
		set => _lowestLength.Value = value;
	}

	/// <summary>
	/// Previous candles of the highest close.
	/// </summary>
	public int HighestLength
	{
		get => _highestLength.Value;
		set => _highestLength.Value = value;
	}

	/// <summary>
	/// Require the close above the EMA for entries.
	/// </summary>
	public bool UseEmaFilter
	{
		get => _useEmaFilter.Value;
		set => _useEmaFilter.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public ThreeBarLowStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "EMA period of the filter", "Indicators");

		_lowestLength = Param(nameof(LowestLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Lowest Length", "Previous candles of the lowest close", "Indicators");

		_highestLength = Param(nameof(HighestLength), 7)
			.SetGreaterThanZero()
			.SetDisplay("Highest Length", "Previous candles of the highest close", "Indicators");

		_useEmaFilter = Param(nameof(UseEmaFilter), false)
			.SetDisplay("Use EMA Filter", "Require the close above the EMA for entries", "Filters");

		_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()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevLowest = null;
		_prevHighest = null;
	}

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

		_prevLowest = null;
		_prevHighest = null;

		var ema = new ExponentialMovingAverage { Length = MaPeriod };
		_lowestClose = new Lowest { Length = LowestLength };
		_highestClose = new Highest { Length = HighestLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(ema, ProcessCandle)
			.Start();

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

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

		var close = candle.ClosePrice;

		// Both levels are measured on the candles before this one.
		var lowest = _prevLowest;
		var highest = _prevHighest;

		var currentLowest = _lowestClose.Process(close, candle.OpenTime, true).ToDecimal();
		var currentHighest = _highestClose.Process(close, candle.OpenTime, true).ToDecimal();

		if (_lowestClose.IsFormed)
			_prevLowest = currentLowest;

		if (_highestClose.IsFormed)
			_prevHighest = currentHighest;

		if (lowest is not decimal lowLevel || highest is not decimal highLevel)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position > 0)
		{
			if (close > highLevel)
				SellMarket(Position);

			return;
		}

		var emaOk = !UseEmaFilter || (emaValue.IsFormed && close > emaValue.GetValue<decimal>());

		if (Position == 0 && close < lowLevel && emaOk)
			BuyMarket(Volume);
	}
}