DCA-Strategie
Diese Strategie kauft in regelmäßigen Abständen einen festen Quotierungswährungsbetrag, entweder an einem gewählten Wochentag oder nach einer festgelegten Anzahl von Kerzen. Optional werden alle Positionen nach einem angegebenen Enddatum geschlossen.
Tatsächlicher Zeitplan und Volumen
Standardwerte: CandleType=5m, CapitalToInvest=200 Einheiten der Notierungswährung, StartDate=DateTime.MinValue, EndDate=DateTime.MaxValue, CloseAllOnLastCandle=false, UseDayOfWeek=false, DayOfWeek=Wednesday, EveryXCandles=72.
Auf abgeschlossenen Kerzen mit StartDate <= Kerzendatum < EndDate: Intervallmodus kauft alle 72 zulässigen Kerzen; Wochentagsmodus kauft auf der ersten zulässigen Kerze jedes Mittwochs, höchstens einmal pro Datum. 200 / Close wird auf den Security.VolumeStep abgerundet, unter Beachtung der Volumengrenzen. Es gibt nur kumulative Markt-Käufe, keine EMA-Signale und keine Shorts. Ab EndDate werden keine Käufe mehr ausgeführt. Mit CloseAllOnLastCandle=true wird die tatsächliche Restposition bei der ersten zulässigen Gelegenheit einmal geschlossen. Kein separater Preisstop; Slippage kann den Ausführungswert verändern. Die Zahlenwerte wurden hier als Defaults festgelegt, weil das ursprüngliche README sie nicht angab.
Parameter
- Kerzentyp
- Kapital pro Handel
- Startdatum
- Enddatum
- Alles bei letzter Kerze schließen
- Wochentagsmodus verwenden
- Wochentag
- Alle X Kerzen
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fixed quote-currency investment either at regular completed-candle
/// intervals or once per configured weekday. Never shorts.
/// </summary>
public class DcaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _capitalToInvest;
private readonly StrategyParam<DateTime> _startDate;
private readonly StrategyParam<DateTime> _endDate;
private readonly StrategyParam<bool> _closeAllOnLastCandle;
private readonly StrategyParam<bool> _useDayOfWeek;
private readonly StrategyParam<DayOfWeek> _dayOfWeek;
private readonly StrategyParam<int> _everyXCandles;
private int _eligibleBars;
private DateTime? _lastWeekdayPurchase;
private bool _endReached;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal CapitalToInvest { get => _capitalToInvest.Value; set => _capitalToInvest.Value = value; }
public DateTime StartDate { get => _startDate.Value; set => _startDate.Value = value; }
public DateTime EndDate { get => _endDate.Value; set => _endDate.Value = value; }
public bool CloseAllOnLastCandle { get => _closeAllOnLastCandle.Value; set => _closeAllOnLastCandle.Value = value; }
public bool UseDayOfWeek { get => _useDayOfWeek.Value; set => _useDayOfWeek.Value = value; }
public DayOfWeek DayOfWeek { get => _dayOfWeek.Value; set => _dayOfWeek.Value = value; }
public int EveryXCandles { get => _everyXCandles.Value; set => _everyXCandles.Value = value; }
public DcaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Completed-candle interval for scheduling", "General");
_capitalToInvest = Param(nameof(CapitalToInvest), 200m)
.SetGreaterThanZero()
.SetDisplay("Capital To Invest", "Fixed quote-currency amount for each purchase", "Trading");
_startDate = Param(nameof(StartDate), DateTime.MinValue)
.SetDisplay("Start Date", "Inclusive start of the buying range", "Calendar");
_endDate = Param(nameof(EndDate), DateTime.MaxValue)
.SetDisplay("End Date", "Exclusive end of the buying range", "Calendar");
_closeAllOnLastCandle = Param(nameof(CloseAllOnLastCandle), false)
.SetDisplay("Close All On Last Candle", "Liquidate accumulated holdings when EndDate is reached", "Calendar");
_useDayOfWeek = Param(nameof(UseDayOfWeek), false)
.SetDisplay("Use Day Of Week", "Purchase once per selected weekday instead of every N bars", "Calendar");
_dayOfWeek = Param(nameof(DayOfWeek), System.DayOfWeek.Wednesday)
.SetDisplay("Day Of Week", "Weekday for scheduled purchases", "Calendar");
_everyXCandles = Param(nameof(EveryXCandles), 72)
.SetGreaterThanZero()
.SetDisplay("Every X Candles", "Completed candles per periodic purchase", "Calendar");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_eligibleBars = 0;
_lastWeekdayPurchase = null;
_endReached = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (StartDate.Date > EndDate.Date)
throw new ArgumentException("StartDate must not be after EndDate.");
_eligibleBars = 0;
_lastWeekdayPurchase = null;
_endReached = false;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var date = candle.OpenTime.Date;
if (date < StartDate.Date)
return;
// Treat EndDate as the first day on which new investments cease;
// optionally exit once, using actual holdings rather than Volume.
if (date >= EndDate.Date)
{
if (!_endReached && CloseAllOnLastCandle &&
Position > 0m && IsFormedAndOnlineAndAllowTrading())
{
SellMarket(Position);
_endReached = true;
}
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (UseDayOfWeek)
{
if (candle.OpenTime.DayOfWeek != DayOfWeek ||
_lastWeekdayPurchase == date)
return;
}
else
{
_eligibleBars++;
if (_eligibleBars % EveryXCandles != 0)
return;
}
var price = candle.ClosePrice;
if (price <= 0m || Security is null)
return;
// The budget is expressed in quoted currency. Floor to the native
// volume step and never silently round up into over-investment.
var step = Security.VolumeStep is > 0m ? Security.VolumeStep.Value : 0.001m;
var maxVolume = Security.MaxVolume is > 0m ? Security.MaxVolume.Value : decimal.MaxValue;
var budgetVolume = Math.Min(CapitalToInvest / price, maxVolume);
var volume = Math.Floor(budgetVolume / step) * step;
if (volume <= 0m || (Security.MinVolume > 0m && volume < Security.MinVolume))
return;
BuyMarket(volume);
if (UseDayOfWeek)
_lastWeekdayPurchase = date;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo.Strategies")
from System import DateTime, DayOfWeek, TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class dca_strategy(Strategy):
"""Buy a fixed quote-currency amount on a weekday or every N finished candles."""
def __init__(self):
super(dca_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Completed-candle schedule", "General")
self._capital_to_invest = self.Param("CapitalToInvest", 200.0) \
.SetGreaterThanZero() \
.SetDisplay("Capital To Invest", "Fixed quote-currency amount per purchase", "Trading")
self._start_date = self.Param("StartDate", DateTime.MinValue) \
.SetDisplay("Start Date", "Inclusive buying start", "Calendar")
self._end_date = self.Param("EndDate", DateTime.MaxValue) \
.SetDisplay("End Date", "Exclusive buying end", "Calendar")
self._close_all_on_last_candle = self.Param("CloseAllOnLastCandle", False) \
.SetDisplay("Close All On Last Candle", "Close holdings when EndDate is reached", "Calendar")
self._use_day_of_week = self.Param("UseDayOfWeek", False) \
.SetDisplay("Use Day Of Week", "Use weekday rather than N bars", "Calendar")
self._day_of_week = self.Param("DayOfWeek", DayOfWeek.Wednesday) \
.SetDisplay("Day Of Week", "Selected purchase weekday", "Calendar")
self._every_x_candles = self.Param("EveryXCandles", 72) \
.SetGreaterThanZero() \
.SetDisplay("Every X Candles", "Period of purchases in completed candles", "Calendar")
self._eligible_bars = 0
self._last_weekday_purchase = None
self._end_reached = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dca_strategy, self).OnReseted()
self._eligible_bars = 0
self._last_weekday_purchase = None
self._end_reached = False
def OnStarted2(self, time):
super(dca_strategy, self).OnStarted2(time)
if self._start_date.Value.Date > self._end_date.Value.Date:
raise ValueError("StartDate must not be after EndDate")
self._eligible_bars = 0
self._last_weekday_purchase = None
self._end_reached = False
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
date = candle.OpenTime.Date
if date < self._start_date.Value.Date:
return
if date >= self._end_date.Value.Date:
if not self._end_reached and bool(self._close_all_on_last_candle.Value) and self.Position > 0 and self.IsFormedAndOnlineAndAllowTrading():
self.SellMarket(self.Position)
self._end_reached = True
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
weekday_mode = bool(self._use_day_of_week.Value)
if weekday_mode:
if candle.OpenTime.DayOfWeek != self._day_of_week.Value or self._last_weekday_purchase == date:
return
else:
self._eligible_bars += 1
if self._eligible_bars % int(self._every_x_candles.Value) != 0:
return
price = float(candle.ClosePrice)
if price <= 0 or self.Security is None:
return
step = float(self.Security.VolumeStep)
if step <= 0:
step = 0.001
max_volume = float(self.Security.MaxVolume)
if max_volume <= 0:
max_volume = float("inf")
# Never round a quote-valued investment UP above the budget.
volume = math.floor(min(float(self._capital_to_invest.Value) / price, max_volume) / step) * step
min_volume = float(self.Security.MinVolume)
if volume <= 0 or (min_volume > 0 and volume < min_volume):
return
self.BuyMarket(Decimal.Parse(str(volume)))
if weekday_mode:
self._last_weekday_purchase = date
def CreateClone(self):
return dca_strategy()