GitHub で見る

CCI MA v1.5 戦略

この戦略は、StockSharp の高レベル API 内に MetaTrader「CCI_MA v1.5」エキスパート アドバイザを再作成します。元のロボットは、商品チャネル指数 (CCI) が CCI の値自体に基づいて計算された単純移動平均を超えるのを待ち、二次的な CCI を使用して、±100 のしきい値付近での出口を監視します。 StockSharp ポートは、同じシグナル順序、オプションの資金管理、ポイントベースのストップ/ターゲット距離を維持しながら、すべてをキャンドルサブスクリプションとインジケーターバインディングに適応させます。

仕組み

  • データ ソース – ユーザー定義のローソク足シリーズ (デフォルトでは 15 分足のローソク足) が両方の CCI に供給されます。インジケーターはローソク足の終値を読み取り、MetaTrader の PRICE_CLOSE 設定を反映します。
  • コアインジケーター – プライマリ CommodityChannelIndex (パラメータ CciPeriod) は、モメンタムの読み取り値を提供します。期間 MaPeriodSimpleMovingAverage が CCI 値のストリームに適用されて、トリガー ラインを形成します。セカンダリ CCI (SignalCciPeriod) は、買われすぎと売られすぎの反転を±100 付近で監視します。
  • エントリーロジック – 上向きクロスオーバーに続いてバーでロング取引が開始されます。前に完了したローソク足 (prevCci) は、CCI 移動平均よりも上に位置している必要がありますが、その前のローソク足 (prev2Cci) は下にありました。短い信号は、下向きの対称交差点です。既存の反対のポジションは、現在のポジションの絶対値を新しい注文サイズに追加することによって閉じられ、反転され、MQL バージョンの動作と一致します。
  • 出口ロジック – 監視対象の CCI が +100 を超えて +100 未満に低下したとき、または主軸 CCI が移動平均を下回ったときにロングが清算されます (これも、以前に終了した 2 つのローソク足で評価されます)。ショートは逆の条件で終了します。プロテクティブストップは、MetaTrader からのポイントベースの距離をエミュレートします。この戦略は、商品 PriceStep からピップサイズを導き出し(3 桁または 5 桁の相場の場合は 10 を掛けます)、完成した各ローソク足のローソク足の極値を entry ± distance と比較します。
  • ポジションのサイジングLotVolume は基本注文サイズを定義します。 UseMoneyManagement が有効な場合、戦略は MaxMultiplier を上限とする floor(balance / DepositPerLot) に等しい整数係数を乗算し、エキスパートアドバイザーからのデポジットラダーを再現します。注文量は、送信前に商品の VolumeStepMinVolume、および MaxVolume の制約に合わせて調整されます。

パラメーター

  • ローソク足タイプ – すべてのインジケーターの計算に使用されるローソク足のデータ タイプ。
  • CCI 周期 – プライマリ CCI オシレーターの長さ。
  • 出口 CCI 期間 – しきい値出口に使用される監視 CCI の長さ。
  • CCI MA 期間 – プライマリ CCI に適用される単純移動平均の期間。
  • ロットボリューム – 資金管理のスケーリング前の基本取引量。
  • 資金管理を有効にする – デポジットベースのロットボリュームのスケーリングを有効にします。
  • ロットごとの入金 – ロット乗数を 1 増やすために必要な残高増分 (資金管理がアクティブな場合にのみ使用されます)。
  • 最大乗数 – 資金管理が到達できる最大乗数。
  • ストップロス (pips) – 保護ストップまでの距離 (pips)。無効にするにはゼロに設定します。
  • 利益確定 (pips) – 利益目標までの距離 (pips)。無効にするにはゼロに設定します。

この戦略は、2 本のバーのクロスオーバー比較が MQL エキスパートの遅延実行と正確に一致するように、最初の注文を発行する前に 2 つの完全に閉じたローソク足を待ちます。ストップロスとテイクプロフィットのチェックは、完成したローソクの上限/下限を使用して実行されます。これは、高レベルの StockSharp API 内に留まりながら、MetaTrader のサーバー側保護命令に近似します。

namespace StockSharp.Samples.Strategies;

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;

/// <summary>
/// Commodity Channel Index strategy converted from the MetaTrader "CCI_MA v1.5" expert advisor.
/// Uses a primary CCI with a manually computed SMA of CCI values as a signal line.
/// A secondary CCI provides overbought/oversold exit confirmation.
/// </summary>
public class CciMaV15Strategy : Strategy
{
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<int> _signalCciPeriod;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private CommodityChannelIndex _cci;
	private CommodityChannelIndex _signalCci;

	private readonly List<decimal> _cciHistory = new();
	private decimal? _prevCciMa;
	private decimal? _prevCci;
	private decimal? _prevSignalCci;

	/// <summary>
	/// Primary CCI period.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// Secondary CCI period for exit signals.
	/// </summary>
	public int SignalCciPeriod
	{
		get => _signalCciPeriod.Value;
		set => _signalCciPeriod.Value = value;
	}

	/// <summary>
	/// SMA period applied to the primary CCI values.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance in absolute points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public CciMaV15Strategy()
	{
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "Length of the primary CCI", "CCI")
			.SetOptimize(7, 35, 7);

		_signalCciPeriod = Param(nameof(SignalCciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Exit CCI Period", "Length of the secondary CCI", "CCI")
			.SetOptimize(7, 35, 7);

		_maPeriod = Param(nameof(MaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("CCI MA Period", "SMA length applied to the CCI", "CCI")
			.SetOptimize(3, 21, 3);

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Protective stop distance in absolute points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Profit target distance in absolute points", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Market data series", "General");

		Volume = 1;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_cci = null;
		_signalCci = null;
		_cciHistory.Clear();
		_prevCciMa = null;
		_prevCci = null;
		_prevSignalCci = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		_cci = new CommodityChannelIndex { Length = CciPeriod };
		_signalCci = new CommodityChannelIndex { Length = SignalCciPeriod };

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

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

			var indArea = CreateChartArea();
			if (indArea != null)
			{
				DrawIndicator(indArea, _cci);
				DrawIndicator(indArea, _signalCci);
			}
		}

		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		if (tp != null || sl != null)
			StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		// Maintain CCI history for manual SMA calculation
		_cciHistory.Add(cciValue);
		if (_cciHistory.Count > MaPeriod)
			_cciHistory.RemoveAt(0);

		// Compute SMA of CCI
		decimal? cciMa = null;
		if (_cciHistory.Count >= MaPeriod)
		{
			decimal sum = 0;
			for (int i = 0; i < _cciHistory.Count; i++)
				sum += _cciHistory[i];
			cciMa = sum / _cciHistory.Count;
		}

		if (cciMa == null || _prevCci == null || _prevCciMa == null || _prevSignalCci == null)
		{
			_prevCci = cciValue;
			_prevCciMa = cciMa;
			_prevSignalCci = signalCciValue;
			return;
		}

		// Exit logic: secondary CCI overbought/oversold reversal
		if (Position > 0 && _prevSignalCci > 100 && signalCciValue <= 100)
		{
			SellMarket(Position);
		}
		else if (Position < 0 && _prevSignalCci < -100 && signalCciValue >= -100)
		{
			BuyMarket(Math.Abs(Position));
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevCci = cciValue;
			_prevCciMa = cciMa;
			_prevSignalCci = signalCciValue;
			return;
		}

		// Entry: CCI crosses above its MA (buy) or below (sell)
		if (_prevCci < _prevCciMa && cciValue > cciMa.Value && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
		}
		else if (_prevCci > _prevCciMa && cciValue < cciMa.Value && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Position);
			SellMarket(Volume);
		}

		_prevCci = cciValue;
		_prevCciMa = cciMa;
		_prevSignalCci = signalCciValue;
	}
}