GitHub で見る

BARS Alligator戦略

BARS Alligator戦略は、同名のMetaTraderエキスパートアドバイザーの直接ポートです。ビル・ウィリアムズのAlligatorインジケーターを使用して目覚めるトレンドを検出します:緑のリップス(lips)ラインが青いジョー(jaw)ラインを上回るとシステムはそれを強気のブレイクアウトとして扱い、下向きのクロスは弱気のモメンタムを示します。エグジットはリップスが赤いティース(teeth)ラインをクロスすることに依存し、モメンタムが消えたらすぐにポジションがクローズされます。保護的なストップロス、テイクプロフィット、トレーリングストップの距離はpipで設定され、計器の価格ステップと小数精度に基づいて自動的に価格単位に変換されます。

トレードロジック

  1. インジケーターの構築
    • 設定可能な長さ、シフト、タイプ(シンプル、指数、スムーズまたは加重)を持つ3つの移動平均がAlligatorを形成します。
    • 適用される価格は各ローソク足のクローズ、オープン、ハイ、ロー、メジアン、ティピカルまたは加重価格です。
    • シフトは各ラインの小さなローリングバッファを格納することで尊重され、クロスオーバーがMetaTraderチャートに表示されるのと同じ値を使用するようにします。
  2. エントリー条件
    • ロング:前のバーのリップスラインがジョーより上にあり、2バー前には下にあった(強気の上向きクロス)。
    • ショート:前のバーのリップスラインがジョーより下にあり、2バー前には上にあった(弱気の下向きクロス)。
    • 新しいエントリーは現在のポジションがフラットか既にシグナルの方向に整列しており、集計ポジションサイズがMaxPositions × OrderVolume(またはリスクサイジングされた相当量)を下回る場合にのみ許可されます。
  3. エグジット条件
    • ロングエグジット:リップスラインがティースラインを下回るクロスをし、ポジションが平均エントリー価格に対して利益を上げている。
    • ショートエグジット:リップスラインがティースラインを上回るクロスをし、ポジションが利益を上げている。
    • 静的なストップロスまたはテイクプロフィットのレベルが破られた場合にもエグジットが発生します。
  4. トレーリングストップ
    • 有効にすると、価格がトレードの方向にTrailingStopPips + TrailingStepPipsを超えて動くとトレーリングストップが保護ストップを再配置します。ストップはその後TrailingStopPips pipの距離で価格に追従しますが、価格が少なくともTrailingStepPips pipの新しい進展をする場合にのみ前進します。
  5. マネー管理
    • MoneyMode = FixedVolumeでは、注文はOrderVolumeのサイズを直接使用します。
    • MoneyMode = RiskPercentでは、設定されたMoneyValueパーセントのポートフォリオ資本がストップロスが到達した場合に失われるようにボリュームを割り当てます。ユニットあたりのリスクは価格単位で表されたストップロス距離に等しいです。結果は最も近いVolumeStepに切り捨てられます(step情報が欠けている場合は1に)。

パラメーター

パラメーター デフォルト 説明
CandleType DataType TimeSpan.FromHours(1).TimeFrame() Alligator計算に使用するタイムフレーム。
OrderVolume decimal 0.1 MoneyModeFixedVolumeの場合の固定トレードボリューム。
MoneyMode MoneyManagementMode FixedVolume 固定ボリュームとリスクパーセントサイジングの間を選択します。
MoneyValue decimal 1 MoneyModeRiskPercentの場合に適用されるリスクパーセンテージ;それ以外は無視されます。
MaxPositions int 1 方向ごとの加算的エントリーの最大数(計算された注文ボリュームの倍数として表現)。
StopLossPips int 150 pipでのストップロス距離。ゼロで保護ストップを無効化します。
TakeProfitPips int 150 pipでのテイクプロフィット距離。ゼロで利益目標を無効化します。
TrailingStopPips int 5 pipでのトレーリングストップ距離。ゼロでトレーリングを無効化します。
TrailingStepPips int 5 トレーリングストップが前進する前に価格が移動しなければならない余分な距離。トレーリングが有効な場合は正でなければなりません。
JawPeriod int 13 ジョー移動平均の長さ。
JawShift int 8 ジョーシリーズに適用される前方シフト(バー単位)。
TeethPeriod int 8 ティース移動平均の長さ。
TeethShift int 5 ティースシリーズに適用される前方シフト。
LipsPeriod int 5 リップス移動平均の長さ。
LipsShift int 3 リップスシリーズに適用される前方シフト。
MaType MovingAverageType Smoothed 3つのAlligatorラインすべてに使用される移動平均アルゴリズム。
AppliedPrice AppliedPriceType Median 移動平均に供給されるローソク足価格(クローズ、オープン、ハイ、ロー、メジアン、ティピカルまたは加重)。

pip変換

戦略はpip設定をセキュリティのPriceStepで乗算します。計器が3または5桁の小数を使用する場合、値は分数クォートに対するMetaTraderのpip定義を模倣するために×10で調整されます。価格ステップが利用できない場合、1の値が仮定されます。

実装メモ

  • StockSharpはネッティングモードで動作するため、MaxPositionsは集計ポジションサイズで機能します。追加のエントリーは別のポジションチケットを作成するのではなく、平均価格を増加させます。
  • ストップロスとテイクプロフィットは内部で追跡され、閾値を違反する最初のローソク足で成行注文で実行され、元のMQLエキスパートの動作と一致します。
  • リスクベースのサイジングはゼロ以外のストップロス距離を必要とします;そうでなければシステムは固定のOrderVolumeにフォールバックします。
  • すべてのインジケーター値は早期シグナルを避けるために完了したローソク足(CandleStates.Finished)でのみ更新されます。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// Bill Williams Alligator strategy: trades on lips/jaw crossover and exits on lips/teeth crossover.
/// </summary>
public class BarsAlligatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<decimal> _takeProfitPercent;

	private readonly SmoothedMovingAverage _jaw = new() { Length = 13 };
	private readonly SmoothedMovingAverage _teeth = new() { Length = 8 };
	private readonly SmoothedMovingAverage _lips = new() { Length = 5 };

	private decimal _previousJaw;
	private decimal _previousTeeth;
	private decimal _previousLips;
	private bool _hasPrevious;
	private decimal? _entryPrice;
	private int _cooldownLeft;

	public BarsAlligatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_cooldownBars = Param(nameof(CooldownBars), 6).SetNotNegative().SetDisplay("Cooldown Bars", "Bars between completed trades", "Trading");
		_stopLossPercent = Param(nameof(StopLossPercent), 3m).SetDisplay("Stop Loss %", "Stop distance as percentage of entry price", "Risk");
		_takeProfitPercent = Param(nameof(TakeProfitPercent), 3m).SetDisplay("Take Profit %", "Take-profit distance as percentage of entry price", "Risk");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
	public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }

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

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

		_previousJaw = 0m;
		_previousTeeth = 0m;
		_previousLips = 0m;
		_hasPrevious = false;
		_entryPrice = null;
		_cooldownLeft = 0;
	}

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

		OnReseted();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_jaw, _teeth, _lips, ProcessCandle)
			.Start();
	}

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

		if (_cooldownLeft > 0)
			_cooldownLeft--;

		if (Position != 0 && _entryPrice is null)
			_entryPrice = candle.ClosePrice;

		if (TryExitByRisk(candle))
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (!_hasPrevious)
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		// Exit conditions: lips crosses teeth against position
		var closeLong = lips < teeth && _previousLips >= _previousTeeth && Position > 0;
		var closeShort = lips > teeth && _previousLips <= _previousTeeth && Position < 0;

		if (closeLong)
		{
			SellMarket(Position);
			_entryPrice = null;
			_cooldownLeft = CooldownBars;
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (closeShort)
		{
			BuyMarket(Math.Abs(Position));
			_entryPrice = null;
			_cooldownLeft = CooldownBars;
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading() || _cooldownLeft > 0)
		{
			UpdatePrevious(jaw, teeth, lips);
			return;
		}

		// Entry: lips crosses jaw with proper Alligator ordering
		var buySignal = lips > jaw && _previousLips <= _previousJaw && lips > teeth;
		var sellSignal = lips < jaw && _previousLips >= _previousJaw && lips < teeth;

		if (buySignal && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
			}
			else
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_cooldownLeft = CooldownBars;
			}
		}
		else if (sellSignal && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
			}
			else
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_cooldownLeft = CooldownBars;
			}
		}

		UpdatePrevious(jaw, teeth, lips);
	}

	private bool TryExitByRisk(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entryPrice || Position == 0 || entryPrice == 0)
			return false;

		var stopDistance = entryPrice * StopLossPercent / 100m;
		var takeDistance = entryPrice * TakeProfitPercent / 100m;

		if (Position > 0)
		{
			if ((stopDistance > 0 && candle.LowPrice <= entryPrice - stopDistance) ||
				(takeDistance > 0 && candle.HighPrice >= entryPrice + takeDistance))
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}
		else if (Position < 0)
		{
			var volume = Math.Abs(Position);

			if ((stopDistance > 0 && candle.HighPrice >= entryPrice + stopDistance) ||
				(takeDistance > 0 && candle.LowPrice <= entryPrice - takeDistance))
			{
				BuyMarket(volume);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}

		return false;
	}

	private void UpdatePrevious(decimal jaw, decimal teeth, decimal lips)
	{
		_previousJaw = jaw;
		_previousTeeth = teeth;
		_previousLips = lips;
		_hasPrevious = true;
	}
}