Auf GitHub ansehen

BadOrders Strategy

Overview

The BadOrders Strategy is a direct port of the MetaTrader 4 expert advisor BadOrders.mq4. The original script was intentionally written to demonstrate how incorrect order management leads to rejected trades. On every incoming tick it:

  1. Forcefully closes the most recently opened position at the current bid price.
  2. Places a new buy stop 100 points above the bid.
  3. Immediately modifies that pending order to sit 100 points below the bid, violating broker distance rules and provoking an error.

The StockSharp version reproduces this behaviour with the high-level API. It subscribes to Level 1 quotes to monitor the best bid and replays the same close–place–invalidate cycle whenever a quote arrives.

Implementation details

  • Data stream: SubscribeLevel1() is used because the MT4 script reacts to every tick rather than candle completions.
  • Order management: Open positions are closed with the ClosePosition() helper. Pending stops are managed through BuyStop() and ReRegisterOrder() so we can immediately move the stop order to an illegal price, mimicking the broken workflow of the source code.
  • Price normalisation: All prices are normalised via Security.ShrinkPrice() and the MetaTrader concept of Point is emulated through the instrument PriceStep. When no tick size is available the strategy falls back to 0.0001.
  • Protective logic: Before calling ClosePosition() the code checks for existing liquidation orders to avoid stacking duplicate exit requests.

Parameters

Name Description Default
DistancePoints Distance in MetaTrader “points” added above and below the current bid when placing or re-registering the stop order. 100

Behaviour summary

  • Whenever the bid changes the strategy attempts to flatten any open position.
  • A buy stop is submitted at bid + DistancePoints * PointValue after the position is closed.
  • The same order is immediately re-registered to bid - DistancePoints * PointValue, which violates exchange rules and is expected to fail — precisely mirroring the intentional mistakes in BadOrders.mq4.

Note: This project exists purely for parity with the MT4 sample and is not intended for live trading.

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bad Orders strategy - ATR breakout with EMA filter.
/// Buys when price breaks above EMA + ATR threshold.
/// Sells when price breaks below EMA - ATR threshold.
/// </summary>
public class BadOrdersStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public BadOrdersStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "EMA lookback", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR lookback", "Indicators");

		_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
			.SetDisplay("ATR Multiplier", "ATR breakout multiplier", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); }

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

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, atr, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;
		var upper = ema + atr * AtrMultiplier;
		var lower = ema - atr * AtrMultiplier;

		if (close > upper && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (close < lower && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}