在 GitHub 上查看

Huge Income 策略

概述

本策略是 MetaTrader 4 智能交易系统 “Huge Income” 的 StockSharp 移植版本。原始脚本关注日内价格相对每日开盘价的大幅拉伸,并在突破方向上开立单一仓位。StockSharp 版本保持这一思想:通过日内 K 线重建当日的开盘价、最高价和最低价,只允许同时持有一个仓位,并在配置的收市时间到来之前强制平仓。

数据与运行环境

  • 标的:任何提供有效价格步长(PriceStep)的品种。策略最初为外汇设计,调整参数后也可用于其他市场。
  • 时间框架:默认订阅 15 分钟 K 线来还原当日统计数据;如果数据源有更细的时间粒度,可以更改 CandleType
  • 交易时区:要求图表时间与券商/服务器时间一致,与原始 MetaTrader 版本相同。请按照该时区配置各个截止时间。

交易逻辑

  1. 每当有新 K 线到达时更新当日统计信息。当天第一根 K 线提供开盘价,并初始化最高/最低价。
  2. 系统任意时刻只允许一个仓位,既不开多个方向,也不挂出限价单或止损单,所有操作都使用市价单完成。
  3. 做多条件
    • 当前收盘价高于当日开盘价;
    • 当日开盘价与当日最低价之间的距离大于 MinimumRangePips(通过 PriceStep 转换为绝对价格);
    • 当前小时数小于 BuyCutoffHour
  4. 做空条件
    • 当前收盘价低于当日开盘价;
    • 当日最高价与开盘价之间的距离大于 MinimumRangePips
    • 当前小时数小于 SellCutoffHour
  5. 满足条件时,策略以 TradeVolume 的固定手数发送市价单,并在仓位平掉之前不再评估新的进场信号。
  6. 到达 MarketCloseHour 后,所有未平仓位都会通过市价单立即平仓,对应于原版 EA 在周五收盘前关闭订单的行为。

风险与资金管理

  • TradeVolume 用于设定固定下单手数。原策略没有加仓或马丁算法,移植版本同样保持恒定仓位规模。
  • 策略没有内置止损或止盈;风险控制依赖于日内区间过滤和强制收盘逻辑。若需要,可以在此基础上扩展保护模块(例如调用 StartProtection)。

参数说明

参数 说明
TradeVolume 发送 BuyMarket / SellMarket 市价单时使用的手数。
MinimumRangePips 当日开盘价与相反极值之间要求的最小距离(单位:点)。会根据 Security.PriceStep 转换成绝对价格。
BuyCutoffHour 允许开多单的最后小时(0–23),采用严格比较:currentHour < BuyCutoffHour
SellCutoffHour 允许开空单的最后小时(0–23)。
MarketCloseHour 强制平仓的小时数。设置为 23 可以再现原脚本在周五临近休市时的行为。
CandleType 用于订阅数据并重建当日统计信息的 K 线类型。

与 MT4 版本的差异

  • StockSharp 端使用 K 线数据而非逐笔报价。如果原策略依赖 Tick 级别,请选择更短的 CandleType 以保持响应速度。
  • 当标的没有提供 PriceStep 时,MinimumRangePips 过滤会自动失效,此时任何突破开盘价的机会都会被接受。
  • 所有操作都通过市价单执行,并在 MarketCloseHour 立即平仓,相当于原代码中的 OrderClose 循环,但不需要维护挂单。

使用建议

  • 根据所需的响应速度调整 K 线周期。周期越短,重建的日内高低点越准确,但对数据量要求也越高。
  • 确认标的实际交易时间。如果市场收盘早于 MarketCloseHour,则强制平仓会在下一个交易日触发。
  • 在实盘前结合账户级风险控制(如最大回撤限制、止损策略),以适应自己的资金管理需求。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Huge Income: EMA trend following with ATR stops.
/// </summary>
public class HugeIncomeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevClose;
	private decimal _entryPrice;

	public HugeIncomeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

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

		_prevClose = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0; _entryPrice = 0;
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (atrVal <= 0 || _prevClose == 0) { _prevClose = candle.ClosePrice; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close < emaVal || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if (close > emaVal || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (close > emaVal && _prevClose <= emaVal) { _entryPrice = close; BuyMarket(); }
			else if (close < emaVal && _prevClose >= emaVal) { _entryPrice = close; SellMarket(); }
		}
		_prevClose = close;
	}
}