GitHub で見る

Dojiアロー戦略

コンセプト

Dojiアロー戦略は、オリジナルのMetaTrader「Doji Arrows」エキスパートアドバイザーをStockSharpの高レベルAPIに変換します。アイデアは本物のDojiローソク足を待ち、そのレンジのブレイクアウトを取引することです。Dojiローソク足は優柔不断を表しているため、Dojiの高値を上回るクローズは強気の勢いを示し、Dojiの安値を下回るクローズは弱気の支配を示します。

  1. 戦略は設定されたCandleTypeサブスクリプションから完成したローソク足のみを処理します。
  2. 前のローソク足を分析してDojiかどうかを判断します。始値と終値の絶対差がDojiBodyPointsに銘柄の価格ステップを乗じた値以下である場合にローソク足はDojiとして分類されます。パラメーターが0に設定されている場合、MQL5バージョンの厳格な等価性チェックと一致する1つの価格ステップが許容値として使用されます。
  3. 次のローソク足がDojiの高値を上回って終了すると、戦略は成行買い注文を送信します。次のローソク足がDojiの安値を下回って終了すると、成行売り注文が発行されます。既存の反対ポジションは成行注文のボリュームによって自動的にフラット化されます。

この順序は各新しいバーの始値で一度反応したオリジナルのエキスパートアドバイザーを反映しています。

リスク管理

変換された実装はMQLスクリプトの保護動作を維持します:

  • ストップロス: StopLossPointsは、エントリー価格からの初期ストップロスが価格ステップ単位でどれだけ離れて配置されるかを制御します。固定ストップを無効にするにはゼロに設定してください。
  • テイクプロフィット: TakeProfitPointsは価格ステップ単位で利益目標への距離を定義します。目標をスキップするにはゼロに設定してください。
  • トレーリングストップ: TrailingStopPointsTrailingStepPointsはトレーリングメカニズムを再現します。取引がTrailingStopPoints + TrailingStepPoints以上獲得すると、ストップレベルが最新のクローズ(ロングは最高クローズ、ショートは最低クローズ)からTrailingStopPointsに引き寄せられます。トレーリングはオプションで、TrailingStopPointsがゼロより大きい場合にのみ有効になります。

ストップと目標は各完成したローソク足で評価されます。いずれかのレベルが違反された場合(ローソク足の高値/安値を使用)、戦略は成行注文でポジションを終了し、保護状態をリセットします。

パラメータ

パラメータ デフォルト 説明
StopLossPoints 30 価格ステップ単位の初期ストップロス距離。
TakeProfitPoints 90 価格ステップ単位のテイクプロフィット距離。
TrailingStopPoints 15 価格ステップ単位のトレーリングストップが使用する距離。
TrailingStepPoints 5 トレーリングストップを調整する前に必要な追加利益(価格ステップ)。
DojiBodyPoints 1 Dojiとして扱うための価格ステップ単位の前のローソク足の最大許容ボディサイズ。0は許容値として1価格ステップを使用します。
CandleType 1時間 シグナル生成のためにサブスクライブされるローソク足タイプ。

実装ノート

  • 戦略はSubscribeCandles(CandleType).Bind(ProcessCandle)を通じてローソク足をサブスクライブし、メモリ内には最後に完成したローソク足のみを保持します。
  • 銘柄の価格ステップはSecurity?.PriceStepを通じて取得されます。利用できない場合、フォールバック値1が使用されるため、戦略は合成または履歴データでも動作できます。
  • 保護レベルは各エントリー後に再計算され、トレーリングロジックは固定ストップロスが無効の場合でもストップを作成できます(トレーリングストップがゼロから開始できるMQL動作に対応)。
  • すべてのアクションは即座の市場執行に依存したオリジナルのアドバイザーとの整合性を維持するために成行注文で実行されます。

使用のヒント

  1. 戦略を開始する前にSecurityPortfolioVolumeプロパティを設定してください。
  2. 取引される銘柄に応じてポイントベースのパラメーターを調整してください。分数pipsで引用されている銘柄の場合、ブローカーのティックサイズに合わせて値を増やしてください。
  3. より高度なポジションサイジングが必要な場合は、戦略をStockSharpのリスクコントロールまたは分析モジュールと組み合わせてください。変換はオリジナルコードの固定ボリュームロジックを維持しています。
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>
/// Doji breakout strategy with optional fixed and trailing protection.
/// </summary>
public class DojiArrowsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _trailingStepPoints;
	private readonly StrategyParam<decimal> _dojiBodyPoints;
	private readonly StrategyParam<DataType> _candleType;

	private bool _hasPreviousCandle;
	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	public decimal TrailingStepPoints
	{
		get => _trailingStepPoints.Value;
		set => _trailingStepPoints.Value = value;
	}

	public decimal DojiBodyPoints
	{
		get => _dojiBodyPoints.Value;
		set => _dojiBodyPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DojiArrowsStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
			.SetNotNegative()
			.SetDisplay("Stop Loss Points", "Stop loss distance in price steps.", "Risk")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 90m)
			.SetNotNegative()
			.SetDisplay("Take Profit Points", "Take profit distance in price steps.", "Risk")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 15m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop Points", "Trailing distance in price steps.", "Risk")
			;

		_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Trailing Step Points", "Minimum profit before the trailing stop moves.", "Risk")
			;

		_dojiBodyPoints = Param(nameof(DojiBodyPoints), 1m)
			.SetNotNegative()
			.SetDisplay("Doji Body Points", "Maximum difference between open and close to treat the candle as a doji.", "Pattern")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for signal generation.", "General");
	}

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

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

		_hasPreviousCandle = false;
		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		ResetProtection();
	}

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

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();

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

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

		ManageActivePosition(candle);

		if (!_hasPreviousCandle)
		{
			CachePreviousCandle(candle);
			return;
		}

		var step = Security?.PriceStep ?? 1m;
		var tolerance = DojiBodyPoints <= 0m ? step : DojiBodyPoints * step;
		var bodySize = Math.Abs(_prevOpen - _prevClose);
		var isDoji = bodySize <= tolerance;

		var breakoutUp = isDoji && candle.ClosePrice > _prevHigh;
		var breakoutDown = isDoji && candle.ClosePrice < _prevLow;

		if (breakoutUp && Position == 0)
		{
			BuyMarket();
		}
		else if (breakoutDown && Position == 0)
		{
			SellMarket();
		}

		CachePreviousCandle(candle);
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position == 0)
			return;

		var step = Security?.PriceStep ?? 1m;
		var trailingDistance = TrailingStopPoints > 0m ? TrailingStopPoints * step : 0m;
		var trailingStep = TrailingStepPoints > 0m ? TrailingStepPoints * step : 0m;

		if (Position > 0)
		{
			if (trailingDistance > 0m && _entryPrice.HasValue)
			{
				var gain = candle.ClosePrice - _entryPrice.Value;

				if (gain > trailingDistance + trailingStep)
				{
					var newStop = candle.ClosePrice - trailingDistance;

					if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}
		}
		else if (Position < 0)
		{
			if (trailingDistance > 0m && _entryPrice.HasValue)
			{
				var gain = _entryPrice.Value - candle.ClosePrice;

				if (gain > trailingDistance + trailingStep)
				{
					var newStop = candle.ClosePrice + trailingDistance;

					if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetProtection();
				return;
			}
		}
	}

	private void InitializeProtection(decimal price, bool isLong, decimal step)
	{
		_entryPrice = price;

		if (StopLossPoints > 0m)
		{
			var offset = StopLossPoints * step;
			_stopPrice = isLong ? price - offset : price + offset;
		}
		else
		{
			_stopPrice = null;
		}

		if (TakeProfitPoints > 0m)
		{
			var offset = TakeProfitPoints * step;
			_takePrice = isLong ? price + offset : price - offset;
		}
		else
		{
			_takePrice = null;
		}
	}

	private void ResetProtection()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}

	private void CachePreviousCandle(ICandleMessage candle)
	{
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPreviousCandle = true;
	}
}