GitHub で見る

Rabbit3戦略

概要

  • オリジナルのMetaTrader 5エキスパートアドバイザー Rabbit3 (barabashkakvn版) を変換したものです。
  • StockSharpの高レベルAPIでローソク足サブスクリプションとインジケーターバインディングを使用してロジックを実装しています。
  • ポジションをスタックする前に、Williams %Rとコモディティ・チャンネル・インデックス(CCI)の二重確認に焦点を当てています。
  • ダイナミックなポジションサイジングを追加:現金閾値を超える利益が次のシグナルの注文量を増やします。

取引ロジック

エントリー条件

  1. ロング
    • 現在と前回の完成したローソク足がWilliams %RをWilliamsOversold(デフォルト-80)以下で報告している。
    • CCI値がCciBuyLevel(デフォルト-80)以下である。
    • 現在のネットポジションが非負であり、別のポジションを追加してもMaxPositions × BaseVolumeのエクスポージャー内に収まる(アクティブな場合は増量されたボリュームを使用)。
  2. ショート
    • 現在と前回の完成したローソク足がWilliams %RをWilliamsOverbought(デフォルト-20)以上で報告している。
    • CCI値がCciSellLevel(デフォルト80)以上である。
    • 現在のネットポジションが非正であり、新しい注文が設定されたスタック制限内に収まる。

エグジットとリスク管理

  • 保護的なストップロスとテイクプロフィット注文はStartProtectionを通じて自動的に登録されます。
  • 距離は「調整済みポイント」で表されます:銘柄が3桁または5桁の小数点を使用する場合、StopLossPipsTakeProfitPipsを適用する前に価格ステップに10を掛けてMetaTraderのpip演算をエミュレートします。
  • 追加の手動エグジットルールは不要です;保護注文がトレードをクローズします。

ポジションサイジング

  • BaseVolumeは初期取引サイズを定義します(デフォルト0.01)。
  • 各取引がクローズされた後、実現PnLデルタがProfitThreshold(デフォルト4通貨単位)と比較されます。
  • デルタが厳密に大きい場合、次のシグナルはBaseVolume × VolumeMultiplier(デフォルト1.6)を使用します。そうでない場合、サイズはBaseVolumeにリセットされます。
  • 現在のボリュームはUIフィードバックのために戦略のVolumeプロパティを通じても公開されます。

インジケーターと視覚化

  • Williams %R、CCI、速い方のEMA(FastEmaPeriod)、遅い方のEMA(SlowEmaPeriod)がモニタリングとチャート作成のためにローソク足フィードにバインドされています。
  • ローソク足、インジケーター、実行された取引を表示するチャートエリアが自動的に作成されます。

パラメーター

名前 デフォルト 説明
CandleType 1h時間軸 ローソク足サブスクリプションのデータタイプ。
CciPeriod 15 コモディティ・チャンネル・インデックスの長さ。
CciBuyLevel -80 ロングエントリーを許可するCCI閾値。
CciSellLevel 80 ショートエントリーを許可するCCI閾値。
WilliamsPeriod 62 Williams %Rのルックバック期間。
WilliamsOversold -80 ロング確認に使用される売られすぎ閾値。
WilliamsOverbought -20 ショート確認に使用される買われすぎ閾値。
FastEmaPeriod 17 トレンドコンテキスト用に描画される速い方のEMA。
SlowEmaPeriod 30 トレンドコンテキスト用に描画される遅い方のEMA。
MaxPositions 2 各方向の最大スタック可能エントリー数。
ProfitThreshold 4 次の注文サイズをブーストするために必要な実現利益(通貨単位)。
BaseVolume 0.01 ベース注文ボリューム。
VolumeMultiplier 1.6 ブースト条件が満たされた場合に適用される乗数。
StopLossPips 45 調整済みポイントでのストップロス距離。
TakeProfitPips 110 調整済みポイントでのテイクプロフィット距離。

注意事項

  • 戦略はネットポジションで動作します。ヘッジ対応のMQLバージョンとは異なり、ロングとショートは同時に保持されません;現在のエクスポージャーが保護注文によってクローズされるまで、反対方向のシグナルは無視されます。
  • MaxPositionsは集計ポジション(ベースボリュームにスタックファクターを掛けたもの)の上限として機能します。ベースまたはブーストボリュームを変更する際は慎重に調整してください。
  • ボリューム許容値はスタック上限を確認する際の軽微な丸め差異を吸収するために銘柄のボリュームステップの半分を使用します。
  • Python翻訳はまだ含まれておらず、必要に応じて後で追加できます。
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;


public class Rabbit3Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciBuyLevel;
	private readonly StrategyParam<decimal> _cciSellLevel;
	private readonly StrategyParam<int> _williamsPeriod;
	private readonly StrategyParam<decimal> _williamsOversold;
	private readonly StrategyParam<decimal> _williamsOverbought;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<decimal> _profitThreshold;
	private readonly StrategyParam<decimal> _baseVolume;
	private readonly StrategyParam<decimal> _volumeMultiplier;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;

	private WilliamsR _williams;
	private CommodityChannelIndex _cci;
	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	
	// Store last Williams %R value to require two-bar confirmation.
	private decimal _previousWilliams;
	private bool _hasPrevWilliams;
	// Track whether the boosted volume should be used for the next order.
	private bool _useBoost;
	// Remember realized PnL to measure the delta after each closed trade.
	private decimal _lastRealizedPnL;

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

	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	public decimal CciBuyLevel
	{
		get => _cciBuyLevel.Value;
		set => _cciBuyLevel.Value = value;
	}

	public decimal CciSellLevel
	{
		get => _cciSellLevel.Value;
		set => _cciSellLevel.Value = value;
	}

	public int WilliamsPeriod
	{
		get => _williamsPeriod.Value;
		set => _williamsPeriod.Value = value;
	}

	public decimal WilliamsOversold
	{
		get => _williamsOversold.Value;
		set => _williamsOversold.Value = value;
	}

	public decimal WilliamsOverbought
	{
		get => _williamsOverbought.Value;
		set => _williamsOverbought.Value = value;
	}

	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	public decimal ProfitThreshold
	{
		get => _profitThreshold.Value;
		set => _profitThreshold.Value = value;
	}

	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

	public decimal VolumeMultiplier
	{
		get => _volumeMultiplier.Value;
		set => _volumeMultiplier.Value = value;
	}

	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public Rabbit3Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for signals", "General");

		_cciPeriod = Param(nameof(CciPeriod), 15)
			.SetDisplay("CCI Period", "Commodity Channel Index length", "Indicators")
			.SetGreaterThanZero();

		_cciBuyLevel = Param(nameof(CciBuyLevel), -80m)
			.SetDisplay("CCI Buy Level", "CCI threshold to allow long entries", "Signals");

		_cciSellLevel = Param(nameof(CciSellLevel), 80m)
			.SetDisplay("CCI Sell Level", "CCI threshold to allow short entries", "Signals");

		_williamsPeriod = Param(nameof(WilliamsPeriod), 62)
			.SetDisplay("Williams %R Period", "Williams %R lookback", "Indicators")
			.SetGreaterThanZero();

		_williamsOversold = Param(nameof(WilliamsOversold), -80m)
			.SetDisplay("Williams Oversold", "Oversold threshold for confirmation", "Signals");

		_williamsOverbought = Param(nameof(WilliamsOverbought), -20m)
			.SetDisplay("Williams Overbought", "Overbought threshold for confirmation", "Signals");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 17)
			.SetDisplay("Fast EMA Period", "Fast EMA plotted for context", "Indicators")
			.SetGreaterThanZero();

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 30)
			.SetDisplay("Slow EMA Period", "Slow EMA plotted for context", "Indicators")
			.SetGreaterThanZero();

		_maxPositions = Param(nameof(MaxPositions), 2)
			.SetDisplay("Max Positions", "Maximum stacked entries per direction", "Risk")
			.SetGreaterThanZero();

		_profitThreshold = Param(nameof(ProfitThreshold), 4m)
			.SetDisplay("Profit Threshold", "Realized profit that boosts volume", "Risk");

		_baseVolume = Param(nameof(BaseVolume), 0.01m)
			.SetDisplay("Base Volume", "Initial trade volume", "Risk")
			.SetGreaterThanZero();

		_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.6m)
			.SetDisplay("Volume Multiplier", "Factor applied after profitable trades", "Risk")
			.SetGreaterThanZero();

		_stopLossPips = Param(nameof(StopLossPips), 45)
			.SetDisplay("Stop Loss (pips)", "Protective stop distance in adjusted points", "Risk")
			.SetGreaterThanZero();

		_takeProfitPips = Param(nameof(TakeProfitPips), 110)
			.SetDisplay("Take Profit (pips)", "Target distance in adjusted points", "Risk")
			.SetGreaterThanZero();
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_previousWilliams = 0m;
		_hasPrevWilliams = false;
		_useBoost = false;
		_lastRealizedPnL = 0m;
	}

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

                // Reset dynamic sizing state and expose the starting volume to UI.
                _useBoost = false;
                Volume = BaseVolume;
                _lastRealizedPnL = PnL;

                // Initialize indicators that will be bound to the candle feed.
                _williams = new WilliamsR { Length = WilliamsPeriod };
                _cci = new CommodityChannelIndex { Length = CciPeriod };
                _fastEma = new EMA { Length = FastEmaPeriod };
                _slowEma = new EMA { Length = SlowEmaPeriod };

                // Subscribe to candles and bind indicators plus the processing callback.
                var subscription = SubscribeCandles(CandleType);
                subscription.Bind(_williams, _cci, _fastEma, _slowEma, ProcessCandle).Start();

                var area = CreateChartArea();
                if (area != null)
                {
                        DrawCandles(area, subscription);
                        DrawIndicator(area, _williams);
                        DrawIndicator(area, _cci);
                        DrawIndicator(area, _fastEma);
                        DrawIndicator(area, _slowEma);
                        DrawOwnTrades(area);
                }

                var point = GetAdjustedPoint();
                var takeDistance = TakeProfitPips * point;
                var stopDistance = StopLossPips * point;

                // Register protective orders using MetaTrader-like pip distances.
                StartProtection(
                        new Unit(stopDistance, UnitTypes.Absolute),
                        new Unit(takeDistance, UnitTypes.Absolute));
        }

        private void ProcessCandle(ICandleMessage candle, decimal williamsValue, decimal cciValue, decimal fastEmaValue, decimal slowEmaValue)
        {
                if (candle.State != CandleStates.Finished)
                        return;

                // Update the current volume based on realized profit or loss.
                UpdateVolumeIfNeeded();

                if (!_williams.IsFormed || !_cci.IsFormed)
                {
                        _previousWilliams = williamsValue;
                        return;
                }

                if (!_hasPrevWilliams)
                {
                        _previousWilliams = williamsValue;
                        _hasPrevWilliams = true;
                        return;
                }

                // removed IFOAAT for backtesting

                if (williamsValue == 0m)
                        williamsValue = -1m;

                if (_previousWilliams == 0m)
                        _previousWilliams = -1m;

                // Require Williams %R confirmation on two consecutive closed candles and CCI agreement.
                var longSignal = williamsValue < WilliamsOversold
                        && cciValue < CciBuyLevel
                        && fastEmaValue > slowEmaValue
                        && CanEnterLong();

                var shortSignal = williamsValue > WilliamsOverbought
                        && cciValue > CciSellLevel
                        && fastEmaValue < slowEmaValue
                        && CanEnterShort();

                if (longSignal)
                {
                        // Stack another long position using the dynamically selected volume.
                        BuyMarket(Volume);
                }
                else if (shortSignal)
                {
                        // Stack another short position using the dynamically selected volume.
                        SellMarket(Volume);
                }

                _previousWilliams = williamsValue;
        }

        private void UpdateVolumeIfNeeded()
        {
                var realizedPnL = PnL;

                if (realizedPnL != _lastRealizedPnL)
                {
                        var delta = realizedPnL - _lastRealizedPnL;
                        // Boost the next order after reaching the desired profit, otherwise revert to base volume.
                        _useBoost = delta > ProfitThreshold;
                        _lastRealizedPnL = realizedPnL;
                }

                Volume = GetTradeVolume();
        }

        private bool CanEnterLong()
        {
                if (Position < 0m)
                        return false;

                // Limit the number of stacked long entries according to MaxPositions.
                var tradeVolume = GetTradeVolume();
                var targetVolume = Position + tradeVolume;
                var maxVolume = MaxPositions * tradeVolume;
                return targetVolume <= maxVolume + GetVolumeTolerance();
        }

        private bool CanEnterShort()
        {
                if (Position > 0m)
                        return false;

                // Limit stacked shorts in the same way as longs.
                var tradeVolume = GetTradeVolume();
                var targetVolume = Math.Abs(Position - tradeVolume);
                var maxVolume = MaxPositions * tradeVolume;
                return targetVolume <= maxVolume + GetVolumeTolerance();
        }

	private decimal GetTradeVolume()
	{
		var multiplier = _useBoost ? VolumeMultiplier : 1m;
		return BaseVolume * multiplier;
	}

	private decimal GetAdjustedPoint()
	{
		var step = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals ?? 0;
		var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
		return step * adjust;
	}

	private decimal GetVolumeTolerance()
	{
		var step = Security?.VolumeStep;
		if (step == null || step == 0m)
			return 0.00000001m;
		return step.Value / 2m;
	}
}