GitHub で見る

ForexProfitBoost 戦略

概要

ForexProfitBoost 戦略は、高速指数移動平均(EMA)と低速単純移動平均(SMA)を組み合わせたリバーサルトレーディングシステムです。戦略は高速EMAが低速SMAをクロスするのを待ち、その後クロスの方向に逆らってトレードを行い、価格の戻りを期待します。リスク管理のために、絶対価格ポイントでのオプションのストップロスとテイクプロフィットレベルを設定できます。

インジケーター

  • EMA(高速): デフォルト期間 7。
  • SMA(低速): デフォルト期間 21。

トレードルール

  1. 選択したローソク足の時間軸をサブスクライブする。
  2. 各完成ローソク足でEMAとSMAの値を計算する。
  3. 高速EMAが低速SMAを下方向にクロスしたとき:
    • ショートポジションをクローズする。
    • 新しいロングポジションを開く。
  4. 高速EMAが低速SMAを上方向にクロスしたとき:
    • ロングポジションをクローズする。
    • 新しいショートポジションを開く。
  5. 指定されている場合、エントリー価格に対するストップロスとテイクプロフィットレベルを適用する。

パラメーター

名前 説明 デフォルト
FastPeriod 高速EMAの期間。 7
SlowPeriod 低速SMAの期間。 21
StopLoss 価格ポイントでのストップロス距離。 1000
TakeProfit 価格ポイントでのテイクプロフィット距離。 2000
CandleType 計算に使用する時間軸。 1時間

注意事項

  • この戦略はStockSharpの高レベルAPIを使用し、過去のコレクションを保存しません。
  • トレードはローソク足が完成した後にのみ成行注文で実行されます。
  • ソースコード内のすべてのコメントは、要件に従って英語で記述されています。
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>
/// Forex Profit Boost reversal strategy.
/// Opens long when fast EMA crosses below slow SMA.
/// Opens short when fast EMA crosses above slow SMA.
/// Uses optional stop-loss and take-profit in price points.
/// </summary>
public class ForexProfitBoostStrategy : Strategy
{
	private static readonly TimeSpan _signalCooldown = TimeSpan.FromHours(18);

	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<DataType> _candleType;

	private bool? _wasFastAboveSlow;
	private decimal _entryPrice;
	private bool _isLongPosition;
	private DateTime _lastSignalTime;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow SMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price points.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in price points.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of <see cref="ForexProfitBoostStrategy"/>.
	/// </summary>
	public ForexProfitBoostStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA Period", "Period of the fast EMA", "Parameters")
			
			.SetOptimize(5, 15, 1);

		_slowPeriod = Param(nameof(SlowPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA Period", "Period of the slow SMA", "Parameters")
			
			.SetOptimize(10, 40, 5);

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetDisplay("Stop Loss", "Stop loss distance in price points", "Risk Management")
			
			.SetOptimize(500m, 2000m, 100m);

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetDisplay("Take Profit", "Take profit distance in price points", "Risk Management")
			
			.SetOptimize(1000m, 4000m, 100m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

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

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

		_wasFastAboveSlow = null;
		_entryPrice = 0m;
		_isLongPosition = false;
		_lastSignalTime = default;
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowSma = new SimpleMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(fastEma, slowSma, ProcessCandle)
			.Start();

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var isFastAboveSlow = fastValue > slowValue;

		if (_wasFastAboveSlow is null)
		{
			_wasFastAboveSlow = isFastAboveSlow;
			return;
		}

		var isBullishSignal = _wasFastAboveSlow == true && !isFastAboveSlow;
		var isBearishSignal = _wasFastAboveSlow == false && isFastAboveSlow;

		if ((isBullishSignal || isBearishSignal) && _lastSignalTime != default && candle.CloseTime < _lastSignalTime + _signalCooldown)
		{
			_wasFastAboveSlow = isFastAboveSlow;
			CheckRisk(candle.ClosePrice);
			return;
		}

		// Detect crossover and trade against the direction (reversal)
		if (isBullishSignal)
		{
			// Fast EMA crossed below slow SMA -> open long
			if (Position <= 0)
			{
				var volume = Position < 0 ? Math.Abs(Position) + Volume : Volume;

				BuyMarket(volume);
				_entryPrice = candle.ClosePrice;
				_isLongPosition = true;
				_lastSignalTime = candle.CloseTime;
			}
		}
		else if (isBearishSignal)
		{
			// Fast EMA crossed above slow SMA -> open short
			if (Position >= 0)
			{
				var volume = Position > 0 ? Position + Volume : Volume;

				SellMarket(volume);
				_entryPrice = candle.ClosePrice;
				_isLongPosition = false;
				_lastSignalTime = candle.CloseTime;
			}
		}

		// Update crossover state
		_wasFastAboveSlow = isFastAboveSlow;

		// Check stop loss and take profit
		CheckRisk(candle.ClosePrice);
	}

	private void CheckRisk(decimal currentPrice)
	{
		if (Position == 0 || _entryPrice == 0)
			return;

		if (_isLongPosition)
		{
			if (_stopLoss.Value > 0m && currentPrice <= _entryPrice - _stopLoss.Value)
			{
				SellMarket(Position);
				_entryPrice = 0m;
				return;
			}

			if (_takeProfit.Value > 0m && currentPrice >= _entryPrice + _takeProfit.Value)
			{
				SellMarket(Position);
				_entryPrice = 0m;
			}
		}
		else
		{
			if (_stopLoss.Value > 0m && currentPrice >= _entryPrice + _stopLoss.Value)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = 0m;
				return;
			}

			if (_takeProfit.Value > 0m && currentPrice <= _entryPrice - _takeProfit.Value)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = 0m;
			}
		}
	}
}