在 GitHub 上查看

Forex Sky 策略

概述

Forex Sky 策略 是 MetaTrader 智能交易系统 Forex_SKY.mq4 的直接移植版本。它依旧以 MACD 动能反转为核心,并保持“每天仅允许一笔交易、每根 K 线最多一笔”的原始限制。StockSharp 版本保留了所有阈值,同时使用高层 API 完成指标绑定与订单管理。

策略会根据参数 CandleType 订阅指定周期(默认 15 分钟)的 K 线,并在每根收盘 K 线上计算经典 MACD(12/26/9)。

交易逻辑

  • 做多入场:满足以下条件时市价买入:
    • 当前 MACD 主线大于 0;
    • 同时超过 +0.00009,确认动能足够;
    • 最近三根 MACD 值中至少有一次小于等于 0,意味着指标刚刚从负区间翻正。
  • 做空入场:以下任一条件成立时市价卖出:
    • 当前 MACD 主线小于 0,且低于 -0.0004;最近三根 MACD 至少有一次为非负值;四根之前的 MACD 值至少为 +0.001
    • 或者 四根之前的 MACD 值达到 ≥ +0.003,该条件会像原始 EA 一样直接触发做空。
  • 仓位管理:策略复制了 Time0CheckTodaysOrders() 的限制——每根 K 线最多一笔交易,并且每天仅交易一次。入场后由 StockSharp 的 StartProtection 自动挂出止盈止损,使保护单与仓位数量保持一致。

策略本身不会主动平仓,依赖止盈、止损或人工干预,与原版 EA 的行为完全一致。

参数

名称 默认值 说明
FastPeriod 12 MACD 快速 EMA 的周期。
SlowPeriod 26 MACD 慢速 EMA 的周期。
SignalPeriod 9 MACD 信号线的 EMA 周期。
TakeProfitPoints 100 止盈距离(按交易品种的最小价格单位表示)。最终价格 = 该值 × Security.PriceStep
StopLossPoints 3000 止损距离(最小价格单位)。
TradeVolume 0.1 市价订单的基础手数。
CandleType 15 分钟 用于计算 MACD 和产生信号的 K 线周期。

点值换算

TakeProfitPointsStopLossPoints 的定义与 MetaTrader 中的 Point 完全一致。若外汇品种的 PriceStep = 0.00001(五位报价):

  • 止盈距离:100 × 0.00001 = 0.001
  • 止损距离:3000 × 0.00001 = 0.03

风险管理

当仓位建立后,StartProtection 会立即挂出相应的止盈与止损订单,触发时使用市价单执行,行为与 MetaTrader 保持一致。将任一参数设置为 0 可关闭对应的保护单。

迁移说明

  • 使用类成员缓存最近四个 MACD 数值,避免调用指标的历史索引,实现与原始脚本等价的判断。
  • 每日交易次数与每根 K 线的交易次数限制,完整复刻了 CheckTodaysOrders()Time0 的检查逻辑。
  • 全部注释改写为英文,指标处理使用 StockSharp 的高层 Bind 机制,无需手动读取值。

使用提示

  • 根据需要调整 CandleType,原始 EA 会直接继承图表周期,此处通过参数控制。
  • 因为每天仅允许一笔交易,建议选择日内波动较大的品种,或在高波动环境下适当提高 MACD 阈值。
  • 日界限基于 K 线开盘时间计算,部署时请确认交易服务器时区,以免提前或延迟重置交易配额。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Forex Sky: MACD zero-line cross with EMA trend filter.
/// </summary>
public class ForexSkyStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevMacd;
	private decimal _entryPrice;

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

		_fastLength = Param(nameof(FastLength), 12)
			.SetDisplay("Fast EMA", "MACD fast period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 26)
			.SetDisplay("Slow EMA", "MACD slow period.", "Indicators");

		_emaLength = Param(nameof(EmaLength), 50)
			.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 FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.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();

		_prevMacd = 0;
		_entryPrice = 0;
	}

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

		_prevMacd = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, ema, atr, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrVal <= 0)
			return;

		var macd = fastVal - slowVal;
		var close = candle.ClosePrice;

		if (_prevMacd == 0)
		{
			_prevMacd = macd;
			return;
		}

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

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

		_prevMacd = macd;
	}
}