在 GitHub 上查看

回撤反弹策略

概述

回撤反弹策略是 MQL5 专家顾问“TST (barabashkakvn's edition)”的 C# 版本。它在 CandleType 指定的时间框架上监控单一品种,并寻找先扩张后回撤的价格形态。当多头K线从高点回落超过设置的回撤阈值时买入;当空头K线从低点反弹超过阈值时卖出。策略使用 StockSharp 的高级 K 线订阅接口,并将所有保护性订单以点(pip)表示后换算成绝对价格。

点值基于标的的 PriceStep 计算,对拥有三位或五位小数的品种会自动乘以 10,以符合 MetaTrader 对 pip 的定义。交易数量取自策略的 Volume 属性。

入场逻辑

  • 仅在所选 CandleType 的已完成 K 线上评估信号。
  • ReverseSignal = false(默认)时:
    • 多头条件: K 线收盘价低于开盘价,且最高价与收盘价的差值超过 RollbackRatePips(换算成价格)。这意味着价格向上扩张后出现足够的回撤,从而触发逆势买入。
    • 空头条件: K 线收盘价高于开盘价,且收盘价与最低价的差值超过 RollbackRatePips,与多头逻辑对称。
  • ReverseSignal = true 时,买卖条件对调。
  • 仅在当前仓位为空或与信号方向相反时开仓,委托数量为 Volume + |Position|,确保反向仓位被先行平仓。

离场逻辑

  • 开仓后会记录基于点值换算出的止损和止盈价位,只要 K 线范围触及某个价位,便以市价单离场。
  • StopLossPipsTakeProfitPips 设为 0 可分别禁用止损或止盈。
  • 当浮动利润超过 TrailingStopPips + TrailingStepPips 时激活跟踪止损:
    • 多头仓位的止损移动到 最高价 - TrailingStopPips,前提是该新值至少比旧止损高 TrailingStepPips
    • 空头仓位的止损移动到 最低价 + TrailingStopPips,前提是该新值至少比旧止损低 TrailingStepPips
    • 如果价格反向突破当前的跟踪止损,立即以市价平仓。
  • 当仓位清零时会重置所有内部状态,防止遗留数据。

参数

参数 说明 默认值
CandleType 用于计算的 K 线类型。 15 分钟 K 线
StopLossPips 止损距离(点),为 0 时关闭止损。 30
TakeProfitPips 止盈距离(点),为 0 时关闭止盈。 90
TrailingStopPips 跟踪止损的基础距离(点),为 0 时关闭跟踪。 1
TrailingStepPips 每次移动跟踪止损所需的额外盈利(点),启用跟踪时必须大于 0。 15
RollbackRatePips 触发信号所需的回撤幅度(点)。 15
ReverseSignal 反转买卖方向。 false

使用说明

  • 启动前需要设置 Volume 属性,它决定每次下单的数量。
  • 若启用跟踪止损,必须保证 TrailingStopPips > 0TrailingStepPips > 0,否则策略会在启动时抛出错误。
  • 原始 EA 在 K 线生成过程中基于逐笔数据判断,这一版本使用完成 K 线的最高价、最低价和收盘价近似原逻辑,从而保持与 StockSharp 高级 API 的兼容性。
  • 策略一次仅处理一个标的,若需多品种交易请创建多个策略实例。
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>
/// Rollback Rebound strategy that follows the TST (barabashkakvn's edition) expert advisor logic.
/// The strategy buys after a bullish bar pulls back from its high and sells after a bearish bar rebounds from its low.
/// Protective orders are managed in pips and include optional trailing logic with a rollback filter.
/// </summary>
public class RollbackReboundStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<decimal> _rollbackRatePips;
	private readonly StrategyParam<bool> _reverseSignal;

	private decimal _pipSize;
	private decimal _stopLossOffset;
	private decimal _takeProfitOffset;
	private decimal _trailingStopOffset;
	private decimal _trailingStepOffset;
	private decimal _rollbackOffset;

	private decimal _longEntryPrice;
	private decimal _longStopPrice;
	private decimal _longTakeProfitPrice;

	private decimal _shortEntryPrice;
	private decimal _shortStopPrice;
	private decimal _shortTakeProfitPrice;

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

	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Additional profit in pips required before the trailing stop moves.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Pullback threshold in pips to validate signals.
	/// </summary>
	public decimal RollbackRatePips
	{
		get => _rollbackRatePips.Value;
		set => _rollbackRatePips.Value = value;
	}

	/// <summary>
	/// Inverts entry direction.
	/// </summary>
	public bool ReverseSignal
	{
		get => _reverseSignal.Value;
		set => _reverseSignal.Value = value;
	}

	/// <summary>
	/// Initialize parameters with defaults derived from the original MQL expert.
	/// </summary>
	public RollbackReboundStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Candle series used for calculations.", "General");

		_stopLossPips = Param(nameof(StopLossPips), 30m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Distance of the protective stop in pips.", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 90m)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Distance of the take profit in pips.", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 20m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (pips)", "Trailing stop offset in pips.", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Step (pips)", "Additional profit required before trailing adjusts.", "Risk");

		_rollbackRatePips = Param(nameof(RollbackRatePips), 40m)
			.SetNotNegative()
			.SetDisplay("Rollback Threshold (pips)", "Minimum pullback from the bar extreme to trigger entries.", "Signal");

		_reverseSignal = Param(nameof(ReverseSignal), false)
			.SetDisplay("Reverse Signal", "Invert entry logic (buy becomes sell).", "Signal");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_pipSize = 0m;
		_stopLossOffset = 0m;
		_takeProfitOffset = 0m;
		_trailingStopOffset = 0m;
		_trailingStepOffset = 0m;
		_rollbackOffset = 0m;

		ResetLongState();
		ResetShortState();
	}

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

		// Validate that trailing configuration matches the behaviour of the original expert.
		if (TrailingStopPips > 0m && TrailingStepPips <= 0m)
			throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");

		// Convert pip-based parameters into absolute price offsets.
		_pipSize = Security?.PriceStep ?? 1m;
		if (Security != null && (Security.Decimals == 3 || Security.Decimals == 5))
			_pipSize *= 10m;

		_stopLossOffset = StopLossPips * _pipSize;
		_takeProfitOffset = TakeProfitPips * _pipSize;
		_trailingStopOffset = TrailingStopPips * _pipSize;
		_trailingStepOffset = TrailingStepPips * _pipSize;
		_rollbackOffset = RollbackRatePips * _pipSize;

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

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

	private void ProcessCandle(ICandleMessage candle)
	{
		// Work only with finished candles to emulate the IsNewBar check from the MQL expert.
		if (candle.State != CandleStates.Finished)
			return;

		// Update trailing stops and exit conditions before generating new signals.
		ManageOpenPosition(candle);

		// Skip signal generation until the strategy is online and allowed to trade.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Translate the rollback filters from the original EA using candle statistics.
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		var longCondition = open > close && high - close > _rollbackOffset;
		var shortCondition = close > open && close - low > _rollbackOffset;

		if (ReverseSignal)
		{
			(longCondition, shortCondition) = (shortCondition, longCondition);
		}

		if (longCondition && Position <= 0)
		{
			// Enter long when the rollback condition is met and the strategy is not already in a long position.
			var volume = Volume + Math.Abs(Position);
			if (volume <= 0m)
				return;

			BuyMarket(volume);
			InitializeLongState(candle);
		}
		else if (shortCondition && Position >= 0)
		{
			// Enter short when the bearish rollback occurs and we are not currently short.
			var volume = Volume + Math.Abs(Position);
			if (volume <= 0m)
				return;

			SellMarket(volume);
			InitializeShortState(candle);
		}
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		// Mirror the trailing and exit logic from the MetaTrader expert to keep behaviour identical.
		if (Position > 0)
		{
			// Use the candle high as the most optimistic price for long positions.
			var extreme = candle.HighPrice;

			if (_longEntryPrice == 0m)
				// Store the actual entry price once the trade is filled.
				_longEntryPrice = candle.ClosePrice;

			if (_trailingStopOffset > 0m)
			{
				// Apply the trailing algorithm for the active position.
				// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
				if (extreme - _longEntryPrice > _trailingStopOffset + _trailingStepOffset)
				{
					var threshold = extreme - (_trailingStopOffset + _trailingStepOffset);
					if (_longStopPrice == 0m || _longStopPrice < threshold)
						_longStopPrice = extreme - _trailingStopOffset;
				}
			}

			if (_longTakeProfitPrice > 0m && candle.HighPrice >= _longTakeProfitPrice)
			{
				// Exit the long position once the take-profit level is touched.
				SellMarket(Math.Abs(Position));
				ResetLongState();
				return;
			}

			if (_longStopPrice > 0m && candle.LowPrice <= _longStopPrice)
			{
				// Close the long position if the initial or trailing stop is triggered.
				SellMarket(Math.Abs(Position));
				ResetLongState();
				return;
			}
		}
		else if (Position < 0)
		{
			// Use the candle low as the best price in favour of the short position.
			var extreme = candle.LowPrice;

			if (_shortEntryPrice == 0m)
				// Capture the short entry price after execution.
				_shortEntryPrice = candle.ClosePrice;

			if (_trailingStopOffset > 0m)
			{
				// Apply the trailing algorithm for short positions.
				// Move the stop only when profit exceeds trailing stop plus step, exactly as in the MQL code.
				if (_shortEntryPrice - extreme > _trailingStopOffset + _trailingStepOffset)
				{
					var threshold = extreme + (_trailingStopOffset + _trailingStepOffset);
					if (_shortStopPrice == 0m || _shortStopPrice > threshold)
						_shortStopPrice = extreme + _trailingStopOffset;
				}
			}

			if (_shortTakeProfitPrice > 0m && candle.LowPrice <= _shortTakeProfitPrice)
			{
				// Exit the short position when the take-profit is hit.
				BuyMarket(Math.Abs(Position));
				ResetShortState();
				return;
			}

			if (_shortStopPrice > 0m && candle.HighPrice >= _shortStopPrice)
			{
				// Cover the short position if the stop level is breached.
				BuyMarket(Math.Abs(Position));
				ResetShortState();
				return;
			}
		}
		else
		{
			// Clear cached levels when no position is open.
			ResetLongState();
			ResetShortState();
		}
	}

	private void InitializeLongState(ICandleMessage candle)
	{
		// Clear short-side state because the strategy operates in netting mode.
		ResetShortState();

		var entry = candle.ClosePrice;
		// Save reference prices for managing the long position.
		_longEntryPrice = entry;
		_longStopPrice = StopLossPips > 0m ? entry - _stopLossOffset : 0m;
		_longTakeProfitPrice = TakeProfitPips > 0m ? entry + _takeProfitOffset : 0m;
	}

	private void InitializeShortState(ICandleMessage candle)
	{
		// Clear long-side state before opening a short position.
		ResetLongState();

		var entry = candle.ClosePrice;
		// Store price references for the short position.
		_shortEntryPrice = entry;
		_shortStopPrice = StopLossPips > 0m ? entry + _stopLossOffset : 0m;
		_shortTakeProfitPrice = TakeProfitPips > 0m ? entry - _takeProfitOffset : 0m;
	}

	private void ResetLongState()
	{
		_longEntryPrice = 0m;
		_longStopPrice = 0m;
		_longTakeProfitPrice = 0m;
	}

	private void ResetShortState()
	{
		_shortEntryPrice = 0m;
		_shortStopPrice = 0m;
		_shortTakeProfitPrice = 0m;
	}
}