在 GitHub 上查看
HTH Trader 对冲策略
概述
本策略为 MetaTrader "HTH Trader" 智能交易系统的移植版本。它通过构建由 EURUSD、USDCHF、GBPUSD、AUDUSD 组成的四腿外汇篮子来博取日内均值回归。StockSharp 版本保留了原策略的时间安排、风险控制以及应急加仓逻辑,并改用高级 API 处理多品种交易。
主要特点:
- 每天在服务器时间 00:05–00:12 之间开仓一次,对四个品种同时建立对冲头寸。
- 根据 EURUSD 最近两个日线收盘价的变化方向来决定整个篮子的多空方向。
- 同时管理四个证券:EURUSD(主证券)、USDCHF、GBPUSD、AUDUSD。
- 以“点”为单位跟踪未实现盈亏,并支持整篮的止盈/止损目标。
- 当总体亏损达到阈值时,可对当前盈利的腿执行应急加仓。
- 在 23:00 或达到目标条件时平掉全部仓位。
数据要求
- 日内 K 线:所有四个品种都必须提供
IntradayCandleType 指定的日内周期(默认 5 分钟),用于更新时间和最新价格。
- 日线 K 线:每个品种都需要提供日线数据,以便获取最近两个完整交易日的收盘价。
交易流程
- 每根日内 K 线完成后,策略会计算当前篮子的总浮动盈亏:
- 如果启用了
AllowEmergencyTrading,且总盈亏 ≤ -EmergencyLossPips,则对所有盈利的腿执行一次加倍建仓,并在当日内停用该功能。
- 如果启用了
UseProfitTarget 且盈亏 ≥ ProfitTargetPips,立即平掉所有仓位。
- 如果启用了
UseLossLimit 且盈亏 ≤ -LossLimitPips,立即平掉所有仓位。
- 当时间到达 23:00 时,无条件平仓。
- 在没有持仓且时间处于 00:05–00:12 区间内时,比较 EURUSD 最近两个日线收盘价:
- 上涨:买入 EURUSD、USDCHF、AUDUSD,卖出 GBPUSD。
- 下跌:卖出 EURUSD、USDCHF、AUDUSD,买入 GBPUSD。
- 若变化为零或缺少数据,则当天不交易。
- 通过
ClosePosition 使用市价单关闭所有头寸。
参数说明
| 参数 |
作用 |
默认值 |
TradeEnabled |
是否允许提交订单。 |
true |
ShowProfitInfo |
在持仓期间记录篮子的浮动盈亏(点)。 |
true |
UseProfitTarget |
是否启用止盈目标。 |
false |
UseLossLimit |
是否启用止损目标。 |
false |
AllowEmergencyTrading |
是否允许应急加仓。 |
true |
EmergencyLossPips |
触发应急加仓的亏损阈值(点)。 |
60 |
ProfitTargetPips |
触发止盈的盈利阈值(点)。 |
80 |
LossLimitPips |
触发止损的亏损阈值(点)。 |
40 |
TradingVolume |
每条腿使用的下单量。 |
0.01 |
Symbol2 |
第二个品种(默认 USDCHF)。 |
null |
Symbol3 |
第三个品种(默认 GBPUSD)。 |
null |
Symbol4 |
第四个品种(默认 AUDUSD)。 |
null |
IntradayCandleType |
日内行情周期。 |
5 分钟 K 线 |
使用建议
- 在启动前将
Strategy.Security 设为 EURUSD(或其他主导品种),并为 Symbol2、Symbol3、Symbol4 指定相应的证券。
- 确保所有证券都设置了有效的
PriceStep,否则无法计算点值,应急逻辑也不会触发。
- 应急加仓只会作用于当前盈利的腿,以避免扩大现有亏损。
- 默认假设市价单在最近一根 K 线收盘价附近成交,如需更精确结果,请连接实时、稳定的日内行情源。
- 由于策略在 K 线结束时做出决策,执行时刻可能与 MetaTrader 的逐笔版本略有差异,但整体决策流程保持一致。
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>
/// Hedge strategy simplified from HTH Trader. Trades based on daily close deviation.
/// </summary>
public class HthTraderStrategy : Strategy
{
private readonly StrategyParam<bool> _tradeEnabled;
private readonly StrategyParam<bool> _useProfitTarget;
private readonly StrategyParam<bool> _useLossLimit;
private readonly StrategyParam<int> _profitTargetPips;
private readonly StrategyParam<int> _lossLimitPips;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose1;
private decimal _prevClose2;
private decimal _entryPrice;
private decimal _priceStep;
/// <summary>
/// Enable automated trading.
/// </summary>
public bool TradeEnabled
{
get => _tradeEnabled.Value;
set => _tradeEnabled.Value = value;
}
/// <summary>
/// Enable closing by reaching the profit target.
/// </summary>
public bool UseProfitTarget
{
get => _useProfitTarget.Value;
set => _useProfitTarget.Value = value;
}
/// <summary>
/// Enable closing by reaching the loss limit.
/// </summary>
public bool UseLossLimit
{
get => _useLossLimit.Value;
set => _useLossLimit.Value = value;
}
/// <summary>
/// Profit target in pips.
/// </summary>
public int ProfitTargetPips
{
get => _profitTargetPips.Value;
set => _profitTargetPips.Value = value;
}
/// <summary>
/// Loss limit in pips.
/// </summary>
public int LossLimitPips
{
get => _lossLimitPips.Value;
set => _lossLimitPips.Value = value;
}
/// <summary>
/// Candle type for monitoring.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes parameters.
/// </summary>
public HthTraderStrategy()
{
_tradeEnabled = Param(nameof(TradeEnabled), true)
.SetDisplay("Trade Enabled", "Allow the strategy to submit orders", "General");
_useProfitTarget = Param(nameof(UseProfitTarget), true)
.SetDisplay("Use Profit Target", "Close when profit target is reached", "Risk");
_useLossLimit = Param(nameof(UseLossLimit), true)
.SetDisplay("Use Loss Limit", "Close when loss limit is reached", "Risk");
_profitTargetPips = Param(nameof(ProfitTargetPips), 80)
.SetDisplay("Profit Target (pips)", "Profit target in pips", "Risk");
_lossLimitPips = Param(nameof(LossLimitPips), 40)
.SetDisplay("Loss Limit (pips)", "Loss limit in pips", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for monitoring", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose1 = 0m;
_prevClose2 = 0m;
_entryPrice = 0m;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 0.0001m;
if (_priceStep <= 0m)
_priceStep = 0.0001m;
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!TradeEnabled)
return;
// Check exit conditions
if (Position != 0 && _entryPrice > 0m)
{
var priceDiff = Position > 0
? candle.ClosePrice - _entryPrice
: _entryPrice - candle.ClosePrice;
var pipsDiff = priceDiff / _priceStep;
if (UseProfitTarget && pipsDiff >= ProfitTargetPips)
{
if (Position > 0) SellMarket();
else BuyMarket();
_entryPrice = 0m;
return;
}
if (UseLossLimit && pipsDiff <= -LossLimitPips)
{
if (Position > 0) SellMarket();
else BuyMarket();
_entryPrice = 0m;
return;
}
}
// Entry logic based on daily close deviation
if (Position == 0 && _prevClose1 > 0m && _prevClose2 > 0m)
{
var deviation = (100m * _prevClose1 / _prevClose2) - 100m;
if (deviation > 0.1m)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (deviation < -0.1m)
{
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
_prevClose2 = _prevClose1;
_prevClose1 = candle.ClosePrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class hth_trader_strategy(Strategy):
def __init__(self):
super(hth_trader_strategy, self).__init__()
self._trade_enabled = self.Param("TradeEnabled", True)
self._use_profit_target = self.Param("UseProfitTarget", True)
self._use_loss_limit = self.Param("UseLossLimit", True)
self._profit_target_pips = self.Param("ProfitTargetPips", 80)
self._loss_limit_pips = self.Param("LossLimitPips", 40)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_close1 = 0.0
self._prev_close2 = 0.0
self._entry_price = 0.0
self._price_step = 0.0
@property
def TradeEnabled(self):
return self._trade_enabled.Value
@TradeEnabled.setter
def TradeEnabled(self, value):
self._trade_enabled.Value = value
@property
def UseProfitTarget(self):
return self._use_profit_target.Value
@UseProfitTarget.setter
def UseProfitTarget(self, value):
self._use_profit_target.Value = value
@property
def UseLossLimit(self):
return self._use_loss_limit.Value
@UseLossLimit.setter
def UseLossLimit(self, value):
self._use_loss_limit.Value = value
@property
def ProfitTargetPips(self):
return self._profit_target_pips.Value
@ProfitTargetPips.setter
def ProfitTargetPips(self, value):
self._profit_target_pips.Value = value
@property
def LossLimitPips(self):
return self._loss_limit_pips.Value
@LossLimitPips.setter
def LossLimitPips(self, value):
self._loss_limit_pips.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(hth_trader_strategy, self).OnStarted2(time)
self._price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
if self._price_step <= 0.0:
self._price_step = 0.0001
self._prev_close1 = 0.0
self._prev_close2 = 0.0
self._entry_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.TradeEnabled:
return
close = float(candle.ClosePrice)
# Check exit conditions
if self.Position != 0 and self._entry_price > 0.0:
if self.Position > 0:
price_diff = close - self._entry_price
else:
price_diff = self._entry_price - close
pips_diff = price_diff / self._price_step
if self.UseProfitTarget and pips_diff >= int(self.ProfitTargetPips):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
return
if self.UseLossLimit and pips_diff <= -int(self.LossLimitPips):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
return
# Entry logic
if self.Position == 0 and self._prev_close1 > 0.0 and self._prev_close2 > 0.0:
deviation = (100.0 * self._prev_close1 / self._prev_close2) - 100.0
if deviation > 0.1:
self.BuyMarket()
self._entry_price = close
elif deviation < -0.1:
self.SellMarket()
self._entry_price = close
self._prev_close2 = self._prev_close1
self._prev_close1 = close
def OnReseted(self):
super(hth_trader_strategy, self).OnReseted()
self._prev_close1 = 0.0
self._prev_close2 = 0.0
self._entry_price = 0.0
self._price_step = 0.0
def CreateClone(self):
return hth_trader_strategy()