Estrategia de Reversión a la Media V-F
Esta estrategia compra en hasta cinco capas cuando el precio cae por debajo de una media móvil en porcentajes predefinidos. Las posiciones se cierran en un take profit fijo con trailing opcional.
Detalles
- Entrada: el precio cruza por debajo de los niveles de desviación desde la media móvil seleccionada.
- Salida: se alcanza el profit objetivo o se activa el trailing stop.
- Indicadores: Media Móvil.
- Dirección: Solo largos.
- Stops: Take profit, trailing opcional.
using System;
using System.Linq;
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;
public class MeanReversionVFStrategy : Strategy
{
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _deviation1;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private WeightedMovingAverage _ma;
private decimal _entryPrice;
private decimal _prevClose;
private bool _hasPrevClose;
private int _barsFromSignal;
public int MaLength { get => _maLength.Value; set => _maLength.Value = value; }
public decimal Deviation1 { get => _deviation1.Value; set => _deviation1.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MeanReversionVFStrategy()
{
_maLength = Param(nameof(MaLength), 24)
.SetGreaterThanZero()
.SetDisplay("MA Length", "WMA length", "General");
_deviation1 = Param(nameof(Deviation1), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Deviation %", "Lower deviation from WMA", "General");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 4m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Target profit percent", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 80)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = null;
_entryPrice = 0m;
_prevClose = 0m;
_hasPrevClose = false;
_barsFromSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma = new WeightedMovingAverage { Length = MaLength };
_entryPrice = 0;
_prevClose = 0m;
_hasPrevClose = false;
_barsFromSignal = SignalCooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (maValue == 0)
return;
var l1 = maValue * (1 - Deviation1 / 100m);
var close = candle.ClosePrice;
_barsFromSignal++;
if (Position > 0 && _entryPrice > 0)
{
var tpPrice = _entryPrice * (1 + TakeProfitPercent / 100m);
if (candle.HighPrice >= tpPrice)
{
SellMarket();
_entryPrice = 0;
return;
}
}
var crossedBelow = _hasPrevClose && _prevClose >= l1 && close < l1;
if (_barsFromSignal >= SignalCooldownBars && crossedBelow && Position <= 0)
{
BuyMarket();
_entryPrice = close;
_barsFromSignal = 0;
}
else if (close > maValue && Position > 0)
{
SellMarket();
_entryPrice = 0;
}
_prevClose = close;
_hasPrevClose = true;
}
}
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
from StockSharp.Algo.Indicators import WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class mean_reversion_vf_strategy(Strategy):
"""
Mean Reversion VF: WMA deviation entry with take profit.
"""
def __init__(self):
super(mean_reversion_vf_strategy, self).__init__()
self._ma_length = self.Param("MaLength", 24).SetDisplay("MA Length", "WMA length", "Indicators")
self._deviation = self.Param("Deviation1", 0.5).SetDisplay("Deviation %", "Lower deviation from WMA", "Signals")
self._take_profit_pct = self.Param("TakeProfitPercent", 4.0).SetDisplay("TP %", "Target profit percent", "Risk")
self._cooldown_bars = self.Param("SignalCooldownBars", 80).SetDisplay("Cooldown", "Min bars between entries", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._entry_price = 0.0
self._prev_close = 0.0
self._has_prev = False
self._bars_from_signal = 80
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mean_reversion_vf_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_close = 0.0
self._has_prev = False
self._bars_from_signal = self._cooldown_bars.Value
def OnStarted2(self, time):
super(mean_reversion_vf_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._prev_close = 0.0
self._has_prev = False
self._bars_from_signal = self._cooldown_bars.Value
wma = WeightedMovingAverage()
wma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(wma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, wma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
ma = float(ma_val)
if ma == 0:
return
close = float(candle.ClosePrice)
dev = float(self._deviation.Value)
l1 = ma * (1.0 - dev / 100.0)
self._bars_from_signal += 1
tp_pct = float(self._take_profit_pct.Value) / 100.0
if self.Position > 0 and self._entry_price > 0:
tp_price = self._entry_price * (1.0 + tp_pct)
if float(candle.HighPrice) >= tp_price:
self.SellMarket()
self._entry_price = 0.0
return
crossed_below = self._has_prev and self._prev_close >= l1 and close < l1
if self._bars_from_signal >= self._cooldown_bars.Value and crossed_below and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._bars_from_signal = 0
elif close > ma and self.Position > 0:
self.SellMarket()
self._entry_price = 0.0
self._prev_close = close
self._has_prev = True
def CreateClone(self):
return mean_reversion_vf_strategy()