Auf GitHub ansehen

Exp Cronex AO Strategy

This strategy ports the MetaTrader expert advisor Exp_CronexAO to the StockSharp high-level API. The original robot trades crosses between the two lines of the Cronex Awesome Oscillator (AO). The StockSharp version subscribes to a configurable candle series, computes the AO, smooths it twice with moving averages to reproduce the Cronex lines, and opens or closes positions when the fast line crosses the slow line.

Trading logic

  1. Build the Awesome Oscillator from the selected candles.
  2. Smooth the oscillator twice with simple moving averages. The first smoothing creates the "fast" Cronex line, the second smoothing produces the "signal" line.
  3. Look back SignalBar completed candles and compare the Cronex lines on that bar and on the previous one.
  4. A buy signal appears when the fast line is above the slow line and performed an upward cross on the lookback bar. The strategy optionally closes any short position and, if allowed, opens a long market order.
  5. A sell signal mirrors the previous rule: the fast line must be below the slow line and must have crossed downward on the lookback bar. The strategy optionally closes any long position and, if allowed, opens a short market order.
  6. Stop-loss and take-profit levels, expressed in instrument points, are attached to the resulting position whenever a new trade is opened.

Only one net position is maintained. When the direction changes, the strategy combines the volume required to close the opposite position with the new trade volume to emulate MetaTrader's netting mode.

Parameters

Parameter Description
CandleType Data type of the candles used for the Cronex AO calculations. The default is an 8-hour time frame.
FastPeriod Length of the first smoothing applied to the Awesome Oscillator.
SlowPeriod Length of the second smoothing applied to the fast line.
SignalBar Number of completed bars back that must contain the cross signal. The strategy also inspects the following bar to confirm the direction.
BuyOpenEnabled / SellOpenEnabled Enable or disable opening of long or short positions.
BuyCloseEnabled / SellCloseEnabled Control whether opposite positions may be closed when an inverse signal appears.
TakeProfit Profit target in points, applied after every new entry if greater than zero.
StopLoss Protective stop in points, also applied after every new entry if greater than zero.

Risk management

The stop-loss and take-profit distances mimic the point-based inputs of the MetaTrader version. They are recalculated every time a new trade is sent so that protective orders always match the current net position size.

Differences from the MetaTrader version

  • The StockSharp implementation uses simple moving averages for both Cronex smoothing stages. The original XMA implementation allows several smoothing methods, but the default configuration corresponds to the simple average that is reproduced here.
  • Slippage and money-management routines from the TradeAlgorithms library are not replicated. Position sizing is controlled via the standard Volume property.
  • Trade execution relies on StockSharp's netting behaviour. When the direction reverses, a single market order is issued with enough volume to flatten and flip the position in one step, mirroring the MT5 netting account logic.
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;

public class ExpCronexAOStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ExpCronexAOStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}