Estrategia EMA Moving Away
EMA Moving Away rastrea cuánto se aleja el precio de una media móvil exponencial. Cuando una secuencia de velas empuja un porcentaje definido por debajo del EMA, la estrategia apuesta a un retorno a la media.
El setup se enfoca en el lado largo: después de un movimiento bajista extendido que
lleva el precio por debajo del EMA en MovingAwayPercent, se abre una posición.
Los filtros de tamaño del cuerpo y de racha pueden añadirse para asegurar que el
movimiento esté estirado en lugar de ser ruido. Un stop-loss porcentual protege el
capital si la reversión falla.
Detalles
- Datos: Velas de precio.
- Criterios de entrada:
- Largo: Cierre por debajo del EMA en
MovingAwayPercentcon los filtros de racha/tamaño requeridos. - Corto: no se utiliza.
- Largo: Cierre por debajo del EMA en
- Criterios de salida: Retorno al EMA o activación del stop-loss.
- Stops: Stop porcentual basado en
StopLossPercent. - Valores predeterminados:
EmaLength= 55MovingAwayPercent= 2.0StopLossPercent= 2.0
- Filtros:
- Categoría: Reversión a la media
- Dirección: Solo largos
- Indicadores: EMA
- Complejidad: Moderado
- Nivel de riesgo: Medio
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// EMA Moving Away Strategy.
/// Buys when price moves too far below EMA (mean reversion).
/// Sells when price moves too far above EMA.
/// Exits when price returns to EMA.
/// </summary>
public class EmaMovingAwayStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _movingAwayPercent;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _ema;
private int _cooldownRemaining;
public EmaMovingAwayStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_emaLength = Param(nameof(EmaLength), 55)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Moving Average");
_movingAwayPercent = Param(nameof(MovingAwayPercent), 1.5m)
.SetDisplay("Moving away (%)", "Required percentage that price moves away from EMA", "Strategy");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public decimal MovingAwayPercent
{
get => _movingAwayPercent.Value;
set => _movingAwayPercent.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var close = candle.ClosePrice;
// Calculate entry zones
var longEntryLevel = emaValue * (1 - MovingAwayPercent / 100);
var shortEntryLevel = emaValue * (1 + MovingAwayPercent / 100);
// Exit conditions - price returns to EMA
if (Position > 0 && close >= emaValue)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
return;
}
else if (Position < 0 && close <= emaValue)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
return;
}
// Entry: price far below EMA - buy (mean reversion)
if (close <= longEntryLevel && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Entry: price far above EMA - sell (mean reversion)
else if (close >= shortEntryLevel && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_moving_away_strategy(Strategy):
"""EMA Moving Away Strategy - mean reversion from EMA."""
def __init__(self):
super(ema_moving_away_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._ema_length = self.Param("EmaLength", 55) \
.SetDisplay("EMA Length", "EMA period", "Moving Average")
self._moving_away_pct = self.Param("MovingAwayPercent", 1.5) \
.SetDisplay("Moving away (%)", "Required percentage that price moves away from EMA", "Strategy")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._ema = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_moving_away_strategy, self).OnReseted()
self._ema = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ema_moving_away_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
close = float(candle.ClosePrice)
ev = float(ema_val)
pct = float(self._moving_away_pct.Value)
cooldown = int(self._cooldown_bars.Value)
long_entry = ev * (1.0 - pct / 100.0)
short_entry = ev * (1.0 + pct / 100.0)
if self.Position > 0 and close >= ev:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
return
elif self.Position < 0 and close <= ev:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
return
if close <= long_entry and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif close >= short_entry and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
def CreateClone(self):
return ema_moving_away_strategy()