View on GitHub

CCFp Currencies Strength Strategy

Overview

This strategy ports the classic MetaTrader CCFp expert advisor into the StockSharp high-level API. It calculates a relative strength score for the eight major currencies (USD, EUR, GBP, CHF, JPY, AUD, CAD, NZD) using ratios between fast and slow simple moving averages on the seven USD-based majors (EURUSD, GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF, USDJPY). When the difference between two currency strengths breaks above a configurable threshold, the strategy opens market positions that express the stronger currency against the weaker one.

The implementation follows the recommended high-level architecture: each instrument has its own candle subscription, indicators are bound via Bind, and order management uses RegisterOrder with market orders. Comments on executed orders reuse the original (TOPDOWN) format to keep the same bookkeeping style as the MQL version.

Required instruments

Attach the following securities to the strategy parameters:

  • EURUSD
  • GBPUSD
  • AUDUSD
  • NZDUSD
  • USDCAD
  • USDCHF
  • USDJPY

All seven pairs must share the same timeframe that is set through the Candle Type parameter.

Parameters

Parameter Description
Fast MA Fast moving average period used inside the strength calculation.
Slow MA Slow moving average period used inside the strength calculation.
Strength Step Minimal difference between two currencies that must be exceeded to trigger a new signal.
Close Opposite If enabled the strategy closes opposite positions before submitting a new order.
Candle Type Candle series processed by the indicators.
Base Volume Taken from the standard Strategy.Volume property and used for every submitted market order.

Trading logic

  1. Each of the seven USD majors is subscribed with its own pair of simple moving averages (fast and slow).
  2. Every time a finished candle arrives the strategy converts the ratio of the slow and fast averages into the same synthetic strength values produced by the original CCFp indicator.
  3. After all seven pairs are updated, the eight currency strength scores are recomputed.
  4. When the difference between a “top” currency and a “down” currency crosses the Strength Step level upward, while the top currency is rising and the down currency is falling, an opportunity is detected.
  5. The strategy opens market orders that express long exposure to the strong currency and short exposure to the weak currency:
    • If USD is the strong currency only one order is placed on the counterpart pair (for example, short EURUSD).
    • If USD is the weak currency the strategy buys the pair where the strong currency is the base (for example, long EURUSD).
    • When both currencies are non-USD the strategy sends two orders: long the top currency versus USD and short the down currency versus USD.
  6. If Close Opposite is enabled and an opposite position is still open on a target pair, the strategy sends a closing market order before entering a fresh trade.

Risk management

  • The strategy does not attach explicit stop-loss or take-profit orders; risk control is handled by the Close Opposite flag together with manual portfolio management tools.
  • Entry size is controlled by the Volume property. Configure it according to account size and desired exposure per leg.

Differences vs the original MQL implementation

  • The currency strength calculation uses StockSharp SimpleMovingAverage indicators on a single timeframe. Multi-timeframe coefficient stacking from the MQL indicator can be emulated by adjusting the Fast MA and Slow MA periods.
  • Protective stops are not automatically trailed; instead, the strategy focuses on reproducing the entry/exit logic and leaves advanced risk control to StockSharp’s portfolio layer.
  • Order routing uses the high-level RegisterOrder helper and StockSharp’s security references instead of MetaTrader trade objects.
using System;
using System.Collections.Generic;
using System.Linq;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// CCFp-style relative currency strength across the seven USD majors.
/// </summary>
public class CcfpCurrencyStrengthStrategy : Strategy
{
	private static readonly string[] _currencies = ["USD", "EUR", "GBP", "CHF", "JPY", "AUD", "CAD", "NZD"];

	private readonly StrategyParam<string> _eurusd;
	private readonly StrategyParam<string> _gbpusd;
	private readonly StrategyParam<string> _audusd;
	private readonly StrategyParam<string> _nzdusd;
	private readonly StrategyParam<string> _usdcad;
	private readonly StrategyParam<string> _usdchf;
	private readonly StrategyParam<string> _usdjpy;
	private readonly StrategyParam<int> _fastMa;
	private readonly StrategyParam<int> _slowMa;
	private readonly StrategyParam<decimal> _strengthStep;
	private readonly StrategyParam<bool> _closeOpposite;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Dictionary<string, PairState> _pairs = new(StringComparer.OrdinalIgnoreCase);
	private readonly Dictionary<string, decimal> _previousStrengths = new(StringComparer.OrdinalIgnoreCase);
	private DateTimeOffset? _lastEvaluationTime;

	public string EURUSD { get => _eurusd.Value; set => _eurusd.Value = value; }
	public string GBPUSD { get => _gbpusd.Value; set => _gbpusd.Value = value; }
	public string AUDUSD { get => _audusd.Value; set => _audusd.Value = value; }
	public string NZDUSD { get => _nzdusd.Value; set => _nzdusd.Value = value; }
	public string USDCAD { get => _usdcad.Value; set => _usdcad.Value = value; }
	public string USDCHF { get => _usdchf.Value; set => _usdchf.Value = value; }
	public string USDJPY { get => _usdjpy.Value; set => _usdjpy.Value = value; }
	public int FastMa { get => _fastMa.Value; set => _fastMa.Value = value; }
	public int SlowMa { get => _slowMa.Value; set => _slowMa.Value = value; }
	public decimal StrengthStep { get => _strengthStep.Value; set => _strengthStep.Value = value; }
	public bool CloseOpposite { get => _closeOpposite.Value; set => _closeOpposite.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public CcfpCurrencyStrengthStrategy()
	{
		_eurusd = Param(nameof(EURUSD), "EURUSD").SetDisplay("EURUSD", "EUR/USD security id.", "Securities");
		_gbpusd = Param(nameof(GBPUSD), "GBPUSD").SetDisplay("GBPUSD", "GBP/USD security id.", "Securities");
		_audusd = Param(nameof(AUDUSD), "AUDUSD").SetDisplay("AUDUSD", "AUD/USD security id.", "Securities");
		_nzdusd = Param(nameof(NZDUSD), "NZDUSD").SetDisplay("NZDUSD", "NZD/USD security id.", "Securities");
		_usdcad = Param(nameof(USDCAD), "USDCAD").SetDisplay("USDCAD", "USD/CAD security id.", "Securities");
		_usdchf = Param(nameof(USDCHF), "USDCHF").SetDisplay("USDCHF", "USD/CHF security id.", "Securities");
		_usdjpy = Param(nameof(USDJPY), "USDJPY").SetDisplay("USDJPY", "USD/JPY security id.", "Securities");
		_fastMa = Param(nameof(FastMa), 5).SetGreaterThanZero().SetDisplay("Fast MA", "Fast SMA period.", "Indicators");
		_slowMa = Param(nameof(SlowMa), 20).SetGreaterThanZero().SetDisplay("Slow MA", "Slow SMA period.", "Indicators");
		_strengthStep = Param(nameof(StrengthStep), 0.001m).SetGreaterThanZero().SetDisplay("Strength Step", "Minimum top/down strength spread.", "Signal");
		_closeOpposite = Param(nameof(CloseOpposite), true).SetDisplay("Close Opposite", "Close opposite exposure before entering.", "Trading");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Common timeframe.", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		foreach (var (_, id, _, _) in PairDefinitions())
			yield return (Resolve(id), CandleType);
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_pairs.Clear();
		_previousStrengths.Clear();
		_lastEvaluationTime = null;
	}

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

		foreach (var (name, id, baseCurrency, quoteCurrency) in PairDefinitions())
		{
			var security = Resolve(id);
			var fast = new SimpleMovingAverage { Length = FastMa };
			var slow = new SimpleMovingAverage { Length = SlowMa };
			var state = new PairState(name, security, baseCurrency, quoteCurrency);
			_pairs[name] = state;

			SubscribeCandles(CandleType, security: security)
				.Bind(fast, slow, (candle, fastValue, slowValue) =>
				{
					if (candle.State != CandleStates.Finished || !fast.IsFormed || !slow.IsFormed || fastValue == 0m || slowValue == 0m)
						return;

					state.Ratio = fastValue / slowValue;
					state.HasValue = true;
					state.LastTime = candle.OpenTime;

					if (!_pairs.Values.All(p => p.HasValue))
						return;

					var times = _pairs.Values.Select(p => p.LastTime).Distinct().ToArray();
					if (times.Length != 1 || times[0] is null || times[0] == _lastEvaluationTime)
						return;

					_lastEvaluationTime = times[0];
					Evaluate();
				})
				.Start();
		}
	}

	private void Evaluate()
	{
		// Fast/slow ratio of each currency against USD; a cross ratio is the quotient of two of them.
		var ratios = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase)
		{
			["USD"] = 1m,
		};

		foreach (var pair in _pairs.Values)
		{
			if (pair.QuoteCurrency.EqualsIgnoreCase("USD"))
				ratios[pair.BaseCurrency] = pair.Ratio;
			else
				ratios[pair.QuoteCurrency] = 1m / pair.Ratio;
		}

		var strengths = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase);

		foreach (var currency in _currencies)
		{
			var strength = 0m;

			foreach (var other in _currencies)
			{
				if (!other.EqualsIgnoreCase(currency))
					strength += ratios[currency] / ratios[other] - 1m;
			}

			strengths[currency] = strength;
		}

		var top = strengths.MaxBy(p => p.Value);
		var down = strengths.MinBy(p => p.Value);
		var spread = top.Value - down.Value;

		if (_previousStrengths.Count == strengths.Count)
		{
			var previousSpread = _previousStrengths[top.Key] - _previousStrengths[down.Key];
			var topRising = top.Value > _previousStrengths[top.Key];
			var downFalling = down.Value < _previousStrengths[down.Key];

			if (previousSpread < StrengthStep && spread >= StrengthStep && topRising && downFalling)
				TradeCurrencySpread(top.Key, down.Key);
		}

		_previousStrengths.Clear();
		foreach (var pair in strengths)
			_previousStrengths[pair.Key] = pair.Value;
	}

	private void TradeCurrencySpread(string top, string down)
	{
		if (top.EqualsIgnoreCase("USD"))
			TradeAgainstUsd(down, wantCurrencyLong: false);
		else if (down.EqualsIgnoreCase("USD"))
			TradeAgainstUsd(top, wantCurrencyLong: true);
		else
		{
			TradeAgainstUsd(top, wantCurrencyLong: true);
			TradeAgainstUsd(down, wantCurrencyLong: false);
		}
	}

	private void TradeAgainstUsd(string currency, bool wantCurrencyLong)
	{
		var pair = _pairs.Values.FirstOrDefault(p =>
			(p.BaseCurrency.EqualsIgnoreCase(currency) && p.QuoteCurrency.EqualsIgnoreCase("USD")) ||
			(p.BaseCurrency.EqualsIgnoreCase("USD") && p.QuoteCurrency.EqualsIgnoreCase(currency)));

		if (pair is null)
			return;

		var currencyIsBase = pair.BaseCurrency.EqualsIgnoreCase(currency);
		var buyPair = wantCurrencyLong == currencyIsBase;
		Submit(pair.Security, buyPair ? Sides.Buy : Sides.Sell);
	}

	private void Submit(Security security, Sides side)
	{
		var position = GetPositionValue(security, Portfolio) ?? 0m;

		if (side == Sides.Buy ? position > 0m : position < 0m)
			return;

		if (position != 0m && CloseOpposite)
			RegisterMarket(security, side, Math.Abs(position));

		RegisterMarket(security, side, Volume);
	}

	private void RegisterMarket(Security security, Sides side, decimal volume)
	{
		RegisterOrder(new Order
		{
			Security = security,
			Portfolio = Portfolio,
			Type = OrderTypes.Market,
			Side = side,
			Volume = volume,
			Comment = "(TOPDOWN)",
		});
	}

	private Security Resolve(string id)
		=> this.LookupById(id) ?? new Security { Id = id };

	private IEnumerable<(string Name, string Id, string Base, string Quote)> PairDefinitions()
	{
		yield return ("EURUSD", EURUSD, "EUR", "USD");
		yield return ("GBPUSD", GBPUSD, "GBP", "USD");
		yield return ("AUDUSD", AUDUSD, "AUD", "USD");
		yield return ("NZDUSD", NZDUSD, "NZD", "USD");
		yield return ("USDCAD", USDCAD, "USD", "CAD");
		yield return ("USDCHF", USDCHF, "USD", "CHF");
		yield return ("USDJPY", USDJPY, "USD", "JPY");
	}

	private sealed class PairState(string name, Security security, string baseCurrency, string quoteCurrency)
	{
		public string Name { get; } = name;
		public Security Security { get; } = security;
		public string BaseCurrency { get; } = baseCurrency;
		public string QuoteCurrency { get; } = quoteCurrency;
		public decimal Ratio { get; set; }
		public bool HasValue { get; set; }
		public DateTimeOffset? LastTime { get; set; }
	}
}