GitHub で見る

Billy Expert プルバックバイヤー

概要

Billy Expert はMetaTrader 5エキスパートアドバイザー「Billy expert」から変換されたロングオンリーのプルバック戦略です。下降する高値の連続を待ち、ベース時間軸でオープンし、その後異なる上位時間軸で計算された2つのストキャスティクスオシレーターからの強気の確認を確認します。両方のオシレーターが上昇モメンタムが存在することに同意すると、システムは設定可能な制限まで新しいロングポジションを追加します。

変換はStockSharp高レベルAPIガイドラインに従います。取引ボリューム、最大同時エントリー数、保護ストップとテイクプロフィットは戦略パラメーターを通じて制御され、元のMQLロジックに合わせた動作を実現します。

仕組み

  1. 一次ローソク足シリーズ(デフォルト1分)とストキャスティクスオシレーター用の2つの上位時間軸(デフォルト5分と6分)をサブスクライブします。
  2. ベース時間軸での最新4本の完成したローソク足を追跡します。有効なプルバックには、それら4本のバーにわたって厳密に下降する高値始値が必要です。
  3. 高速と低速のストキャスティクスオシレーターを評価します。戦略は、各オシレーターの最新と前回の%K値が%Dを上回り続けることを要求し、両方の時間軸でモメンタムがすでに上昇に転換したことを示します。
  4. プルバックとモメンタムフィルターが確認され、オープンロングトレード数がMaxPositions未満の場合、TradeVolumeのサイズで市場買い注文を送信します。
  5. オプションのストップロスとテイクプロフィットレベルはpipsで表現され、インスツルメントのPriceStepを使用して絶対価格距離に変換されます。いずれかの距離がゼロに設定されると、対応する保護注文は省略されます。
  6. ポジションはこれらの保護レベルを通じてのみ閉じられ、元のエキスパートアドバイザーの動作を模倣します。

パラメーター

  • TradeVolume – 各エントリーの注文サイズ(デフォルト 0.01)。
  • StopLossPips – pipsでのストップ距離(デフォルト 0、無効)。
  • TakeProfitPips – pipsでの利益目標(デフォルト 32)。
  • MaxPositions – 最大同時ロングトレード数(デフォルト 6)。
  • Signal Candle – 価格パターン用のベース時間軸(デフォルト 1 分)。
  • Fast Stochastic TF – 高速オシレーター用の時間軸(デフォルト 5 分)。
  • Slow Stochastic TF – 低速オシレーター用の時間軸(デフォルト 6 分)。高速時間軸より長くなければならない。

フィルターと動作

  • 方向:ロングのみ。
  • エントリートリガー:下降する始値と高値を持つ4バーのプルバック。
  • モメンタムフィルター:現在と前回の読みで%Kが%Dより上にあるデュアルストキャスティクスオシレーター。
  • リスク管理:オプションのpipsベースのストップロスとテイクプロフィット。トレーリングロジックなし。
  • ポジションサイジング:エントリーごとに固定TradeVolumeMaxPositionsで上限。
  • マーケット:小数pipで引用されるFXペア向けに設計されていますが、有効なPriceStepを提供する任意のインスツルメントで動作します。

使用上の注意

  • Fast Stochastic TFSlow Stochastic TFよりも厳密に短いことを確認してください、そうでないと戦略は起動時に停止します。
  • 決済が完全に保護注文に依存しているため、インスツルメントのボラティリティに合わせてStopLossPipsTakeProfitPipsを調整します。
  • 戦略は弱気シグナルを無視し、スケールアウトしません;追加の保護にはポートフォリオレベルのリスクコントロールを使用してください。
  • バックテストでは、最初のトレードの前に両方のストキャスティクスオシレーターが形成できるよう十分なウォームアップローソク足を提供してください。
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>
/// Billy Expert strategy converted from MetaTrader 5 Expert Advisor.
/// Focuses on buying during pullbacks confirmed by dual timeframe Stochastic signals.
/// </summary>
public class BillyExpertStrategy : Strategy
{
	private readonly StrategyParam<decimal> _volumeTolerance;

	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<TimeSpan> _stochasticTimeFrame1;
	private readonly StrategyParam<TimeSpan> _stochasticTimeFrame2;

	private StochasticOscillator _fastStochastic = null!;
	private StochasticOscillator _slowStochastic = null!;

	private decimal _open1;
	private decimal _open2;
	private decimal _open3;
	private decimal _open4;

	private decimal _high1;
	private decimal _high2;
	private decimal _high3;
	private decimal _high4;

	private int _historyCount;

	private decimal _fastMainCurrent;
	private decimal _fastMainPrevious;
	private decimal _fastSignalCurrent;
	private decimal _fastSignalPrevious;
	private bool _fastHasCurrent;
	private bool _fastHasPrevious;

	private decimal _slowMainCurrent;
	private decimal _slowMainPrevious;
	private decimal _slowSignalCurrent;
	private decimal _slowSignalPrevious;
	private bool _slowHasCurrent;
	private bool _slowHasPrevious;

	private decimal _pipSize;

	/// <summary>
	/// Volume tolerance used to compare accumulated volumes.
	/// </summary>
	public decimal VolumeTolerance
	{
		get => _volumeTolerance.Value;
		set => _volumeTolerance.Value = value;
	}

	/// <summary>
	/// Trade volume used for each entry.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Maximum number of simultaneous long entries.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	/// <summary>
	/// Primary candle type that drives the price pattern checks.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Timeframe for the faster Stochastic oscillator.
	/// </summary>
	public TimeSpan StochasticTimeFrame1
	{
		get => _stochasticTimeFrame1.Value;
		set => _stochasticTimeFrame1.Value = value;
	}

	/// <summary>
	/// Timeframe for the slower Stochastic oscillator.
	/// </summary>
	public TimeSpan StochasticTimeFrame2
	{
		get => _stochasticTimeFrame2.Value;
		set => _stochasticTimeFrame2.Value = value;
	}

	/// <summary>
	/// Initializes parameters for the strategy.
	/// </summary>
	public BillyExpertStrategy()
	{
		_volumeTolerance = Param(nameof(VolumeTolerance), 0.0000001m)
			.SetGreaterThanZero()
			.SetDisplay("Volume Tolerance", "Tolerance for comparing volume sums", "Risk");

		_tradeVolume = Param(nameof(TradeVolume), 0.01m)
		.SetGreaterThanZero()
		.SetDisplay("Trade Volume", "Order size for each entry", "General");

		_stopLossPips = Param(nameof(StopLossPips), 0)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 320)
		.SetNotNegative()
		.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk");

		_maxPositions = Param(nameof(MaxPositions), 6)
		.SetGreaterThanZero()
		.SetDisplay("Max Positions", "Maximum number of open trades", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Signal Candle", "Primary timeframe used for price filters", "General");

		_stochasticTimeFrame1 = Param(nameof(StochasticTimeFrame1), TimeSpan.FromHours(1))
		.SetDisplay("Fast Stochastic TF", "Timeframe for the fast Stochastic", "Indicators");

		_stochasticTimeFrame2 = Param(nameof(StochasticTimeFrame2), TimeSpan.FromHours(4))
		.SetDisplay("Slow Stochastic TF", "Timeframe for the slow Stochastic", "Indicators");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return
		[
		(Security, CandleType),
		(Security, StochasticTimeFrame1.TimeFrame()),
		(Security, StochasticTimeFrame2.TimeFrame())
		];
	}

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

		_open1 = _open2 = _open3 = _open4 = 0m;
		_high1 = _high2 = _high3 = _high4 = 0m;
		_historyCount = 0;

		_fastMainCurrent = _fastMainPrevious = 0m;
		_fastSignalCurrent = _fastSignalPrevious = 0m;
		_fastHasCurrent = false;
		_fastHasPrevious = false;

		_slowMainCurrent = _slowMainPrevious = 0m;
		_slowSignalCurrent = _slowSignalPrevious = 0m;
		_slowHasCurrent = false;
		_slowHasPrevious = false;

		_pipSize = 0m;
	}

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

		if (StochasticTimeFrame1 >= StochasticTimeFrame2)
		{
			LogError("Fast stochastic timeframe must be shorter than the slow timeframe.");
			Stop();
			return;
		}

		Volume = TradeVolume;

		_fastStochastic = new StochasticOscillator { K = { Length = 14 }, D = { Length = 3 } };
		_slowStochastic = new StochasticOscillator { K = { Length = 14 }, D = { Length = 3 } };

		var candleSubscription = SubscribeCandles(CandleType);
		candleSubscription
		.Bind(ProcessSignalCandle)
		.Start();

		var fastSubscription = SubscribeCandles(StochasticTimeFrame1.TimeFrame());
		fastSubscription
		.BindEx(_fastStochastic, ProcessFastStochastic)
		.Start();

		var slowSubscription = SubscribeCandles(StochasticTimeFrame2.TimeFrame());
		slowSubscription
		.BindEx(_slowStochastic, ProcessSlowStochastic)
		.Start();

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

		_pipSize = CalculatePipSize();

		var takeProfit = TakeProfitPips > 0 ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : null;
		var stopLoss = StopLossPips > 0 ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : null;

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(takeProfit, stopLoss);
		}
	}

	private void ProcessFastStochastic(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!value.IsFinal)
			return;

		if (!_fastStochastic.IsFormed)
			return;

		if (_fastHasCurrent)
		{
			_fastMainPrevious = _fastMainCurrent;
			_fastSignalPrevious = _fastSignalCurrent;
			_fastHasPrevious = true;
		}

		var typed = (StochasticOscillatorValue)value;
		_fastMainCurrent = typed.K ?? 0m;
		_fastSignalCurrent = typed.D ?? 0m;
		_fastHasCurrent = true;
	}

	private void ProcessSlowStochastic(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!value.IsFinal)
			return;

		if (!_slowStochastic.IsFormed)
			return;

		if (_slowHasCurrent)
		{
			_slowMainPrevious = _slowMainCurrent;
			_slowSignalPrevious = _slowSignalCurrent;
			_slowHasPrevious = true;
		}

		var typed = (StochasticOscillatorValue)value;
		_slowMainCurrent = typed.K ?? 0m;
		_slowSignalCurrent = typed.D ?? 0m;
		_slowHasCurrent = true;
	}

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

		if (_historyCount >= 4 && _fastHasPrevious && _slowHasPrevious)
		{
			var decreasingHighs = _high1 < _high2 && _high2 < _high3 && _high3 < _high4;
			var decreasingOpens = _open1 < _open2 && _open2 < _open3 && _open3 < _open4;
			var fastBullish = _fastMainPrevious > _fastSignalPrevious && _fastMainCurrent > _fastSignalCurrent;
			var slowBullish = _slowMainPrevious > _slowSignalPrevious && _slowMainCurrent > _slowSignalCurrent;

			var maxLongVolume = MaxPositions * TradeVolume;
			var currentLongVolume = Math.Max(Position, 0m);
			var projectedVolume = currentLongVolume + TradeVolume;

			if (decreasingHighs && decreasingOpens && fastBullish && slowBullish && projectedVolume <= maxLongVolume + VolumeTolerance)
			{
					BuyMarket();
			}
		}

		_high4 = _high3;
		_high3 = _high2;
		_high2 = _high1;
		_high1 = candle.HighPrice;

		_open4 = _open3;
		_open3 = _open2;
		_open2 = _open1;
		_open1 = candle.OpenPrice;

		if (_historyCount < 4)
		{
			_historyCount++;
		}
	}

	private decimal CalculatePipSize()
	{
		var priceStep = Security?.PriceStep ?? 0m;

		if (priceStep <= 0m)
			return 1m;

		var decimals = GetDecimalPlaces(priceStep);
		var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;

		return priceStep * adjust;
	}

	private static int GetDecimalPlaces(decimal value)
	{
		var bits = decimal.GetBits(value);
		return (bits[3] >> 16) & 0xFF;
	}
}