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
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>
/// EMA Moving Away strategy (long only).
/// Buys when the close is at least MovingAwayPercent below the EMA, optionally requiring a streak of bearish candles
/// (BearishStreak) and a minimum candle body (MinBodyPercent). The long closes when price returns to the EMA, and a
/// percent stop-loss protects it if the reversion fails.
/// </summary>
public class EmaMovingAwayStrategy : Strategy
{
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _movingAwayPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _bearishStreak;
private readonly StrategyParam<decimal> _minBodyPercent;
private readonly StrategyParam<DataType> _candleType;
private int _bearishCount;
/// <summary>
/// EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Required distance of the close below the EMA, in percent.
/// </summary>
public decimal MovingAwayPercent
{
get => _movingAwayPercent.Value;
set => _movingAwayPercent.Value = value;
}
/// <summary>
/// Stop-loss percentage. 0 disables it.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Consecutive bearish candles required before an entry. 0 disables the filter.
/// </summary>
public int BearishStreak
{
get => _bearishStreak.Value;
set => _bearishStreak.Value = value;
}
/// <summary>
/// Minimum body of the signal candle in percent of its open. 0 disables the filter.
/// </summary>
public decimal MinBodyPercent
{
get => _minBodyPercent.Value;
set => _minBodyPercent.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EmaMovingAwayStrategy()
{
_emaLength = Param(nameof(EmaLength), 55)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Moving Average");
_movingAwayPercent = Param(nameof(MovingAwayPercent), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Moving away (%)", "Required distance of the close below the EMA", "Strategy");
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk");
_bearishStreak = Param(nameof(BearishStreak), 0)
.SetNotNegative()
.SetDisplay("Bearish Streak", "Consecutive bearish candles required, 0 disables", "Filters");
_minBodyPercent = Param(nameof(MinBodyPercent), 0m)
.SetNotNegative()
.SetDisplay("Min Body %", "Minimum body of the signal candle in percent, 0 disables", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bearishCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bearishCount = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, ProcessCandle)
.Start();
if (StopLossPercent > 0)
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
_bearishCount = candle.ClosePrice < candle.OpenPrice ? _bearishCount + 1 : 0;
if (!emaValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ema = emaValue.ToDecimal();
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= ema)
SellMarket(Position);
return;
}
var stretched = close <= ema * (1m - MovingAwayPercent / 100m);
var streakOk = BearishStreak <= 0 || _bearishCount >= BearishStreak;
var bodyOk = MinBodyPercent <= 0 || candle.OpenPrice > 0 && Math.Abs(candle.OpenPrice - close) / candle.OpenPrice * 100m >= MinBodyPercent;
if (stretched && streakOk && bodyOk)
BuyMarket(Volume + Math.Abs(Position));
}
}
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
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_moving_away_strategy(Strategy):
"""
EMA Moving Away strategy (long only).
Buys when the close is at least MovingAwayPercent below the EMA, optionally requiring a streak of bearish candles
(BearishStreak) and a minimum candle body (MinBodyPercent). The long closes when price returns to the EMA, and a
percent stop-loss protects it if the reversion fails.
"""
def __init__(self):
super(ema_moving_away_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 55) \
.SetGreaterThanZero() \
.SetDisplay("EMA Length", "EMA period", "Moving Average")
self._moving_away_percent = self.Param("MovingAwayPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Moving away (%)", "Required distance of the close below the EMA", "Strategy")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk")
self._bearish_streak = self.Param("BearishStreak", 0) \
.SetNotNegative() \
.SetDisplay("Bearish Streak", "Consecutive bearish candles required, 0 disables", "Filters")
self._min_body_percent = self.Param("MinBodyPercent", 0.0) \
.SetNotNegative() \
.SetDisplay("Min Body %", "Minimum body of the signal candle in percent, 0 disables", "Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation", "General")
self._bearish_count = 0
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(ema_moving_away_strategy, self).OnReseted()
self._bearish_count = 0
def OnStarted2(self, time):
super(ema_moving_away_strategy, self).OnStarted2(time)
self._bearish_count = 0
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(ema, self._process_candle).Start()
stop_loss = float(self._stop_loss_percent.Value)
if stop_loss > 0:
self.StartProtection(Unit(), Unit(Decimal(stop_loss), UnitTypes.Percent), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
self._bearish_count = self._bearish_count + 1 if candle.ClosePrice < candle.OpenPrice else 0
if not ema_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ema = float(ema_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
if self.Position > 0:
if close >= ema:
self.SellMarket(self.Position)
return
streak = int(self._bearish_streak.Value)
min_body = float(self._min_body_percent.Value)
stretched = close <= ema * (1.0 - float(self._moving_away_percent.Value) / 100.0)
streak_ok = streak <= 0 or self._bearish_count >= streak
body_ok = min_body <= 0 or (open_price > 0 and abs(open_price - close) / open_price * 100.0 >= min_body)
if stretched and streak_ok and body_ok:
self.BuyMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return ema_moving_away_strategy()