HPCS Inter5 策略
概览
HPCS Inter5 Strategy 源自 MetaTrader 4 脚本 _HPCS_Inter5_MT4_EA_V01_WE,属于一次性动量策略。策略启动时会检查最近完成的 K 线,如果五根之前的收盘价高于最新收盘价,就会立即发送一笔市价买单。可选的止损与止盈距离按照 MetaTrader 中的“点(pip)”换算,以保持与原始脚本一致的风险管理。
交易逻辑
- 订阅设定的 K 线类型,并维护最近六根已完成 K 线的收盘价。
- 当缓冲区填满后,比较五根之前的收盘价与最近一次收盘价(等同于 MetaTrader 中的
Close[5] > Close[1]条件)。 - 若条件成立且尚未下单,则按设定手数提交一笔市价买单。
- 在
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(),确保数据已就绪且允许交易。
使用建议
- 请选择已正确设置价格步长和交易量参数的外汇合约。
- 根据需求调整
Candle Type的时间周期。 - 如果希望手动管理退出,可将止损或止盈设为
0。 - 该策略每次运行只会尝试入场一次,若想重新评估信号,请重新启动策略。
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class hpcs_inter5_strategy(Strategy):
"""
HPCS Inter5: momentum strategy comparing close from 5 bars ago
vs current close. Buys when older close exceeds recent by threshold.
Sells on reverse. Uses StartProtection for SL/TP.
"""
def __init__(self):
super(hpcs_inter5_strategy, self).__init__()
self._stop_loss_pips = self.Param("StopLossPips", 20) \
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk Management")
self._take_profit_pips = self.Param("TakeProfitPips", 20) \
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(120))) \
.SetDisplay("Candle Type", "Candle type for close comparison", "General")
self._recent_closes = [None] * 6
self._pip_size = 0.0
self._was_long_signal = False
self._has_signal = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hpcs_inter5_strategy, self).OnReseted()
self._recent_closes = [None] * 6
self._pip_size = 0.0
self._was_long_signal = False
self._has_signal = False
def OnStarted2(self, time):
super(hpcs_inter5_strategy, self).OnStarted2(time)
step = 0.01
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 0.01
decimals = 0
if self.Security is not None and self.Security.Decimals is not None:
decimals = int(self.Security.Decimals)
pip_factor = 10.0 if decimals in (3, 5) else 1.0
self._pip_size = step * pip_factor
sl_pips = self._stop_loss_pips.Value
tp_pips = self._take_profit_pips.Value
sl = None
tp = None
if sl_pips > 0 and self._pip_size > 0:
sl = Unit(sl_pips * self._pip_size, UnitTypes.Absolute)
if tp_pips > 0 and self._pip_size > 0:
tp = Unit(tp_pips * self._pip_size, UnitTypes.Absolute)
if sl is not None or tp is not None:
self.StartProtection(tp, sl)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
for i in range(len(self._recent_closes) - 1, 0, -1):
self._recent_closes[i] = self._recent_closes[i - 1]
self._recent_closes[0] = close
last_close = self._recent_closes[1]
older_close = self._recent_closes[5]
if last_close is None or older_close is None:
return
threshold = last_close * 0.005
long_signal = older_close - last_close > threshold
short_signal = last_close - older_close > threshold
crossed_long = long_signal and (not self._has_signal or not self._was_long_signal)
crossed_short = short_signal and (not self._has_signal or self._was_long_signal)
if crossed_long and self.Position <= 0:
self.BuyMarket()
elif crossed_short and self.Position >= 0:
self.SellMarket()
if long_signal or short_signal:
self._was_long_signal = long_signal
self._has_signal = True
def CreateClone(self):
return hpcs_inter5_strategy()