在 GitHub 上查看

Mission Impossible Power Two Open 策略

概述

本策略是 MetaTrader 智能交易程序 “Mission Impossible Power Two Open” 在 StockSharp 平台上的移植版本。策略追踪最近收盘完成的蜡烛方向,并在同一方向开启交易篮子。当价格朝不利方向移动时,会按照固定的点差网格继续加仓。每次加仓的手数会根据浮动亏损与 Power 系数成比例地放大,与原版 EA 的逻辑保持一致。所有仓位共用同一组止盈与止损,且在每次成交后都会重新计算。

交易流程

  1. 信号识别:在每根蜡烛收盘时比较上一根蜡烛的开盘价与收盘价。
    • 若收盘价高于开盘价,则生成做多信号。
    • 若收盘价低于开盘价,则生成做空信号。
    • 若收盘价等于开盘价,则不启动新的篮子。
  2. 首笔下单:若对应方向尚无活动网格,则以 BaseVolume 手数发送市价单。
  3. 网格加仓:当篮子存在时,持续评估最新成交价与当前收盘价之间的距离。
    • 多头在价格下跌至少 GridStepPips * PriceStep 后才允许再次买入。
    • 空头在价格上涨同样的距离后才允许再次卖出。
    • 每个方向的加仓次数最多为 MaxTrades 次。
  4. 手数放大:每次准备加仓时,先计算篮子的浮动亏损,将其乘以 Power * 0.0001 并加到 BaseVolume 上。最终手数会根据交易品种的 VolumeStep 进行对齐,并受 MinVolumeMaxVolume 以及参数 MaxVolume 限制。
  5. 止盈止损:每次成交后重新计算整篮仓位的目标价格。
    • 只有一笔仓位时,止盈距离为 TakeProfitFirstPips,止损距离为 StopLossPips
    • 两笔及以上仓位时,两者都基于成交量加权平均价计算,止盈距离改为 TakeProfitNextPips
    • 一旦价格触及任一目标,立即通过反向市价单平掉该方向的全部仓位。
  6. 多空独立:多头和空头篮子各自管理,可在信号频繁反转时同时持有对冲仓位。

参数

参数 类型 默认值 说明
BaseVolume decimal 0.01 新篮子的初始手数。
MaxVolume decimal 2 单笔市价单允许的最大手数。
Power decimal 13 根据浮动亏损放大手数时使用的系数。
StopLossPips int 400 共享止损距离,对应价格步长数量。
TakeProfitFirstPips int 15 仅有首笔仓位时的止盈距离。
TakeProfitNextPips int 7 网格包含两笔及以上仓位时的止盈距离。
GridStepPips int 21 触发下一次加仓所需的最小不利移动(以价格步长表示)。
MaxTrades int 16 单个方向允许的最大网格次数。
CandleType DataType TimeSpan.FromMinutes(5).TimeFrame() 用于生成信号和控制网格的蜡烛类型。

说明

  • 手数始终与交易所提供的 VolumeStep 对齐,并遵守 MinVolumeMaxVolume 限制。
  • 多头与空头状态完全独立,实现了原始 EA 中针对不同魔术号分别管理仓位的行为。
  • 每次加仓后都会重新计算止盈/止损,并将价格四舍五入到最近的 PriceStep,这与原策略频繁修改订单的方式一致。
  • 策略无需任何技术指标,完全依赖蜡烛数据和组合状态进行决策,与 MQL4 版本保持一致。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Mission Impossible Power Two Open strategy: Candle direction with EMA trend filter.
/// Buys on bullish candle when above EMA, sells on bearish candle when below EMA.
/// </summary>
public class MissionImpossiblePowerTwoOpenStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private bool _wasBullishSignal;
	private bool _hasPrevSignal;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }

	public MissionImpossiblePowerTwoOpenStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_wasBullishSignal = false;
		_hasPrevSignal = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_wasBullishSignal = false;
		_hasPrevSignal = false;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

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

		var close = candle.ClosePrice;
		var open = candle.OpenPrice;
		var bullish = close > open;
		var bearish = close < open;
		var bullishSignal = bullish && close > emaValue;
		var bearishSignal = bearish && close < emaValue;
		var crossedUp = bullishSignal && (!_hasPrevSignal || !_wasBullishSignal);
		var crossedDown = bearishSignal && (!_hasPrevSignal || _wasBullishSignal);

		if (crossedUp && Position <= 0)
			BuyMarket();
		else if (crossedDown && Position >= 0)
			SellMarket();

		if (bullishSignal || bearishSignal)
		{
			_wasBullishSignal = bullishSignal;
			_hasPrevSignal = true;
		}
	}
}