Estrategia de Promediado a la Baja
Esta estrategia abre una posición cuando el precio se mueve fuera de una banda basada en ATR alrededor del EMA. Si el mercado se mueve en contra de la posición, la estrategia añade a ella usando desviaciones porcentuales escalonadas (DCA). Se toma ganancia cuando el precio vuelve a la entrada promediada más un porcentaje fijo.
Parámetros
- Candle Type – tipo de velas a procesar.
- EMA Length – período para el filtro de tendencia EMA.
- ATR Length – período para ATR.
- ATR Mult – multiplicador para las bandas ATR.
- TP % – porcentaje de toma de ganancias desde la entrada promedio.
- Base Deviation % – desviación inicial para el primer nivel DCA.
- Step Scale – multiplicador aplicado a la desviación para cada nuevo nivel DCA.
- DCA Size Multiplier – multiplicador de volumen para cada orden DCA.
- Max DCA Levels – número máximo de entradas de promediado.
- Initial Volume – volumen de la primera orden.
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>
/// Averaging Down Strategy.
/// Uses EMA + ATR bands to detect entry zones, then averages down
/// if price moves against position. Takes profit at target %.
/// </summary>
public class AveragingDownStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _tpPercent;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _ema;
private AverageTrueRange _atr;
private decimal _entryPrice;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
public decimal TpPercent
{
get => _tpPercent.Value;
set => _tpPercent.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public AveragingDownStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetDisplay("ATR Multiplier", "ATR band multiplier", "Indicators");
_tpPercent = Param(nameof(TpPercent), 2m)
.SetDisplay("TP %", "Take profit percent", "Trading");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_atr = null;
_entryPrice = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaLength };
_atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _atr, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_atr.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var price = candle.ClosePrice;
var upperBand = emaVal + atrVal * AtrMultiplier;
var lowerBand = emaVal - atrVal * AtrMultiplier;
// Entry: price breaks above upper band -> buy
if (price > upperBand && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_entryPrice = price;
_cooldownRemaining = CooldownBars;
}
// Entry: price breaks below lower band -> sell
else if (price < lowerBand && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_entryPrice = price;
_cooldownRemaining = CooldownBars;
}
// Take profit on long
else if (Position > 0 && _entryPrice > 0 && price >= _entryPrice * (1 + TpPercent / 100m))
{
SellMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
// Take profit on short
else if (Position < 0 && _entryPrice > 0 && price <= _entryPrice * (1 - TpPercent / 100m))
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
// Exit long at EMA (stop)
else if (Position > 0 && price < emaVal)
{
SellMarket(Math.Abs(Position));
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
// Exit short at EMA (stop)
else if (Position < 0 && price > emaVal)
{
BuyMarket(Math.Abs(Position));
_entryPrice = 0;
_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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class averaging_down_strategy(Strategy):
"""Averaging Down Strategy."""
def __init__(self):
super(averaging_down_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "ATR band multiplier", "Indicators")
self._tp_percent = self.Param("TpPercent", 2.0) \
.SetDisplay("TP %", "Take profit percent", "Trading")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._ema = None
self._atr = None
self._entry_price = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(averaging_down_strategy, self).OnReseted()
self._ema = None
self._atr = None
self._entry_price = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(averaging_down_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
self._atr = AverageTrueRange()
self._atr.Length = int(self._atr_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._atr, 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, atr_val):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed or not self._atr.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
price = float(candle.ClosePrice)
ema_v = float(ema_val)
atr_v = float(atr_val)
multiplier = float(self._atr_multiplier.Value)
upper_band = ema_v + atr_v * multiplier
lower_band = ema_v - atr_v * multiplier
tp_pct = float(self._tp_percent.Value)
cooldown = int(self._cooldown_bars.Value)
if price > upper_band and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._entry_price = price
self._cooldown_remaining = cooldown
elif price < lower_band and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._entry_price = price
self._cooldown_remaining = cooldown
elif self.Position > 0 and self._entry_price > 0 and price >= self._entry_price * (1 + tp_pct / 100.0):
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position < 0 and self._entry_price > 0 and price <= self._entry_price * (1 - tp_pct / 100.0):
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position > 0 and price < ema_v:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self.Position < 0 and price > ema_v:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = 0.0
self._cooldown_remaining = cooldown
def CreateClone(self):
return averaging_down_strategy()