GitHub で見る

Extreme EA (StockSharp Conversion)

The Extreme EA strategy is a trend-following Expert Advisor originally written for MetaTrader. It combines two moving averages with a Commodity Channel Index (CCI) filter and an adaptive money-management module. This port keeps the trading logic intact while exposing all important knobs through StockSharp's high-level API. The strategy operates on finished candles only and is compatible with multiple timeframes by running the moving averages and CCI on independent candle subscriptions.

Strategy Overview

  1. Trend filter: Two moving averages are calculated on the configurable MaCandleType. The fast average tracks short-term momentum while the slow average defines the dominant trend slope. The strategy checks the slope of the slow average using the previous two values to mimic the original CopyBuffer array offsets from the MQL code.
  2. Momentum filter: The CCI is evaluated on its own timeframe (CciCandleType) and price source. The latest completed value is cached and reused until a new CCI candle appears, which matches the behaviour of the MetaTrader buffers.
  3. Entry rules:
    • Enter long when the slow MA is rising, the fast MA is rising, and the CCI drops below the lower level.
    • Enter short when the slow MA is falling, the fast MA is falling, and the CCI climbs above the upper level.
  4. Exit rules:
    • Close all longs if the slow MA stops rising.
    • Close all shorts if the slow MA stops falling.

Risk Management

  • MaximumRisk controls the target position size based on current portfolio equity and the latest price. If the computed volume is zero or the portfolio values are unavailable, the strategy falls back to the configured Volume or the exchange minimum.
  • DecreaseFactor reduces the calculated volume after two or more consecutive losing trades. The reduction mirrors the original formula lot = lot - lot * losses / DecreaseFactor.
  • HistoryDays caps how long a loss streak is remembered. If a closing trade happens after the specified number of days, the streak is reset before applying the reduction.
  • MaxPositions limits pyramiding by bounding the net exposure per direction. When the cap is reached, new entries are suppressed until the exposure drops.

Parameters

Name Type Default Description
MaximumRisk decimal 0.05 Fraction of equity used to size each new trade.
DecreaseFactor decimal 6 Loss-streak reduction factor. Set to 0 to disable.
HistoryDays int 60 Number of days preserved when counting consecutive losses.
MaxPositions int 3 Maximum simultaneous entries per direction.
FastMaPeriod int 15 Period for the fast moving average.
SlowMaPeriod int 75 Period for the slow moving average.
CciPeriod int 12 Lookback length for the CCI.
CciUpperLevel decimal 50 Upper CCI threshold used for shorts.
CciLowerLevel decimal -50 Lower CCI threshold used for longs.
MaCandleType DataType 15m Timeframe for both moving averages and execution.
CciCandleType DataType 30m Timeframe for the CCI filter.
MaMethod MaMethod Exponential Smoothing method (Simple, Exponential, Smoothed, LinearWeighted).
MaPriceMode AppliedPriceMode Median Price input for the moving averages.
CciPriceMode AppliedPriceMode Typical Price input for the CCI.

Implementation Notes

  • The strategy subscribes to the moving-average timeframe once and optionally to a second subscription for the CCI. When both timeframes match, a single subscription feeds both components, reproducing the original single-chart workflow.
  • Previous indicator values are cached in private fields to emulate the ma_slow_array[1], ma_slow_array[2], and ma_fast_array[0] comparisons without resorting to manual indicator buffers.
  • Position sizing is normalised against the instrument volume step, minimum, and maximum to avoid rejected orders.
  • The risk module records entry and exit prices to estimate realised PnL per completed position, which replaces the HistoryDealGet loop used in MetaTrader.

Differences from the MQL Version

  • MetaTrader-specific functions such as FreeMarginCheck, MarginCheck, and HistorySelect are approximated with StockSharp portfolio metrics and the internal loss streak tracker.
  • The StockSharp port operates on net positions. Closing orders flatten the entire exposure in the relevant direction, aligning with the consolidated position model.
  • Logging routines from the original EA were omitted in favour of StockSharp's built-in diagnostics.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Extreme EA strategy using fast/slow EMA crossover with CCI filter.
/// Buys when both EMAs rising and CCI below lower level (oversold bounce).
/// Sells when both EMAs falling and CCI above upper level (overbought reversal).
/// </summary>
public class ExtremeEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciUpperLevel;
	private readonly StrategyParam<decimal> _cciLowerLevel;

	private ExponentialMovingAverage _fastMa;
	private ExponentialMovingAverage _slowMa;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevFast2;
	private decimal _prevSlow2;
	private bool _hasPrev;

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

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// CCI indicator length.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// Upper CCI threshold for sell entries.
	/// </summary>
	public decimal CciUpperLevel
	{
		get => _cciUpperLevel.Value;
		set => _cciUpperLevel.Value = value;
	}

	/// <summary>
	/// Lower CCI threshold for buy entries.
	/// </summary>
	public decimal CciLowerLevel
	{
		get => _cciLowerLevel.Value;
		set => _cciLowerLevel.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public ExtremeEaStrategy()
	{
		_fastMaPeriod = Param(nameof(FastMaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast EMA period", "Indicator");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow EMA period", "Indicator");

		_cciPeriod = Param(nameof(CciPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI lookback period", "Indicator");

		_cciUpperLevel = Param(nameof(CciUpperLevel), 50m)
			.SetDisplay("CCI Upper", "Upper CCI threshold for sell", "Levels");

		_cciLowerLevel = Param(nameof(CciLowerLevel), -50m)
			.SetDisplay("CCI Lower", "Lower CCI threshold for buy", "Levels");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fastMa = null;
		_slowMa = null;
		_prevFast = 0;
		_prevSlow = 0;
		_prevFast2 = 0;
		_prevSlow2 = 0;
		_hasPrev = false;
	}

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

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fastMa, _slowMa, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fastMa.IsFormed || !_slowMa.IsFormed)
		{
			_prevFast2 = _prevFast;
			_prevSlow2 = _prevSlow;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			_hasPrev = true;
			return;
		}

		if (!_hasPrev)
		{
			_prevFast2 = _prevFast;
			_prevSlow2 = _prevSlow;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			_hasPrev = true;
			return;
		}

		var slowIsRising = _prevSlow > _prevSlow2;
		var slowIsFalling = _prevSlow < _prevSlow2;
		var fastIsRising = fastValue > _prevFast;
		var fastIsFalling = fastValue < _prevFast;

		// Buy: fast crosses above slow
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
		}
		// Sell: fast crosses below slow
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
		}

		_prevFast2 = _prevFast;
		_prevSlow2 = _prevSlow;
		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}