Estrategia de Promedio de Costo en Dólares Óptimo
Esta estrategia acumula una posición invirtiendo una cantidad fija de capital a intervalos regulares entre fechas de inicio y fin definidas por el usuario. Cada compra se realiza al precio de cierre del marco temporal seleccionado independientemente del precio, implementando un enfoque clásico de promedio de costo en dólares.
Detalles
- Criterios de entrada:
- En cada intervalo (diario, semanal o mensual) entre las fechas de inicio y fin, la estrategia compra al precio de cierre por la cantidad configurada.
- Largo/Corto: Solo largos.
- Criterios de salida:
- Las posiciones se mantienen; no se incluye lógica de salida automática.
- Stops: Ninguno.
- Valores predeterminados:
- Monto por período = 100.
- Intervalo = Semanal.
- Fecha de inicio = 2018-01-01, Fecha de fin = 2020-01-28.
- Filtros:
- Categoría: Acumulación.
- Dirección: Largo.
- Indicadores: Ninguno.
- Stops: No.
- Complejidad: Bajo.
- Marco temporal: Cualquiera.
- Estacionalidad: No.
- Redes neuronales: No.
- Divergencia: No.
- Nivel de riesgo: Bajo.
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>
/// Dollar Cost Average strategy — accumulates position at regular intervals,
/// sells on RSI overbought with forced sell after max accumulation period.
/// </summary>
public class BestDollarCostAverageStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _buyIntervalBars;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiSellLevel;
private readonly StrategyParam<int> _maxAccumulationBars;
private RelativeStrengthIndex _rsi;
private int _barsSinceLastBuy;
private int _totalBarsInPosition;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int BuyIntervalBars { get => _buyIntervalBars.Value; set => _buyIntervalBars.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal RsiSellLevel { get => _rsiSellLevel.Value; set => _rsiSellLevel.Value = value; }
public int MaxAccumulationBars { get => _maxAccumulationBars.Value; set => _maxAccumulationBars.Value = value; }
public BestDollarCostAverageStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_buyIntervalBars = Param(nameof(BuyIntervalBars), 350)
.SetGreaterThanZero()
.SetDisplay("Buy Interval", "Bars between DCA buys", "DCA");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period for sell signal", "Indicators");
_rsiSellLevel = Param(nameof(RsiSellLevel), 60m)
.SetDisplay("RSI Sell Level", "RSI level to trigger sell", "Indicators");
_maxAccumulationBars = Param(nameof(MaxAccumulationBars), 1200)
.SetGreaterThanZero()
.SetDisplay("Max Accumulation", "Max bars before forced sell", "DCA");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_barsSinceLastBuy = 0;
_totalBarsInPosition = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
var rsiArea = CreateChartArea();
if (rsiArea != null)
{
DrawIndicator(rsiArea, _rsi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
return;
_barsSinceLastBuy++;
if (Position > 0)
_totalBarsInPosition++;
// Forced sell after max accumulation period
if (Position > 0 && _totalBarsInPosition >= MaxAccumulationBars)
{
SellMarket();
_totalBarsInPosition = 0;
_barsSinceLastBuy = 0;
return;
}
// Sell accumulated position when RSI is overbought
if (Position > 0 && rsiValue >= RsiSellLevel && _totalBarsInPosition >= BuyIntervalBars)
{
SellMarket();
_totalBarsInPosition = 0;
_barsSinceLastBuy = 0;
return;
}
// DCA buy at regular intervals
if (_barsSinceLastBuy >= BuyIntervalBars)
{
BuyMarket();
_barsSinceLastBuy = 0;
if (_totalBarsInPosition == 0)
_totalBarsInPosition = 1;
}
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class best_dollar_cost_average_strategy(Strategy):
def __init__(self):
super(best_dollar_cost_average_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._buy_interval_bars = self.Param("BuyIntervalBars", 350) \
.SetGreaterThanZero() \
.SetDisplay("Buy Interval", "Bars between DCA buys", "DCA")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "RSI period for sell signal", "Indicators")
self._rsi_sell_level = self.Param("RsiSellLevel", 60.0) \
.SetDisplay("RSI Sell Level", "RSI level to trigger sell", "Indicators")
self._max_accumulation_bars = self.Param("MaxAccumulationBars", 1200) \
.SetGreaterThanZero() \
.SetDisplay("Max Accumulation", "Max bars before forced sell", "DCA")
self._bars_since_last_buy = 0
self._total_bars_in_position = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(best_dollar_cost_average_strategy, self).OnReseted()
self._bars_since_last_buy = 0
self._total_bars_in_position = 0
def OnStarted2(self, time):
super(best_dollar_cost_average_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
rsi_area = self.CreateChartArea()
if rsi_area is not None:
self.DrawIndicator(rsi_area, rsi)
def OnProcess(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
rsi_v = float(rsi_val)
self._bars_since_last_buy += 1
if self.Position > 0:
self._total_bars_in_position += 1
max_acc = self._max_accumulation_bars.Value
if self.Position > 0 and self._total_bars_in_position >= max_acc:
self.SellMarket()
self._total_bars_in_position = 0
self._bars_since_last_buy = 0
return
sell_level = float(self._rsi_sell_level.Value)
interval = self._buy_interval_bars.Value
if self.Position > 0 and rsi_v >= sell_level and self._total_bars_in_position >= interval:
self.SellMarket()
self._total_bars_in_position = 0
self._bars_since_last_buy = 0
return
if self._bars_since_last_buy >= interval:
self.BuyMarket()
self._bars_since_last_buy = 0
if self._total_bars_in_position == 0:
self._total_bars_in_position = 1
def CreateClone(self):
return best_dollar_cost_average_strategy()