在 GitHub 上查看

Franks 4 Hour Limit Orders 策略

概述

Franks 4 Hour Limit Orders StrategyMQL/7684/Franks_4hour_limit_orders.mq4 中的 MetaTrader 4 专家顾问移植到 StockSharp 高阶 API。原版 EA 结合了 Alexander Elder 的“三重筛选”思想:在四小时图上利用 MACD 直方图(OsMA)和 Force Index 判断动能,再在上一根蜡烛的高低点附近挂出逆势限价单。移植版本完全遵守仓库规则(使用制表符、高阶 API、避免自建集合),并在代码中加入了详细的英文注释。

交易逻辑

  1. 数据来源:策略订阅可配置的蜡烛序列,默认使用四小时蜡烛。与原始 EA 一样,仅在蜡烛收盘后处理信号。
  2. 指标组合
    • MovingAverageConvergenceDivergenceSignal(12, 26, 9) 提供 MACD 线与信号线,两者的差值构成 OsMA 直方图。
    • ForceIndex(24) 评估上一根蜡烛的力度。仅使用最终值。
  3. 历史依赖:为了得到斜率方向,需要至少两根完成的历史蜡烛,因此内部变量会缓存上一根和上上一根的 OsMA、上一根的 Force Index 以及上一根蜡烛的高低点。
  4. 做空设置:当 OsMA 直方图上升(OsMA[1] > OsMA[2])且上一根 Force Index 为负时,策略准备挂出 sell limit:
    • 基准价格为上一根蜡烛的最高价加一个点。
    • 同时保证与当前买价至少相差 16 点(可调),最终下单价取基准价与 Bid + buffer 之间的较大值。
    • 止损和止盈距离按点数配置(默认 35 和 150 点),并根据品种的价格步长对齐。
  5. 做多设置:当 OsMA 直方图下降(OsMA[1] < OsMA[2])且上一根 Force Index 为正时,在市场下方布置 buy limit:
    • 基准价格为上一根蜡烛的最低价减一个点。
    • 同样执行 16 点缓冲,最终价格取基准价与 Ask - buffer 的较小值。
  6. 挂单维护:如果 OsMA 斜率反向,尚未成交的挂单立即取消。某一侧成交后,另一侧的挂单也会被撤销,以避免同时持仓。
  7. 持仓管理:成交后记录入场价,同时激活预先计算好的止损与止盈价格。策略还实现了基于点数的追踪止损(默认 30 点),只在价格向有利方向移动足够距离时推进保护性止损。
  8. 退出机制:在每根完成蜡烛上检查价格是否触及保护或目标。如果多头的蜡烛最低价穿过止损或最高价到达目标,则平仓;空头逻辑相反。

参数

参数 默认值 说明
OrderVolume 1 挂单使用的固定手数。
StopLossPips 35 入场价到止损位的点数距离。
TakeProfitPips 150 入场价到止盈位的点数距离。
TrailingStopPips 30 追踪止损的点数距离。
EntryBufferPips 16 挂单与当前市场价格之间的最小点差。
PipSize 0.0001 每一点对应的价格增量,可按品种调整。
CandleType 四小时 用于计算的蜡烛时间框架。

文件结构

  • CS/Franks4HourLimitOrdersStrategy.cs —— 主要的 C# 实现,包含详细英文注释。
  • README.md —— 英文说明。
  • README_ru.md —— 俄文说明。
  • README_zh.md —— 本文档。

实现细节

  • 仅使用 StockSharp 高阶 API:SubscribeCandles、指标绑定以及封装的下单方法。
  • 所有价格均会根据交易所的价格步长对齐,以避免无效价位。
  • 状态变量只保存必需的历史数据,符合仓库“不自建集合”的要求。
  • 止损、止盈与追踪止损的管理在蜡烛处理函数中完成,从而重现 MT4 EA 的移动保护逻辑。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Franks 4H strategy - EMA crossover with momentum filter.
/// Buys on bullish EMA crossover with positive momentum.
/// Sells on bearish EMA crossover with negative momentum.
/// </summary>
public class Franks4HourLimitOrdersStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Franks4HourLimitOrdersStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_momentumPeriod = Param(nameof(MomentumPeriod), 10)
			.SetDisplay("Momentum", "Momentum period", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_hasPrev = false;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var momentum = new Momentum { Length = MomentumPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, momentum, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal momentum)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fast > slow;
		var crossDown = _prevFast >= _prevSlow && fast < slow;

		if (crossUp && momentum > 0 && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (crossDown && momentum < 0 && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}