在 GitHub 上查看

MFI 水平交叉策略

该策略利用资金流量指数(MFI)震荡指标来识别超买和超卖。当 MFI 穿越预设阈值时,策略会开仓或反转仓位。根据选择的趋势模式,它可以顺势交易或逆势交易。

默认情况下,策略监控四小时K线并计算周期为14的 MFI。当 MFI 跌破下限时开多,当 MFI 突破上限时开空。若设置为 "Against" 模式,则信号逻辑反转,在指标方向的反面进行交易。

风险控制通过内置的止损和止盈完成,参数以入场价的百分比表示。

细节

  • 入场条件
    • Trend Mode: Direct
      • 多头:前一根 MFI > 下限且当前 MFI ≤ 下限。
      • 空头:前一根 MFI < 上限且当前 MFI ≥ 上限。
    • Trend Mode: Against
      • 多头:前一根 MFI < 上限且当前 MFI ≥ 上限。
      • 空头:前一根 MFI > 下限且当前 MFI ≤ 下限。
  • 多空方向:双向。
  • 出场条件:出现反向信号时反手,或由保护模块止损/止盈。
  • 止损/止盈:以入场价百分比表示。
  • 默认值
    • K线类型 = 四小时。
    • MFI 周期 = 14。
    • 下限 = 40。
    • 上限 = 60。
    • 止损 % = 1。
    • 止盈 % = 2。
  • 过滤器
    • 分类:震荡指标
    • 方向:可配置
    • 指标:Money Flow Index
    • 止损:是
    • 复杂度:基础
    • 时间框架:中期
    • 季节性:否
    • 神经网络:否
    • 背离:否
    • 风险级别:中等

备注

该实现基于 StockSharp 的高级 API。策略订阅K线数据,直接绑定 MFI 指标,并在条件满足时执行市价单。启动时仅调用一次风险保护模块,以自动管理风险。

using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Money Flow Index based strategy that opens positions when the indicator crosses predefined levels.
/// The strategy can trade in the direction of the crossing or in the opposite direction based on Trend Mode.
/// </summary>
public class MfiLevelCrossStrategy : Strategy
{
	public enum TrendModes
	{
		Direct,
		Against
	}

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _lowLevel;
	private readonly StrategyParam<decimal> _highLevel;
	private readonly StrategyParam<TrendModes> _trend;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<decimal> _takeProfitPercent;

	private decimal _prevMfi;
	private bool _isFirst;

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>
	/// Period for the Money Flow Index indicator.
	/// </summary>
	public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }

	/// <summary>
	/// Oversold threshold for the MFI.
	/// </summary>
	public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }

	/// <summary>
	/// Overbought threshold for the MFI.
	/// </summary>
	public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }

	/// <summary>
	/// Trading mode selection.
	/// </summary>
	public TrendModes Trend { get => _trend.Value; set => _trend.Value = value; }

	/// <summary>
	/// Stop loss in percent from entry price.
	/// </summary>
	public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }

	/// <summary>
	/// Take profit in percent from entry price.
	/// </summary>
	public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }

	/// <summary>
	/// Initializes a new instance of the <see cref="MfiLevelCrossStrategy"/>.
	/// </summary>
	public MfiLevelCrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe of candles used", "General");

		_mfiPeriod = Param(nameof(MfiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("MFI Period", "Period of the Money Flow Index indicator", "Indicator");

		_lowLevel = Param(nameof(LowLevel), 30m)
			.SetRange(0m, 100m)
			.SetDisplay("Low Level", "Oversold threshold for MFI", "Signal");

		_highLevel = Param(nameof(HighLevel), 70m)
			.SetRange(0m, 100m)
			.SetDisplay("High Level", "Overbought threshold for MFI", "Signal");

		_trend = Param(nameof(Trend), TrendModes.Direct)
			.SetDisplay("Trend Mode", "Trade with trend (Direct) or against it (Against)", "Signal");

		_stopLossPercent = Param(nameof(StopLossPercent), 1m)
			.SetRange(0m, 100m)
			.SetDisplay("Stop Loss %", "Stop loss as percent from entry price", "Risk");

		_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
			.SetRange(0m, 100m)
			.SetDisplay("Take Profit %", "Take profit as percent from entry price", "Risk");
	}

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

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

		_prevMfi = 0m;
		_isFirst = true;
	}

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

		StartProtection(
			new Unit(TakeProfitPercent, UnitTypes.Percent),
			new Unit(StopLossPercent, UnitTypes.Percent));

		var mfi = new MoneyFlowIndex { Length = MfiPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(mfi, ProcessCandle)
			.Start();

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

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

		if (_isFirst)
		{
			_prevMfi = mfiValue;
			_isFirst = false;
			return;
		}

		var crossBelowLow = _prevMfi > LowLevel && mfiValue <= LowLevel;
		var crossAboveHigh = _prevMfi < HighLevel && mfiValue >= HighLevel;

		if (Trend == TrendModes.Direct)
		{
			if (crossBelowLow && Position <= 0)
				BuyMarket();
			else if (crossAboveHigh && Position >= 0)
				SellMarket();
		}
		else
		{
			if (crossBelowLow && Position >= 0)
				SellMarket();
			else if (crossAboveHigh && Position <= 0)
				BuyMarket();
		}

		_prevMfi = mfiValue;
	}
}