MFIヒストグラム戦略
MFIヒストグラム戦略は、マネーフローインデックス(MFI)を使用して、設定可能な閾値を通じて買われすぎと売られすぎの状態を検出します。MFIは価格とボリュームを組み合わせて資金流入と流出の強度を測定します。インジケーターが下から高いレベルを上に突き抜けると、戦略はこれを買い圧力の上昇と解釈してロングポジションを開き、既存のショートポジションを閉じます。逆に、低いレベルを下に突き抜けるとショートエントリーが発動し、既存のロングポジションを閉じます。ストップロスとテイクプロフィットの値は組み込みの保護メカニズムを通じてティックで管理されます。
戦略はユーザー定義のローソク足の時間軸(デフォルト4時間)で動作し、追加フィルターなしで単一のインジケーターに依存します。パラメーターによりMFI期間、閾値レベル、リスク上限を最適化でき、様々な市場とボラティリティのレジームに適応可能です。
詳細
- エントリー条件:
- ロング:
MFIが下からHighLevelを上に突き抜ける。 - ショート:
MFIが上からLowLevelを下に突き抜ける。
- ロング:
- ロング/ショート: 両方。
- エグジット条件:
- 逆のシグナルが反転を生成する。
- ストップロスまたはテイクプロフィットに到達する。
- ストップ:
StopLossおよびTakeProfit(ティック単位)。 - デフォルト値:
MFI Period= 14HighLevel= 60LowLevel= 40Candle Type= 4-hourStopLoss= 1000 ticksTakeProfit= 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates
from StockSharp.Algo.Indicators import MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class mfi_histogram_strategy(Strategy):
"""MFI threshold crossings; risk distances are instrument price steps, not raw prices."""
def __init__(self):
super(mfi_histogram_strategy, self).__init__()
self._mfi_period = self.Param("MfiPeriod", 14).SetDisplay("MFI Period", "Period for Money Flow Index", "MFI")
self._high_level = self.Param("HighLevel", 60.0).SetDisplay("High Level", "Upper MFI crossing threshold", "MFI")
self._low_level = self.Param("LowLevel", 40.0).SetDisplay("Low Level", "Lower MFI crossing threshold", "MFI")
# As in C#, keep Unit parameters. The numeric value is the count of price steps.
self._stop_loss = self.Param("StopLoss", Unit(1000, UnitTypes.Absolute)).SetDisplay("Stop Loss", "Distance in instrument price steps", "Risk")
self._take_profit = self.Param("TakeProfit", Unit(2000, UnitTypes.Absolute)).SetDisplay("Take Profit", "Distance in instrument price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe of candles", "General")
self._prev_mfi = None
self._pending_entry = None
@property
def mfi_period(self): return self._mfi_period.Value
@property
def high_level(self): return self._high_level.Value
@property
def low_level(self): return self._low_level.Value
@property
def stop_loss(self): return self._stop_loss.Value
@property
def take_profit(self): return self._take_profit.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(mfi_histogram_strategy, self).OnReseted()
self._prev_mfi = None
self._pending_entry = None
def _absolute_price_distance(self, count, step, name):
if count is None or count.Type != UnitTypes.Absolute or float(count.Value) <= 0:
raise ValueError(name + " must be a positive number of price steps")
return Unit(float(count.Value) * float(step), UnitTypes.Absolute)
def OnStarted2(self, time):
super(mfi_histogram_strategy, self).OnStarted2(time)
self._prev_mfi = None
self._pending_entry = None
step = self.Security.PriceStep if self.Security is not None else None
if step is None or float(step) <= 0:
raise ValueError("MFI Histogram requires a positive Security.PriceStep")
self.StartProtection(
takeProfit=self._absolute_price_distance(self.take_profit, step, "TakeProfit"),
stopLoss=self._absolute_price_distance(self.stop_loss, step, "StopLoss"),
useMarketOrders=True, isLocalStop=True)
mfi = MoneyFlowIndex()
mfi.Length = self.mfi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(mfi, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
indicator_area = self.CreateChartArea()
if indicator_area is not None:
self.DrawIndicator(indicator_area, mfi)
def process_candle(self, candle, mfi_value):
if candle.State != CandleStates.Finished:
return
mfi = float(mfi_value)
previous = self._prev_mfi
self._prev_mfi = mfi
if previous is None or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_entry is not None and self._pending_entry.State not in (OrderStates.Done, OrderStates.Failed):
return
if previous <= float(self.high_level) and mfi > float(self.high_level) and self.Position <= 0:
self._pending_entry = self.BuyMarket(self.Volume + abs(self.Position))
elif previous >= float(self.low_level) and mfi < float(self.low_level) and self.Position >= 0:
self._pending_entry = self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return mfi_histogram_strategy()