在 GitHub 上查看

GLFX 策略

将 MetaTrader 4 的 GLFX 专家顾问移植到 StockSharp 高阶 API。保留了原版“先通过高一级别图表确认、再开仓”的核心思想,同时剔除了依赖外部指标的大量附加模块,使策略更易维护。

交易逻辑

  1. 策略在一个工作级别(默认 M15)运行,并可按 MetaTrader 的时间阶梯(M15 → M30 → H1 → H4 → D1 → W1 → MN)向上寻找用于确认的时间框。
  2. 使用高周期 RSI(默认周期 57)判断动量变化。当 RSI 向上但尚未进入超买区时形成多头确认;当 RSI 向下但仍高于超卖线时形成空头确认。
  3. 使用高周期 简单移动平均线(默认周期 60)衡量价格相对均值的偏离。若均线抬头并高于当前收盘价,视为多头回调;若均线下行且低于收盘价,则视为空头回调。
  4. 每个启用的过滤器贡献 +1(多)或 -1(空)。只有当合计值达到启用过滤器的数量时才视为有效信号。计数器记录连续出现的完全信号次数(SignalsRepeat)。若信号强度降至阈值以下且 SignalsReset 打开,则计数器清零。
  5. 在空仓且方向开关允许的情况下,当计数器满足条件即以 Volume 指定的手数市价开仓。静态止盈止损以点(pip)为单位配置,并根据品种最小跳动换算成价格,交由 StartProtection() 管理。
  6. 若已有仓位,出现强烈的反向信号时可提前平仓(AllowLongExit / AllowShortExit)。否则由止损或止盈负责退出。

原版中的 Quantum、Twitter 情绪、开盘相关性、参数集合测试以及复杂的资金管理阶梯等功能在移植时均被省略,因为它们依赖外部指标或文件,不适用于 StockSharp 环境。

参数

参数 默认值 说明
CandleType M15 用于决策的基础时间框。
HigherTimeFrameShift 1 沿 MT4 阶梯向上移动的级数。设为 0 表示不切换时间框。
UseRsiSignal true 启用高周期 RSI 过滤。
RsiPeriod 57 确认用 RSI 周期。
RsiUpperThreshold 65 RSI 超过该值时禁止再开多。
RsiLowerThreshold 25 RSI 低于该值时禁止再开空。
UseMaSignal true 启用高周期均线过滤。
MaPeriod 60 确认用简单均线周期。
SignalsRepeat 1 需要连续出现多少个完全信号才入场。
SignalsReset true 信号减弱时是否重置计数器。
TakeProfitPips 308 止盈距离(点),0 表示关闭。
StopLossPips 290 止损距离(点),0 表示关闭。
Volume 0.1 每次开仓的手数。
AllowLongEntry / AllowShortEntry true 是否允许开多 / 开空。
AllowLongExit / AllowShortExit true 是否允许在出现反向信号时自动平多 / 平空。

使用建议

  • 选择具备可靠最小跳动的品种。对于三位或五位报价的外汇品种,程序会自动将 PriceStep 乘以 10,以匹配 MetaTrader 的“点”定义。
  • 若希望所有指标都在同一时间框上计算,可将 HigherTimeFrameShift 设为 0。此时高周期指标直接使用主时间框的数据,不会额外订阅第二条蜡烛流。
  • 想完全依赖止损/止盈而不因信号反向而离场,可关闭对应的 Allow*Exit 选项。
  • 原策略的资金加仓/减仓体系、复杂跟踪止盈以及其他附加过滤器已移除。如需这些功能,可在该精简版本基础上自行扩展。

与原始 EA 的差异

功能 MT4 版本 StockSharp 版本
确认过滤 RSI、MA、Quantum、TSI、多品种相关性 仅保留 RSI 与 MA(核心逻辑)
入场控制 连续信号 + 时间过滤 连续信号 + 可选计数重置
风险控制 静态 TP/SL + 大量跟踪模块 静态 TP/SL(StartProtection()
资金管理 逐步加减仓、亏损递减 固定仓位参数
外部依赖 自定义指标与外部文件

这样就得到一个紧凑且易维护的 GLFX 版本:仍然等待高周期确认,并在信号多次同向后入场,但结构清晰、便于在 StockSharp 框架下继续扩展。

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// GLFX strategy: RSI + SMA confirmation for entry.
/// Requires consecutive confirmations before trading.
/// </summary>
public class GlfxStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiUpper;
	private readonly StrategyParam<decimal> _rsiLower;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _signalsRepeat;

	private decimal _prevRsi;
	private decimal _prevMa;
	private int _buyCount;
	private int _sellCount;
	private decimal _entryPrice;

	public GlfxStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI period.", "Indicators");

		_rsiUpper = Param(nameof(RsiUpper), 65m)
			.SetDisplay("RSI Upper", "Overbought level.", "Indicators");

		_rsiLower = Param(nameof(RsiLower), 35m)
			.SetDisplay("RSI Lower", "Oversold level.", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 60)
			.SetDisplay("MA Period", "SMA period.", "Indicators");

		_signalsRepeat = Param(nameof(SignalsRepeat), 2)
			.SetDisplay("Signals Repeat", "Consecutive confirmations needed.", "Signals");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiUpper
	{
		get => _rsiUpper.Value;
		set => _rsiUpper.Value = value;
	}

	public decimal RsiLower
	{
		get => _rsiLower.Value;
		set => _rsiLower.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public int SignalsRepeat
	{
		get => _signalsRepeat.Value;
		set => _signalsRepeat.Value = value;
	}

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

		_prevRsi = 0;
		_prevMa = 0;
		_buyCount = 0;
		_sellCount = 0;
		_entryPrice = 0;
	}

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

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ma = new SimpleMovingAverage { Length = MaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ma);
			DrawOwnTrades(area);
		}
	}

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

		if (_prevRsi == 0 || _prevMa == 0)
		{
			_prevRsi = rsiVal;
			_prevMa = maVal;
			return;
		}

		var close = candle.ClosePrice;

		// RSI signal: rising and below upper = bullish, falling and above lower = bearish
		var rsiSignal = 0;
		if (rsiVal > _prevRsi && rsiVal < RsiUpper)
			rsiSignal = 1;
		else if (rsiVal < _prevRsi && rsiVal > RsiLower)
			rsiSignal = -1;

		// MA signal: price above rising MA = bullish, below falling MA = bearish
		var maSignal = 0;
		if (maVal > _prevMa && close > maVal)
			maSignal = 1;
		else if (maVal < _prevMa && close < maVal)
			maSignal = -1;

		// Both signals must agree
		if (rsiSignal > 0 && maSignal > 0)
		{
			_buyCount++;
			_sellCount = 0;
		}
		else if (rsiSignal < 0 && maSignal < 0)
		{
			_sellCount++;
			_buyCount = 0;
		}
		else
		{
			_buyCount = 0;
			_sellCount = 0;
		}

		// Exit on opposite signal
		if (Position > 0 && _sellCount >= SignalsRepeat)
		{
			SellMarket();
			_entryPrice = 0;
			_buyCount = 0;
			_sellCount = 0;
		}
		else if (Position < 0 && _buyCount >= SignalsRepeat)
		{
			BuyMarket();
			_entryPrice = 0;
			_buyCount = 0;
			_sellCount = 0;
		}

		// Entry after required confirmations
		if (Position == 0)
		{
			if (_buyCount >= SignalsRepeat)
			{
				_entryPrice = close;
				BuyMarket();
				_buyCount = 0;
				_sellCount = 0;
			}
			else if (_sellCount >= SignalsRepeat)
			{
				_entryPrice = close;
				SellMarket();
				_buyCount = 0;
				_sellCount = 0;
			}
		}

		_prevRsi = rsiVal;
		_prevMa = maVal;
	}
}