Auf GitHub ansehen

DVD 100-50-Cent-Strategie

Überblick

Die DVD-100-50-Cent-Strategie ist ein konträres Limit-Order-System, das vom ursprünglichen MT4-Expertenberater portiert wurde. Die Logik bewertet den Markt über vier Zeitrahmen (M1, M30, H1, D1) und bewertet potenzielle Setups, bevor Kauf- oder Verkaufslimitaufträge um das nächstgelegene Preisraster der „100-Ebene“ geparkt werden. Wenn die Limit-Order ausgeführt wird, verwaltet die Strategie die Position mit vorberechneten Stop-Loss- und Take-Profit-Levels.

Indikatoren und Daten

  • RAVI (Range Action Verification Index) für H1 und D1, berechnet mit SMA(2) und SMA(24) für den Eröffnungspreis.
  • Kerzenrohdaten zu M1, M30 und H1 für Musterfilter wie Spike-Ablehnung, Konsolidierungsprüfungen und Momentumtests.
  • Rundung des Preisrasters, die den aktuellen Preis mithilfe einer Zwei-Dezimal-Rundung und einem konfigurierbaren 0,1-Pip-Offset auf die nächste 100er-Ebene bringt.

Eingabelogik

  1. Berechnen Sie den gerundeten „Level 100“-Preis, indem Sie den letzten M1 auf nahezu zwei Dezimalstellen runden und ihn um PointFromLevelGoPips verschieben (Standard 50 → 5 Pips).
  2. Initialisieren Sie einen internen Score (BAL) bei 0 und addieren/subtrahieren Sie Punkte gemäß:
    • Trendfilter: Fügen Sie 10 Punkte hinzu, wenn der H1-RAVI bei Long-Setups unter Null oder bei Short-Setups über Null liegt.
    • Stündliche Spitzenbestätigung: Fügen Sie 7 Punkte hinzu, wenn die beiden vorherigen H1-Hochs/Tiefs das Raster um RiseFilterPips überschreiten.
    • Strukturausrichtung: Fügen Sie 45 Punkte hinzu, wenn der aktuelle M1-Schluss wieder das Niveau überschreitet und die letzten drei H1-Tiefs/Höchststände über/unter dem Sicherheitspuffer (PointFromLevelGoPips ± 30 * 0.1 pip) bleiben.
    • Volatilitätswächter: Ziehen Sie 50 Punkte ab, wenn die jüngsten M1-Hochs/Tiefs HighLevelPips überschreiten (Standard 600 → 60 Pips) oder wenn schnelle Impulsausbrüche auftreten, während der D1 RAVI ein starkes Richtungsregime bestätigt.
    • Bestätigung des Ausbruchs: 50 Punkte abziehen, wenn die letzten 15 H1-Kerzen nie den Schwellenwert LowLevel2Pips überschritten haben.
    • Konsolidierungsfilter: 50 Punkte abziehen, wenn die letzten acht M30-Kerzen alle innerhalb des LowLevelPips-Bands bleiben.
  3. Geben Sie eine Limit-Order nur dann auf, wenn der Endwert mindestens 50 beträgt und kein anderes Risiko (Position oder ausstehende Order) besteht.

Auftragserteilung

  • Kauflimit: 10 Pips unter dem letzten M1-Schluss. Der Stop-Loss liegt StopLossPips unter dem Grenzpreis, der Take-Profit liegt TakeProfitPips darüber. Wenn der D1 RAVI in den letzten vier Tagen eine steigende Treppe zwischen -1 und +5 zeigt, erhält der Take-Profit eine zusätzliche Verlängerung um 25 Pip.
  • Verkaufslimit: 7 Pips über dem letzten M1-Schluss mit symmetrischen Stop- und Zielregeln. Wenn der D1 RAVI eine fallende Treppe zwischen -5 und -1 zeigt, wird das Ziel um 25 Pips erweitert.
  • Ausstehende Bestellungen verfallen automatisch nach OrderExpiryMinutes (Standard: 20 Minuten). Bei Stornierung einer Bestellung werden die gespeicherten Schutzstufen zurückgesetzt.

Positionsmanagement

  • Nach der Erfüllung behält die Strategie die gespeicherten Stop-Loss- und Take-Profit-Werte intern bei und gibt Marktausstiegsaufträge aus, wenn der Preis eines der beiden Niveaus erreicht.
  • In der portierten Version wird kein Trailing Stop angewendet; Das ursprüngliche EA hat die nachgestellte Logik standardmäßig deaktiviert.
  • Neue Geschäfte werden blockiert, solange eine aktive Position oder eine ausstehende Limit-Order besteht.

Money-Management

  • Wenn UseMoneyManagement aktiviert ist, ähnelt die Losgröße der MT4-Implementierung: Sie skaliert um TradeSizePercent des aktuellen Eigenkapitals, passt sich an Minikonten an und begrenzt das Ergebnis auf [0.1, MaxVolume] (Mini) oder [1, MaxVolume] (Standard).
  • Durch die Deaktivierung der Geldverwaltung wird ein festes Volumen erzwungen, das durch den Parameter FixedVolume gesteuert wird.
  • Der Handel wird gestoppt, wenn das Portfolioeigenkapital unter MarginCutoff fällt.

Parameter

Name Beschreibung Standard
AccountIsMini Verwenden Sie die Regeln zur Volumenrundung für Minikonten true
UseMoneyManagement Aktivieren Sie die adaptive Losgröße true
TradeSizePercent Pro Trade zugewiesener Eigenkapitalanteil 10
FixedVolume Volumen, das verwendet wird, wenn die Geldverwaltung deaktiviert ist 0.01
MaxVolume Maximal zulässiges Handelsvolumen 4
StopLossPips Stop-Loss-Distanz in Pips 210
TakeProfitPips Take-Profit-Distanz in Pips 18
PointFromLevelGoPips Basisniveauverschiebung in 0,1 Pips 50
RiseFilterPips Stündlicher Spike-Bestätigungsabstand (0,1 Pips) 700
HighLevelPips Einminütiger Spike-Ablehnungsschwellenwert (0,1 Pips) 600
LowLevelPips 30-Minuten-Konsolidierungsband (0,1 Pips) 250
LowLevel2Pips Stündliche Breakout-Bestätigungsdistanz (0,1 Pips) 450
MarginCutoff Eigenkapitaluntergrenze verhindert neue Geschäfte 300
OrderExpiryMinutes Dauer der ausstehenden Bestellung in Minuten 20

Nutzungshinweise

  • Die Konvertierung basiert auf fertigen Kerzen aus jedem Zeitrahmen; Stellen Sie sicher, dass der historische Datenstrom synchronisierte M1-, M30-, H1- und D1-Kerzen bereitstellt.
  • Der Schutzstopp und das Schutzziel werden mit Marktaufträgen ausgeführt, um das MT4-Verhalten der angehängten SL/TP-Werte widerzuspiegeln.
  • Da die Logik empfindlich auf die Pip-Größe reagiert, stellen Sie sicher, dass die Eigenschaften PriceStep und Decimals des Instruments das Kursformat korrekt beschreiben.
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>
/// Mean-reversion limit strategy converted from the MT4 expert advisor "DVD 100-50 cent".
/// </summary>
public class Dvd10050CentStrategy : Strategy
{

	private readonly StrategyParam<bool> _accountIsMini;
	private readonly StrategyParam<bool> _useMoneyManagement;
	private readonly StrategyParam<decimal> _tradeSizePercent;
	private readonly StrategyParam<decimal> _fixedVolume;
	private readonly StrategyParam<decimal> _maxVolume;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _pointFromLevelGoPips;
	private readonly StrategyParam<decimal> _riseFilterPips;
	private readonly StrategyParam<decimal> _highLevelPips;
	private readonly StrategyParam<decimal> _lowLevelPips;
	private readonly StrategyParam<decimal> _lowLevel2Pips;
	private readonly StrategyParam<decimal> _marginCutoff;
	private readonly StrategyParam<int> _orderExpiryMinutes;
	private readonly StrategyParam<int> _m1HistoryLength;
	private readonly StrategyParam<int> _m30HistoryLength;
	private readonly StrategyParam<int> _h1HistoryLength;

	private SimpleMovingAverage _h1Fast = null!;
	private SimpleMovingAverage _h1Slow = null!;
	private SimpleMovingAverage _d1Fast = null!;
	private SimpleMovingAverage _d1Slow = null!;

	private readonly List<ICandleMessage> _m1History = new();
	private readonly List<ICandleMessage> _m30History = new();
	private readonly List<ICandleMessage> _h1Finished = new();
	private ICandleMessage _h1Current;

	private decimal? _raviH1;
	private decimal? _raviD1Current;
	private decimal? _raviD1Prev1;
	private decimal? _raviD1Prev2;
	private decimal? _raviD1Prev3;

	private decimal _pipSize;
	private decimal _pointValue;

	private DateTimeOffset? _buyOrderExpiry;
	private DateTimeOffset? _sellOrderExpiry;

	private decimal? _pendingBuyStop;
	private decimal? _pendingBuyTake;
	private decimal? _pendingSellStop;
	private decimal? _pendingSellTake;

	private decimal? _longStop;
	private decimal? _longTake;
	private decimal? _shortStop;
	private decimal? _shortTake;

	private decimal _previousPosition;

	/// <summary>
	/// Initializes a new instance of the <see cref="Dvd10050CentStrategy"/> class.
	/// </summary>
	public Dvd10050CentStrategy()
	{
		_accountIsMini = Param(nameof(AccountIsMini), true)
		.SetDisplay("Mini Account", "Use mini account position sizing", "Risk");

		_useMoneyManagement = Param(nameof(UseMoneyManagement), true)
		.SetDisplay("Use Money Management", "Enable adaptive lot sizing", "Risk");

		_tradeSizePercent = Param(nameof(TradeSizePercent), 10m)
		.SetDisplay("Risk Percent", "Percent of equity allocated per trade", "Risk")
		.SetRange(0m, 100m)
		;

		_fixedVolume = Param(nameof(FixedVolume), 0.01m)
		.SetDisplay("Fixed Volume", "Volume used when money management is disabled", "Risk")
		.SetRange(0.01m, 100m)
		;

		_maxVolume = Param(nameof(MaxVolume), 4m)
		.SetDisplay("Max Volume", "Ceiling for calculated trade volume", "Risk")
		.SetRange(0.01m, 100m)
		;

		_stopLossPips = Param(nameof(StopLossPips), 210m)
		.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Orders")
		.SetRange(0m, 1000m)
		;

		_takeProfitPips = Param(nameof(TakeProfitPips), 18m)
		.SetDisplay("Take Profit (pips)", "Initial profit target distance", "Orders")
		.SetRange(0m, 500m)
		;

		_pointFromLevelGoPips = Param(nameof(PointFromLevelGoPips), 50m)
		.SetDisplay("Base Offset (0.1 pips)", "Offset used to build the 100 level grid", "Filters")
		.SetRange(0m, 1000m)
		;

		_riseFilterPips = Param(nameof(RiseFilterPips), 700m)
		.SetDisplay("Rise Filter (0.1 pips)", "Distance for hourly spike confirmation", "Filters")
		.SetRange(0m, 5000m)
		;

		_highLevelPips = Param(nameof(HighLevelPips), 600m)
		.SetDisplay("High Level (0.1 pips)", "One-minute spike rejection threshold", "Filters")
		.SetRange(0m, 5000m)
		;

		_lowLevelPips = Param(nameof(LowLevelPips), 250m)
		.SetDisplay("Low Level (0.1 pips)", "Half-hour consolidation ceiling", "Filters")
		.SetRange(0m, 5000m)
		;

		_lowLevel2Pips = Param(nameof(LowLevel2Pips), 450m)
		.SetDisplay("Low Level 2 (0.1 pips)", "Hourly breakout confirmation threshold", "Filters")
		.SetRange(0m, 5000m)
		;

		_marginCutoff = Param(nameof(MarginCutoff), 300m)
		.SetDisplay("Margin Cutoff", "Stop trading when equity falls below this level", "Risk")
		.SetRange(0m, 1_000_000m)
		;

		_orderExpiryMinutes = Param(nameof(OrderExpiryMinutes), 20)
		.SetDisplay("Order Expiry (minutes)", "Lifetime of pending limit orders", "Orders")
		.SetRange(1, 240)
		;

		_m1HistoryLength = Param(nameof(M1HistoryLength), 64)
			.SetDisplay("M1 History Length", "Number of M1 candles retained for analysis", "History")
			.SetRange(1, 500);

		_m30HistoryLength = Param(nameof(M30HistoryLength), 16)
			.SetDisplay("M30 History Length", "Number of M30 candles retained for analysis", "History")
			.SetRange(1, 200);

		_h1HistoryLength = Param(nameof(H1HistoryLength), 16)
			.SetDisplay("H1 History Length", "Number of H1 candles retained for analysis", "History")
			.SetRange(1, 200);
	}

	/// <summary>
	/// Gets or sets whether the account uses mini lot sizing.
	/// </summary>
	public bool AccountIsMini
	{
		get => _accountIsMini.Value;
		set => _accountIsMini.Value = value;
	}

	/// <summary>
	/// Gets or sets whether money management is enabled.
	/// </summary>
	public bool UseMoneyManagement
	{
		get => _useMoneyManagement.Value;
		set => _useMoneyManagement.Value = value;
	}

	/// <summary>
	/// Gets or sets the risk allocation per trade when money management is enabled.
	/// </summary>
	public decimal TradeSizePercent
	{
		get => _tradeSizePercent.Value;
		set => _tradeSizePercent.Value = value;
	}

	/// <summary>
	/// Gets or sets the fixed trade volume used when money management is disabled.
	/// </summary>
	public decimal FixedVolume
	{
		get => _fixedVolume.Value;
		set => _fixedVolume.Value = value;
	}

	/// <summary>
	/// Gets or sets the maximum volume allowed per trade.
	/// </summary>
	public decimal MaxVolume
	{
		get => _maxVolume.Value;
		set => _maxVolume.Value = value;
	}

	/// <summary>
	/// Gets or sets the stop loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Gets or sets the take profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Gets or sets the base offset that defines the 100 level grid in 0.1 pip units.
	/// </summary>
	public decimal PointFromLevelGoPips
	{
		get => _pointFromLevelGoPips.Value;
		set => _pointFromLevelGoPips.Value = value;
	}

	/// <summary>
	/// Gets or sets the spike confirmation distance for hourly candles in 0.1 pip units.
	/// </summary>
	public decimal RiseFilterPips
	{
		get => _riseFilterPips.Value;
		set => _riseFilterPips.Value = value;
	}

	/// <summary>
	/// Gets or sets the rejection distance for one-minute highs in 0.1 pip units.
	/// </summary>
	public decimal HighLevelPips
	{
		get => _highLevelPips.Value;
		set => _highLevelPips.Value = value;
	}

	/// <summary>
	/// Gets or sets the consolidation ceiling distance for 30-minute highs in 0.1 pip units.
	/// </summary>
	public decimal LowLevelPips
	{
		get => _lowLevelPips.Value;
		set => _lowLevelPips.Value = value;
	}

	/// <summary>
	/// Gets or sets the hourly breakout confirmation distance in 0.1 pip units.
	/// </summary>
	public decimal LowLevel2Pips
	{
		get => _lowLevel2Pips.Value;
		set => _lowLevel2Pips.Value = value;
	}

	/// <summary>
	/// Gets or sets the equity level that disables new trades when reached.
	/// </summary>
	public decimal MarginCutoff
	{
		get => _marginCutoff.Value;
		set => _marginCutoff.Value = value;
	}

	/// <summary>
	/// Gets or sets the pending order lifetime in minutes.
	/// </summary>
	public int OrderExpiryMinutes
	{
		get => _orderExpiryMinutes.Value;
		set => _orderExpiryMinutes.Value = value;
	}

	/// <summary>
	/// Number of one-minute candles retained for intraday analysis.
	/// </summary>
	public int M1HistoryLength
	{
		get => _m1HistoryLength.Value;
		set => _m1HistoryLength.Value = value;
	}

	/// <summary>
	/// Number of thirty-minute candles retained for intraday analysis.
	/// </summary>
	public int M30HistoryLength
	{
		get => _m30HistoryLength.Value;
		set => _m30HistoryLength.Value = value;
	}

	/// <summary>
	/// Number of hourly candles retained for intraday analysis.
	/// </summary>
	public int H1HistoryLength
	{
		get => _h1HistoryLength.Value;
		set => _h1HistoryLength.Value = value;
	}

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

		_h1Fast = new SimpleMovingAverage { Length = 2 };
		_h1Slow = new SimpleMovingAverage { Length = 24 };
		_d1Fast = new SimpleMovingAverage { Length = 2 };
		_d1Slow = new SimpleMovingAverage { Length = 24 };

		_pipSize = CalculatePipSize();
		_pointValue = _pipSize / 10m;

		var m1Subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		m1Subscription.Bind(ProcessM1).Start();

		var m30Subscription = SubscribeCandles(TimeSpan.FromMinutes(30).TimeFrame());
		m30Subscription.Bind(ProcessM30).Start();

		var h1Subscription = SubscribeCandles(TimeSpan.FromHours(1).TimeFrame());
		h1Subscription.Bind(ProcessH1).Start();

		var d1Subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		d1Subscription.Bind(ProcessD1).Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);
	}

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

		_h1Fast = null!;
		_h1Slow = null!;
		_d1Fast = null!;
		_d1Slow = null!;

		_m1History.Clear();
		_m30History.Clear();
		_h1Finished.Clear();
		_h1Current = null;

		_raviH1 = null;
		_raviD1Current = null;
		_raviD1Prev1 = null;
		_raviD1Prev2 = null;
		_raviD1Prev3 = null;

		_pipSize = 0m;
		_pointValue = 0m;

		_buyOrderExpiry = null;
		_sellOrderExpiry = null;
		_pendingBuyStop = null;
		_pendingBuyTake = null;
		_pendingSellStop = null;
		_pendingSellTake = null;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
		_previousPosition = 0m;
	}

	private void ProcessM1(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_m1History.Add(candle);
		TrimHistory(_m1History, M1HistoryLength);

		HandlePositionState(candle);
		ManageOrderExpirations(candle.CloseTime);
		ManageActivePosition(candle);

		if (!_h1Fast.IsFormed || !_h1Slow.IsFormed)
		return;

		if (HasExposure())
		return;

		if (!HasSufficientMargin())
		return;

		var orderPlaced = false;

		if (TryCalculateBuyScore(candle, out var buyLevel, out var buyScore) && buyScore >= 0m)
		{
			orderPlaced = PlaceBuyLimit(candle);
		}

		if (!orderPlaced && TryCalculateSellScore(candle, out var sellLevel, out var sellScore) && sellScore >= 0m)
		{
			PlaceSellLimit(candle);
		}
	}

	private void ProcessM30(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_m30History.Add(candle);
		TrimHistory(_m30History, M30HistoryLength);
	}

	private void ProcessH1(ICandleMessage candle)
	{
		_h1Current = candle;

		if (candle.State != CandleStates.Finished)
		return;

		_h1Finished.Add(candle);
		TrimHistory(_h1Finished, H1HistoryLength);

		_h1Fast.Process(new DecimalIndicatorValue(_h1Fast, candle.OpenPrice, candle.CloseTime) { IsFinal = true });
		_h1Slow.Process(new DecimalIndicatorValue(_h1Slow, candle.OpenPrice, candle.CloseTime) { IsFinal = true });

		if (!_h1Fast.IsFormed || !_h1Slow.IsFormed)
		return;

		var slow = _h1Slow.GetCurrentValue();
		if (slow == 0m)
		return;

		var fast = _h1Fast.GetCurrentValue();
		_raviH1 = 100m * (fast - slow) / slow;
	}

	private void ProcessD1(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_d1Fast.Process(new DecimalIndicatorValue(_d1Fast, candle.OpenPrice, candle.CloseTime) { IsFinal = true });
		_d1Slow.Process(new DecimalIndicatorValue(_d1Slow, candle.OpenPrice, candle.CloseTime) { IsFinal = true });

		if (!_d1Fast.IsFormed || !_d1Slow.IsFormed)
		return;

		var slow = _d1Slow.GetCurrentValue();
		if (slow == 0m)
		return;

		var fast = _d1Fast.GetCurrentValue();
		var ravi = 100m * (fast - slow) / slow;

		_raviD1Prev3 = _raviD1Prev2;
		_raviD1Prev2 = _raviD1Prev1;
		_raviD1Prev1 = _raviD1Current;
		_raviD1Current = ravi;
	}

	private void HandlePositionState(ICandleMessage candle)
	{
		var currentPosition = Position;

		if (currentPosition > 0m && _previousPosition <= 0m)
		{
			_longStop = _pendingBuyStop;
			_longTake = _pendingBuyTake;
			_pendingBuyStop = null;
			_pendingBuyTake = null;
			_buyOrderExpiry = null;
		}
		else if (currentPosition < 0m && _previousPosition >= 0m)
		{
			_shortStop = _pendingSellStop;
			_shortTake = _pendingSellTake;
			_pendingSellStop = null;
			_pendingSellTake = null;
			_sellOrderExpiry = null;
		}
		else if (currentPosition == 0m && _previousPosition != 0m)
		{
			ResetTradeLevels();
		}

		_previousPosition = currentPosition;
	}

	private void ManageOrderExpirations(DateTimeOffset currentTime)
	{
		if (_buyOrderExpiry is DateTimeOffset buyExpiry)
		{
			if (!HasActiveLimitOrder(Sides.Buy))
			{
				_buyOrderExpiry = null;
			}
			else if (currentTime >= buyExpiry)
			{
				CancelSideOrders(Sides.Buy);
				_buyOrderExpiry = null;
				_pendingBuyStop = null;
				_pendingBuyTake = null;
			}
		}

		if (_sellOrderExpiry is DateTimeOffset sellExpiry)
		{
			if (!HasActiveLimitOrder(Sides.Sell))
			{
				_sellOrderExpiry = null;
			}
			else if (currentTime >= sellExpiry)
			{
				CancelSideOrders(Sides.Sell);
				_sellOrderExpiry = null;
				_pendingSellStop = null;
				_pendingSellTake = null;
			}
		}
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			if (_longStop is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket(Math.Abs(Position));
				ResetTradeLevels();
				return;
			}

			if (_longTake is decimal take && candle.HighPrice >= take)
			{
				SellMarket(Math.Abs(Position));
				ResetTradeLevels();
			}
		}
		else if (Position < 0m)
		{
			if (_shortStop is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket(Math.Abs(Position));
				ResetTradeLevels();
				return;
			}

			if (_shortTake is decimal take && candle.LowPrice <= take)
			{
				BuyMarket(Math.Abs(Position));
				ResetTradeLevels();
			}
		}
	}

	private bool TryCalculateBuyScore(ICandleMessage candle, out decimal level100, out decimal score)
	{
		score = 0m;
		level100 = 0m;

		if (_raviH1 is not decimal raviH1 || _raviD1Current is not decimal raviD1)
		return false;

		var previousM1 = GetM1Candle(1);
		var h1Low0 = GetH1Low(0);
		var h1Low1 = GetH1Low(1);
		var h1Low2 = GetH1Low(2);
		var h1High1 = GetH1High(1);
		var h1High2 = GetH1High(2);

		if (previousM1 is null || h1Low0 is null || h1Low1 is null || h1Low2 is null || h1High1 is null || h1High2 is null)
		return false;

		level100 = Math.Round(candle.ClosePrice, 2, MidpointRounding.AwayFromZero) + PointFromLevelGoPips * _pointValue;
		var riseThreshold = level100 + RiseFilterPips * _pointValue;
		var baseLow = level100 - PointFromLevelGoPips * _pointValue;
		var tolerance = 30m * _pointValue;

		if (raviH1 < 0m)
		score += 10m;

		if (h1High1 > riseThreshold || h1High2 > riseThreshold)
		score += 7m;

		if (candle.ClosePrice < level100 && previousM1.ClosePrice > level100 &&
		h1Low0.Value > baseLow + tolerance && h1Low1.Value > baseLow + tolerance && h1Low2.Value > baseLow)
		{
			score += 45m;
		}

		if (CheckM1HighAbove(level100 + HighLevelPips * _pointValue, 12))
		score -= 50m;

		if (raviD1 < -2m && CheckM1ImpulseForBuy())
		score -= 50m;

		if (!CheckH1BreakAbove(level100 + LowLevel2Pips * _pointValue))
		score -= 50m;

		if (CheckM30CompressionAbove(level100 + LowLevelPips * _pointValue))
		score -= 50m;

		return true;
	}

	private bool TryCalculateSellScore(ICandleMessage candle, out decimal level100, out decimal score)
	{
		score = 0m;
		level100 = 0m;

		if (_raviH1 is not decimal raviH1 || _raviD1Current is not decimal raviD1)
		return false;

		var previousM1 = GetM1Candle(1);
		var h1High0 = GetH1High(0);
		var h1High1 = GetH1High(1);
		var h1High2 = GetH1High(2);
		var h1Low1 = GetH1Low(1);
		var h1Low2 = GetH1Low(2);

		if (previousM1 is null || h1High0 is null || h1High1 is null || h1High2 is null || h1Low1 is null || h1Low2 is null)
		return false;

		level100 = Math.Round(candle.ClosePrice, 2, MidpointRounding.AwayFromZero) - PointFromLevelGoPips * _pointValue;
		var fallThreshold = level100 - RiseFilterPips * _pointValue;
		var baseHigh = level100 + PointFromLevelGoPips * _pointValue;
		var tolerance = 30m * _pointValue;

		if (raviH1 > 0m)
		score += 10m;

		if (h1Low1 < fallThreshold || h1Low2 < fallThreshold)
		score += 7m;

		if (candle.ClosePrice > level100 && previousM1.ClosePrice < level100 &&
		h1High0.Value < baseHigh - tolerance && h1High1.Value < baseHigh - tolerance && h1High2.Value < baseHigh)
		{
			score += 45m;
		}

		if (CheckM1LowBelow(level100 - HighLevelPips * _pointValue, 12))
		score -= 50m;

		if (raviD1 > 2m && CheckM1ImpulseForSell())
		score -= 50m;

		if (!CheckH1BreakBelow(level100 - LowLevel2Pips * _pointValue))
		score -= 50m;

		if (CheckM30CompressionBelow(level100 - LowLevelPips * _pointValue))
		score -= 50m;

		return true;
	}

	private bool PlaceBuyLimit(ICandleMessage candle)
	{
		var volume = CalculateOrderVolume();
		if (volume <= 0m)
		return false;

		var entryPrice = Math.Max(candle.ClosePrice - 10m * _pipSize, 0m);
		var stopPrice = entryPrice - StopLossPips * _pipSize;
		var takePrice = entryPrice + TakeProfitPips * _pipSize;

		if (_raviD1Current is decimal ravi && _raviD1Prev1 is decimal prev1 && _raviD1Prev2 is decimal prev2 && _raviD1Prev3 is decimal prev3)
		{
			if (ravi > 1m && ravi < 5m && prev1 < ravi && prev2 < prev1 && prev3 < prev2)
			{
				takePrice += 25m * _pipSize;
			}
		}

		BuyLimit(price: entryPrice, volume: volume);
		_buyOrderExpiry = candle.CloseTime + TimeSpan.FromMinutes(OrderExpiryMinutes);
		_pendingBuyStop = stopPrice;
		_pendingBuyTake = takePrice;
		return true;
	}

	private bool PlaceSellLimit(ICandleMessage candle)
	{
		var volume = CalculateOrderVolume();
		if (volume <= 0m)
		return false;

		var entryPrice = candle.ClosePrice + 7m * _pipSize;
		var stopPrice = entryPrice + StopLossPips * _pipSize;
		var takePrice = entryPrice - TakeProfitPips * _pipSize;

		if (_raviD1Current is decimal ravi && _raviD1Prev1 is decimal prev1 && _raviD1Prev2 is decimal prev2 && _raviD1Prev3 is decimal prev3)
		{
			if (ravi < -1m && ravi > -5m && prev1 > ravi && prev2 > prev1 && prev3 > prev2)
			{
				takePrice -= 25m * _pipSize;
			}
		}

		SellLimit(price: entryPrice, volume: volume);
		_sellOrderExpiry = candle.CloseTime + TimeSpan.FromMinutes(OrderExpiryMinutes);
		_pendingSellStop = stopPrice;
		_pendingSellTake = takePrice;
		return true;
	}

	private bool CheckM1HighAbove(decimal threshold, int candles)
	{
		for (var i = 0; i < candles; i++)
		{
			var candle = GetM1Candle(i);
			if (candle is null)
			break;

			if (candle.HighPrice > threshold)
			return true;
		}

		return false;
	}

	private bool CheckM1LowBelow(decimal threshold, int candles)
	{
		for (var i = 0; i < candles; i++)
		{
			var candle = GetM1Candle(i);
			if (candle is null)
			break;

			if (candle.LowPrice < threshold)
			return true;
		}

		return false;
	}

	private bool CheckM1ImpulseForBuy()
	{
		for (var shift = 0; shift <= 30; shift++)
		{
			var current = GetM1Candle(shift);
			var future = GetM1Candle(shift + 3);

			if (current is null || future is null)
			break;

			if (future.HighPrice - current.LowPrice > 300m * _pointValue && future.OpenPrice > current.ClosePrice)
			return true;
		}

		return false;
	}

	private bool CheckM1ImpulseForSell()
	{
		for (var shift = 0; shift <= 30; shift++)
		{
			var current = GetM1Candle(shift);
			var future = GetM1Candle(shift + 3);

			if (current is null || future is null)
			break;

			if (current.HighPrice - future.LowPrice > 300m * _pointValue && current.ClosePrice > future.OpenPrice)
			return true;
		}

		return false;
	}

	private bool CheckH1BreakAbove(decimal threshold)
	{
		for (var shift = 0; shift <= 14; shift++)
		{
			var high = GetH1High(shift);
			if (high is null)
			break;

			if (high.Value > threshold)
			return true;
		}

		return false;
	}

	private bool CheckH1BreakBelow(decimal threshold)
	{
		for (var shift = 0; shift <= 14; shift++)
		{
			var low = GetH1Low(shift);
			if (low is null)
			break;

			if (low.Value < threshold)
			return true;
		}

		return false;
	}

	private bool CheckM30CompressionAbove(decimal threshold)
	{
		for (var shift = 0; shift <= 7; shift++)
		{
			var candle = GetM30Candle(shift);
			if (candle is null)
			return false;

			if (candle.HighPrice >= threshold)
			return false;
		}

		return true;
	}

	private bool CheckM30CompressionBelow(decimal threshold)
	{
		for (var shift = 0; shift <= 7; shift++)
		{
			var candle = GetM30Candle(shift);
			if (candle is null)
			return false;

			if (candle.LowPrice <= threshold)
			return false;
		}

		return true;
	}

	private ICandleMessage GetM1Candle(int shift)
	{
		var index = _m1History.Count - 1 - shift;
		return index >= 0 && index < _m1History.Count ? _m1History[index] : null;
	}

	private ICandleMessage GetM30Candle(int shift)
	{
		var index = _m30History.Count - 1 - shift;
		return index >= 0 && index < _m30History.Count ? _m30History[index] : null;
	}

	private decimal? GetH1High(int shift)
	{
		if (shift == 0)
		return _h1Current?.HighPrice;

		var index = _h1Finished.Count - shift;
		return index >= 0 && index < _h1Finished.Count ? _h1Finished[index].HighPrice : null;
	}

	private decimal? GetH1Low(int shift)
	{
		if (shift == 0)
		return _h1Current?.LowPrice;

		var index = _h1Finished.Count - shift;
		return index >= 0 && index < _h1Finished.Count ? _h1Finished[index].LowPrice : null;
	}

	private void CancelSideOrders(Sides side)
	{
		foreach (var order in Orders.Where(o => o.State is OrderStates.Active or OrderStates.Pending))
		{
			if (order.Type != OrderTypes.Limit || order.Side != side)
			continue;

			CancelOrder(order);
		}
	}

	private bool HasActiveLimitOrder(Sides side)
	{
		foreach (var order in Orders.Where(o => o.State is OrderStates.Active or OrderStates.Pending))
		{
			if (order.Type == OrderTypes.Limit && order.Side == side)
			return true;
		}

		return false;
	}

	private bool HasExposure()
	{
		if (Position != 0m)
		return true;

		foreach (var order in Orders.Where(o => o.State is OrderStates.Active or OrderStates.Pending))
		{
			if (order.Type == OrderTypes.Limit)
			return true;
		}

		return false;
	}

	private bool HasSufficientMargin()
	{
		if (MarginCutoff <= 0m)
		return true;

		var portfolio = Portfolio;
		if (portfolio is null)
		return true;

		var equity = portfolio.CurrentValue ?? 0m;
		if (equity <= 0m)
		equity = portfolio.BeginValue ?? 0m;

		return equity >= MarginCutoff;
	}

	private decimal CalculateOrderVolume()
	{
		if (!UseMoneyManagement)
		return FixedVolume;

		var portfolio = Portfolio;
		if (portfolio is null)
		return FixedVolume;

		var equity = portfolio.CurrentValue ?? 0m;
		if (equity <= 0m)
		equity = portfolio.BeginValue ?? 0m;

		if (equity <= 0m)
		return FixedVolume;

		var lot = Math.Floor(equity * TradeSizePercent / 1000m) / 100m;

		if (AccountIsMini)
		{
			lot = Math.Floor(lot * 100m) / 100m;
			if (lot < 0.1m)
			lot = 0.1m;
		}
		else
		{
			if (lot < 1m)
			lot = 1m;
		}

		if (lot > MaxVolume)
		lot = MaxVolume;

		return lot;
	}

	private void ResetTradeLevels()
	{
		_pendingBuyStop = null;
		_pendingBuyTake = null;
		_pendingSellStop = null;
		_pendingSellTake = null;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
	}

	private static void TrimHistory(ICollection<ICandleMessage> history, int maxCount)
	{
		while (history.Count > maxCount)
		{
			if (history is List<ICandleMessage> list)
			list.RemoveAt(0);
			else
			break;
		}
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		var decimals = Security?.Decimals ?? 0;
		var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
		var pip = step * adjust;
		return pip == 0m ? 0.0001m : pip;
	}
}