在 GitHub 上查看

HPCS Inter5 策略

概览

HPCS Inter5 Strategy 源自 MetaTrader 4 脚本 _HPCS_Inter5_MT4_EA_V01_WE,属于一次性动量策略。策略启动时会检查最近完成的 K 线,如果五根之前的收盘价高于最新收盘价,就会立即发送一笔市价买单。可选的止损与止盈距离按照 MetaTrader 中的“点(pip)”换算,以保持与原始脚本一致的风险管理。

交易逻辑

  1. 订阅设定的 K 线类型,并维护最近六根已完成 K 线的收盘价。
  2. 当缓冲区填满后,比较五根之前的收盘价与最近一次收盘价(等同于 MetaTrader 中的 Close[5] > Close[1] 条件)。
  3. 若条件成立且尚未下单,则按设定手数提交一笔市价买单。
  4. OnStarted 阶段调用 StartProtection 激活保护性订单。若标的的小数位数为 3 或 5,则将 PriceStep 乘以 10 计算每个点的价格,否则直接使用 PriceStep

策略在首次建仓后不再响应后续信号,也不会追加加仓。

参数

名称 默认值 说明
Candle Type 1 分钟周期 用于采集收盘价的 K 线类型,请根据需要的时间框架进行调整。
Stop Loss (pips) 10 保护性止损的点数距离,设置为 0 表示不启用止损。
Take Profit (pips) 10 保护性止盈的点数距离,设置为 0 表示不启用止盈。
Trade Volume 1 当入场条件成立时提交的市价单数量。

实现细节

  • 策略依赖 Security.PriceStep(或 Security.Step)来换算点值,若未配置,将无法计算止损/止盈距离,但入场信号仍会触发。
  • 仅处理状态为 CandleStates.Finished 的 K 线,以复刻 MetaTrader 只使用已完成柱体的做法。
  • 使用固定长度的收盘价缓冲区,不依赖额外的指标历史数据,保持原脚本的简洁性。
  • 在下单前调用 IsFormedAndOnlineAndAllowTrading(),确保数据已就绪且允许交易。

使用建议

  1. 请选择已正确设置价格步长和交易量参数的外汇合约。
  2. 根据需求调整 Candle Type 的时间周期。
  3. 如果希望手动管理退出,可将止损或止盈设为 0
  4. 该策略每次运行只会尝试入场一次,若想重新评估信号,请重新启动策略。
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Single-shot momentum strategy converted from the "_HPCS_Inter5" MetaTrader script.
/// Places one market buy when the close price from five bars ago exceeds the most recent close.
/// </summary>
public class HpcsInter5Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<decimal> _tradeVolume;

	private readonly decimal?[] _recentCloses = new decimal?[6];
	private decimal _pipSize;
	private bool _wasLongSignal;
	private bool _hasSignal;

	/// <summary>
	/// Candle type used to evaluate the closing prices.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in MetaTrader-style pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in MetaTrader-style pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trade volume submitted with the market entry.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="HpcsInter5Strategy"/> class.
	/// </summary>
	public HpcsInter5Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
			.SetDisplay("Candle Type", "Candle type used for the close comparison", "General");

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance expressed in pips", "Risk Management");

		_takeProfitPips = Param(nameof(TakeProfitPips), 20)
			.SetDisplay("Take Profit (pips)", "Take-profit distance expressed in pips", "Risk Management");

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Volume submitted with the market entry", "Trading");
	}

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

		Array.Clear(_recentCloses, 0, _recentCloses.Length);
		_pipSize = 0m;
		_wasLongSignal = false;
		_hasSignal = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		if (Security is null)
			throw new InvalidOperationException("Security must be assigned before starting the strategy.");

		base.OnStarted2(time);

		InitializePipSize();
		Volume = TradeVolume;
		_wasLongSignal = false;
		_hasSignal = false;

		var stopLoss = StopLossPips > 0 && _pipSize > 0m
			? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
			: null;

		var takeProfit = TakeProfitPips > 0 && _pipSize > 0m
			? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
			: null;

		if (stopLoss != null || takeProfit != null)
			StartProtection(stopLoss: stopLoss, takeProfit: takeProfit);

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

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

		ShiftCloses(candle.ClosePrice);

		if (_recentCloses[1] is not decimal lastClose || _recentCloses[5] is not decimal olderClose)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var threshold = lastClose * 0.005m;
		var longSignal = olderClose - lastClose > threshold;
		var shortSignal = lastClose - olderClose > threshold;
		var crossedLong = longSignal && (!_hasSignal || !_wasLongSignal);
		var crossedShort = shortSignal && (!_hasSignal || _wasLongSignal);

		if (crossedLong && Position <= 0)
		{
			BuyMarket();
		}
		else if (crossedShort && Position >= 0)
		{
			SellMarket();
		}

		if (longSignal || shortSignal)
		{
			_wasLongSignal = longSignal;
			_hasSignal = true;
		}
	}

	private void ShiftCloses(decimal close)
	{
		for (var i = _recentCloses.Length - 1; i > 0; i--)
			_recentCloses[i] = _recentCloses[i - 1];

		_recentCloses[0] = close;
	}

	private void InitializePipSize()
	{
		var step = Security.PriceStep ?? 0.01m;
		if (step <= 0m)
			step = 0.01m;

		var pipFactor = Security.Decimals is 3 or 5 ? 10m : 1m;
		_pipSize = step * pipFactor;
	}
}