Открыть на GitHub

Стратегия Forex Profit Boost

Обзор

Forex Profit Boost — это контртрендовая торговая система, основанная на пересечении быстрой экспоненциальной скользящей средней (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 час

Примечания

  • Стратегия использует высокоуровневый API StockSharp и не сохраняет исторические коллекции.
  • Сделки выполняются рыночными ордерами только после формирования свечи.
  • Все комментарии в исходном коде написаны на английском языке, как требуется.
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;
			}
		}
	}
}