在 GitHub 上查看

上一根蜡烛突破策略

本策略移植自 Soubra2003 的 MetaTrader 专家顾问 BreakOut。算法保存上一根已完成蜡烛的最高价与最低价,只要当 前收盘价突破这些参照水平,就会立即平仓并按突破方向开仓。用户还可以以价格单位设置可选的止损与止盈距离, 以控制风险或锁定利润。

策略概览

  • 订阅单一的蜡烛序列(默认使用 1 小时周期)。
  • 每根蜡烛收盘后记录其高点与低点,作为下一根蜡烛的突破触发值。
  • 所有交易均在蜡烛收盘时执行,从而在没有逐笔数据的情况下重现原始 MQL4 逻辑。

交易规则

  1. 突破入场与反转
    • 如果当前收盘价高于上一根蜡烛的最高价:
      • 如有空头持仓则先以市价买入平仓。
      • 随后立即开立新的多头头寸(反转在同一次蜡烛处理过程中完成)。
    • 如果当前收盘价低于上一根蜡烛的最低价:
      • 如有多头持仓则先以市价卖出平仓。
      • 随后开立新的空头头寸。
  2. 保护性离场(可选)
    • 当止损距离大于 0 时,多头在收盘价低于入场价减去该距离时平仓;空头在收盘价高于入场价加上该距离时平仓。
    • 当止盈距离大于 0 时,多头在收盘价高于入场价加上该距离时平仓;空头在收盘价低于入场价减去该距离时平仓。
  3. 水平更新
    • 每次处理完成后,当前蜡烛的高低价会成为下一根蜡烛的突破参照。

参数说明

  • Candle Type – 蜡烛数据类型(默认为 1 小时)。请设置为与原始 MetaTrader 图表相同的周期。
  • Stop Loss – 入场价与止损之间的绝对价格距离,设置为 0 表示不启用止损。
  • Take Profit – 入场价与止盈之间的绝对价格距离,设置为 0 表示不启用止盈。

备注

  • 原版在下单时直接设置 SL/TP。StockSharp 版本改为在收盘价触发条件时发送市价单离场。
  • 配置止损/止盈时请结合品种的最小报价单位。例如最小变动价位为 0.01 且希望使用 20 个跳动点,则参数应设置为 0.20
  • 由于策略仅参考最近一根蜡烛的区间,在趋势行情或高波动时表现最佳。

来源

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>
/// Breakout strategy that trades when the close crosses the previous candle's high or low.
/// Ported from the BreakOut.mq4 expert by Soubra2003.
/// </summary>
public class PreviousCandleBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossOffset;
	private readonly StrategyParam<decimal> _takeProfitOffset;

	private decimal? _previousHigh;
	private decimal? _previousLow;
	private decimal _entryPrice;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public decimal StopLossOffset { get => _stopLossOffset.Value; set => _stopLossOffset.Value = value; }
	public decimal TakeProfitOffset { get => _takeProfitOffset.Value; set => _takeProfitOffset.Value = value; }

	public PreviousCandleBreakoutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for candle subscription", "General");

		_stopLossOffset = Param(nameof(StopLossOffset), 1000m)
			.SetDisplay("Stop Loss", "Price distance for the stop-loss. Set 0 to disable.", "Risk")
			;

		_takeProfitOffset = Param(nameof(TakeProfitOffset), 1500m)
			.SetDisplay("Take Profit", "Price distance for the take-profit. Set 0 to disable.", "Risk")
			;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousHigh = null;
		_previousLow = null;
		_entryPrice = 0m;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

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

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

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

		if (_previousHigh is null || _previousLow is null)
		{
			// Store the first finished candle to obtain reference high/low levels.
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			return;
		}

		var previousHigh = _previousHigh.Value;
		var previousLow = _previousLow.Value;
		var close = candle.ClosePrice;

		var breakoutAbove = close > previousHigh;
		var breakoutBelow = close < previousLow;

		// Manage protective exits while a position is open.
		if (Position > 0)
		{
			if (StopLossOffset > 0m && close <= _entryPrice - StopLossOffset)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
			}
			else if (TakeProfitOffset > 0m && close >= _entryPrice + TakeProfitOffset)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
			}
		}
		else if (Position < 0)
		{
			if (StopLossOffset > 0m && close >= _entryPrice + StopLossOffset)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
			}
			else if (TakeProfitOffset > 0m && close <= _entryPrice - TakeProfitOffset)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
			}
		}

		// Breakout above the previous high opens or reverses into a long position.
		if (breakoutAbove)
		{
			if (Position < 0)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
			}

			if (Position <= 0)
			{
				BuyMarket();
				_entryPrice = close;
			}
		}
		else if (breakoutBelow)
		{
			// Breakout below the previous low opens or reverses into a short position.
			if (Position > 0)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
			}

			if (Position >= 0)
			{
				SellMarket();
				_entryPrice = close;
			}
		}

		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
	}
}