在 GitHub 上查看

BadOrders 策略

概述

BadOrders 策略 是 MetaTrader 4 专家顾问 BadOrders.mq4 的完整移植版本。原始脚本的目的就是展示「错误」的下单流程:

  1. 在每个新报价上来时强制平掉最近的持仓,按当前买价市价离场。
  2. 立刻在买价上方 100 个点位处挂出买入止损单。
  3. 随即把这张挂单重新改到买价下方 100 个点位的位置,故意违反经纪商关于最小距离的要求,从而触发拒单。

StockSharp 版本用高级 API 复现了这一连串动作。策略订阅 Level 1 行情来获取最新买价,每次更新都会执行「平仓—挂单—非法改价」的循环,完全贴合原始示例的教学目的。

实现细节

  • 数据来源:使用 SubscribeLevel1(),因为 MT4 版本是逐 tick 运行,并不依赖蜡烛收盘。
  • 订单管理:通过 ClosePosition() 平仓,随后用 BuyStop() 创建买入止损,再利用 ReRegisterOrder() 立即把价格改到违规位置,重现原脚本的错误示范。
  • 价格归一化:所有价格均经 Security.ShrinkPrice() 处理;MetaTrader 中的 Point 概念则由交易品种的 PriceStep 模拟,当缺少报价精度时退化到 0.0001
  • 保护逻辑:在尝试平仓前会检查是否已经存在用于离场的挂单,避免重复提交相同方向的订单。

参数

名称 说明 默认值
DistancePoints 以 MetaTrader “点” 为单位的距离,加到当前买价之上 / 之下,用于提交或改价止损单。 100

行为总结

  • 每次买价更新时,如果账户有持仓,就尝试立即平仓。
  • 平仓后会在 买价 + DistancePoints * PointValue 位置挂出买入止损。
  • 之后马上把同一张订单改到 买价 - DistancePoints * PointValue,由于价格位于买价下方,订单通常会被交易所或经纪商拒绝,这正是原始脚本想要演示的“坏单”情景。

提示:该示例仅用于教学,展示违规的下单流程,不建议在实盘环境中使用。

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();
		}
	}
}