GitHub で見る

スイートスポットエクストリーム戦略

Sweet Spot Extreme は、StockSharp の高レベルの API 上に構築された MetaTrader 4 エキスパート アドバイザ「Sweet_Spot_Extreme.mq4」の直接ポートです。この戦略は、15 分ローソク足の 2 つの指数移動平均と 30 分コモディティ チャネル インデックス (CCI) フィルターを組み合わせて、既存のトレンド内での強い引き戻しを探します。ポジションのサイジングは、連敗後の MetaTrader スタイルのロット削減など、元のリスク管理を反映しています。

コアロジック

  1. トレンドの傾きの確認。 主要な EMA (MaPeriod、デフォルト 85) と終値 EMA (CloseMaPeriod、デフォルト 70) には 15 分間の中央値が入力されます。セットアップが長い場合は、両方の EMA が上向きに傾く必要があります。短いセットアップでは、両方を下向きに傾斜させる必要があります。
  2. CCI 枯渇フィルター。 2 番目のキャンドル サブスクリプション (デフォルトでは 30 分) により、CciPeriod CCI が稼働します。ロング取引は CCI が BuyCciLevel (-200) を下回った場合にのみ発生しますが、ショート取引では CCI が SellCciLevel (+200) を超える必要があります。
  3. ピラミッド制限。 合計ネット ポジションは MaxTradesPerSymbol × volume を超えることはできません。新しい信号が現れると、この戦略は反対側のエクスポージャを閉じてから、信号方向の許容容量まで加算します。
  4. 終了。 ポジションは、トレンド EMA がそのスロープ アドバンテージを失ったとき (MQL 条件 MA <= MAprevious を反映)、または価格がポジションに有利な StopPoints インスツルメント ポイント移動した後にクローズされます。

リスク管理

  • リスクベースのボリューム。 注文サイズのデフォルトは Portfolio.CurrentValue × MaximumRisk ÷ price です。株式情報が欠落している場合、エンジンは Lots パラメータ (または戦略 Volume) に戻ります。
  • 連敗調整。 2 回以上連続して負けた取引の後、新しい注文サイズは volume × losses ÷ DecreaseFactor だけ減少し、MQL ヘルパー LotsOptimized() に一致します。
  • 正規化。 最終的なボリュームは、楽器の VolumeStep に合わせて調整され、MinVolume によって境界が設定され、指定されている場合は Security.MaxVolume によってクリップされます。

パラメーター

名前 デフォルト 説明
MaxTradesPerSymbol 3 方向ごとに許可される集約エントリの最大数。
Lots 1 ポートフォリオの資本が利用できない場合のフォールバック固定ロットサイズ。
MaximumRisk 0.05 新しい取引の規模を決定するために使用される資本の割合。
DecreaseFactor 6 連続負けの後に次の注文を縮小するディバイダー。
StopPoints 10 器具ポイント単位での利益目標距離。無効にするには、0 に設定します。
MaPeriod 85 EMA 期間は、トレンドの傾きチェックのために 15 分足のローソク足に適用されます。
CloseMaPeriod 70 EMA 期間は、クローズ平滑化フィルターの 15 分足のローソク足に適用されます。
CciPeriod 12 30 分の CCI フィルターに使用されるルックバック。
BuyCciLevel -200 ロングエントリーには売られ過ぎの CCI しきい値が必要です。
SellCciLevel 200 ショートエントリーには買われすぎのしきい値 CCI が必要です。
MinVolume 0.1 正規化後に許可される最小ボリューム。
TrendCandleType 15m EMA の計算に使用されるローソク足のタイプ (中央価格)。
CciCandleType 30m CCI フィルターに使用されるキャンドル タイプ。

注意事項と制限事項

  • StockSharp はネッティング モードで動作するため、複数の MT4 チケットは 1 つの集約されたポジションとして表されます。したがって、MaxTradesPerSymbol ガードは、個々の注文をカウントするのではなく、正味のエクスポージャを制限します。
  • 元の EA は、サイズ設定を AccountFreeMargin に依存していました。このポートは Portfolio.CurrentValue で近似します。ブローカーの契約仕様に合わせて MaximumRisk または Lots を調整します。
  • データ ソースで両方のキャンドル サブスクリプションが有効になっていることを確認してください。有効になっていないと、EMA または CCI フィルターが形成されず、戦略はアイドル状態のままになります。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA slope + CCI filter strategy.
/// Buys when EMA slope is up and CCI is oversold.
/// Sells when EMA slope is down and CCI is overbought.
/// Exits on EMA slope reversal.
/// </summary>
public class SweetSpotExtremeStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _buyCciLevel;
	private readonly StrategyParam<decimal> _sellCciLevel;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevEma;
	private decimal _prevPrevEma;
	private bool _hasPrevEma;

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

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

	public decimal BuyCciLevel
	{
		get => _buyCciLevel.Value;
		set => _buyCciLevel.Value = value;
	}

	public decimal SellCciLevel
	{
		get => _sellCciLevel.Value;
		set => _sellCciLevel.Value = value;
	}

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

	public SweetSpotExtremeStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetDisplay("EMA Period", "Trend EMA period", "Indicators");

		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetDisplay("CCI Period", "CCI lookback", "Indicators");

		_buyCciLevel = Param(nameof(BuyCciLevel), -50m)
			.SetDisplay("Buy CCI", "Oversold CCI level for buy", "Indicators");

		_sellCciLevel = Param(nameof(SellCciLevel), 50m)
			.SetDisplay("Sell CCI", "Overbought CCI level for sell", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle series", "General");
	}

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

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

		_prevEma = 0m;
		_prevPrevEma = 0m;
		_hasPrevEma = false;
	}

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

		_prevEma = 0;
		_prevPrevEma = 0;
		_hasPrevEma = false;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var cci = new CommodityChannelIndex { Length = CciPeriod };

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

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

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

		if (!_hasPrevEma)
		{
			_prevPrevEma = emaValue;
			_prevEma = emaValue;
			_hasPrevEma = true;
			return;
		}

		var slopeUp = emaValue > _prevEma && _prevEma > _prevPrevEma;
		var slopeDown = emaValue < _prevEma && _prevEma < _prevPrevEma;

		// Entry
		if (slopeUp && cciValue <= BuyCciLevel && Position <= 0)
		{
			BuyMarket();
		}
		else if (slopeDown && cciValue >= SellCciLevel && Position >= 0)
		{
			SellMarket();
		}
		// Exit on slope reversal
		else if (Position > 0 && emaValue < _prevEma)
		{
			SellMarket();
		}
		else if (Position < 0 && emaValue > _prevEma)
		{
			BuyMarket();
		}

		_prevPrevEma = _prevEma;
		_prevEma = emaValue;
	}
}