每月购买策略(动态合约规模)
在每月指定日期以账户权益的固定百分比购买动态数量的合约,并跟踪回撤用于参考。
细节
- 入场条件:时间 >= StartDate 且 月份日 = BuyDay
- 多空方向:仅做多
- 出场条件:无
- 止损:无
- 默认值:
CandleType= 1 天StartDate= 2010-01-01PercentOfEquity= 0.03BuyDay= 1
- 过滤器:
- 类别:定投
- 方向:多头
- 指标:无
- 止损:无
- 复杂度:初级
- 时间框架:长期
- 季节性:每月
- 神经网络:否
- 背离:否
- 风险等级:低
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Monthly purchase strategy with dynamic contract size.
/// From StartDate on, buys on the candle whose day of month equals BuyDay. The size is PercentOfEquity of the
/// current portfolio value divided by the close price. Positions are never closed; the equity drawdown is only tracked.
/// </summary>
public class MonthlyPurchaseWithDynamicContractSizeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DateTimeOffset> _startDate;
private readonly StrategyParam<decimal> _percentOfEquity;
private readonly StrategyParam<int> _buyDay;
private DateTime? _lastBuyDate;
private decimal _peakEquity;
private decimal _maxDrawdown;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Purchases are allowed from this date on.
/// </summary>
public DateTimeOffset StartDate
{
get => _startDate.Value;
set => _startDate.Value = value;
}
/// <summary>
/// Fraction of equity spent on each purchase.
/// </summary>
public decimal PercentOfEquity
{
get => _percentOfEquity.Value;
set => _percentOfEquity.Value = value;
}
/// <summary>
/// Day of the month to buy.
/// </summary>
public int BuyDay
{
get => _buyDay.Value;
set => _buyDay.Value = value;
}
/// <summary>
/// Largest equity drawdown seen so far, for information only.
/// </summary>
public decimal MaxDrawdown => _maxDrawdown;
/// <summary>
/// Constructor.
/// </summary>
public MonthlyPurchaseWithDynamicContractSizeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_startDate = Param(nameof(StartDate), new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("Start Date", "Purchases are allowed from this date on", "General");
_percentOfEquity = Param(nameof(PercentOfEquity), 0.03m)
.SetGreaterThanZero()
.SetDisplay("Percent of Equity", "Fraction of equity spent on each purchase", "Trading");
_buyDay = Param(nameof(BuyDay), 1)
.SetRange(1, 31)
.SetDisplay("Buy Day", "Day of the month to buy", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastBuyDate = null;
_peakEquity = 0m;
_maxDrawdown = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lastBuyDate = null;
_peakEquity = 0m;
_maxDrawdown = 0m;
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 equity = Portfolio?.CurrentValue ?? 0m;
if (equity > _peakEquity)
_peakEquity = equity;
if (_peakEquity > 0m)
_maxDrawdown = Math.Max(_maxDrawdown, (_peakEquity - equity) / _peakEquity);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var openTime = candle.OpenTime;
if (openTime < StartDate.UtcDateTime || openTime.Day != BuyDay || _lastBuyDate == openTime.Date)
return;
var close = candle.ClosePrice;
if (close <= 0m || equity <= 0m)
return;
var step = Security?.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
var volume = Math.Floor(equity * PercentOfEquity / close / step) * step;
if (volume <= 0m)
return;
BuyMarket(volume);
_lastBuyDate = openTime.Date;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DateTimeOffset, Decimal, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class monthly_purchase_with_dynamic_contract_size_strategy(Strategy):
"""
Monthly purchase strategy with dynamic contract size.
From StartDate on, buys on the candle whose day of month equals BuyDay. The size is PercentOfEquity of the
current portfolio value divided by the close price. Positions are never closed; the equity drawdown is only tracked.
"""
def __init__(self):
super(monthly_purchase_with_dynamic_contract_size_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._start_date = self.Param("StartDate", DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero)).SetDisplay("Start Date", "Purchases are allowed from this date on", "General")
self._percent_of_equity = self.Param("PercentOfEquity", 0.03).SetGreaterThanZero().SetDisplay("Percent of Equity", "Fraction of equity spent on each purchase", "Trading")
self._buy_day = self.Param("BuyDay", 1).SetRange(1, 31).SetDisplay("Buy Day", "Day of the month to buy", "Trading")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
@property
def max_drawdown(self):
return self._max_drawdown
def _reset_state(self):
self._last_buy_date = None
self._peak_equity = Decimal(0)
self._max_drawdown = Decimal(0)
def OnReseted(self):
super(monthly_purchase_with_dynamic_contract_size_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(monthly_purchase_with_dynamic_contract_size_strategy, self).OnStarted2(time)
self._reset_state()
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
equity = Decimal(0)
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = self.Portfolio.CurrentValue
if equity > self._peak_equity:
self._peak_equity = equity
if self._peak_equity > 0:
self._max_drawdown = max(self._max_drawdown, (self._peak_equity - equity) / self._peak_equity)
if not self.IsFormedAndOnlineAndAllowTrading():
return
open_time = candle.OpenTime
if open_time < self._start_date.Value.UtcDateTime or open_time.Day != self._buy_day.Value or self._last_buy_date == open_time.Date:
return
close = candle.ClosePrice
if close <= 0 or equity <= 0:
return
step = Decimal(1)
if self.Security is not None and self.Security.VolumeStep is not None and self.Security.VolumeStep > 0:
step = self.Security.VolumeStep
volume = Math.Floor(equity * Decimal(self._percent_of_equity.Value) / close / step) * step
if volume <= 0:
return
self.BuyMarket(volume)
self._last_buy_date = open_time.Date
def CreateClone(self):
return monthly_purchase_with_dynamic_contract_size_strategy()