Auf GitHub ansehen

MFI Strategy with Oversold Zone Exit and Averaging

The strategy waits for the Money Flow Index (MFI) to enter the oversold zone. Once MFI rises above the oversold level, it places a limit buy order a fixed percentage below the current close. If the order is not filled within a specified number of bars, it is canceled. Stop-loss and take-profit are applied via StartProtection.

Details

  • Entry Criteria:
    • MFI climbs above MfiOversoldLevel after being below it; place limit buy at LongEntryPercentage below close.
  • Long/Short: Long only.
  • Exit Criteria:
    • Position closed by take-profit or stop-loss (ExitGainPercentage, StopLossPercentage).
  • Stops: Yes, via StartProtection.
  • Default Values:
    • MfiPeriod = 14
    • MfiOversoldLevel = 20
    • LongEntryPercentage = 0.1
    • StopLossPercentage = 1
    • ExitGainPercentage = 1
    • CancelAfterBars = 5
  • Filters:
    • Category: Mean reversion
    • Direction: Long
    • Indicators: MFI
    • Stops: Yes
    • Complexity: Low
    • Timeframe: Any
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Low
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 enters long when MFI exits the oversold zone.
/// Places a limit order below the close and cancels it after a specified number of bars.
/// Uses StartProtection for stop-loss and take-profit.
/// </summary>
public class MfiWithOversoldZoneExitAndAveragingStrategy : Strategy
{
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _mfiOversoldLevel;
	private readonly StrategyParam<decimal> _longEntryPercentage;
	private readonly StrategyParam<decimal> _stopLossPercentage;
	private readonly StrategyParam<decimal> _exitGainPercentage;
	private readonly StrategyParam<int> _cancelAfterBars;
	private readonly StrategyParam<int> _signalCooldownBars;
	private readonly StrategyParam<DataType> _candleType;
	
	private Order _entryOrder;
	private decimal _longEntryPrice;
	private int _barsSinceEntryOrder;
	private int _barsFromSignal;
	private bool _inOversoldZone;
	
	/// <summary>
	/// Period for MFI calculation.
	/// </summary>
	public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }
	
	/// <summary>
	/// Oversold threshold for MFI.
	/// </summary>
	public decimal MfiOversoldLevel { get => _mfiOversoldLevel.Value; set => _mfiOversoldLevel.Value = value; }
	
	/// <summary>
	/// Percentage below close price for limit entry.
	/// </summary>
	public decimal LongEntryPercentage { get => _longEntryPercentage.Value; set => _longEntryPercentage.Value = value; }
	
	/// <summary>
	/// Stop-loss percentage.
	/// </summary>
	public decimal StopLossPercentage { get => _stopLossPercentage.Value; set => _stopLossPercentage.Value = value; }
	
	/// <summary>
	/// Take-profit percentage.
	/// </summary>
	public decimal ExitGainPercentage { get => _exitGainPercentage.Value; set => _exitGainPercentage.Value = value; }
	
	/// <summary>
	/// Number of bars after which unfilled order is canceled.
	/// </summary>
	public int CancelAfterBars { get => _cancelAfterBars.Value; set => _cancelAfterBars.Value = value; }
	
	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>
	/// Minimum bars between new entry orders.
	/// </summary>
	public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
	
	/// <summary>
	/// Initializes a new instance of the <see cref="MfiWithOversoldZoneExitAndAveragingStrategy"/>.
	/// </summary>
	public MfiWithOversoldZoneExitAndAveragingStrategy()
	{
		_mfiPeriod = Param(nameof(MfiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("MFI Period", "Period for the MFI indicator", "Indicator");
		_mfiOversoldLevel = Param(nameof(MfiOversoldLevel), 20m)
		.SetRange(0m, 50m)
		.SetDisplay("MFI Oversold", "Oversold level for MFI", "Signal");
		_longEntryPercentage = Param(nameof(LongEntryPercentage), 0.1m)
		.SetRange(0m, 5m)
		.SetDisplay("Entry %", "Percent below close for limit entry", "Trading");
		_stopLossPercentage = Param(nameof(StopLossPercentage), 1m)
		.SetRange(0m, 10m)
		.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk");
		_exitGainPercentage = Param(nameof(ExitGainPercentage), 1m)
		.SetRange(0m, 10m)
		.SetDisplay("Take Profit %", "Take-profit percentage", "Risk");
		_cancelAfterBars = Param(nameof(CancelAfterBars), 5)
		.SetRange(1, 100)
		.SetDisplay("Cancel After Bars", "Bars before canceling limit order", "Trading");
		_signalCooldownBars = Param(nameof(SignalCooldownBars), 20)
		.SetRange(1, 500)
		.SetDisplay("Signal Cooldown Bars", "Minimum bars between entry orders", "Trading");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles to use", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		
		_entryOrder = null;
		_longEntryPrice = 0m;
		_barsSinceEntryOrder = 0;
		_barsFromSignal = 0;
		_inOversoldZone = false;
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		var mfi = new MoneyFlowIndex { Length = MfiPeriod };
		
		var subscription = SubscribeCandles(CandleType);
		
		subscription
		.Bind(mfi, ProcessCandle)
		.Start();
		
		StartProtection(
		new Unit(ExitGainPercentage, UnitTypes.Percent),
		new Unit(StopLossPercentage, UnitTypes.Percent)
		);
		
		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 (!IsFormedAndOnlineAndAllowTrading())
		return;

		_barsFromSignal++;
		
		if (mfiValue < MfiOversoldLevel)
		{
			_inOversoldZone = true;
		}
		else if (_inOversoldZone && mfiValue > MfiOversoldLevel && Position == 0 && _entryOrder == null && _barsFromSignal >= SignalCooldownBars)
		{
			_inOversoldZone = false;
			_longEntryPrice = candle.ClosePrice * (1 - LongEntryPercentage / 100m);
			_entryOrder = BuyLimit(_longEntryPrice, Volume);
			_barsSinceEntryOrder = 0;
			_barsFromSignal = 0;
		}
		
		if (_entryOrder != null)
		{
			if (_entryOrder.State == OrderStates.Active)
			{
				_barsSinceEntryOrder++;
				
				if (_barsSinceEntryOrder >= CancelAfterBars && Position == 0)
				{
					CancelOrder(_entryOrder);
					_entryOrder = null;
				}
			}
			else
			{
				_entryOrder = null;
			}
		}
	}
}