在 GitHub 上查看

Hpcs Inter7 策略

概述

Hpcs Inter7 策略是从 MetaTrader 4 专家顾问 _HPCS_Inter7_MT4_EA_V01_We.mq4 转换而来的布林带突破系统。算法在所选 K 线序列上计算标准布林带,当收盘价向带外突破时,会顺势开仓。每次开仓后都会立即设置固定距离的止损与止盈,以复现原始 EA 的风险控制方式。

交易逻辑

  • 做空入场:若前一根 K 线收盘价位于下轨之上,而最新完成的 K 线收盘价跌破下轨,则以市价卖出,对应原始条件 Close[0] < LowerBand[0] && Close[1] > LowerBand[1]
  • 做多入场:若前一根 K 线收盘价位于上轨之下,而最新完成的 K 线收盘价突破上轨,则以市价买入,复现条件 Close[0] > UpperBand[0] && Close[1] < UpperBand[1]
  • 每根 K 线仅一笔交易:策略记录触发信号的 K 线开盘时间,如果同一根 K 线再次出现信号将被忽略,对应 MQL4 中的 gdt_Candle 变量。
  • 保护性订单:在开仓后立即调用 SetStopLossSetTakeProfit,按照配置的距离在入场价上下对称放置止损止盈,确保持仓始终具有预定义的风险与收益。

参数

名称 说明 默认值 可优化
BollingerLength 参与布林带计算的 K 线数量。 20
BollingerDeviation 布林带宽度所使用的标准差倍数。 2
CandleType 用于计算的 K 线类型(默认 1 分钟)。 1 分钟 K 线
ProtectionDistancePoints 以价格步长为单位的止损与止盈距离。 10

其他说明

  • 策略使用 StockSharp 高阶 API(SubscribeCandles().Bind(...)),无需维护自定义历史数组。
  • 启动时调用 StartProtection(),平台会自动管理由 SetStopLossSetTakeProfit 创建的保护单。
  • 下单量由基类 Strategy.Volume 控制,与原始 EA 固定 1 手的做法一致。
  • 该策略源自外汇市场,但只要标的提供有效的布林带信号并具有合理的 PriceStep,同样可以应用于其他品种。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy converted from _HPCS_Inter7_MT4_EA_V01_We.mq4.
/// Sells when price crosses below the lower Bollinger band and buys when price crosses above the upper band.
/// </summary>
public class HpcsInter7Strategy : Strategy
{
	private readonly StrategyParam<int> _bollingerLength;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _bandPercent;

	private decimal? _prevClose;
	private decimal? _prevLower;
	private decimal? _prevUpper;

	/// <summary>
	/// Initializes a new instance of the <see cref="HpcsInter7Strategy"/> class.
	/// </summary>
	public HpcsInter7Strategy()
	{
		_bollingerLength = Param(nameof(BollingerLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Length", "Number of candles included in the Bollinger Bands calculation", "Indicators");

		_bollingerDeviation = Param(nameof(BollingerDeviation), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for the Bollinger Bands", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for Bollinger Bands", "General");

		_bandPercent = Param(nameof(BandPercent), 0.01m)
			.SetGreaterThanZero()
			.SetDisplay("Band Percent", "MA percentage band width", "Indicators");
	}

	/// <summary>
	/// Bollinger Bands length.
	/// </summary>
	public int BollingerLength
	{
		get => _bollingerLength.Value;
		set => _bollingerLength.Value = value;
	}

	/// <summary>
	/// Bollinger Bands deviation multiplier.
	/// </summary>
	public decimal BollingerDeviation
	{
		get => _bollingerDeviation.Value;
		set => _bollingerDeviation.Value = value;
	}

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

	public decimal BandPercent
	{
		get => _bandPercent.Value;
		set => _bandPercent.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = null;
		_prevLower = null;
		_prevUpper = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = null;
		_prevLower = null;
		_prevUpper = null;

		var bollinger = new ExponentialMovingAverage { Length = BollingerLength };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var upper = middle * (1 + BandPercent);
		var lower = middle * (1 - BandPercent);

		if (_prevClose.HasValue && _prevLower.HasValue && _prevUpper.HasValue)
		{
			// Downward cross through the lower band - open short
			if (_prevClose.Value > _prevLower.Value && candle.ClosePrice < lower && Position >= 0)
			{
				SellMarket();
			}
			// Upward cross through the upper band - open long
			else if (_prevClose.Value < _prevUpper.Value && candle.ClosePrice > upper && Position <= 0)
			{
				BuyMarket();
			}
		}

		_prevClose = candle.ClosePrice;
		_prevLower = lower;
		_prevUpper = upper;
	}
}