Стратегия MFI с выходом из перепроданной зоны и усреднением
Стратегия ожидает, когда индекс денежного потока (MFI) войдет в перепроданную зону. После выхода MFI выше уровня перепроданности стратегия размещает лимитную заявку на покупку на процент ниже цены закрытия. Если заявка не исполняется в течение заданного числа баров, она отменяется. Стоп-лосс и тейк-профит устанавливаются через StartProtection.
Подробности
- Условия входа:
- MFI поднимается выше
MfiOversoldLevelпосле нахождения ниже него; размещается лимитная покупка наLongEntryPercentageниже закрытия.
- MFI поднимается выше
- Длинные/Короткие: Только длинные.
- Условия выхода:
- Позиция закрывается по тейк-профиту или стоп-лоссу (
ExitGainPercentage,StopLossPercentage).
- Позиция закрывается по тейк-профиту или стоп-лоссу (
- Стопы: Да, через StartProtection.
- Значения по умолчанию:
MfiPeriod= 14MfiOversoldLevel= 20LongEntryPercentage= 0.1StopLossPercentage= 1ExitGainPercentage= 1CancelAfterBars= 5
- Фильтры:
- Категория: Средний возврат
- Направление: Long
- Индикаторы: 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 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;
}
}
}
}
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
from StockSharp.Algo.Indicators import MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class mfi_with_oversold_zone_exit_and_averaging_strategy(Strategy):
"""
MFI oversold zone exit: buy market when MFI exits oversold, with percent SL/TP.
Simplified from C# (no limit orders, uses market orders and StartProtection).
"""
def __init__(self):
super(mfi_with_oversold_zone_exit_and_averaging_strategy, self).__init__()
self._mfi_period = self.Param("MfiPeriod", 14).SetDisplay("MFI Period", "MFI period", "Indicator")
self._mfi_oversold = self.Param("MfiOversoldLevel", 20.0).SetDisplay("MFI Oversold", "Oversold level", "Signal")
self._stop_loss_pct = self.Param("StopLossPercentage", 1.0).SetDisplay("SL %", "Stop loss pct", "Risk")
self._take_profit_pct = self.Param("ExitGainPercentage", 1.0).SetDisplay("TP %", "Take profit pct", "Risk")
self._cooldown_bars = self.Param("SignalCooldownBars", 20).SetDisplay("Cooldown", "Min bars between entries", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._in_oversold = False
self._bars_from_signal = 20
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mfi_with_oversold_zone_exit_and_averaging_strategy, self).OnReseted()
self._in_oversold = False
self._bars_from_signal = self._cooldown_bars.Value
def OnStarted2(self, time):
super(mfi_with_oversold_zone_exit_and_averaging_strategy, self).OnStarted2(time)
mfi = MoneyFlowIndex()
mfi.Length = self._mfi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(mfi, self._process_candle).Start()
sl_pct = float(self._stop_loss_pct.Value)
tp_pct = float(self._take_profit_pct.Value)
self.StartProtection(
Unit(tp_pct, UnitTypes.Percent),
Unit(sl_pct, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, mfi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, mfi_val):
if candle.State != CandleStates.Finished:
return
mfi = float(mfi_val)
self._bars_from_signal += 1
oversold = float(self._mfi_oversold.Value)
if mfi < oversold:
self._in_oversold = True
elif self._in_oversold and mfi > oversold and self.Position == 0 and self._bars_from_signal >= self._cooldown_bars.Value:
self._in_oversold = False
self.BuyMarket()
self._bars_from_signal = 0
def CreateClone(self):
return mfi_with_oversold_zone_exit_and_averaging_strategy()