在 GitHub 上查看
HarVesteR 策略
概述
HarVesteR 策略源自同名的 MetaTrader 智能交易系统,属于典型的趋势跟随模型。策略通过 MACD 动能确认与两条简单移动平均线配合,判定市场方向并管理持仓。可选的 ADX 滤波器能够在趋势充足时才允许开仓,从而减少震荡期的交易。
默认参数完全复刻原版 EA:MACD(12, 24, 9)、50 周期管理 SMA、100 周期趋势 SMA,以及当价格走出两倍初始风险距离时的分批止盈机制。
交易逻辑
- 趋势偏向——100 周期 SMA 充当方向门槛。收盘价跌破该均线时激活多头准备,收盘价上破时激活空头准备。每次交易完成后标志会被清零,只有当价格重新穿越趋势均线时才会再次允许同方向入场。
- MACD 确认——只有当 MACD 主线位于零轴预期一侧,且在最近 MACD 确认柱数 内至少有一次位于另一侧时才算有效信号,对应了原始 MQL 代码中向前回溯查找异号的循环。
- 入场条件——多头要求收盘价加上设定的点差偏移量同时高于两条 SMA,MACD 为正值,并且(若启用)ADX 大于 50;空头逻辑为镜像条件。
- 初始止损——止损价格取最近 止损柱数 根已完成 K 线的最低价(多头)或最高价(空头),与 MQL 中
iLowest/iHighest 以偏移 1 根的调用保持一致。
- 仓位管理——当价格向盈利方向运行,达到初始风险距离的 风险倍数 时平掉一半仓位,并将止损移动到入场价。剩余仓位在 50 周期 SMA 与经偏移调整后的收盘价发生反向交叉时全部离场。
- 保护性离场——任意一根 K 线触及当前止损价位都会立即平仓,防止回撤扩大。
参数
| 参数 |
说明 |
默认值 |
Fast EMA |
MACD 中使用的快速 EMA 周期。 |
12 |
Slow EMA |
MACD 中使用的慢速 EMA 周期。 |
24 |
Signal EMA |
MACD 信号线平滑周期。 |
9 |
MACD Confirmation Bars |
MACD 需在该数量的 K 线内出现一次反向值。 |
6 |
Trend SMA |
管理用 SMA 的周期,控制拖尾退出。 |
50 |
Filter SMA |
趋势过滤 SMA 的周期。 |
100 |
Offset (points) |
与均线比较时加/减的点数偏移。 |
10 |
Stop Bars |
用于计算初始止损的历史 K 线数量。 |
6 |
Risk Multiplier |
触发分批止盈时的风险倍数。 |
2.0 |
Use ADX |
是否启用 ADX>50 的趋势强度过滤。 |
关闭 |
ADX Period |
启用过滤时 ADX 的计算周期。 |
14 |
Candle Type |
指标所用的 K 线类型(默认 1 小时)。 |
1 小时 |
实现细节
- 点值换算优先采用
Security.Step 或 Security.PriceStep,若行情未提供最小跳动则退化为 0.0001,与原始外汇策略的“Point” 概念保持一致。
- 分批止盈通过市价单直接平掉当前仓位的一半,完全复刻原 EA 中的手数调整方式。
- 调用了
StartProtection(),确保策略在真正进场前已经启动内置的保护机制。
- 当 ADX 过滤关闭时,算法会用常数 60 代替 ADX 数值,因此整体行为与历史脚本完全一致。
使用建议
- 启动策略前请设置
Volume(下单手数),该值同时影响初始建仓与分批平仓的数量。
- 根据研究周期调整
Candle Type。原版针对小时线进行设计,如需尝试更短周期可结合参数优化。
MACD Confirmation Bars、Offset (points) 与 Risk Multiplier 对胜率和交易频率影响最大,建议优先纳入回测调优范围。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class HarVesteRMacdTrendStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast; private decimal _prevSlow; private bool _hasPrev;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public HarVesteRMacdTrendStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = default;
_prevSlow = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fast; _prevSlow = slow;
return;
}
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 2;
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 2;
}
_prevFast = fast; _prevSlow = slow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class har_veste_r_macd_trend_strategy(Strategy):
def __init__(self):
super(har_veste_r_macd_trend_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 26).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False; self._cooldown = 0
@property
def fast_period(self): return self._fast_period.Value
@property
def slow_period(self): return self._slow_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(har_veste_r_macd_trend_strategy, self).OnReseted()
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False; self._cooldown = 0
def OnStarted2(self, time):
super(har_veste_r_macd_trend_strategy, self).OnStarted2(time)
self._has_prev = False; self._cooldown = 0
fast = ExponentialMovingAverage(); fast.Length = self.fast_period
slow = ExponentialMovingAverage(); slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished: return
if not self.IsFormedAndOnlineAndAllowTrading(): return
fast_val = float(fast); slow_val = float(slow)
if not self._has_prev:
self._prev_fast = fast_val; self._prev_slow = slow_val; self._has_prev = True; return
if self._cooldown > 0:
self._cooldown -= 1; self._prev_fast = fast_val; self._prev_slow = slow_val; return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume); self._cooldown = 2
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume); self._cooldown = 2
self._prev_fast = fast_val; self._prev_slow = slow_val
def CreateClone(self): return har_veste_r_macd_trend_strategy()