在 GitHub 上查看

Myfriend Forex Instruments 策略

Myfriend Forex Instruments 策略复刻了 2006 年的 MetaTrader "MyFriend" 智能交易系统。策略默认在 EUR/USD 30 分钟周期上运行,通过结合上一交易日的枢轴位、Donchian 通道扩张以及 3/9 周期收盘价动量差来寻找入场机会。当价格强势穿越枢轴位或 Donchian 通道突然放宽,并且方向与动量偏差一致时,系统会开出唯一的一笔订单,并立即设定保护性止损与止盈。

交易流程

  1. 日内枢轴图:使用前一日的最高价、最低价和收盘价计算经典的枢轴点(PivotR1S1)。这些水平在当日保持不变,界定了预期的波动区间。
  2. 动量脉冲:分别对收盘价计算 3 与 9 周期简单移动平均,将它们的差值乘以 1000(与原始指标一致),判断多空力量孰强孰弱。
  3. 突破过滤器
    • 枢轴冲击:若上一根蜡烛实体超过 12 点并穿越枢轴,同时当前蜡烛收盘延续突破方向,则触发信号。
    • Donchian 扩张:当 16 周期 Donchian 通道宽度突破 R1 - S1 的范围且价格配合方向移动时,同样触发信号。
  4. 订单管理:始终只保留一个持仓。做多时将止损设置在前一根蜡烛的最低价减去缓冲,止盈固定为 70 点;做空时使用对称逻辑。
  5. 离场规则
    • 时间窗口出场:进场后的第 3 到第 4 根蜡烛之间,如果上一根收盘价逆向移动超过 3 点,则提前平仓。
    • 移动止损:当浮盈超过 5 点且 Donchian 边界继续向有利方向移动时,将止损沿通道加减 1 点缓冲跟踪。
    • 硬性目标:价格触及设定的止损或止盈时立即平仓。

参数

参数 说明 默认值
BaseVolume 每次开仓使用的合约数量。 1
TakeProfitPoints 止盈距离(以 MetaTrader 点为单位)。 70
StopLossBufferPoints 在前一根蜡烛极值之外附加的止损缓冲。 13
ChannelPeriod Donchian 通道周期,用于宽度检测与移动止损。 16
UseTrailingStop 是否启用 Donchian 移动止损。 true
TrailingStartPoints 启用移动止损前所需的最小浮盈(点)。 5
TrailingBufferPoints 沿 Donchian 边界移动止损时使用的点数缓冲。 1
UseTimeClose 是否启用 3–4 根蜡烛的时间出场规则。 true
CandleType 主图周期(默认 30 分钟)。 M30
DailyCandleType 计算枢轴时使用的日线周期。 D1

注意事项

  • 策略为 EUR/USD 和 30 分钟周期量身打造,其他品种或周期可能需要重新调参。
  • 所有以“点”表示的参数都会乘以标的的 PriceStep。若行情源未提供 PriceStep,则退化为 1 个价格单位。
  • 仅处理已收盘的蜡烛,与原版 MetaTrader 专家的行为保持一致。
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>
/// MyFriend Forex strategy - Donchian channel breakout with SMA momentum filter.
/// Buys when close breaks above upper Donchian and fast SMA > slow SMA.
/// Sells when close breaks below lower Donchian and fast SMA less than slow SMA.
/// </summary>
public class MyfriendForexInstrumentsStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevUpper;
	private decimal _prevLower;
	private bool _hasPrev;

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

	public MyfriendForexInstrumentsStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 16)
			.SetDisplay("Channel Period", "Donchian channel period", "Indicators");

		_fastPeriod = Param(nameof(FastPeriod), 3)
			.SetDisplay("Fast SMA", "Fast SMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 9)
			.SetDisplay("Slow SMA", "Slow SMA period", "Indicators");

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

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = 0m;
		_prevUpper = 0m;
		_prevLower = 0m;
		_hasPrev = false;
	}

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

		_hasPrev = false;

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };
		var fastSma = new SimpleMovingAverage { Length = FastPeriod };
		var slowSma = new SimpleMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, fastSma, slowSma, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevClose = close;
			_prevUpper = high;
			_prevLower = low;
			_hasPrev = true;
			return;
		}

		// Breakout above channel with bullish momentum
		if (_prevClose <= _prevUpper && close > high && fast > slow && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Breakout below channel with bearish momentum
		else if (_prevClose >= _prevLower && close < low && fast < slow && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Mean reversion: close crosses midpoint
		else
		{
			var mid = (high + low) / 2m;
			var prevMid = (_prevUpper + _prevLower) / 2m;

			if (_prevClose <= prevMid && close > mid && fast > slow && Position <= 0)
			{
				if (Position < 0)
					BuyMarket();
				BuyMarket();
			}
			else if (_prevClose >= prevMid && close < mid && fast < slow && Position >= 0)
			{
				if (Position > 0)
					SellMarket();
				SellMarket();
			}
		}

		_prevClose = close;
		_prevUpper = high;
		_prevLower = low;
	}
}