GitHub で見る

StellarLite ICT EA 戦略

概要

StellarLite ICT EA は、「Stellar Lite」prop-firm ハンドブックを StockSharp に変換する裁量スタイルのアルゴリズムです。この戦略は、2 つのインナー サークル トレーダー (ICT) エントリー モデル、Silver Bullet と 2022 年モデルを統合し、元の MetaTrader エキスパート アドバイザーで使用されていた部分的なテイクプロフィット プランを自動化します。ローソク足、価格ステップ、出来高ステップの情報を提供するあらゆる商品で機能します。

コアワークフロー

  1. より高い時間枠からの方向性バイアス – 選択したより高い時間枠の移動平均は取引方向に傾いており、価格は平均を超えて終了する必要があります。バイアスが確認された後にのみ、より低いタイムフレームのロジックが評価されます。
  2. 流動性スイープの確認 – この戦略は、設定可能なルックバック ウィンドウを監視し、最近の高値または安値のブレイクを探します。シルバーブレットでは取引方向へのスイープが必要ですが、2022 年モデルでは反対方向への誘導スイープが必要です。
  3. 市場構造シフト (MSS) – 終了した最後の 3 つのローソク足はシフトを確認する必要があります。ロング取引の場合は前の高値を上回る終値、またはショート取引の場合は前の安値を下回る低い終値です。
  4. 公正価値ギャップ (FVG) 検出 – この戦略は、直近の 10 本のローソク足をスキャンして、ディスプレイスメント ローソク足によって生じた強気または弱気の不均衡を検出します。エントリーは、現在の終値が検出されたギャップ内にある場合にのみ許可されます。
  5. NDOG / NWOG フィルター – 現在のローソク足は狭い範囲のバーである必要があります。その高低範囲は、AtrThresholdAverageTrueRange の値を乗算した値を超えてはなりません。
  6. エントリー、ストップ、ターゲット – エントリー価格は、ギャップの中央、または Fibonacci 比率パラメーターで定義された OTE (最適取引エントリー) リトレースメントのいずれかに配置されます。プロテクティブストップは最近のスイング流動性を超えた位置にあり、設定されたリスクリワード比率を使用して 3 つのテイクプロフィットレベルが予測されます。
  7. 取引管理 – ポジションは、選択したリスク割合に従ってサイズ設定されるか、戦略ボリュームにフォールバックします。 TP1、TP2、および TP3 がヒットすると、この戦略はデフォルトでポジションの 50%、25%、および 25% をクローズし、TP1 の後にストップを損益分岐点に移動し (オプションのオフセットあり)、TP2 の後にトレーリングストップをアクティブにし、TP3 またはストップのヒット時に残りを清算します。

パラメーター

  • エントリーローソク足 (CandleType) – エントリーシグナルに使用される下限時間枠のローソク足。
  • 上位タイムフレーム (HigherTimeframeType) – バイアス移動平均をフィードするローソク足。
  • 上位 MA 期間 (HigherMaPeriod) – バイアス検出の移動平均の長さ。
  • ATR 期間 (AtrPeriod) – ATR 統合フィルタのルックバック。
  • 流動性ルックバック (LiquidityLookback) – 流動性プールを見つけるために検査されたキャンドルの数。
  • ATR しきい値 (AtrThreshold) – ATR の分数として許可されるローソク足の最大範囲。
  • TP1/TP2/TP3 リスクリワード (Tp1RatioTp2RatioTp3Ratio) – ターゲットのリスクリワード乗数。
  • TP1/TP2/TP3 成約率 (Tp1PercentTp2PercentTp3Percent) – 部分的な成約率。
  • TP1 後の損益分岐点 (MoveToBreakEven) – 損益分岐点調整を切り替えます。
  • 損益分岐点オフセット (BreakEvenOffset) – ストップを移動するときに追加または減算される価格ステップの数。
  • トレーリング距離 (TrailingDistance) – TP2 後に有効化されたトレーリング ストップ距離 (価格ステップ単位)。
  • Silver Bullet を使用 / 2022 モデルを使用 (UseSilverBulletUse2022Model) – 各セットアップを有効または無効にします。
  • OTE エントリー (UseOteEntry) を使用 – 最適な貿易エントリー ゾーン内のエントリーを計算します。
  • リスク % (RiskPercent) – ポジション サイズを導き出すために取引ごとにリスクを負う株式の割合。
  • OTE 下限 (OteLowerLevel) – OTE レベルの Fibonacci 係数。

実用上の注意

  • この戦略には完成したキャンドルが必要です。データ フィードが終値と数量ステップを提供していることを確認します。
  • ポートフォリオ値またはティック値情報が利用できない場合、ポジションのサイジングは戦略 Volume パラメーターに戻ります。
  • 流動性検出と MSS ロジックは、最新の履歴キャッシュ (デフォルトでは 20 キャンドル) に依存します。戦略がシグナルを予期する前に十分なデータを収集できるようにします。
  • 部分的な終了では、楽器の音量ステップが考慮されます。要求された端数が最小取引可能量より小さい場合、クローズはスキップされます。
  • トレーリング ロジックは利益方向にのみストップを更新し続け、既存のリスク管理を緩めることはありません。

ファイル

  • CS/StellarLiteIctEaStrategy.cs – StockSharp 戦略の実装。
  • README.md – 英語のドキュメント。
  • README_zh.md – 簡体字中国語のドキュメント。
  • README_ru.md – ロシア語のドキュメント。

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>
/// Stellar Lite ICT strategy that combines Silver Bullet and 2022 model setups.
/// The strategy reads ICT style order flow concepts on finished candles
/// and places partial take profits with adaptive stop management.
/// </summary>
public class StellarLiteIctEaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DataType> _higherTimeframeType;
	private readonly StrategyParam<int> _higherMaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _liquidityLookback;
	private readonly StrategyParam<decimal> _atrThreshold;
	private readonly StrategyParam<decimal> _tp1Ratio;
	private readonly StrategyParam<decimal> _tp2Ratio;
	private readonly StrategyParam<decimal> _tp3Ratio;
	private readonly StrategyParam<decimal> _tp1Percent;
	private readonly StrategyParam<decimal> _tp2Percent;
	private readonly StrategyParam<decimal> _tp3Percent;
	private readonly StrategyParam<bool> _moveToBreakEven;
	private readonly StrategyParam<decimal> _breakEvenOffset;
	private readonly StrategyParam<decimal> _trailingDistance;
	private readonly StrategyParam<bool> _useSilverBullet;
	private readonly StrategyParam<bool> _use2022Model;
	private readonly StrategyParam<bool> _useOteEntry;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<decimal> _oteLowerLevel;

	private SimpleMovingAverage _higherMa;
	private AverageTrueRange _atr;

	private decimal? _lastHtfMa;
	private decimal? _previousHtfMa;
	private Sides? _currentBias;

	private readonly ICandleMessage[] _history = new ICandleMessage[20];
	private int _historyCount;
	private decimal _latestAtr;

	/// <summary>
	/// Initializes a new instance of the <see cref="StellarLiteIctEaStrategy"/>.
	/// </summary>
	public StellarLiteIctEaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Entry Candle", "Primary timeframe used for entries", "General");

		_higherTimeframeType = Param(nameof(HigherTimeframeType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Higher Timeframe", "Timeframe used for directional bias", "General");

		_higherMaPeriod = Param(nameof(HigherMaPeriod), 20)
			.SetDisplay("Higher MA Period", "Moving average length for higher timeframe bias", "Bias")
			;

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "Average True Range lookback", "Volatility")
			;

		_liquidityLookback = Param(nameof(LiquidityLookback), 20)
			.SetDisplay("Liquidity Lookback", "Number of candles to detect liquidity pools", "Structure")
			;

		_atrThreshold = Param(nameof(AtrThreshold), 2.0m)
			.SetDisplay("ATR Threshold", "Maximum candle range relative to ATR", "Structure")
			;

		_tp1Ratio = Param(nameof(Tp1Ratio), 1m)
			.SetDisplay("TP1 Risk Reward", "Risk reward multiplier for the first target", "Targets")
			;

		_tp2Ratio = Param(nameof(Tp2Ratio), 2m)
			.SetDisplay("TP2 Risk Reward", "Risk reward multiplier for the second target", "Targets")
			;

		_tp3Ratio = Param(nameof(Tp3Ratio), 3m)
			.SetDisplay("TP3 Risk Reward", "Risk reward multiplier for the final target", "Targets")
			;

		_tp1Percent = Param(nameof(Tp1Percent), 50m)
			.SetDisplay("TP1 Close %", "Percentage of volume closed at the first target", "Targets")
			;

		_tp2Percent = Param(nameof(Tp2Percent), 25m)
			.SetDisplay("TP2 Close %", "Percentage of volume closed at the second target", "Targets")
			;

		_tp3Percent = Param(nameof(Tp3Percent), 25m)
			.SetDisplay("TP3 Close %", "Percentage of volume closed at the final target", "Targets")
			;

		_moveToBreakEven = Param(nameof(MoveToBreakEven), true)
			.SetDisplay("Break Even After TP1", "Move the stop to break even after the first partial", "Protection");

		_breakEvenOffset = Param(nameof(BreakEvenOffset), 1m)
			.SetDisplay("Break Even Offset", "Additional price steps added to the break even stop", "Protection")
			;

		_trailingDistance = Param(nameof(TrailingDistance), 10m)
			.SetDisplay("Trailing Distance", "Price steps used after TP2 for trailing stop", "Protection")
			;

		_useSilverBullet = Param(nameof(UseSilverBullet), true)
			.SetDisplay("Use Silver Bullet", "Enable the Silver Bullet setup", "Structure");

		_use2022Model = Param(nameof(Use2022Model), true)
			.SetDisplay("Use 2022 Model", "Enable the 2022 model setup", "Structure");

		_useOteEntry = Param(nameof(UseOteEntry), true)
			.SetDisplay("Use OTE Entry", "Place entries inside the optimal trade entry zone", "Structure");

		_riskPercent = Param(nameof(RiskPercent), 0.25m)
			.SetDisplay("Risk %", "Risk percentage of account equity used to size trades", "Risk")
			;

		_oteLowerLevel = Param(nameof(OteLowerLevel), 0.618m)
			.SetDisplay("OTE Lower", "Lower Fibonacci level used for the entry", "Structure")
			;
	}

	/// <summary>
	/// Primary candle type used to generate entries.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Higher timeframe candle type that provides directional bias.
	/// </summary>
	public DataType HigherTimeframeType
	{
		get => _higherTimeframeType.Value;
		set => _higherTimeframeType.Value = value;
	}

	/// <summary>
	/// Higher timeframe moving average period.
	/// </summary>
	public int HigherMaPeriod
	{
		get => _higherMaPeriod.Value;
		set => _higherMaPeriod.Value = value;
	}

	/// <summary>
	/// ATR calculation period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Number of candles used to search for liquidity pools.
	/// </summary>
	public int LiquidityLookback
	{
		get => _liquidityLookback.Value;
		set => _liquidityLookback.Value = value;
	}

	/// <summary>
	/// Maximum allowed candle range relative to ATR to confirm consolidation.
	/// </summary>
	public decimal AtrThreshold
	{
		get => _atrThreshold.Value;
		set => _atrThreshold.Value = value;
	}

	/// <summary>
	/// Risk reward multiplier for the first target.
	/// </summary>
	public decimal Tp1Ratio
	{
		get => _tp1Ratio.Value;
		set => _tp1Ratio.Value = value;
	}

	/// <summary>
	/// Risk reward multiplier for the second target.
	/// </summary>
	public decimal Tp2Ratio
	{
		get => _tp2Ratio.Value;
		set => _tp2Ratio.Value = value;
	}

	/// <summary>
	/// Risk reward multiplier for the third target.
	/// </summary>
	public decimal Tp3Ratio
	{
		get => _tp3Ratio.Value;
		set => _tp3Ratio.Value = value;
	}

	/// <summary>
	/// Percentage of the position closed at TP1.
	/// </summary>
	public decimal Tp1Percent
	{
		get => _tp1Percent.Value;
		set => _tp1Percent.Value = value;
	}

	/// <summary>
	/// Percentage of the position closed at TP2.
	/// </summary>
	public decimal Tp2Percent
	{
		get => _tp2Percent.Value;
		set => _tp2Percent.Value = value;
	}

	/// <summary>
	/// Percentage of the position closed at TP3.
	/// </summary>
	public decimal Tp3Percent
	{
		get => _tp3Percent.Value;
		set => _tp3Percent.Value = value;
	}

	/// <summary>
	/// Enables moving the stop to break even after TP1.
	/// </summary>
	public bool MoveToBreakEven
	{
		get => _moveToBreakEven.Value;
		set => _moveToBreakEven.Value = value;
	}

	/// <summary>
	/// Additional price steps added to the break even stop.
	/// </summary>
	public decimal BreakEvenOffset
	{
		get => _breakEvenOffset.Value;
		set => _breakEvenOffset.Value = value;
	}

	/// <summary>
	/// Distance in price steps for the trailing stop activated after TP2.
	/// </summary>
	public decimal TrailingDistance
	{
		get => _trailingDistance.Value;
		set => _trailingDistance.Value = value;
	}

	/// <summary>
	/// Enables the Silver Bullet setup.
	/// </summary>
	public bool UseSilverBullet
	{
		get => _useSilverBullet.Value;
		set => _useSilverBullet.Value = value;
	}

	/// <summary>
	/// Enables the 2022 model setup.
	/// </summary>
	public bool Use2022Model
	{
		get => _use2022Model.Value;
		set => _use2022Model.Value = value;
	}

	/// <summary>
	/// Enables the optimal trade entry calculation.
	/// </summary>
	public bool UseOteEntry
	{
		get => _useOteEntry.Value;
		set => _useOteEntry.Value = value;
	}

	/// <summary>
	/// Risk percentage used for dynamic position sizing.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Lower bound of the OTE retracement window.
	/// </summary>
	public decimal OteLowerLevel
	{
		get => _oteLowerLevel.Value;
		set => _oteLowerLevel.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	=> [(Security, CandleType), (Security, HigherTimeframeType)];

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

		_higherMa = null;
		_atr = null;

		_lastHtfMa = null;
		_previousHtfMa = null;
		_currentBias = null;

		Array.Clear(_history, 0, _history.Length);
		_historyCount = 0;
		_latestAtr = 0m;
	}

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

		_higherMa = new SimpleMovingAverage { Length = HigherMaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		Indicators.Add(_atr);

		var mainSubscription = SubscribeCandles(CandleType);
		mainSubscription
			.Bind(ProcessMainCandle)
			.Start();

		var higherSubscription = SubscribeCandles(HigherTimeframeType);
		higherSubscription
			.Bind(_higherMa, ProcessHigherCandle)
			.Start();

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, mainSubscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessHigherCandle(ICandleMessage candle, decimal maValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_previousHtfMa = _lastHtfMa;
		_lastHtfMa = maValue;

		if (_previousHtfMa is not decimal prev || _lastHtfMa is not decimal current)
			return;

		if (candle.ClosePrice > current && current > prev)
		{
			_currentBias = Sides.Buy;
		}
		else if (candle.ClosePrice < current && current < prev)
		{
			_currentBias = Sides.Sell;
		}
		else
		{
			_currentBias = null;
		}
	}

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

		var atrValue = _atr.Process(candle);

		StoreCandle(candle);

		if (!_atr.IsFormed)
			return;

		_latestAtr = atrValue.ToDecimal();

		if (Position != 0)
			return;

		if (_currentBias is not Sides bias)
			return;

		if (_historyCount < 3)
			return;

		// Simplified ICT entry: market structure shift + bias alignment
		var prev = _history[1];
		var prev2 = _history[2];
		if (prev == null || prev2 == null)
			return;

		if (bias == Sides.Buy)
		{
			// Bullish MSS: current close breaks above previous high after a down move
			if (candle.ClosePrice > prev.HighPrice && prev.ClosePrice < prev2.OpenPrice)
				BuyMarket();
		}
		else
		{
			// Bearish MSS: current close breaks below previous low after an up move
			if (candle.ClosePrice < prev.LowPrice && prev.ClosePrice > prev2.OpenPrice)
				SellMarket();
		}
	}

	private void StoreCandle(ICandleMessage candle)
	{
		for (var i = _history.Length - 1; i > 0; i--)
		{
			_history[i] = _history[i - 1];
		}

		_history[0] = candle;
		if (_historyCount < _history.Length)
			_historyCount++;
	}
}