GitHub で見る

ガンダルフ PRO プロジェクション戦略

概要

Gandalf PRO 戦略は、MetaTrader 4 エキスパート アドバイザー Gandalf_PRO の StockSharp 移植です。オリジナルのロボットが構築するのは、 加重移動平均と再帰トレンド コンポーネントからの適応平滑化フィルター。予想価格が次のように動くとき 現在の市場価格を少なくとも 15 ピップス上回ると、EA は遠くのストップロスとテイクプロフィットでその方向にエントリーします。 予想されるレベル。 StockSharp 変換は、高レベルのローソク足に依存しながら、同じフィルターと決定ロジックを再現します。 API したがって、すべての計算は完成したバーに対して実行されます。

取引ロジック

  1. CandleType で選択された時間枠 (デフォルト: 1 時間のローソク足) をサブスクライブし、完了したローソク足のみを処理します。
  2. CountBuyCountSell の最大値と 1 本の追加バーをカバーするのに十分な大きさの終値のローリング履歴を維持します。
  3. MetaTrader Out() 関数を再作成します。(1 バー シフトを使用して) 線形重み付き単純移動平均を計算し、 再帰的な s コンポーネントと t コンポーネントを構成された価格とトレンド要因で実行し、予測価格 s[1] + t[1] を取得します。
  4. 長いセットアップの場合 (EnableBuy):
    • 予測価格が最新終値(MT4 では Bid + 15*x*Point)より少なくとも 15 ピップス上であることを確認します。
    • オープンなロングポジションがない場合は、設定された数量を購入します (BaseVolume および BuyRiskMultiplier を参照)。
    • 予想価格をテイクプロフィットとして保存し、価格ステップに変換された BuyStopLossPips を減算してストップロスを計算します。
  5. 短いセットアップの場合 (EnableSell):
    • 予想価格が最後の終値より少なくとも 15 ピップス下に収まるようにする必要があります。
    • オープンなショート ポジションがない場合は、設定された数量を売ります (必要に応じて既存のロングを反転します)。
    • 予想価格を利食いとして保存し、ストップロスを市場より SellStopLossPips ピップス上に設定します。
  6. ポジションが存在する間、完成したすべてのローソク足を監視します。
    • ローソク足の安値が保存されたストップを横切るか、高値がテイクプロフィットに達した場合、エグジットロングとなります。
    • ローソク足の高値がストップを通過するか、安値がターゲットに達した場合はショートを終了します。
    • イグジットは ClosePosition() を使用し、StockSharp の純エクスポージャーを平坦化します。

パラメーター

名前 種類 デフォルト 説明
EnableBuy bool true 戦略がロングポジションをオープンできるようにします。
CountBuy int 24 長い投影に使用される平滑化フィルターの長さ。
BuyPriceFactor decimal 0.18 長い再帰フィルターにおける現在の終値の重み。
BuyTrendFactor decimal 0.18 長期予測を作成するときにトレンド項に適用される重み。
BuyStopLossPips int 62 ロングポジションのストップロス距離 (pips 単位で測定)。
BuyRiskMultiplier decimal 0 ロング注文を送信する前に BaseVolume に適用される乗数 (0 は基本数量を維持します)。
EnableSell bool true 戦略がショートポジションをオープンできるようにします。
CountSell int 24 短い投影に使用される平滑化フィルターの長さ。
SellPriceFactor decimal 0.18 短期再帰フィルターにおける現在のクローズの重み。
SellTrendFactor decimal 0.18 短期予測を作成するときにトレンド項に適用される重み。
SellStopLossPips int 62 ショートポジションのストップロス距離 (ピップ単位で測定)。
SellRiskMultiplier decimal 0 ショートオーダーを送信する前に BaseVolume に適用される乗数 (0 は基本数量を維持します)。
BaseVolume decimal 1 両方のリスク乗数がゼロの場合に使用される基本注文サイズ。
CandleType DataType 1時間枠 戦略的に加工されたキャンドルシリーズ。

オリジナルの MetaTrader EA との違い

  • MetaTrader は、独立したチケットの購入と販売を同時に保持できます。 StockSharp はネット ポジションを使用しているため、ポートが閉じるか、 反対側をオープンする前に、既存のポジションを反転します。
  • MT4のロット機能は口座無料証拠金を使用しました。この変換により、BaseVolume と 2 つのリスク乗数が明らかになります。それらがゼロのとき 基本ボリュームはそのまま使用されます。それ以外の場合、ボリュームは単純にスケーリングされます (BaseVolume * RiskMultiplier)。
  • ストップロスとテイクプロフィットのレベルは、完了したローソク足を監視することによって実行されます。したがって、バー内の塗りつぶしは MetaTrader とは異なる場合があります 秘密保持命令はブローカーによって管理されます。
  • 5 桁の Digits/Point 調整は、Security.DecimalsSecurity.PriceStep を検査して pip を変換することでエミュレートされます。 距離を絶対価格に変換します。
  • すべてのインジケーターの計算は、iMA を呼び出すことなくマネージ コードで実行されます。再帰フィルタは次のように再作成されます。 CalculateTarget は、MQL 関数と同じ係数を使用します。

使用上の注意

  • 開始する前に、目的の楽器を Strategy.Security に割り当ててください。セキュリティが付加されていない場合、この戦略は例外をスローします。
  • 会場で予想される契約規模に合わせて BaseVolume を構成します。スケールしたい場合にのみリスク乗数を調整してください ベースボリュームに対する露出。
  • 取引を生成するには、ローソク足履歴に少なくとも max(CountBuy, CountSell) + 1 バーが含まれている必要があります。十分な量を提供する データをウォームアップするか、過去のローソク足をロードして戦略を開始します。
  • 15 ピップのエントリーバッファーは固定されています (EA と同様)。 CountBuy/CountSell を増やして投影を滑らかにするか、調整します。 MetaTrader で観察された動きと一致する価格/トレンド要因。
  • 終了はローソク足の極値に依存するため、実行レイテンシーに合った時間枠を有効にしてください。タイムフレームが短いほど、より早く反応します ただし、より多くの履歴データが必要であり、より多くのシグナルが生成される可能性があります。

実装の詳細

  • SubscribeCandles()Bind(ProcessCandle) とともに使用するため、すべての決定は最終的なローソク足に基づいて行われます。
  • 最近の取引のコンパクトなリストを保持し、オンデマンドで再帰的な s/t フィルタを再構築し、Out() ルーチンを模倣します。
  • MetaTrader x * Point スケーリングを複製するために、商品のティック サイズと小数精度を介して pip ベースのオフセットを変換します。
  • ClosePosition() は保護レベルに違反したときに呼び出され、別のエントリーが行われる前にネットポジションがフラット化されるようにします。 検討した。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Gandalf PRO trend-following strategy using adaptive smoothing filter.
/// Opens trades when projected price exceeds a buffer threshold.
/// </summary>
public class GandalfProProjectionStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _filterLength;
	private readonly StrategyParam<decimal> _priceFactor;
	private readonly StrategyParam<decimal> _trendFactor;
	private readonly StrategyParam<int> _atrLength;

	private readonly List<decimal> _closeBuffer = new();
	private decimal _entryPrice;

	public GandalfProProjectionStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_filterLength = Param(nameof(FilterLength), 24)
			.SetDisplay("Filter Length", "Smoothing filter length.", "Filter");

		_priceFactor = Param(nameof(PriceFactor), 0.18m)
			.SetDisplay("Price Factor", "Close price weight in filter.", "Filter");

		_trendFactor = Param(nameof(TrendFactor), 0.18m)
			.SetDisplay("Trend Factor", "Trend term weight in filter.", "Filter");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for entry buffer.", "Indicators");
	}

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

	public int FilterLength
	{
		get => _filterLength.Value;
		set => _filterLength.Value = value;
	}

	public decimal PriceFactor
	{
		get => _priceFactor.Value;
		set => _priceFactor.Value = value;
	}

	public decimal TrendFactor
	{
		get => _trendFactor.Value;
		set => _trendFactor.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_closeBuffer.Clear();
		_entryPrice = 0;
	}

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

		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

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

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

		_closeBuffer.Add(candle.ClosePrice);
		var maxDepth = FilterLength + 2;
		while (_closeBuffer.Count > maxDepth)
			_closeBuffer.RemoveAt(0);

		if (_closeBuffer.Count <= FilterLength || atrVal <= 0)
			return;

		var close = candle.ClosePrice;
		var target = CalculateTarget();
		if (target == null)
			return;

		var targetPrice = target.Value;
		var buffer = atrVal * 0.3m;

		// Manage position
		if (Position > 0)
		{
			// Exit if projection flips below close or on stop
			if (targetPrice < close - buffer)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (targetPrice > close + buffer)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry
		if (Position == 0)
		{
			if (targetPrice > close + buffer)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (targetPrice < close - buffer)
			{
				_entryPrice = close;
				SellMarket();
			}
		}
	}

	private decimal? CalculateTarget()
	{
		var n = FilterLength;
		if (n < 2 || _closeBuffer.Count < n + 1)
			return null;

		var sum = 0m;
		for (var i = 1; i <= n; i++)
			sum += GetClose(i);

		var sm = sum / n;

		var weightedSum = 0m;
		for (var i = 0; i < n; i++)
		{
			var price = GetClose(i + 1);
			var weight = n - i;
			weightedSum += price * weight;
		}

		var denominator = (decimal)n * (n + 1) / 2m;
		if (denominator <= 0m)
			return null;

		var lm = weightedSum / denominator;
		var divisor = n - 1;
		if (divisor <= 0)
			return null;

		var s = new decimal[n + 2];
		var t = new decimal[n + 2];

		var tn = (6m * lm - 6m * sm) / divisor;
		var sn = 4m * sm - 3m * lm - tn;
		s[n] = sn;
		t[n] = tn;

		for (var k = n - 1; k > 0; k--)
		{
			var close = GetClose(k);
			s[k] = PriceFactor * close + (1m - PriceFactor) * (s[k + 1] + t[k + 1]);
			t[k] = TrendFactor * (s[k] - s[k + 1]) + (1m - TrendFactor) * t[k + 1];
		}

		return s[1] + t[1];
	}

	private decimal GetClose(int index)
	{
		var idx = _closeBuffer.Count - 1 - index;
		if (idx < 0) idx = 0;
		if (idx >= _closeBuffer.Count) idx = _closeBuffer.Count - 1;
		return _closeBuffer[idx];
	}
}