在 GitHub 上查看

Monthly Breakout 策略

该策略只在选定的月份交易当前月份的高点或低点突破。可以选择在月高或月低做多或做空,持仓在固定的柱数后平仓。

详情

  • 入场条件:根据 EntryOption 与所选月份,如收盘价突破当月高点。
  • 多空方向:可配置。
  • 出场条件:持仓达到 HoldingPeriod 根K线后平仓。
  • 止损:无。
  • 默认值
    • EntryOption = LongAtHigh
    • HoldingPeriod = 5
    • CandleType = TimeSpan.FromDays(1)
  • 过滤器
    • 分类:Breakout
    • 方向:可配置
    • 指标:无
    • 止损:无
    • 复杂度:基础
    • 时间框架:日线
    • 季节性:有
    • 神经网络:无
    • 背离:无
    • 风险等级:中等
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Monthly breakout strategy using selectable months and holding period.
/// </summary>
public class MonthlyBreakoutStrategy : Strategy
{
	public enum EntryOptions
	{
		LongAtHigh,
		ShortAtHigh,
		LongAtLow,
		ShortAtLow
	}
	
	private readonly StrategyParam<EntryOptions> _entryOption;
	private readonly StrategyParam<int> _holdingPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _january;
	private readonly StrategyParam<bool> _february;
	private readonly StrategyParam<bool> _march;
	private readonly StrategyParam<bool> _april;
	private readonly StrategyParam<bool> _may;
	private readonly StrategyParam<bool> _june;
	private readonly StrategyParam<bool> _july;
	private readonly StrategyParam<bool> _august;
	private readonly StrategyParam<bool> _september;
	private readonly StrategyParam<bool> _october;
	private readonly StrategyParam<bool> _november;
	private readonly StrategyParam<bool> _december;
	
	private decimal _monthlyHigh;
	private decimal _monthlyLow;
	private decimal _prevMonthlyHigh;
	private decimal _prevMonthlyLow;
	private decimal _prevClose;
	private int _currentMonth;
	private int _barIndex;
	private int? _entryBar;
	
	public EntryOptions EntryOption
	{
		get => _entryOption.Value;
		set => _entryOption.Value = value;
	}
	
	public int HoldingPeriod
	{
		get => _holdingPeriod.Value;
		set => _holdingPeriod.Value = value;
	}
	
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	public bool January { get => _january.Value; set => _january.Value = value; }
	public bool February { get => _february.Value; set => _february.Value = value; }
	public bool March { get => _march.Value; set => _march.Value = value; }
	public bool April { get => _april.Value; set => _april.Value = value; }
	public bool May { get => _may.Value; set => _may.Value = value; }
	public bool June { get => _june.Value; set => _june.Value = value; }
	public bool July { get => _july.Value; set => _july.Value = value; }
	public bool August { get => _august.Value; set => _august.Value = value; }
	public bool September { get => _september.Value; set => _september.Value = value; }
	public bool October { get => _october.Value; set => _october.Value = value; }
	public bool November { get => _november.Value; set => _november.Value = value; }
	public bool December { get => _december.Value; set => _december.Value = value; }
	
	public MonthlyBreakoutStrategy()
	{
		_entryOption = Param(nameof(EntryOption), EntryOptions.LongAtLow)
		.SetDisplay("Entry Option", "Breakout direction", "General");
		
		_holdingPeriod = Param(nameof(HoldingPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Holding Period", "Bars to hold position", "General");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Working candle timeframe", "General");
		
		_january = Param(nameof(January), true).SetDisplay("January", "Enable trading in January", "Months");
		_february = Param(nameof(February), true).SetDisplay("February", "Enable trading in February", "Months");
		_march = Param(nameof(March), true).SetDisplay("March", "Enable trading in March", "Months");
		_april = Param(nameof(April), true).SetDisplay("April", "Enable trading in April", "Months");
		_may = Param(nameof(May), true).SetDisplay("May", "Enable trading in May", "Months");
		_june = Param(nameof(June), true).SetDisplay("June", "Enable trading in June", "Months");
		_july = Param(nameof(July), true).SetDisplay("July", "Enable trading in July", "Months");
		_august = Param(nameof(August), true).SetDisplay("August", "Enable trading in August", "Months");
		_september = Param(nameof(September), true).SetDisplay("September", "Enable trading in September", "Months");
		_october = Param(nameof(October), true).SetDisplay("October", "Enable trading in October", "Months");
		_november = Param(nameof(November), true).SetDisplay("November", "Enable trading in November", "Months");
		_december = Param(nameof(December), true).SetDisplay("December", "Enable trading in December", "Months");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_monthlyHigh = 0m;
		_monthlyLow = 0m;
		_prevMonthlyHigh = 0m;
		_prevMonthlyLow = 0m;
		_prevClose = 0m;
		_currentMonth = 0;
		_barIndex = 0;
		_entryBar = null;
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		var dummyEma1 = new ExponentialMovingAverage { Length = 10 };
		var dummyEma2 = new ExponentialMovingAverage { Length = 20 };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(dummyEma1, dummyEma2, ProcessCandle).Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle, decimal d1, decimal d2)
	{
	if (candle.State != CandleStates.Finished)
	return;
		
		_barIndex++;
		
	var close = candle.ClosePrice;
	var high = candle.HighPrice;
	var low = candle.LowPrice;
	var week = System.Globalization.CultureInfo.InvariantCulture.Calendar
		.GetWeekOfYear(candle.OpenTime, System.Globalization.CalendarWeekRule.FirstDay, DayOfWeek.Monday);
	var month = candle.OpenTime.Month;

	if (week != _currentMonth)
	{
		if (_currentMonth != 0)
		{
			_prevMonthlyHigh = _monthlyHigh;
			_prevMonthlyLow = _monthlyLow;
		}

		_monthlyHigh = high;
		_monthlyLow = low;
		_currentMonth = week;
	}
	else
	{
		_monthlyHigh = Math.Max(_monthlyHigh, high);
		_monthlyLow = Math.Min(_monthlyLow, low);
	}

	if (_prevMonthlyHigh <= 0m || _prevMonthlyLow <= 0m)
	{
		_prevClose = close;
		return;
	}
	
	var crossAboveHigh = _prevClose <= _prevMonthlyHigh && close > _prevMonthlyHigh;
	var crossBelowHigh = _prevClose >= _prevMonthlyHigh && close < _prevMonthlyHigh;
	var crossAboveLow = _prevClose <= _prevMonthlyLow && close > _prevMonthlyLow;
	var crossBelowLow = _prevClose >= _prevMonthlyLow && close < _prevMonthlyLow;
		
		if (Position != 0 && _entryBar.HasValue && _barIndex >= _entryBar.Value + HoldingPeriod)
		{
			if (Position > 0)
			SellMarket();
			else
			BuyMarket();
			_entryBar = null;
		}
		
		if (IsMonthSelected(month))
		{
			switch (EntryOption)
			{
				case EntryOptions.LongAtHigh when crossAboveHigh && Position <= 0:
				BuyMarket();
				_entryBar = _barIndex;
				break;
				case EntryOptions.ShortAtHigh when crossBelowHigh && Position >= 0:
				SellMarket();
				_entryBar = _barIndex;
				break;
				case EntryOptions.LongAtLow when crossAboveLow && Position <= 0:
				BuyMarket();
				_entryBar = _barIndex;
				break;
				case EntryOptions.ShortAtLow when crossBelowLow && Position >= 0:
				SellMarket();
				_entryBar = _barIndex;
				break;
			}
		}
		
		_prevClose = close;
	}
	
	private bool IsMonthSelected(int month)
	{
		return month switch
		{
			1 => January,
			2 => February,
			3 => March,
			4 => April,
			5 => May,
			6 => June,
			7 => July,
			8 => August,
			9 => September,
			10 => October,
			11 => November,
			12 => December,
			_ => false
		};
	}
}