GitHub で見る

MFIヒストグラム戦略

MFIヒストグラム戦略は、マネーフローインデックス(MFI)を使用して、設定可能な閾値を通じて買われすぎと売られすぎの状態を検出します。MFIは価格とボリュームを組み合わせて資金流入と流出の強度を測定します。インジケーターが下から高いレベルを上に突き抜けると、戦略はこれを買い圧力の上昇と解釈してロングポジションを開き、既存のショートポジションを閉じます。逆に、低いレベルを下に突き抜けるとショートエントリーが発動し、既存のロングポジションを閉じます。ストップロスとテイクプロフィットの値は組み込みの保護メカニズムを通じてティックで管理されます。

戦略はユーザー定義のローソク足の時間軸(デフォルト4時間)で動作し、追加フィルターなしで単一のインジケーターに依存します。パラメーターによりMFI期間、閾値レベル、リスク上限を最適化でき、様々な市場とボラティリティのレジームに適応可能です。

詳細

  • エントリー条件:
    • ロング: MFI が下から HighLevel を上に突き抜ける。
    • ショート: MFI が上から LowLevel を下に突き抜ける。
  • ロング/ショート: 両方。
  • エグジット条件:
    • 逆のシグナルが反転を生成する。
    • ストップロスまたはテイクプロフィットに到達する。
  • ストップ: StopLoss および TakeProfit(ティック単位)。
  • デフォルト値:
    • MFI Period = 14
    • HighLevel = 60
    • LowLevel = 40
    • Candle Type = 4-hour
    • StopLoss = 1000 ticks
    • TakeProfit = 2000 ticks
  • フィルター:
    • カテゴリ: オシレーター
    • 方向: 両方
    • インジケーター: 単一
    • ストップ: はい
    • 複雑さ: 基本
    • 時間軸: 中期
    • 季節性: いいえ
    • ニューラルネットワーク: いいえ
    • ダイバージェンス: いいえ
    • リスクレベル: 中
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MFI level-crossing reversal with stop loss/take profit distances in instrument price steps.
/// A crossover signal is acted on only after a completed, formed indicator bar.
/// </summary>
public class MfiHistogramStrategy : Strategy
{
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _highLevel;
	private readonly StrategyParam<decimal> _lowLevel;
	private readonly StrategyParam<Unit> _stopLoss;
	private readonly StrategyParam<Unit> _takeProfit;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevMfi;
	private Order _pendingEntry;

	public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }
	public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
	public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
	/// <summary>Number of price steps to the protective stop.</summary>
	public Unit StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	/// <summary>Number of price steps to the profit target.</summary>
	public Unit TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MfiHistogramStrategy()
	{
		_mfiPeriod = Param(nameof(MfiPeriod), 14).SetGreaterThanZero()
			.SetDisplay("MFI Period", "Period for Money Flow Index", "MFI");
		_highLevel = Param(nameof(HighLevel), 60m)
			.SetDisplay("High Level", "Upper MFI crossing threshold", "MFI");
		_lowLevel = Param(nameof(LowLevel), 40m)
			.SetDisplay("Low Level", "Lower MFI crossing threshold", "MFI");
		// Preserve the existing public Unit parameter API; its numeric value denotes PRICE STEPS.
		_stopLoss = Param(nameof(StopLoss), new Unit(1000m, UnitTypes.Absolute))
			.SetDisplay("Stop Loss", "Distance measured in instrument price steps", "Risk");
		_takeProfit = Param(nameof(TakeProfit), new Unit(2000m, UnitTypes.Absolute))
			.SetDisplay("Take Profit", "Distance measured in instrument price steps", "Risk");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe of candles", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMfi = null;
		_pendingEntry = null;
	}

	private Unit ConvertTicksToPrice(Unit ticks, decimal step, string name)
	{
		if (ticks is null || ticks.Type != UnitTypes.Absolute || ticks.Value <= 0)
			throw new InvalidOperationException($"{name} must be a positive number of price steps (absolute numeric unit).");
		return new Unit(ticks.Value * step, UnitTypes.Absolute);
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevMfi = null;
		_pendingEntry = null;
		var step = Security?.PriceStep;
		if (step is null || step <= 0m)
			throw new InvalidOperationException("MFI Histogram requires a positive Security.PriceStep for tick-based protection.");

		StartProtection(
			takeProfit: ConvertTicksToPrice(TakeProfit, step.Value, nameof(TakeProfit)),
			stopLoss: ConvertTicksToPrice(StopLoss, step.Value, nameof(StopLoss)),
			useMarketOrders: true, isLocalStop: true);

		var mfi = new MoneyFlowIndex { Length = MfiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(mfi, ProcessCandle).Start();

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

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

		if (_prevMfi is decimal previous && IsFormedAndOnlineAndAllowTrading()
			&& (_pendingEntry is null || _pendingEntry.State is OrderStates.Done or OrderStates.Failed))
		{
			if (previous <= HighLevel && mfiValue > HighLevel && Position <= 0m)
				_pendingEntry = BuyMarket(Volume + Math.Abs(Position));
			else if (previous >= LowLevel && mfiValue < LowLevel && Position >= 0m)
				_pendingEntry = SellMarket(Volume + Math.Abs(Position));
		}

		// Always advance the baseline, even while trading is disabled or an order is pending.
		_prevMfi = mfiValue;
	}
}