GitHub で見る

ラッキーコード戦略

Lucky Code は、元の MetaTrader「Lucky_code」エキスパート アドバイザーから転換された短期ブレイクアウト スキャルパーです。この戦略は、スプレッドの極端な値を監視し、最良の売り値が前の見積りを上回ったり、最良の買い値が設定可能な距離だけ前の見積りを下回ったりした場合に反応します。すべての取引は積極的に終了します。価格が有利に推移するとすぐに利益が得られますが、逆方向の変動が保護制限を突破した場合は損失がカットされます。

データと実行

  • 市場データ: 最新の最良買値と売値を読み取るには、レベル 1 の相場の安定したストリームが必要です。
  • 注文タイプ: すべてのエントリーとエグジットに成行注文を使用して、MQL バージョンのティックベースの約定を反映します。
  • ポジション モード: ネッティング口座とヘッジ口座の両方をサポートします。複数の約定が 1 つのネット ポジションに蓄積され、ブロックとして管理されます。

パラメーター

  • シフトポイント – 新しいエントリーのロックを解除する、連続する引用符間の最小ポイント数 (ピップ)。値を大きくすると、取引頻度とノイズ感度が低下します。
  • リミットポイント – ポジションが強制的にクローズされるまでに許容される最大逆距離。値は、商品ティックサイズの価格単位に変換されます。

取引ロジック

  1. 初期化
    • セキュリティティックサイズを使用して、ポイントベースのパラメーターを実際の価格オフセットに変換します。
    • レベル 1 データをサブスクライブし、最後に確認された買値と売値の内部バッファをリセットします。
  2. エントリールール
    • 最良の売値が前の売値よりも少なくとも構成されたシフト分だけ前進すると、戦略はショートポジションをオープンします(上昇スパイクの後に販売する元の EA の動作と一致します)。
    • 最良の入札が前の入札と少なくとも同じシフトだけ下がった場合、この戦略はリバウンドを捉えるためにロングポジションをオープンします。
  3. ボリュームサイジング
    • 戦略 Volume プロパティから開始します。
    • ポートフォリオ値が利用可能な場合、サイズは MetaTrader のマージンベースのサイジングをエミュレートして、round(Equity / 10,000, 1) ロットに増加します。
  4. 退出ルール
    • 買い値が平均エントリー価格を超えるか、売り値が設定された損失制限まで下がると、ロングエクスポージャーはただちにクローズされます。
    • アスクがエントリー価格を下回るか、ビッドが損失限度額を超えて上昇すると、ショートエクスポージャーはクローズされます。

実装メモ

  • この戦略はすべての見積もりの更新に反応するため、実稼働環境ではノイズの多いフィードを抑制するか、シフト パラメーターを増やすことを検討してください。
  • 成行注文は取引の開始と終了の両方に使用されるため、相場の急騰時のスリッページの急増を避けるために十分な流動性を確保してください。
  • 戦略をライブで実行する場合は、追加のポートフォリオ レベルのリスク管理 (毎日のストップ、最大ドローダウンなど) を推奨します。
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Momentum strategy that opens trades when candle price jumps reach a configurable distance and manages exits with profit and drawdown filters.
/// </summary>
public class LuckyCodeStrategy : Strategy
{
	private readonly StrategyParam<int> _shiftPoints;
	private readonly StrategyParam<int> _limitPoints;

	private decimal? _previousClose;
	private decimal _shiftThreshold;
	private decimal _limitThreshold;
	private decimal _entryPrice;
	private bool _thresholdsReady;
	private int _holdBars;

	/// <summary>
	/// Minimum price movement in points required before opening a new trade.
	/// </summary>
	public int ShiftPoints
	{
		get => _shiftPoints.Value;
		set => _shiftPoints.Value = value;
	}

	/// <summary>
	/// Maximum adverse excursion in points tolerated before forcing an exit.
	/// </summary>
	public int LimitPoints
	{
		get => _limitPoints.Value;
		set => _limitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public LuckyCodeStrategy()
	{
		_shiftPoints = Param(nameof(ShiftPoints), 3)
			.SetGreaterThanZero()
			.SetDisplay("Shift points", "Minimum price jump required to trigger entries", "Trading")

			.SetOptimize(1, 20, 1);

		_limitPoints = Param(nameof(LimitPoints), 18)
			.SetGreaterThanZero()
			.SetDisplay("Limit points", "Maximum number of points allowed against the position", "Risk management")

			.SetOptimize(5, 100, 5);
	}

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

		_previousClose = null;
		_shiftThreshold = 0m;
		_limitThreshold = 0m;
		_entryPrice = 0m;
		_thresholdsReady = false;
		_holdBars = 0;
	}

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

		// Subscribe to candles and process each finished candle.
		var tf = TimeSpan.FromMinutes(5).TimeFrame();

		SubscribeCandles(tf)
			.Bind(ProcessCandle)
			.Start();
	}

	private void EnsureThresholds(decimal price)
	{
		if (_thresholdsReady)
			return;

		if (price <= 0m)
			return;

		// Use percentage of price. ShiftPoints=3 means 3% shift, LimitPoints=18 means 18% limit.
		_shiftThreshold = price * ShiftPoints * 0.01m;
		_limitThreshold = price * LimitPoints * 0.01m;
		_thresholdsReady = true;
	}

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

		var close = candle.ClosePrice;

		EnsureThresholds(close);

		if (!_thresholdsReady)
			return;

		// Count hold bars for position management.
		if (Position != 0)
			_holdBars++;

		if (_previousClose is decimal prevClose)
		{
			var delta = close - prevClose;

			// Only enter if flat.
			if (Position == 0)
			{
				// Price dropped sharply -> buy on expected rebound.
				if (-delta >= _shiftThreshold)
				{
					BuyMarket();
					_entryPrice = close;
					_holdBars = 0;
					LogInfo($"Buy triggered by fast price drop. Price={close:0.#####}");
				}
				// Price rose sharply -> sell on expected reversal.
				else if (delta >= _shiftThreshold)
				{
					SellMarket();
					_entryPrice = close;
					_holdBars = 0;
					LogInfo($"Sell triggered by fast price rise. Price={close:0.#####}");
				}
			}
		}

		_previousClose = close;

		TryClosePosition(close);
	}

	private void TryClosePosition(decimal currentPrice)
	{
		if (Position == 0)
			return;

		var avgPrice = _entryPrice;

		if (avgPrice <= 0m)
			return;

		// Minimum hold of 3 bars before checking exit.
		if (_holdBars < 3)
			return;

		// Use half of shift threshold as profit target.
		var profitTarget = _shiftThreshold * 0.5m;

		if (Position > 0)
		{
			// Close long on profit target or drawdown limit.
			if (currentPrice - avgPrice >= profitTarget)
			{
				SellMarket();
				_holdBars = 0;
				LogInfo($"Closed long on profit. Price={currentPrice:0.#####}");
			}
			else if (_limitThreshold > 0m && avgPrice - currentPrice >= _limitThreshold)
			{
				SellMarket();
				_holdBars = 0;
				LogInfo($"Closed long on drawdown limit. Price={currentPrice:0.#####}");
			}
		}
		else if (Position < 0)
		{
			// Close short on profit target or drawdown limit.
			if (avgPrice - currentPrice >= profitTarget)
			{
				BuyMarket();
				_holdBars = 0;
				LogInfo($"Closed short on profit. Price={currentPrice:0.#####}");
			}
			else if (_limitThreshold > 0m && currentPrice - avgPrice >= _limitThreshold)
			{
				BuyMarket();
				_holdBars = 0;
				LogInfo($"Closed short on drawdown limit. Price={currentPrice:0.#####}");
			}
		}
	}
}