Estratégia MFI com Saída da Zona de Sobrevenda e Averaging
A estratégia aguarda o Money Flow Index (MFI) entrar na zona de sobrevenda. Uma vez que o MFI sobe acima do nível de sobrevenda, coloca uma ordem de compra limitada a um percentual fixo abaixo do fechamento atual. Se a ordem não for executada dentro de um número especificado de barras, ela é cancelada. Stop-loss e take-profit são aplicados via StartProtection.
Detalhes
- Critérios de entrada:
- MFI sobe acima de
MfiOversoldLevelapós estar abaixo; coloca compra limitadaLongEntryPercentageabaixo do fechamento.
- MFI sobe acima de
- Comprado/Vendido: Somente comprado.
- Critérios de saída:
- Posição encerrada por take-profit ou stop-loss (
ExitGainPercentage,StopLossPercentage).
- Posição encerrada por take-profit ou stop-loss (
- Stops: Sim, via StartProtection.
- Valores padrão:
MfiPeriod= 14MfiOversoldLevel= 20LongEntryPercentage= 0.1StopLossPercentage= 1ExitGainPercentage= 1CancelAfterBars= 5
- Filtros:
- Categoria: Reversão à média
- Direção: Comprado
- Indicadores: MFI
- Stops: Sim
- Complexidade: Baixo
- Período: Qualquer
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Baixo
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 strategy with oversold zone exit and averaging.
/// After MFI has been below MfiOversoldLevel, the first candle on which it climbs back above the level places a limit buy
/// LongEntryPercentage percent below the close. An order still unfilled after CancelAfterBars candles is cancelled.
/// Positions are closed by the percent take profit and stop loss of StartProtection.
/// </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<DataType> _candleType;
private Order _entryOrder;
private int _barsSinceOrder;
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 the close for the 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 an unfilled order is cancelled.
/// </summary>
public int CancelAfterBars
{
get => _cancelAfterBars.Value;
set => _cancelAfterBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MfiWithOversoldZoneExitAndAveragingStrategy()
{
_mfiPeriod = Param(nameof(MfiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("MFI Period", "Period for the MFI indicator", "Indicators");
_mfiOversoldLevel = Param(nameof(MfiOversoldLevel), 20m)
.SetDisplay("MFI Oversold", "Oversold level for MFI", "Indicators");
_longEntryPercentage = Param(nameof(LongEntryPercentage), 0.1m)
.SetNotNegative()
.SetDisplay("Entry %", "Percent below close for the limit entry", "Trading");
_stopLossPercentage = Param(nameof(StopLossPercentage), 1m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk");
_exitGainPercentage = Param(nameof(ExitGainPercentage), 1m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk");
_cancelAfterBars = Param(nameof(CancelAfterBars), 5)
.SetGreaterThanZero()
.SetDisplay("Cancel After Bars", "Bars before an unfilled limit order is cancelled", "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();
ResetState();
}
private void ResetState()
{
_entryOrder = null;
_barsSinceOrder = 0;
_inOversoldZone = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var mfi = new MoneyFlowIndex { Length = MfiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(mfi, ProcessCandle)
.Start();
StartProtection(
ExitGainPercentage > 0 ? new Unit(ExitGainPercentage, UnitTypes.Percent) : new Unit(),
StopLossPercentage > 0 ? new Unit(StopLossPercentage, UnitTypes.Percent) : new Unit(),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, mfi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_entryOrder != null)
{
if (_entryOrder.State is OrderStates.Active or OrderStates.Pending)
{
_barsSinceOrder++;
if (_barsSinceOrder >= CancelAfterBars)
{
if (_entryOrder.State == OrderStates.Active)
CancelOrder(_entryOrder);
_entryOrder = null;
}
}
else
{
_entryOrder = null;
}
}
var crossedUp = false;
if (mfiValue < MfiOversoldLevel)
{
_inOversoldZone = true;
}
else if (_inOversoldZone && mfiValue > MfiOversoldLevel)
{
_inOversoldZone = false;
crossedUp = true;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Every new signal adds another limit buy, so the position averages down while orders keep filling.
if (crossedUp && _entryOrder == null)
{
var price = candle.ClosePrice * (1m - LongEntryPercentage / 100m);
var step = Security?.PriceStep ?? 0m;
if (step > 0)
price = Math.Floor(price / step) * step;
_entryOrder = BuyLimit(price, Volume);
_barsSinceOrder = 0;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates
from StockSharp.Algo.Indicators import MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class mfi_with_oversold_zone_exit_and_averaging_strategy(Strategy):
"""
MFI strategy with oversold zone exit and averaging.
After MFI has been below MfiOversoldLevel, the first candle on which it climbs back above the level places a limit buy
LongEntryPercentage percent below the close. An order still unfilled after CancelAfterBars candles is cancelled.
Positions are closed by the percent take profit and stop loss of StartProtection.
"""
def __init__(self):
super(mfi_with_oversold_zone_exit_and_averaging_strategy, self).__init__()
self._mfi_period = self.Param("MfiPeriod", 14).SetGreaterThanZero().SetDisplay("MFI Period", "Period for the MFI indicator", "Indicators")
self._mfi_oversold_level = self.Param("MfiOversoldLevel", 20.0).SetDisplay("MFI Oversold", "Oversold level for MFI", "Indicators")
self._long_entry_percentage = self.Param("LongEntryPercentage", 0.1).SetNotNegative().SetDisplay("Entry %", "Percent below close for the limit entry", "Trading")
self._stop_loss_percentage = self.Param("StopLossPercentage", 1.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk")
self._exit_gain_percentage = self.Param("ExitGainPercentage", 1.0).SetNotNegative().SetDisplay("Take Profit %", "Take-profit percentage", "Risk")
self._cancel_after_bars = self.Param("CancelAfterBars", 5).SetGreaterThanZero().SetDisplay("Cancel After Bars", "Bars before an unfilled limit order is cancelled", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._entry_order = None
self._bars_since_order = 0
self._in_oversold_zone = False
def OnReseted(self):
super(mfi_with_oversold_zone_exit_and_averaging_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(mfi_with_oversold_zone_exit_and_averaging_strategy, self).OnStarted2(time)
self._reset_state()
mfi = MoneyFlowIndex()
mfi.Length = self._mfi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(mfi, self._process_candle).Start()
tp = self._exit_gain_percentage.Value
sl = self._stop_loss_percentage.Value
self.StartProtection(
Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit(),
Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, mfi)
def _process_candle(self, candle, mfi_value):
if candle.State != CandleStates.Finished:
return
if self._entry_order is not None:
state = self._entry_order.State
if state == OrderStates.Active or state == OrderStates.Pending:
self._bars_since_order += 1
if self._bars_since_order >= self._cancel_after_bars.Value:
if state == OrderStates.Active:
self.CancelOrder(self._entry_order)
self._entry_order = None
else:
self._entry_order = None
level = Decimal(self._mfi_oversold_level.Value)
crossed_up = False
if mfi_value < level:
self._in_oversold_zone = True
elif self._in_oversold_zone and mfi_value > level:
self._in_oversold_zone = False
crossed_up = True
if not self.IsFormedAndOnlineAndAllowTrading():
return
# Every new signal adds another limit buy, so the position averages down while orders keep filling.
if crossed_up and self._entry_order is None:
price = candle.ClosePrice * (Decimal(1) - Decimal(self._long_entry_percentage.Value) / Decimal(100))
step = self.Security.PriceStep if self.Security is not None else None
if step is not None and step > 0:
price = Math.Floor(price / step) * step
self._entry_order = self.BuyLimit(price, self.Volume)
self._bars_since_order = 0
def CreateClone(self):
return mfi_with_oversold_zone_exit_and_averaging_strategy()