GitHub で見る

Amstell SL 平均化戦略

MetaTrader エキスパート アドバイザー exp_Amstell-SL の変換。このシステムはロングとショートの両方を即座にオープンし、価格が最新のエントリーに対して固定ポイント数だけ変動するたびに新しい注文を追加し、仮想(ソフトウェア管理)のテイクプロフィットとストップロスのレベルに依存して各チケットを個別に決済します。

戦略ロジック

  • 初期エントリー: 戦略が開始され、オープンな取引がない場合、1 つの市場買い (売り値) と 1 つの市場売り (売り値) が送信されます。
  • ドローダウン時のピラミッド:
    • ロングサイド: 現在のアスクが最後のロングエントリー価格より ReentryPoints (デフォルトは 10 ポイント) を下回るたびに、同じ量の新しい買い注文が送信されます。
    • 短い説明: 現在の入札価格が最後の空売り価格を ReentryPoints 上回るたびに、同じ数量の新しい売り注文がオープンされます。
  • 終了ルール (仮想管理):
    • 戦略は、ロングチケットごとに、最良の買い値と最良の売り値を監視します。入札額が注文価格から TakeProfitPoints 上昇するか、売値が StopLossPoints 低下した場合、ポジションは成行でクローズされます。
    • ショートチケットごとに、売値が TakeProfitPoints 低いか、入札額が StopLossPoints 高いかどうかをチェックします。いずれの場合も、売り注文は市場でカバーされます。
  • 処理順序: 新しいエントリーの前に出口が評価され、現在のティックでポジションを閉じた後にさらなるアクションを停止する MetaTrader スクリプトが複製されます。

パラメーター

  • TakeProfitPoints – 収益性の高いポジションを閉じるために使用される距離 (価格ステップ)。デフォルト: 30
  • StopLossPoints – 保護出口までの距離(価格ステップ)。デフォルト: 30
  • Volume – 新しくオープンされた各注文のロットサイズ。デフォルト: 0.01
  • ReentryPoints – 対応するサイドで追加の注文をスタックするために必要な逆の動き(価格ステップで)。デフォルト: 10

追加メモ

  • ポイント値は Security.PriceStep から導出されます。取引所によって提供されない場合は、1 の値が使用されます。
  • この戦略は、チケットの売買を独立して追跡し、元のエキスパートアドバイザーのヘッジスタイルの動作と一致するため、長短を同時に行うことができます。
  • テイクプロフィットとストップロスのレベルは、市場注文によって仮想的に実行されます。為替注文簿には記載されません。
  • 価格が一方向に強くトレンドになると、以前のエクスポージャーを減らさずに追加の注文がオープンされるため、リスクが急速に増加します。
  • 「ポイント」の概念が最小の価格増分に等しいシンボル、たとえば、MetaTrader スタイルの価格設定の主要外国為替ペアに最適です。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Amstell SL: Grid averaging strategy with ATR-based take profit and stop loss.
/// Adds positions on adverse moves and exits on profit/stop targets.
/// </summary>
public class AmstellSlStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _emaLength;

	private decimal _entryPrice;
	private decimal _prevEma;
	private int _gridCount;
	private int _cooldown;

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

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

		_emaLength = Param(nameof(EmaLength), 50)
			.SetDisplay("EMA Length", "EMA trend filter.", "Indicators");
	}

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

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_gridCount = 0;
		_cooldown = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_prevEma = 0;
		_gridCount = 0;
		_cooldown = 0;

		var atr = new AverageTrueRange { Length = AtrLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };

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

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

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

		if (atrVal <= 0 || _prevEma == 0)
		{
			_prevEma = emaVal;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevEma = emaVal;
			if (Position == 0) return;
		}

		var close = candle.ClosePrice;

		// Position management with grid and stops
		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2.5m)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (close <= _entryPrice - atrVal * 4m)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (_gridCount < 1 && close <= _entryPrice - atrVal * 2m)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				BuyMarket();
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2.5m)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (close >= _entryPrice + atrVal * 4m)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
				_cooldown = 10;
			}
			else if (_gridCount < 1 && close >= _entryPrice + atrVal * 2m)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				SellMarket();
			}
		}

		// Entry on EMA trend
		if (Position == 0 && _cooldown == 0)
		{
			if (close > emaVal && emaVal > _prevEma)
			{
				_entryPrice = close;
				_gridCount = 0;
				BuyMarket();
			}
			else if (close < emaVal && emaVal < _prevEma)
			{
				_entryPrice = close;
				_gridCount = 0;
				SellMarket();
			}
		}

		_prevEma = emaVal;
	}
}