Auf GitHub ansehen

Momentum M15-Strategie

Diese Strategie ist eine direkte Portierung des MetaTrader 5-Expert Advisors Momentum-M15 (Originaldatei Momentum-M15.mq5). Sie handelt 15-Minuten-Kerzen und kombiniert einen verschobenen gleitenden Durchschnitt mit einem Momentum-Oszillator, der auf Balkenöffnungen ausgewertet wird. Die Logik zielt darauf ab, extremes Momentum gegenzuhandeln, wenn der Preis auf der gegenüberliegenden Seite des verschobenen Durchschnitts liegt, während ein Gap-Wächter und optionaler Trailing Stop die Exposition begrenzen.

Konvertierungshighlights

  • Indikatoren werden mit StockSharp-Komponenten nachgebaut: ein konfigurierbarer gleitender Durchschnitt (Standard geglättet) und der eingebaute Momentum-Oszillator, der mit dem gewählten Kerzenpreis arbeitet (Standard Open).
  • Die horizontale MA-Verschiebung von MetaTrader wird durch Puffern von Indikatorwerten emuliert und der Wert MaShift abgeschlossene Balken zurück abgerufen. Es wird keine benutzerdefinierte Indikatormathematik neu implementiert.
  • Momentum-Monotoniéprüfungen verwenden die neuesten Historienwerte und behalten nur so viele Elemente wie von den Einstiegs- oder Ausstiegsfenstern benötigt, was die originalen CheckMO_Up / CheckMO_Down-Helfer widerspiegelt.
  • Die Großlücken-Sperrung (GapLevel/GapTimeout) bleibt erhalten. Preisschrittinformationen werden verwendet, um die punktbasierten Schwellenwerte der MQL-Version in StockSharp-Preisschritte umzurechnen.
  • Das Trailing-Stop-Management wird intern durch Marktausstiege gehandhabt, wenn der Preis das verfolgte Niveau kreuzt, was der MQL-Routine entspricht, die Stop-Loss-Orders einmal pro abgeschlossenem Balken modifizierte.

Parameter

Name Beschreibung Standard
TradeVolume Ordergröße für jeden Einstieg. 0.1
CandleType Primärer Zeitrahmen (standardmäßig 15-Minuten-Kerzen). 15m
MaPeriod Rückblicklänge des gleitenden Durchschnitts. 26
MaShift Anzahl der Balken zum horizontalen Verschieben des gleitenden Durchschnitts. 8
MaMethod Typ des gleitenden Durchschnitts (Simple, Exponential, Smoothed, Weighted). Smoothed
MaPrice Kerzenpreis für den gleitenden Durchschnitt. Low
MomentumPeriod Rückblicklänge des Momentums. 23
MomentumPrice Kerzenpreis für den Momentum-Oszillator. Open
MomentumThreshold Basis-Momentumniveau, das Long/Short-Setups trennt. 100
MomentumShift Wert, der zu MomentumThreshold addiert/subtrahiert wird, um asymmetrische Grenzen zu erstellen. -0.2
MomentumOpenLength Balken, die für eine nicht steigende Momentumsequenz vor dem Öffnen von Longs / nicht fallend für Shorts erforderlich sind. 6
MomentumCloseLength Balken, die für dieselbe monotone Sequenz vor dem Schließen von Positionen erforderlich sind. 10
GapLevel Minimale positive Lücke (in Preisschritten), die neue Einstiege pausiert. 30
GapTimeout Anzahl der Balken, für die der Handel nach einer großen Lücke deaktiviert bleibt. 100
TrailingStop Optionaler Trailing-Stop-Abstand in Preisschritten. 0 (deaktiviert)

Handelsregeln

Einstiegskriterien

  • Long-Einstiege

    • Das neueste Momentum liegt unter MomentumThreshold + MomentumShift (für die Standard-Verschiebung von -0.2 ist dies leicht unter dem Hauptschwellenwert).
    • Sowohl der vorherige Balkenschluss als auch die aktuelle Balkenöffnung liegen unter dem verschobenen gleitenden Durchschnitt.
    • Das Momentum war MomentumOpenLength Balken lang nicht steigend (entspricht CheckMO_Down im MQL-Quellcode).
  • Short-Einstiege

    • Das neueste Momentum liegt über MomentumThreshold - MomentumShift (mit der Standard-Verschiebung ist dies leicht über 100).
    • Sowohl der vorherige Balkenschluss als auch die aktuelle Balkenöffnung liegen über dem verschobenen gleitenden Durchschnitt.
    • Das Momentum war MomentumOpenLength Balken lang nicht fallend (entspricht CheckMO_Up).

Einstiege werden nur bewertet, wenn keine Position offen ist und der Handel nicht durch den Lückenfilter ausgesetzt ist.

Ausstiegskriterien

  • Long-Positionen werden geschlossen, wenn eines der Folgenden zutrifft:

    • Das Momentum war MomentumCloseLength Balken lang nicht steigend.
    • Der vorherige Balkenschluss fällt unter den verschobenen gleitenden Durchschnitt.
    • Der Trailing Stop (falls aktiviert) wird berührt. Der Stop folgt dem Kerzentief minus der konfigurierten Distanz in Preisschritten.
  • Short-Positionen werden geschlossen, wenn eines der Folgenden zutrifft:

    • Das Momentum war MomentumCloseLength Balken lang nicht fallend.
    • Der vorherige Balkenschluss steigt über den verschobenen gleitenden Durchschnitt.
    • Der Trailing Stop (falls aktiviert) wird berührt. Der Stop folgt dem Kerzenhoch plus der konfigurierten Distanz.

Gap-Suspensionslogik

Der ursprüngliche Expert Advisor pausierte den Handel nach starken Aufwärtslücken. Die StockSharp-Version misst die Differenz zwischen der aktuellen Balkenöffnung und dem vorherigen Schluss in Preisschritten:

  1. Wenn die Lücke GapLevel überschreitet, wird der Sperrtimer auf GapTimeout zurückgesetzt.
  2. Der Timer wird nach jedem geschlossenen Balken dekrementiert; der Handel wird erst nach Erreichen von null fortgesetzt.

Hinweise und Annahmen

  • Alle Berechnungen verwenden abgeschlossene Kerzen (CandleStates.Finished), um mit den StockSharp-High-Level-API-Praktiken konform zu bleiben. Daher werden Orders beim nächsten Balken nach der Signalbeobachtung ausgegeben, was damit übereinstimmt, wie die ursprüngliche Strategie beim ersten Tick eines neuen Balkens ausgelöst wurde.
  • Das MetaTrader-Konzept von "Pips" wird über Security.PriceStep approximiert. Fehlt dem Instrument ordentliche Schrittdaten, werden der Lückenfilter und Trailing Stop lautlos deaktiviert.
  • Gleitende Durchschnittspreise und Momentum-Eingaben können unabhängig voneinander geändert werden, was die Flexibilität der originalen Eingangsparameter repliziert.
  • Es werden keine automatisierten Stop-Orders registriert; stattdessen reproduzieren Marktausstiege die Stop-Anpassungen, die der MQL-Code über PositionModify ausgab.

Nutzungstipps

  1. Weisen Sie das gewünschte Wertpapier zu und stellen Sie sicher, dass CandleType dem historischen Zeitrahmen entspricht, der während der Backtests verwendet wurde (15- Minuten-Balken im Originalskript).
  2. Konfigurieren Sie TradeVolume auf die Lotgröße, die vom Handelsplatz unterstützt wird.
  3. Passen Sie MomentumOpenLength / MomentumCloseLength an, um zu kontrollieren, wie streng der Momentum-Monotonie-Filter sein soll.
  4. Wenn Sie die Standard-"Pip"-Skala genau spiegeln möchten, setzen Sie TrailingStop und GapLevel entsprechend dem Verhältnis zwischen dem Preisschritt der Börse und einem Pip für das Instrument.
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>
/// Momentum based strategy converted from the MetaTrader 5 "Momentum-M15" expert advisor.
/// </summary>
public class MomentumM15Strategy : Strategy
{
	/// <summary>
	/// Moving average method options aligned with the original expert advisor inputs.
	/// </summary>
	public enum MovingAverageMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed moving average.
		/// </summary>
		Smoothed,

		/// <summary>
		/// Weighted moving average.
		/// </summary>
		Weighted
	}

	public enum CandlePrices
	{
		Open,
		High,
		Low,
		Close,
		Median,
		Typical,
		Weighted
	}

	private readonly StrategyParam<decimal> _volumeParam;
	private readonly StrategyParam<DataType> _candleTypeParam;
	private readonly StrategyParam<int> _maPeriodParam;
	private readonly StrategyParam<int> _maShiftParam;
	private readonly StrategyParam<MovingAverageMethods> _maMethodParam;
	private readonly StrategyParam<CandlePrices> _maPriceParam;
	private readonly StrategyParam<int> _momentumPeriodParam;
	private readonly StrategyParam<CandlePrices> _momentumPriceParam;
	private readonly StrategyParam<decimal> _momentumThresholdParam;
	private readonly StrategyParam<decimal> _momentumShiftParam;
	private readonly StrategyParam<int> _momentumOpenLengthParam;
	private readonly StrategyParam<int> _momentumCloseLengthParam;
	private readonly StrategyParam<int> _gapLevelParam;
	private readonly StrategyParam<int> _gapTimeoutParam;
	private readonly StrategyParam<decimal> _trailingStopParam;

	private IIndicator _ma = null!;
	private Momentum _momentum = null!;
	private readonly List<decimal> _maHistory = new();
	private readonly List<decimal> _momentumHistory = new();
	private decimal? _previousClose;
	private decimal? _longTrailingStop;
	private decimal? _shortTrailingStop;
	private int _gapTimer;

	/// <summary>
	/// Initializes a new instance of <see cref="MomentumM15Strategy"/>.
	/// </summary>
	public MomentumM15Strategy()
	{
		_volumeParam = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Default order volume", "Trading")
			
			.SetOptimize(0.05m, 0.5m, 0.05m);

		_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for calculations", "Common");

		_maPeriodParam = Param(nameof(MaPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average lookback length", "Indicators")
			
			.SetOptimize(10, 60, 5);

		_maShiftParam = Param(nameof(MaShift), 8)
			.SetNotNegative()
			.SetDisplay("MA Shift", "Horizontal shift applied to moving average", "Indicators");

		_maMethodParam = Param(nameof(MaMethod), MovingAverageMethods.Smoothed)
			.SetDisplay("MA Method", "Type of moving average", "Indicators");

		_maPriceParam = Param(nameof(MaPrice), CandlePrices.Low)
			.SetDisplay("MA Price", "Price source for moving average", "Indicators");

		_momentumPeriodParam = Param(nameof(MomentumPeriod), 23)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Momentum indicator lookback", "Indicators")
			
			.SetOptimize(10, 40, 1);

		_momentumPriceParam = Param(nameof(MomentumPrice), CandlePrices.Open)
			.SetDisplay("Momentum Price", "Price source for momentum", "Indicators");

		_momentumThresholdParam = Param(nameof(MomentumThreshold), 100m)
			.SetDisplay("Momentum Threshold", "Baseline momentum threshold", "Trading Rules");

		_momentumShiftParam = Param(nameof(MomentumShift), -0.2m)
			.SetDisplay("Momentum Shift", "Shift applied to momentum threshold", "Trading Rules");

		_momentumOpenLengthParam = Param(nameof(MomentumOpenLength), 6)
			.SetNotNegative()
			.SetDisplay("Momentum Open Length", "Bars required for monotonic momentum on entries", "Trading Rules");

		_momentumCloseLengthParam = Param(nameof(MomentumCloseLength), 10)
			.SetNotNegative()
			.SetDisplay("Momentum Close Length", "Bars required for monotonic momentum on exits", "Trading Rules");

		_gapLevelParam = Param(nameof(GapLevel), 30)
			.SetNotNegative()
			.SetDisplay("Gap Level", "Minimum gap in price steps to pause trading", "Risk Management");

		_gapTimeoutParam = Param(nameof(GapTimeout), 100)
			.SetNotNegative()
			.SetDisplay("Gap Timeout", "Number of bars to skip after a large gap", "Risk Management");

		_trailingStopParam = Param(nameof(TrailingStop), 0m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop", "Trailing stop distance in price steps", "Risk Management");
	}

	/// <summary>
	/// Default trade volume.
	/// </summary>
	public decimal TradeVolume
	{
		get => _volumeParam.Value;
		set => _volumeParam.Value = value;
	}

	/// <summary>
	/// Candle type for the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleTypeParam.Value;
		set => _candleTypeParam.Value = value;
	}

	/// <summary>
	/// Moving average period.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriodParam.Value;
		set => _maPeriodParam.Value = value;
	}

	/// <summary>
	/// Number of bars to shift the moving average.
	/// </summary>
	public int MaShift
	{
		get => _maShiftParam.Value;
		set => _maShiftParam.Value = value;
	}

	/// <summary>
	/// Moving average calculation method.
	/// </summary>
	public MovingAverageMethods MaMethod
	{
		get => _maMethodParam.Value;
		set => _maMethodParam.Value = value;
	}

	/// <summary>
	/// Price source for the moving average.
	/// </summary>
	public CandlePrices MaPrice
	{
		get => _maPriceParam.Value;
		set => _maPriceParam.Value = value;
	}

	/// <summary>
	/// Momentum indicator lookback period.
	/// </summary>
	public int MomentumPeriod
	{
		get => _momentumPeriodParam.Value;
		set => _momentumPeriodParam.Value = value;
	}

	/// <summary>
	/// Price source for the momentum indicator.
	/// </summary>
	public CandlePrices MomentumPrice
	{
		get => _momentumPriceParam.Value;
		set => _momentumPriceParam.Value = value;
	}

	/// <summary>
	/// Baseline momentum threshold.
	/// </summary>
	public decimal MomentumThreshold
	{
		get => _momentumThresholdParam.Value;
		set => _momentumThresholdParam.Value = value;
	}

	/// <summary>
	/// Shift applied to the momentum threshold.
	/// </summary>
	public decimal MomentumShift
	{
		get => _momentumShiftParam.Value;
		set => _momentumShiftParam.Value = value;
	}

	/// <summary>
	/// Sequence length for entry momentum validation.
	/// </summary>
	public int MomentumOpenLength
	{
		get => _momentumOpenLengthParam.Value;
		set => _momentumOpenLengthParam.Value = value;
	}

	/// <summary>
	/// Sequence length for exit momentum validation.
	/// </summary>
	public int MomentumCloseLength
	{
		get => _momentumCloseLengthParam.Value;
		set => _momentumCloseLengthParam.Value = value;
	}

	/// <summary>
	/// Minimum gap (in price steps) that suspends new entries.
	/// </summary>
	public int GapLevel
	{
		get => _gapLevelParam.Value;
		set => _gapLevelParam.Value = value;
	}

	/// <summary>
	/// Number of bars to wait after a gap before trading resumes.
	/// </summary>
	public int GapTimeout
	{
		get => _gapTimeoutParam.Value;
		set => _gapTimeoutParam.Value = value;
	}

	/// <summary>
	/// Trailing stop distance expressed in price steps.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStopParam.Value;
		set => _trailingStopParam.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_ma = null!;
		_momentum = null!;
		_maHistory.Clear();
		_momentumHistory.Clear();
		_previousClose = null;
		_longTrailingStop = null;
		_shortTrailingStop = null;
		_gapTimer = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ma = CreateMovingAverage(MaMethod, MaPeriod);
		_momentum = new Momentum { Length = MomentumPeriod };

		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;

		if (_ma is null || _momentum is null)
		return;

		var maValue = ProcessMovingAverage(candle);
		var momentumValue = ProcessMomentum(candle);

		if (maValue is null || momentumValue is null)
		{
			_previousClose = candle.ClosePrice;
			return;
		}

		var previousClose = _previousClose;
		_previousClose = candle.ClosePrice;

		if (previousClose is null)
		return;

		HandleGapFilter(previousClose.Value, candle.OpenPrice);

		if (_gapTimer > 0)
		{
			_gapTimer--;
			if (_gapTimer > 0)
			return;
		}

		if (Position == 0)
		{
			TryOpenPositions(previousClose.Value, candle.OpenPrice, maValue.Value, momentumValue.Value);
		}
		else
		{
			ManageExistingPosition(previousClose.Value, candle, maValue.Value, momentumValue.Value);
		}
	}

	private decimal? ProcessMovingAverage(ICandleMessage candle)
	{
		var price = GetPrice(candle, MaPrice);
		var value = _ma.Process(new DecimalIndicatorValue(_ma, price, candle.OpenTime) { IsFinal = true });

		if (value.IsEmpty || !_ma.IsFormed)
		return null;

		var ma = value.ToDecimal();
		_maHistory.Add(ma);

		var maxCount = MaShift + 1;
		while (_maHistory.Count > maxCount)
		_maHistory.RemoveAt(0);

		var index = _maHistory.Count - 1 - MaShift;
		if (index < 0 || index >= _maHistory.Count)
		return null;

		return _maHistory[index];
	}

	private decimal? ProcessMomentum(ICandleMessage candle)
	{
		var price = GetPrice(candle, MomentumPrice);
		var value = _momentum.Process(new DecimalIndicatorValue(_momentum, price, candle.OpenTime) { IsFinal = true });

		if (value.IsEmpty || !_momentum.IsFormed)
		return null;

		var momentum = value.ToDecimal();
		_momentumHistory.Add(momentum);

		var maxLen = Math.Max(Math.Max(MomentumOpenLength, MomentumCloseLength), 1);
		while (_momentumHistory.Count > maxLen)
		_momentumHistory.RemoveAt(0);

		return momentum;
	}

	private void HandleGapFilter(decimal previousClose, decimal currentOpen)
	{
		var priceStep = Security.PriceStep ?? 0m;
		if (priceStep <= 0m)
		return;

		var gap = (currentOpen - previousClose) / priceStep;
		if (gap > GapLevel)
		_gapTimer = GapTimeout;
	}

	private void TryOpenPositions(decimal previousClose, decimal currentOpen, decimal maValue, decimal momentumValue)
	{
		var longMomentumOk = MomentumOpenLength > 0 && IsMomentumDownSequence(MomentumOpenLength);
		var shortMomentumOk = MomentumOpenLength > 0 && IsMomentumUpSequence(MomentumOpenLength);

		var longCondition = momentumValue < MomentumThreshold + MomentumShift
		&& previousClose < maValue
		&& currentOpen < maValue
		&& longMomentumOk;

		var shortCondition = momentumValue > MomentumThreshold - MomentumShift
		&& previousClose > maValue
		&& currentOpen > maValue
		&& shortMomentumOk;

		if (longCondition)
		{
			BuyMarket(TradeVolume);
			_longTrailingStop = null;
			_shortTrailingStop = null;
		}
		else if (shortCondition)
		{
			SellMarket(TradeVolume);
			_longTrailingStop = null;
			_shortTrailingStop = null;
		}
	}

	private void ManageExistingPosition(decimal previousClose, ICandleMessage candle, decimal maValue, decimal momentumValue)
	{
		if (Position > 0)
		{
			var exitMomentum = MomentumCloseLength > 0 && IsMomentumDownSequence(MomentumCloseLength);
			var shouldClose = exitMomentum || previousClose < maValue;

			if (shouldClose)
			{
				SellMarket(Position);
				_longTrailingStop = null;
				return;
			}

			UpdateLongTrailingStop(candle);
		}
		else if (Position < 0)
		{
			var exitMomentum = MomentumCloseLength > 0 && IsMomentumUpSequence(MomentumCloseLength);
			var shouldClose = exitMomentum || previousClose > maValue;

			if (shouldClose)
			{
				BuyMarket(Math.Abs(Position));
				_shortTrailingStop = null;
				return;
			}

			UpdateShortTrailingStop(candle);
		}
	}

	private void UpdateLongTrailingStop(ICandleMessage candle)
	{
		if (TrailingStop <= 0m)
		return;

		var priceStep = Security.PriceStep ?? 0m;
		if (priceStep <= 0m)
		return;

		var distance = TrailingStop * priceStep;
		var candidate = candle.LowPrice - distance;

		if (_longTrailingStop is null || candidate > _longTrailingStop)
		_longTrailingStop = candidate;

		if (_longTrailingStop is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket(Position);
			_longTrailingStop = null;
		}
	}

	private void UpdateShortTrailingStop(ICandleMessage candle)
	{
		if (TrailingStop <= 0m)
		return;

		var priceStep = Security.PriceStep ?? 0m;
		if (priceStep <= 0m)
		return;

		var distance = TrailingStop * priceStep;
		var candidate = candle.HighPrice + distance;

		if (_shortTrailingStop is null || candidate < _shortTrailingStop)
		_shortTrailingStop = candidate;

		if (_shortTrailingStop is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket(Math.Abs(Position));
			_shortTrailingStop = null;
		}
	}

	private bool IsMomentumDownSequence(int length)
	{
		if (length <= 0 || _momentumHistory.Count < length)
		return false;

		var start = _momentumHistory.Count - length;
		var previous = _momentumHistory[start];

		for (var i = start + 1; i < _momentumHistory.Count; i++)
		{
			var current = _momentumHistory[i];
			if (current > previous)
			return false;

			previous = current;
		}

		return true;
	}

	private bool IsMomentumUpSequence(int length)
	{
		if (length <= 0 || _momentumHistory.Count < length)
		return false;

		var start = _momentumHistory.Count - length;
		var previous = _momentumHistory[start];

		for (var i = start + 1; i < _momentumHistory.Count; i++)
		{
			var current = _momentumHistory[i];
			if (current < previous)
			return false;

			previous = current;
		}

		return true;
	}

	private static decimal GetPrice(ICandleMessage candle, CandlePrices price)
	{
		return price switch
		{
			CandlePrices.Open => candle.OpenPrice,
			CandlePrices.High => candle.HighPrice,
			CandlePrices.Low => candle.LowPrice,
			CandlePrices.Close => candle.ClosePrice,
			CandlePrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			CandlePrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			CandlePrices.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
			_ => candle.ClosePrice,
		};
	}

	private static IIndicator CreateMovingAverage(MovingAverageMethods method, int period)
	{
		return method switch
		{
			MovingAverageMethods.Simple => new SimpleMovingAverage { Length = period },
			MovingAverageMethods.Exponential => new ExponentialMovingAverage { Length = period },
			MovingAverageMethods.Smoothed => new SmoothedMovingAverage { Length = period },
			MovingAverageMethods.Weighted => new WeightedMovingAverage { Length = period },
			_ => new SimpleMovingAverage { Length = period },
		};
	}
}