在 GitHub 上查看
Huge Income 策略
概述
本策略是 MetaTrader 4 智能交易系统 “Huge Income” 的 StockSharp 移植版本。原始脚本关注日内价格相对每日开盘价的大幅拉伸,并在突破方向上开立单一仓位。StockSharp 版本保持这一思想:通过日内 K 线重建当日的开盘价、最高价和最低价,只允许同时持有一个仓位,并在配置的收市时间到来之前强制平仓。
数据与运行环境
- 标的:任何提供有效价格步长(
PriceStep)的品种。策略最初为外汇设计,调整参数后也可用于其他市场。
- 时间框架:默认订阅 15 分钟 K 线来还原当日统计数据;如果数据源有更细的时间粒度,可以更改
CandleType。
- 交易时区:要求图表时间与券商/服务器时间一致,与原始 MetaTrader 版本相同。请按照该时区配置各个截止时间。
交易逻辑
- 每当有新 K 线到达时更新当日统计信息。当天第一根 K 线提供开盘价,并初始化最高/最低价。
- 系统任意时刻只允许一个仓位,既不开多个方向,也不挂出限价单或止损单,所有操作都使用市价单完成。
- 做多条件:
- 当前收盘价高于当日开盘价;
- 当日开盘价与当日最低价之间的距离大于
MinimumRangePips(通过 PriceStep 转换为绝对价格);
- 当前小时数小于
BuyCutoffHour。
- 做空条件:
- 当前收盘价低于当日开盘价;
- 当日最高价与开盘价之间的距离大于
MinimumRangePips;
- 当前小时数小于
SellCutoffHour。
- 满足条件时,策略以
TradeVolume 的固定手数发送市价单,并在仓位平掉之前不再评估新的进场信号。
- 到达
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;
}
}
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
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class huge_income_strategy(Strategy):
def __init__(self):
super(huge_income_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_close = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(huge_income_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if av <= 0 or self._prev_close == 0:
self._prev_close = close
return
if self.Position > 0:
if close < ev or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close > ev or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > ev and self._prev_close <= ev:
self._entry_price = close
self.BuyMarket()
elif close < ev and self._prev_close >= ev:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(huge_income_strategy, self).OnReseted()
self._prev_close = 0.0
self._entry_price = 0.0
def CreateClone(self):
return huge_income_strategy()