Estrategia de Filtro Elíptico Óptimo Modificado
Esta estrategia aplica el indicador Modified Optimum Elliptic Filter descrito por John F. Ehlers para detectar giros direccionales. El indicador es un filtro digital de dos polos que suaviza el promedio de precios máximos y mínimos usando la siguiente fórmula recursiva:
F(t) = 0.13785*(2*HL2(t) - HL2(t-1))
+ 0.0007 *(2*HL2(t-1) - HL2(t-2))
+ 0.13785*(2*HL2(t-2) - HL2(t-3))
+ 1.2103 * F(t-1) - 0.4867 * F(t-2)
Donde HL2 es el punto medio (High + Low)/2 de cada vela.
La estrategia lee los últimos tres valores del filtro para determinar el momentum. Si el indicador está subiendo y el valor más reciente supera al anterior, se abre una posición larga. Si el indicador está bajando y el valor actual está por debajo del anterior, se abre una posición corta. Las posiciones se revierten cuando ocurre la condición opuesta.
Detalles
- Criterios de entrada:
- Largo:
F(t-1) < F(t-2)yF(t) > F(t-1). - Corto:
F(t-1) > F(t-2)yF(t) < F(t-1).
- Largo:
- Largo/Corto: Ambos.
- Criterios de salida:
- La posición se revierte en la señal opuesta.
- Stops: Sin stops explícitos.
- Valores predeterminados:
Candle Type= marco temporal de 4 horas.
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Ambos
- Indicadores: Único
- Stops: No
- Complejidad: Moderado
- Marco temporal: Medio plazo
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend following strategy based on the Modified Optimum Elliptic Filter indicator.
/// </summary>
public class ModifiedOptimumEllipticFilterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly ModifiedOptimumEllipticFilter _filter = new();
private decimal _prevFilter1;
private decimal _prevFilter2;
private bool _isInitialized;
private int _barsSinceTrade;
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ModifiedOptimumEllipticFilterStrategy"/>.
/// </summary>
public ModifiedOptimumEllipticFilterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle Type", "General");
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_filter.Reset();
_prevFilter1 = 0m;
_prevFilter2 = 0m;
_isInitialized = false;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_filter, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _filter);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal filterValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading() || !_filter.IsFormed)
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
if (!_isInitialized)
{
_prevFilter2 = filterValue;
_prevFilter1 = filterValue;
_isInitialized = true;
return;
}
var crossUp = _prevFilter1 <= _prevFilter2 && filterValue > _prevFilter1;
var crossDown = _prevFilter1 >= _prevFilter2 && filterValue < _prevFilter1;
if (_barsSinceTrade >= CooldownBars)
{
if (crossUp && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
else if (crossDown && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_prevFilter2 = _prevFilter1;
_prevFilter1 = filterValue;
}
private class ModifiedOptimumEllipticFilter : BaseIndicator
{
private decimal _price0;
private decimal _price1;
private decimal _price2;
private decimal _price3;
private decimal _filter0;
private decimal _filter1;
private int _priceCount;
private int _filterCount;
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var candle = input.GetValue<ICandleMessage>();
if (candle == null)
{
IsFormed = false;
return new DecimalIndicatorValue(this, 0m, input.Time);
}
var price = (candle.HighPrice + candle.LowPrice) / 2m;
_price3 = _price2;
_price2 = _price1;
_price1 = _price0;
_price0 = price;
_priceCount = Math.Min(_priceCount + 1, 4);
decimal value;
if (_priceCount < 4 || _filterCount < 2)
{
value = price;
IsFormed = false;
}
else
{
value = 0.13785m * (2m * _price0 - _price1)
+ 0.0007m * (2m * _price1 - _price2)
+ 0.13785m * (2m * _price2 - _price3)
+ 1.2103m * _filter0
- 0.4867m * _filter1;
IsFormed = true;
}
_filter1 = _filter0;
_filter0 = value;
_filterCount = Math.Min(_filterCount + 1, 2);
return new DecimalIndicatorValue(this, value, input.Time);
}
public override void Reset()
{
base.Reset();
_price0 = 0m;
_price1 = 0m;
_price2 = 0m;
_price3 = 0m;
_filter0 = 0m;
_filter1 = 0m;
_priceCount = 0;
_filterCount = 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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class modified_optimum_elliptic_filter_strategy(Strategy):
def __init__(self):
super(modified_optimum_elliptic_filter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle Type", "General")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._prev_filter1 = 0.0
self._prev_filter2 = 0.0
self._is_initialized = False
self._bars_since_trade = 0
self._price0 = 0.0
self._price1 = 0.0
self._price2 = 0.0
self._price3 = 0.0
self._filter0 = 0.0
self._filter1 = 0.0
self._price_count = 0
self._filter_count = 0
self._is_formed = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
def _compute_filter(self, candle):
price = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
self._price3 = self._price2
self._price2 = self._price1
self._price1 = self._price0
self._price0 = price
self._price_count = min(self._price_count + 1, 4)
if self._price_count < 4 or self._filter_count < 2:
value = price
self._is_formed = False
else:
value = (0.13785 * (2.0 * self._price0 - self._price1)
+ 0.0007 * (2.0 * self._price1 - self._price2)
+ 0.13785 * (2.0 * self._price2 - self._price3)
+ 1.2103 * self._filter0
- 0.4867 * self._filter1)
self._is_formed = True
self._filter1 = self._filter0
self._filter0 = value
self._filter_count = min(self._filter_count + 1, 2)
return value
def OnStarted2(self, time):
super(modified_optimum_elliptic_filter_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
filter_value = self._compute_filter(candle)
if not self._is_formed:
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
if not self._is_initialized:
self._prev_filter2 = filter_value
self._prev_filter1 = filter_value
self._is_initialized = True
return
cross_up = self._prev_filter1 <= self._prev_filter2 and filter_value > self._prev_filter1
cross_down = self._prev_filter1 >= self._prev_filter2 and filter_value < self._prev_filter1
if self._bars_since_trade >= self.CooldownBars:
pos = self.Position
if cross_up and pos <= 0:
self.BuyMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
elif cross_down and pos >= 0:
self.SellMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
self._prev_filter2 = self._prev_filter1
self._prev_filter1 = filter_value
def OnReseted(self):
super(modified_optimum_elliptic_filter_strategy, self).OnReseted()
self._prev_filter1 = 0.0
self._prev_filter2 = 0.0
self._is_initialized = False
self._bars_since_trade = self.CooldownBars
self._price0 = 0.0
self._price1 = 0.0
self._price2 = 0.0
self._price3 = 0.0
self._filter0 = 0.0
self._filter1 = 0.0
self._price_count = 0
self._filter_count = 0
self._is_formed = False
def CreateClone(self):
return modified_optimum_elliptic_filter_strategy()