GitHub で見る

Renko Level EA戦略

概要

  • MetaTraderエキスパートアドバイザーRenko Level EA.mq5から変換。
  • BrickSizeパラメーターから導出された上部と下部のRenkoレベルを維持することで元のインジケーターをエミュレートします。
  • CandleType(デフォルト:1分タイムフレーム)によって提供された完了したロウソク足を評価し、Renkoグリッドがシフトするときに反応します。
  • 固定ストップやターゲットを使用しません;各出口は反対シグナルを通じて発生します。

取引ロジック

  1. 最初の完了したロウソク足でクローズ価格はRenkoグリッドに丸められ、上部と下部のレベルを初期化します。
  2. 各後続のロウソク足について:
    • クローズが現在の境界内に留まる場合、グリッドは変更されません。
    • 上部レベルより上のクローズはRenkoブロックを次のグリッド値に上昇させます。
    • 下部レベルより下のクローズはブロックを下に押します。
  3. 上部Renkoレベルの変化は方向性のブレイクアウトとして解釈されます。
    • 上昇する上部レベル → 強気シグナル(ReverseSignalsが有効でない限り)。
    • 下降する上部レベル → 弱気シグナル。
  4. シグナルはオプションで反転(ReverseSignals)またはピラミッド化(AllowIncrease)して元のEAの動作に一致させることができます。

注文管理

  • ロングに入る前に、すべてのショートポジションが決済されます;ショートに入る前はその逆です。
  • AllowIncrease = falseの場合、戦略はその方向にポジションが存在しない場合にのみ新しいトレードを開きます。
  • AllowIncrease = trueの場合、ポジションがすでに開いていてもOrderVolumeサイズの追加注文が許可されます。
  • 専用のストップロスやテイクプロフィットはありません;ポジションリバーサルが出口メカニズムとして機能します。
  • StartProtection()はベースフレームワークとリスクセーフガードを整列させるために一度呼び出されます。

パラメーター

名前 説明 デフォルト 最適化可能
BrickSize Security.PriceStepの倍数として測定されるRenkoブロックサイズ。グリッドをシフトするために価格がどれだけ動く必要があるかを定義します。 30 はい(10 → 100 ステップ10)
OrderVolume 各マーケット注文で送信されるボリューム。 1 いいえ
ReverseSignals 強気と弱気のアクションを反転します。EAのReverse入力を反映します。 false いいえ
AllowIncrease フラットなポジションを待つ代わりに既存のポジションへの追加を許可します。EAのIncreaseフラグを反映します。 false いいえ
CandleType 計算に使用するロウソク足ソース。デフォルトは1分タイムフレームのロウソク足ですが、サポートされているシリーズを指定できます。 TimeFrameCandleMessage(1m) いいえ

実践上の注意事項

  • BrickSizeは取引所が定義したPriceStepを乗算するため、取引する銘柄に自動的に適応します。
  • 決定は純粋にクローズ価格に基づいています;イントラバーの動きは最終的なクローズを形成するときにのみ重要です。
  • ReverseSignalsAllowIncreaseを組み合わせることで、EAの逆トレンドとピラミッド化の両方のバリアントをテストできます。
  • Renkoスタイルのブレイクアウトロジックが関連するすべての市場(外為、先物、暗号通貨を含む)で機能します。

分類

  • レジーム:トレンドフォロー(Renkoブレイクアウト)。
  • 方向:ロング/ショート。
  • 複雑さ:中程度(カスタムレベルトラッキング、最小チューニング)。
  • ストップ:なし;逆シグナルで出口。
  • 時間軸CandleTypeで設定可能。
  • インジケーター:カスタムRenkoレベルプロジェクション。
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>
/// Strategy that mirrors the Renko Level Expert Advisor from MetaTrader.
/// Tracks level changes generated by a Renko style grid and flips positions accordingly.
/// </summary>
public class RenkoLevelEaStrategy : Strategy
{
	private readonly StrategyParam<int> _brickSize;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<bool> _allowIncrease;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _upperLevel;
	private decimal _lowerLevel;
	private decimal? _previousUpperLevel;
	private bool _levelsInitialized;

	/// <summary>
	/// Renko brick size expressed in price steps.
	/// </summary>
	public int BrickSize
	{
		get => _brickSize.Value;
		set => _brickSize.Value = value;
	}

	/// <summary>
	/// Volume for each executed market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// When enabled, long and short signals are swapped.
	/// </summary>
	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	/// <summary>
	/// Allows adding to an existing position instead of waiting for a flat position.
	/// </summary>
	public bool AllowIncrease
	{
		get => _allowIncrease.Value;
		set => _allowIncrease.Value = value;
	}

	/// <summary>
	/// Candle type used to evaluate price movement.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="RenkoLevelEaStrategy"/>.
	/// </summary>
	public RenkoLevelEaStrategy()
	{
		_brickSize = Param(nameof(BrickSize), 3000)
			.SetGreaterThanZero()
			.SetDisplay("Brick Size", "Renko block size in price steps", "Renko Levels")
			
			.SetOptimize(10, 100, 10);

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume for market orders", "Trading");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert long and short actions", "Trading");

		_allowIncrease = Param(nameof(AllowIncrease), false)
			.SetDisplay("Allow Increase", "Allow adding to existing positions", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for calculations", "Data");
	}

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

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

		// Reset previously calculated Renko levels.
		_upperLevel = 0m;
		_lowerLevel = 0m;
		_previousUpperLevel = null;
		_levelsInitialized = false;
	}

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

		// Subscribe to candle data that feeds the Renko level logic.
		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

		// Draw prices and trades if a chart is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}

		// Enable built-in protection features.
		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Trade only on completed candles.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure the strategy is ready to place trades.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

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

		// Update Renko bounds with the latest closing price.
		if (!UpdateLevels(candle.ClosePrice, priceStep))
			return;

		// Skip the very first signal to mirror indicator warm-up.
		if (_previousUpperLevel == null)
		{
			_previousUpperLevel = _upperLevel;
			return;
		}

		// Proceed only if the Renko level actually changed.
		if (AreEqual(_previousUpperLevel.Value, _upperLevel, priceStep))
			return;

		var isUpMove = _upperLevel > _previousUpperLevel.Value;

		if (ReverseSignals)
			isUpMove = !isUpMove;

		if (isUpMove)
			HandleLongSignal();
		else
			HandleShortSignal();

		_previousUpperLevel = _upperLevel;
	}

	private bool UpdateLevels(decimal closePrice, decimal priceStep)
	{
		var stepCount = BrickSize;
		if (stepCount <= 0)
			return false;

		if (!_levelsInitialized)
		{
			CalculateBounds(closePrice, priceStep, stepCount, out var ceil, out var round, out var floor);
			_upperLevel = round;
			_lowerLevel = floor;
			_levelsInitialized = true;
			return true;
		}

		if (closePrice >= _lowerLevel && closePrice <= _upperLevel)
			return false;

		CalculateBounds(closePrice, priceStep, stepCount, out var newCeil, out var newRound, out var newFloor);

		if (closePrice < _lowerLevel)
		{
			if (AreEqual(newRound, _lowerLevel, priceStep))
				return false;

			_upperLevel = newCeil;
			_lowerLevel = newRound;
			return true;
		}

		if (closePrice > _upperLevel)
		{
			if (AreEqual(newRound, _upperLevel, priceStep))
				return false;

			_lowerLevel = newFloor;
			_upperLevel = newRound;
			return true;
		}

		return false;
	}

	private void CalculateBounds(decimal price, decimal priceStep, int stepCount, out decimal priceCeil, out decimal priceRound, out decimal priceFloor)
	{
		var normalizedStep = (decimal)stepCount;

		var ratio = price / priceStep / normalizedStep;
		var rounded = Math.Round(ratio, MidpointRounding.AwayFromZero);

		priceRound = (decimal)rounded * normalizedStep * priceStep;

		var ceilRatio = (priceRound + normalizedStep / 2m * priceStep) / priceStep / normalizedStep;
		var ceilCount = Math.Ceiling((double)ceilRatio);
		priceCeil = (decimal)ceilCount * normalizedStep * priceStep;

		var floorRatio = (priceRound - normalizedStep / 2m * priceStep) / priceStep / normalizedStep;
		var floorCount = Math.Floor((double)floorRatio);
		priceFloor = (decimal)floorCount * normalizedStep * priceStep;
	}

	private bool AreEqual(decimal left, decimal right, decimal priceStep)
	{
		var tolerance = priceStep / 2m;
		return Math.Abs(left - right) <= tolerance;
	}

	private void HandleLongSignal()
	{
		// Close the short side before flipping to long.
		if (Position < 0)
			ClosePosition();

		// Respect the increase toggle to avoid stacking positions unintentionally.
		if (!AllowIncrease && Position > 0)
			return;

		BuyMarket(OrderVolume);
	}

	private void HandleShortSignal()
	{
		// Close the long side before flipping to short.
		if (Position > 0)
			ClosePosition();

		// Respect the increase toggle for short accumulation.
		if (!AllowIncrease && Position < 0)
			return;

		SellMarket(OrderVolume);
	}
}