GitHub で見る

Adaptive Renko Duplex 戦略

概要

Adaptive Renko Duplex 戦略は、元のエキスパートアドバイザー Exp_AdaptiveRenko_Duplex.mq5 をStockSharpにポーティングしたものです。変換されたバージョンは、2つの独立したAdaptive Renkoストリームを実行するというアイデアを維持しています – 1つは強気のセットアップ専用、もう1つは弱気のセットアップ専用 – ハイレベルAPIを通じてロジックを公開しながら。各ストリームは、最近のボラティリティに動的に適応するレンガの高さを持つRenkoスタイルのサポートおよびレジスタンスのレールを構築します。戦略はこれらのレール内で検出されたトレンド転換に反応し、ロングとショートサイドの非対称な設定を維持できます。

合成レンガで動作するクラシックなRenkoトレーディングシステムとは異なり、Duplexアプローチは標準ローソク足を監視し、Adaptive Renkoバッファーを継続的に再計算します。シグナルは再描画を避け、StockSharpのイベント駆動モデルに合わせるために、完全に完了したローソク足でのみ生成されます。

市場データとインジケーター

  • ローソク足サブスクリプション – 2つの独立した DataType パラメーターが、ロングとショートのRenkoストリームを供給するローソク足シリーズを選択します。同じ時間軸または異なる時間軸を指定できます。
  • Adaptive Renko再構築 – 各ストリームは元のインジケーターロジックを組み込んでいます。最小レンガサイズ(ポイントで表現)が K × ボラティリティ と比較され、大きい方が新しいレンガの高さを定義します。インジケーターは上部/下部エンベロープに加え、カラー付きのトレンドレベル(上昇トレンドではサポート、下降トレンドではレジスタンス)を追跡します。
  • ボラティリティソースAverageTrueRange または StandardDeviation インジケーターから選択します。両方とも各ストリームが使用するローソク足シリーズで動作し、カスタムのルックバック長を受け入れます。

トレードロジック

  1. ロングサイドの検出
    • ロングストリームは設定されたパラメーターを使用して適応型レンガを構築します。
    • 上昇トレンドライン(RenkoTrend.Up)が LongSignalBarOffset で定義された遅延バーに現れると、戦略は市場買い注文を発行します。注文サイズは Volume + |Position| で、ショートからロングへの即時転換を可能にします。
    • 設定された遅延後に下降トレンドラインが検出され LongExitsEnabled が真の場合、すべてのロングエクスポージャーが閉じられます。
  2. ショートサイドの検出
    • ショートストリームはロジックを反映します:RenkoTrend.Down シグナルが市場売りを生成し、RenkoTrend.UpShortExitsEnabled が有効な場合に遅延バーでショートを終了します。
  3. シグナル遅延 – 両サイドは SignalBarOffset パラメーターを尊重し、MetaTraderエキスパートが使用した1バーシフトを再現します。オフセットをゼロに設定すると、最新の完了ローソク足に反応します。
  4. ポジションサイジング – StockSharpバージョンは戦略の Volume プロパティに依存します。戦略を開始する前に必ず設定してください。

リスク管理

  • ストップロス / テイクプロフィット – 距離はポイントで指定され、絶対価格を生成するために銘柄の PriceStep を掛けます。StockSharpがサーバーサイドの保護注文を自動的に作成しないため、ストップはサブスクライブしたローソク足が閉じるときにチェックされ、エグジットは市場注文で処理されます。
  • 状態追跡 – 戦略は最後のロングまたはショートエントリーが実行されたときの価格(ローソク足の終値に基づく)を保存し、ストップまたはターゲットまでの距離を評価できます。
  • 手動オーバーライド – アカウントレベルのリスク管理が必要な場合、標準の Stop または Protective モジュールを StartProtection() を外部から呼び出すことで上に追加できます。

パラメーター

パラメーター デフォルト 説明
LongCandleType 4時間足 ロングシグナルの計算に使用するローソク足シリーズ。
ShortCandleType 4時間足 ショートシグナルの計算に使用するローソク足シリーズ。
LongVolatilityMode ATR ロングレンガのボラティリティソース(AverageTrueRange または StandardDeviation)。
ShortVolatilityMode ATR ショートレンガのボラティリティソース。
LongVolatilityPeriod 10 ロングボラティリティインジケーターのルックバック期間。
ShortVolatilityPeriod 10 ショートボラティリティインジケーターのルックバック期間。
LongSensitivity 1.0 ロングレンガを構築する前にボラティリティ値に適用する乗数。
ShortSensitivity 1.0 ショートレンガを構築する前にボラティリティ値に適用する乗数。
LongPriceMode Close ロングRenkoレールを更新するために使用する価格入力(HighLow または Close)。
ShortPriceMode Close ショートRenkoレールを更新するために使用する価格入力。
LongMinimumBrickPoints 2 ロングストリームの最小レンガ高、ポイントで測定。
ShortMinimumBrickPoints 2 ショートストリームの最小レンガ高。
LongSignalBarOffset 1 ロングシグナルを確認する前の遅延(バー単位)。
ShortSignalBarOffset 1 ショートシグナルを確認する前の遅延(バー単位)。
LongEntriesEnabled true ロングエントリーを許可またはブロックするトグル。
LongExitsEnabled true Renko駆動のロングエグジットを許可またはブロックするトグル。
ShortEntriesEnabled true ショートエントリーを許可またはブロックするトグル。
ShortExitsEnabled true Renko駆動のショートエグジットを許可またはブロックするトグル。
LongStopLossPoints 1000 ロングポジションのストップロス距離(ポイント × PriceStep)。
LongTakeProfitPoints 2000 ロングポジションのテイクプロフィット距離。
ShortStopLossPoints 1000 ショートポジションのストップロス距離。
ShortTakeProfitPoints 2000 ショートポジションのテイクプロフィット距離。

ポイント変換 – MQLバージョンはブローカーの「ポイント」定義を使用していました。StockSharpでは、すべての距離がポイントを絶対価格増分に変換するために Security.PriceStep(またはフォールバックとして Security.MinStep)で乗算されます。銘柄のティックサイズに合わせてデフォルトを調整してください。

使用ガイドライン

  1. 環境の設定 – 戦略を開始する前に SecurityPortfolioVolume を割り当てます。データソースが設定されたすべてのローソク足の時間軸を配信できることを確認してください。
  2. 両ストリームの調整 – デフォルトの対称的な設定を維持するか、非対称な動作のためにロングとショートサイドに異なる時間軸/ボラティリティモードを割り当てることができます。
  3. ログの監視 – 戦略はすべてのエントリーとエグジットで LogInfo メッセージを発し、アクションをトリガーしたRenkoレベルを示します。これらのログを使用してシグナルが期待に一致するかを検証してください。
  4. 外部モジュールとの組み合わせ – 戦略がメイン Strategy クラスでシグナルを公開するため、追加のフィルター(セッション制御、資本保護など)をStockSharpのハイレベルAPIを通じて添付できます。
  5. バックテストの考慮事項 – 過去のデータでテストする場合、Adaptive Renkoが一貫したまま保てるように必要な時間軸を再構築できるローソク足ビルダーを優先してください。

元のエキスパートアドバイザーとの違い

  • MetaTrader固有の機能(マジックナンバー、資金管理モード、偏差処理、プッシュ通知)は意図的に省略されています。ポジションサイジングはStockSharpの Volume プロパティのみに依存します。
  • 元のEAはサーバーサイドのストップロスとテイクプロフィット注文を配置していました。変換されたバージョンは各完了ローソク足で設定された距離をチェックし、市場注文でクローズします。
  • シグナルは部分バーの再計算を避けるために完了したローソク足でのみ厳密に評価されます。これはMQL実装で使用された IsNewBar チェックを反映しています。
  • Adaptive Renkoの再構築は公開されたアルゴリズムに従いますが、追加のインジケーターオブジェクトを作成せずにC#で実装されており、StockSharpのハイレベルAPIの規則を尊重しながら更新パスを効率的に保ちます。

推奨される拡張

  • 非流動的な条件での取引を避けるために、高レベルのレジームフィルター(セッションスケジュール、ボラティリティフィルター)とDuplexストリームを組み合わせます。
  • アカウントレベルのセーフガードのために StartProtection() でトレーリングストップモジュールまたは資本ベースの保護を添付します。
  • 裁量的なレビューで戦略を視覚的に検証するために、生成されたサポート/レジスタンスのレールをログまたはプロットします。
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;

using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual-stream adaptive Renko strategy converted from the Exp_AdaptiveRenko_Duplex MQL5 expert advisor.
/// Generates independent long and short signals by projecting the Adaptive Renko indicator onto configurable candle series.
/// </summary>
public class AdaptiveRenkoDuplexStrategy : Strategy
{
	private readonly StrategyParam<DataType> _longCandleType;
	private readonly StrategyParam<DataType> _shortCandleType;
	private readonly StrategyParam<AdaptiveRenkoVolatilityModes> _longVolatilityMode;
	private readonly StrategyParam<AdaptiveRenkoVolatilityModes> _shortVolatilityMode;
	private readonly StrategyParam<int> _longVolatilityPeriod;
	private readonly StrategyParam<int> _shortVolatilityPeriod;
	private readonly StrategyParam<decimal> _longSensitivity;
	private readonly StrategyParam<decimal> _shortSensitivity;
	private readonly StrategyParam<AdaptiveRenkoPriceModes> _longPriceMode;
	private readonly StrategyParam<AdaptiveRenkoPriceModes> _shortPriceMode;
	private readonly StrategyParam<decimal> _longMinimumBrickPoints;
	private readonly StrategyParam<decimal> _shortMinimumBrickPoints;
	private readonly StrategyParam<int> _longSignalBarOffset;
	private readonly StrategyParam<int> _shortSignalBarOffset;
	private readonly StrategyParam<bool> _longEntriesEnabled;
	private readonly StrategyParam<bool> _longExitsEnabled;
	private readonly StrategyParam<bool> _shortEntriesEnabled;
	private readonly StrategyParam<bool> _shortExitsEnabled;
	private readonly StrategyParam<decimal> _longStopLossPoints;
	private readonly StrategyParam<decimal> _longTakeProfitPoints;
	private readonly StrategyParam<decimal> _shortStopLossPoints;
	private readonly StrategyParam<decimal> _shortTakeProfitPoints;

	private readonly AdaptiveRenkoProcessor _longProcessor = new();
	private readonly AdaptiveRenkoProcessor _shortProcessor = new();

	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;

	public AdaptiveRenkoDuplexStrategy()
	{
		_longCandleType = Param(nameof(LongCandleType), TimeSpan.FromDays(1).TimeFrame())
			.SetDisplay("Long Candle Type", "Timeframe used to derive long-side signals", "Long Side");

		_shortCandleType = Param(nameof(ShortCandleType), TimeSpan.FromDays(1).TimeFrame())
			.SetDisplay("Short Candle Type", "Timeframe used to derive short-side signals", "Short Side");

		_longVolatilityMode = Param(nameof(LongVolatilityMode), AdaptiveRenkoVolatilityModes.AverageTrueRange)
			.SetDisplay("Long Volatility Source", "Volatility measure controlling long Renko brick size", "Long Side");

		_shortVolatilityMode = Param(nameof(ShortVolatilityMode), AdaptiveRenkoVolatilityModes.AverageTrueRange)
			.SetDisplay("Short Volatility Source", "Volatility measure controlling short Renko brick size", "Short Side");

		_longVolatilityPeriod = Param(nameof(LongVolatilityPeriod), 10)
			.SetRange(1, 500)
			.SetDisplay("Long Volatility Period", "Lookback period for the volatility calculation", "Long Side")
			;

		_shortVolatilityPeriod = Param(nameof(ShortVolatilityPeriod), 10)
			.SetRange(1, 500)
			.SetDisplay("Short Volatility Period", "Lookback period for the volatility calculation", "Short Side")
			;

		_longSensitivity = Param(nameof(LongSensitivity), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Long Sensitivity", "Multiplier applied to volatility for long bricks", "Long Side")
			;

		_shortSensitivity = Param(nameof(ShortSensitivity), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Short Sensitivity", "Multiplier applied to volatility for short bricks", "Short Side")
			;

		_longPriceMode = Param(nameof(LongPriceMode), AdaptiveRenkoPriceModes.Close)
			.SetDisplay("Long Price Mode", "Price source used when building long bricks", "Long Side");

		_shortPriceMode = Param(nameof(ShortPriceMode), AdaptiveRenkoPriceModes.Close)
			.SetDisplay("Short Price Mode", "Price source used when building short bricks", "Short Side");

		_longMinimumBrickPoints = Param(nameof(LongMinimumBrickPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Long Minimum Brick", "Minimal brick height in points for long bricks", "Long Side");

		_shortMinimumBrickPoints = Param(nameof(ShortMinimumBrickPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Short Minimum Brick", "Minimal brick height in points for short bricks", "Short Side");

		_longSignalBarOffset = Param(nameof(LongSignalBarOffset), 2)
			.SetRange(0, 10)
			.SetDisplay("Long Signal Offset", "Number of closed bars to delay long signals", "Long Side");

		_shortSignalBarOffset = Param(nameof(ShortSignalBarOffset), 2)
			.SetRange(0, 10)
			.SetDisplay("Short Signal Offset", "Number of closed bars to delay short signals", "Short Side");

		_longEntriesEnabled = Param(nameof(LongEntriesEnabled), true)
			.SetDisplay("Enable Long Entries", "Allow long-side market entries", "Long Side");

		_longExitsEnabled = Param(nameof(LongExitsEnabled), true)
			.SetDisplay("Enable Long Exits", "Allow long-side exits triggered by Renko", "Long Side");

		_shortEntriesEnabled = Param(nameof(ShortEntriesEnabled), true)
			.SetDisplay("Enable Short Entries", "Allow short-side market entries", "Short Side");

		_shortExitsEnabled = Param(nameof(ShortExitsEnabled), true)
			.SetDisplay("Enable Short Exits", "Allow short-side exits triggered by Renko", "Short Side");

		_longStopLossPoints = Param(nameof(LongStopLossPoints), 1000m)
			.SetNotNegative()
			.SetDisplay("Long Stop Loss", "Protective stop distance in points for long trades", "Risk");

		_longTakeProfitPoints = Param(nameof(LongTakeProfitPoints), 2000m)
			.SetNotNegative()
			.SetDisplay("Long Take Profit", "Profit target distance in points for long trades", "Risk");

		_shortStopLossPoints = Param(nameof(ShortStopLossPoints), 1000m)
			.SetNotNegative()
			.SetDisplay("Short Stop Loss", "Protective stop distance in points for short trades", "Risk");

		_shortTakeProfitPoints = Param(nameof(ShortTakeProfitPoints), 2000m)
			.SetNotNegative()
			.SetDisplay("Short Take Profit", "Profit target distance in points for short trades", "Risk");
	}

	/// <summary>
	/// Candle stream used to compute long-side Renko structures.
	/// </summary>
	public DataType LongCandleType
	{
		get => _longCandleType.Value;
		set => _longCandleType.Value = value;
	}

	/// <summary>
	/// Candle stream used to compute short-side Renko structures.
	/// </summary>
	public DataType ShortCandleType
	{
		get => _shortCandleType.Value;
		set => _shortCandleType.Value = value;
	}

	/// <summary>
	/// Volatility mode for the long Renko stream.
	/// </summary>
	public AdaptiveRenkoVolatilityModes LongVolatilityMode
	{
		get => _longVolatilityMode.Value;
		set => _longVolatilityMode.Value = value;
	}

	/// <summary>
	/// Volatility mode for the short Renko stream.
	/// </summary>
	public AdaptiveRenkoVolatilityModes ShortVolatilityMode
	{
		get => _shortVolatilityMode.Value;
		set => _shortVolatilityMode.Value = value;
	}

	/// <summary>
	/// Lookback period for the long-side volatility indicator.
	/// </summary>
	public int LongVolatilityPeriod
	{
		get => _longVolatilityPeriod.Value;
		set => _longVolatilityPeriod.Value = value;
	}

	/// <summary>
	/// Lookback period for the short-side volatility indicator.
	/// </summary>
	public int ShortVolatilityPeriod
	{
		get => _shortVolatilityPeriod.Value;
		set => _shortVolatilityPeriod.Value = value;
	}

	/// <summary>
	/// Volatility multiplier that scales long-side bricks.
	/// </summary>
	public decimal LongSensitivity
	{
		get => _longSensitivity.Value;
		set => _longSensitivity.Value = value;
	}

	/// <summary>
	/// Volatility multiplier that scales short-side bricks.
	/// </summary>
	public decimal ShortSensitivity
	{
		get => _shortSensitivity.Value;
		set => _shortSensitivity.Value = value;
	}

	/// <summary>
	/// Price source used while building long bricks.
	/// </summary>
	public AdaptiveRenkoPriceModes LongPriceMode
	{
		get => _longPriceMode.Value;
		set => _longPriceMode.Value = value;
	}

	/// <summary>
	/// Price source used while building short bricks.
	/// </summary>
	public AdaptiveRenkoPriceModes ShortPriceMode
	{
		get => _shortPriceMode.Value;
		set => _shortPriceMode.Value = value;
	}

	/// <summary>
	/// Minimal brick height for the long Renko stream (expressed in points).
	/// </summary>
	public decimal LongMinimumBrickPoints
	{
		get => _longMinimumBrickPoints.Value;
		set => _longMinimumBrickPoints.Value = value;
	}

	/// <summary>
	/// Minimal brick height for the short Renko stream (expressed in points).
	/// </summary>
	public decimal ShortMinimumBrickPoints
	{
		get => _shortMinimumBrickPoints.Value;
		set => _shortMinimumBrickPoints.Value = value;
	}

	/// <summary>
	/// Number of closed bars to wait before using a long-side signal.
	/// </summary>
	public int LongSignalBarOffset
	{
		get => _longSignalBarOffset.Value;
		set => _longSignalBarOffset.Value = value;
	}

	/// <summary>
	/// Number of closed bars to wait before using a short-side signal.
	/// </summary>
	public int ShortSignalBarOffset
	{
		get => _shortSignalBarOffset.Value;
		set => _shortSignalBarOffset.Value = value;
	}

	/// <summary>
	/// Enables long-side entries.
	/// </summary>
	public bool LongEntriesEnabled
	{
		get => _longEntriesEnabled.Value;
		set => _longEntriesEnabled.Value = value;
	}

	/// <summary>
	/// Enables Renko-driven exits for long positions.
	/// </summary>
	public bool LongExitsEnabled
	{
		get => _longExitsEnabled.Value;
		set => _longExitsEnabled.Value = value;
	}

	/// <summary>
	/// Enables short-side entries.
	/// </summary>
	public bool ShortEntriesEnabled
	{
		get => _shortEntriesEnabled.Value;
		set => _shortEntriesEnabled.Value = value;
	}

	/// <summary>
	/// Enables Renko-driven exits for short positions.
	/// </summary>
	public bool ShortExitsEnabled
	{
		get => _shortExitsEnabled.Value;
		set => _shortExitsEnabled.Value = value;
	}

	/// <summary>
	/// Stop-loss distance for long positions expressed in indicator points.
	/// </summary>
	public decimal LongStopLossPoints
	{
		get => _longStopLossPoints.Value;
		set => _longStopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance for long positions expressed in indicator points.
	/// </summary>
	public decimal LongTakeProfitPoints
	{
		get => _longTakeProfitPoints.Value;
		set => _longTakeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop-loss distance for short positions expressed in indicator points.
	/// </summary>
	public decimal ShortStopLossPoints
	{
		get => _shortStopLossPoints.Value;
		set => _shortStopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance for short positions expressed in indicator points.
	/// </summary>
	public decimal ShortTakeProfitPoints
	{
		get => _shortTakeProfitPoints.Value;
		set => _shortTakeProfitPoints.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security == null)
			yield break;

		yield return (Security, LongCandleType);

		if (ShortCandleType != LongCandleType)
			yield return (Security, ShortCandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_longProcessor.Reset();
		_shortProcessor.Reset();
		_longEntryPrice = null;
		_shortEntryPrice = null;
	}

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

		_longProcessor.Reset();
		_shortProcessor.Reset();
		_longEntryPrice = null;
		_shortEntryPrice = null;

		var longIndicator = CreateVolatilityIndicator(LongVolatilityMode, LongVolatilityPeriod);
		var longSubscription = SubscribeCandles(LongCandleType);
		longSubscription.BindEx(longIndicator, ProcessLongCandle);

		var shortIndicator = CreateVolatilityIndicator(ShortVolatilityMode, ShortVolatilityPeriod);

		if (ShortCandleType == LongCandleType)
		{
			longSubscription.BindEx(shortIndicator, ProcessShortCandle);
			longSubscription.Start();
		}
		else
		{
			longSubscription.Start();
			var shortSubscription = SubscribeCandles(ShortCandleType);
			shortSubscription.BindEx(shortIndicator, ProcessShortCandle);
			shortSubscription.Start();
		}
	}

	private void ProcessLongCandle(ICandleMessage candle, IIndicatorValue volatilityValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		ManageLongRisk(candle);

		if (!volatilityValue.IsFinal)
			return;

		var step = GetPriceStep();
		var volatility = volatilityValue.ToDecimal();
		var snapshot = _longProcessor.Process(candle, volatility, LongSensitivity, LongMinimumBrickPoints, LongPriceMode, LongSignalBarOffset, step);

		if (snapshot == null)
			return;

		var signal = _longProcessor.GetSnapshot(LongSignalBarOffset);
		if (signal == null)
			return;

		if (LongExitsEnabled && Position > 0 && signal.Value.Trend == RenkoTrends.Down)
		{
			TryCloseLong("Adaptive Renko bearish reversal", candle);
		}

		if (LongEntriesEnabled && signal.Value.Trend == RenkoTrends.Up)
		{
			TryOpenLong(candle, signal.Value);
		}
	}

	private void ProcessShortCandle(ICandleMessage candle, IIndicatorValue volatilityValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		ManageShortRisk(candle);

		if (!volatilityValue.IsFinal)
			return;

		var step = GetPriceStep();
		var volatility = volatilityValue.ToDecimal();
		var snapshot = _shortProcessor.Process(candle, volatility, ShortSensitivity, ShortMinimumBrickPoints, ShortPriceMode, ShortSignalBarOffset, step);

		if (snapshot == null)
			return;

		var signal = _shortProcessor.GetSnapshot(ShortSignalBarOffset);
		if (signal == null)
			return;

		if (ShortExitsEnabled && Position < 0 && signal.Value.Trend == RenkoTrends.Up)
		{
			TryCloseShort("Adaptive Renko bullish reversal", candle);
		}

		if (ShortEntriesEnabled && signal.Value.Trend == RenkoTrends.Down)
		{
			TryOpenShort(candle, signal.Value);
		}
	}

	private void ManageLongRisk(ICandleMessage candle)
	{
		if (Position <= 0)
		{
			_longEntryPrice = null;
			return;
		}

		if (_longEntryPrice == null)
			_longEntryPrice = candle.ClosePrice;

		var step = GetPriceStep();

		if (LongStopLossPoints > 0m)
		{
			var stopDistance = LongStopLossPoints * step;
			if (stopDistance > 0m && candle.LowPrice <= _longEntryPrice.Value - stopDistance)
			{
				TryCloseLong("Long stop loss reached", candle);
				return;
			}
		}

		if (LongTakeProfitPoints > 0m)
		{
			var targetDistance = LongTakeProfitPoints * step;
			if (targetDistance > 0m && candle.HighPrice >= _longEntryPrice.Value + targetDistance)
			{
				TryCloseLong("Long take profit reached", candle);
			}
		}
	}

	private void ManageShortRisk(ICandleMessage candle)
	{
		if (Position >= 0)
		{
			_shortEntryPrice = null;
			return;
		}

		if (_shortEntryPrice == null)
			_shortEntryPrice = candle.ClosePrice;

		var step = GetPriceStep();

		if (ShortStopLossPoints > 0m)
		{
			var stopDistance = ShortStopLossPoints * step;
			if (stopDistance > 0m && candle.HighPrice >= _shortEntryPrice.Value + stopDistance)
			{
				TryCloseShort("Short stop loss reached", candle);
				return;
			}
		}

		if (ShortTakeProfitPoints > 0m)
		{
			var targetDistance = ShortTakeProfitPoints * step;
			if (targetDistance > 0m && candle.LowPrice <= _shortEntryPrice.Value - targetDistance)
			{
				TryCloseShort("Short take profit reached", candle);
			}
		}
	}

	private void TryOpenLong(ICandleMessage candle, RenkoSnapshot signal)
	{
		if (Position > 0)
			return;

		var volume = Volume + Math.Abs(Position);
		if (volume <= 0m)
		{
			LogWarning("Volume must be positive to open a long position.");
			return;
		}

		BuyMarket(volume);
		_longEntryPrice = candle.ClosePrice;
		_shortEntryPrice = null;
		LogInfo($"Long entry triggered. Trend level: {signal.Support?.ToString("F5") ?? "n/a"}.");
	}

	private void TryOpenShort(ICandleMessage candle, RenkoSnapshot signal)
	{
		if (Position < 0)
			return;

		var volume = Volume + Math.Abs(Position);
		if (volume <= 0m)
		{
			LogWarning("Volume must be positive to open a short position.");
			return;
		}

		SellMarket(volume);
		_shortEntryPrice = candle.ClosePrice;
		_longEntryPrice = null;
		LogInfo($"Short entry triggered. Trend level: {signal.Resistance?.ToString("F5") ?? "n/a"}.");
	}

	private void TryCloseLong(string reason, ICandleMessage candle)
	{
		if (Position <= 0)
		{
			_longEntryPrice = null;
			return;
		}

		SellMarket(Math.Abs(Position));
		_longEntryPrice = null;
		LogInfo($"Long exit: {reason} at {candle.ClosePrice:F5}.");
	}

	private void TryCloseShort(string reason, ICandleMessage candle)
	{
		if (Position >= 0)
		{
			_shortEntryPrice = null;
			return;
		}

		BuyMarket(Math.Abs(Position));
		_shortEntryPrice = null;
		LogInfo($"Short exit: {reason} at {candle.ClosePrice:F5}.");
	}

	private static IIndicator CreateVolatilityIndicator(AdaptiveRenkoVolatilityModes mode, int period)
	{
		return mode switch
		{
			AdaptiveRenkoVolatilityModes.AverageTrueRange => new AverageTrueRange { Length = period },
			AdaptiveRenkoVolatilityModes.StandardDeviation => new StandardDeviation { Length = period },
			_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported volatility mode"),
		};
	}

	private decimal GetPriceStep()
	{
		var security = Security;
		if (security == null)
			return 1m;

		if (security.PriceStep != null && security.PriceStep.Value > 0m)
			return security.PriceStep.Value;

		return 1m;
	}

	private enum RenkoTrends
	{
		None = 0,
		Up = 1,
		Down = -1
	}

	private readonly struct RenkoSnapshot
	{
		public RenkoSnapshot(DateTimeOffset time, RenkoTrends trend, decimal? support, decimal? resistance)
		{
			Time = time;
			Trend = trend;
			Support = support;
			Resistance = resistance;
		}

		public DateTimeOffset Time { get; }

		public RenkoTrends Trend { get; }

		public decimal? Support { get; }

		public decimal? Resistance { get; }
	}

	private sealed class AdaptiveRenkoProcessor : IEquatable<AdaptiveRenkoProcessor>
	{
		private readonly List<RenkoSnapshot> _history = new();
		private bool _initialized;
		private decimal _up;
		private decimal _down;
		private decimal _brick;
		private RenkoTrends _trend;

		public RenkoSnapshot? Process(ICandleMessage candle, decimal volatility, decimal sensitivity, decimal minimumBrickPoints, AdaptiveRenkoPriceModes priceMode, int signalOffset, decimal step)
		{
			var (high, low) = priceMode == AdaptiveRenkoPriceModes.Close
				? (candle.ClosePrice, candle.ClosePrice)
				: (candle.HighPrice, candle.LowPrice);

			var minBrick = Math.Max(minimumBrickPoints * step, 0m);

			if (!_initialized)
			{
				var range = Math.Max(high - low, 0m);
				var initialBrick = Math.Max(sensitivity * range, minBrick);

				_up = high;
				_down = low;
				_brick = initialBrick > 0m ? initialBrick : minBrick;
				_trend = RenkoTrends.None;
				_initialized = true;

				var initialSnapshot = new RenkoSnapshot(GetCandleTime(candle), RenkoTrends.None, null, null);
				AppendSnapshot(initialSnapshot, signalOffset);
				return initialSnapshot;
			}

			var up = _up;
			var down = _down;
			var brick = _brick > 0m ? _brick : minBrick;
			var trend = _trend;

			var adjustedBrick = Math.Max(sensitivity * Math.Abs(volatility), minBrick);
			if (adjustedBrick <= 0m)
				adjustedBrick = minBrick;

			if (brick <= 0m)
				brick = adjustedBrick > 0m ? adjustedBrick : minBrick;

			if (high > up + brick)
			{
				if (brick > 0m)
				{
					var diff = high - up;
					var bricks = Math.Floor(diff / brick);
					if (bricks < 1m)
						bricks = 1m;
					up += bricks * brick;
				}
				else
				{
					up = high;
				}

				brick = adjustedBrick;
				down = up - brick;
			}

			if (low < down - brick)
			{
				if (brick > 0m)
				{
					var diff = down - low;
					var bricks = Math.Floor(diff / brick);
					if (bricks < 1m)
						bricks = 1m;
					down -= bricks * brick;
				}
				else
				{
					down = low;
				}

				brick = adjustedBrick;
				up = down + brick;
			}

			if (_up < up)
				trend = RenkoTrends.Up;

			if (_down > down)
				trend = RenkoTrends.Down;

			_up = up;
			_down = down;
			_brick = brick;
			_trend = trend;

			var support = trend == RenkoTrends.Up ? down - brick : (decimal?)null;
			var resistance = trend == RenkoTrends.Down ? up + brick : (decimal?)null;

			var snapshot = new RenkoSnapshot(GetCandleTime(candle), trend, support, resistance);
			AppendSnapshot(snapshot, signalOffset);
			return snapshot;
		}

		public RenkoSnapshot? GetSnapshot(int shift)
		{
			if (shift < 0)
				shift = 0;

			var index = _history.Count - 1 - shift;
			if (index < 0)
				return null;

			return _history[index];
		}

		public void Reset()
		{
			_history.Clear();
			_initialized = false;
			_up = 0m;
			_down = 0m;
			_brick = 0m;
			_trend = RenkoTrends.None;
		}

		public bool Equals(AdaptiveRenkoProcessor other)
		{
			if (ReferenceEquals(null, other))
				return false;

			if (ReferenceEquals(this, other))
				return true;

			if (_initialized != other._initialized ||
				_up != other._up ||
				_down != other._down ||
				_brick != other._brick ||
				_trend != other._trend ||
				_history.Count != other._history.Count)
				return false;

			for (var i = 0; i < _history.Count; i++)
			{
				if (!_history[i].Equals(other._history[i]))
					return false;
			}

			return true;
		}

		public override bool Equals(object obj)
			=> obj is AdaptiveRenkoProcessor other && Equals(other);

		public override int GetHashCode()
		{
			var hash = HashCode.Combine(_initialized, _up, _down, _brick, _trend, _history.Count);

			foreach (var item in _history)
				hash = HashCode.Combine(hash, item);

			return hash;
		}

		private void AppendSnapshot(RenkoSnapshot snapshot, int signalOffset)
		{
			_history.Add(snapshot);
			var maxHistory = Math.Max(signalOffset + 3, 8);
			var overflow = _history.Count - maxHistory;
			if (overflow > 0)
				_history.RemoveRange(0, overflow);
		}

		private static DateTimeOffset GetCandleTime(ICandleMessage candle)
		{
			if (candle.CloseTime != default)
				return candle.CloseTime;

			return candle.ServerTime;
		}
	}

	public enum AdaptiveRenkoVolatilityModes
	{
		AverageTrueRange,
		StandardDeviation
	}

	public enum AdaptiveRenkoPriceModes
	{
		HighLow,
		Close
	}
}