在 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 小时

备注

  • 策略使用 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;
			}
		}
	}
}