在 GitHub 上查看

Hedge Any Positions 策略

概述

Hedge Any Positions Strategy 是对原始 Hedge any positions (barabashkakvn's edition) MQL5 智能交易系统的 StockSharp 版本。核心思想保持不变:策略会跟踪其创建的每一条持仓腿,当价格朝不利方向移动达到指定的点数时,立即以放大的手数开立反向仓位对冲。实现完全依赖 StockSharp 的高级 API,因此对冲通过市价单完成,仓位状态使用内部列表维护,无需额外的底层交易代码。

策略可选在启动时自动建立第一笔交易。之后它只会响应浮动亏损的扩大,逐步构建对冲链条,并将已经对冲的腿标记为“已处理”,防止同一笔持仓重复触发多个反向订单。

对冲流程

  1. 数据驱动:可配置的 CandleType 作为时间框架,只处理收盘完成的 K 线。
  2. 亏损判断:在每根 K 线收盘时,检查收盘价相对于各持仓腿的入场价是否出现至少 LosingPips × 点值的反向波动。
  3. 执行对冲:若发现满足条件的亏损腿,则按照相反方向发送市价单。新订单的数量等于原始腿的数量乘以 LotCoefficient,并根据交易品种的数量步长、最小和最大允许数量进行调整。
  4. 状态更新:对冲指令发出后,将原腿标记为已对冲,并把新开仓记录为新的腿;若价格再次反向,新腿也可以继续触发下一次对冲。

参数

参数 说明 默认值
CandleType 用于评估价格波动并触发对冲的时间框架。 1 分钟 K 线
LosingPips 价格逆向移动的点数阈值,超过后触发对冲。 5
LotCoefficient 计算对冲单数量时使用的乘数。 2.0
AutoPlaceInitialTrade 启用后,在策略启动时自动发送首笔交易。 关闭
InitialVolume 自动首笔交易使用的数量(会按数量步长取整)。 0.10
InitialDirection 自动首笔交易的方向(买入或卖出)。 买入

提示: 请在启动前设置 Strategy.Volume 属性,指定基础下单数量。上述参数仅影响对冲逻辑。

使用建议

  1. 启动前为策略指定 SecurityPortfolio 以及基础交易数量 Volume
  2. 根据品种波动性和风险承受能力调整 LosingPipsLotCoefficient
  3. 如果希望策略自行建立第一笔仓位,可启用 AutoPlaceInitialTrade;否则请通过手动或其他模块提供初始持仓。
  4. 由于 StockSharp 高级 API 采用净持仓模型,策略内部的腿列表用于模拟锁仓结构。在净额账户上运行时需留意总体敞口与保证金占用。
  5. 注意查看成交报告:所有对冲操作均通过 BuyMarketSellMarket 市价单完成。

与原始版本的差异

  • 移除了保证金检查、滑点控制及详细的结果日志,相关信息可通过 StockSharp 的事件回调获取。
  • 转换版本基于收盘 K 线而非逐笔行情,如需更快响应可选择更小的时间框架。
  • 数量取整依赖 Security.VolumeStepSecurity.MinVolumeSecurity.MaxVolume,从而符合品种的交易规则。
  • 原策略中的通知功能和仅用于测试的随机首笔交易被省略,改为可配置的自动建仓参数。

建议的扩展方向

  • 将该对冲模块与独立的入场策略结合,明确何时建立初始仓位。
  • 增加基于权益的停止条件或链条深度限制,避免无限制地累积对冲。
  • 加入组合层面的风险监控,以确保保证金和资金占用处于可控范围。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Hedge Any Positions strategy using ATR-based mean reversion.
/// Enters long when price drops below EMA by ATR threshold, enters short on rally above.
/// Uses stop-loss and take-profit for risk management.
/// </summary>
public class HedgeAnyPositionsStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _ema;
	private AverageTrueRange _atr;

	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// EMA period.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// ATR period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier applied to ATR for entry threshold.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="HedgeAnyPositionsStrategy"/> class.
	/// </summary>
	public HedgeAnyPositionsStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period for mean price", "Indicator");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR calculation period", "Indicator");

		_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "Multiplier for entry distance", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_ema = null;
		_atr = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ema = new ExponentialMovingAverage { Length = EmaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_ema, _atr, ProcessCandle);
		subscription.Start();
	}

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

		if (!_ema.IsFormed || !_atr.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;
		var threshold = atrValue * AtrMultiplier;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}
		}

		// Mean reversion: buy when price drops below EMA by threshold
		if (close < emaValue - threshold && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		// Mean reversion: sell when price rallies above EMA by threshold
		else if (close > emaValue + threshold && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
	}
}