Открыть на GitHub

Стратегия Mission Impossible Power Two Open

Обзор

Это порт экспертного советника MetaTrader «Mission Impossible Power Two Open» на платформу StockSharp. Стратегия отслеживает направление последней завершённой свечи и открывает в том же направлении корзину сделок. Если цена движется против открытого направления, стратегия наращивает позицию по фиксированной сетке шагов. Объём каждой новой сделки увеличивается вместе с плавающим убытком корзины в соответствии с коэффициентом Power. После каждого исполнения заново рассчитываются общий тейк-профит и стоп-лосс, чтобы вся корзина закрывалась одновременно.

Логика работы

  1. Определение сигнала. На закрытии каждой свечи стратегия сравнивает цену закрытия предыдущей свечи с ценой её открытия.
    • Если закрытие выше открытия — активируется сигнал на покупку.
    • Если закрытие ниже открытия — активируется сигнал на продажу.
    • Если свеча «внутренняя» (закрытие равно открытию), новых корзин не создаётся.
  2. Первый вход. При отсутствии активной сетки в направлении сигнала отправляется рыночная заявка объёмом BaseVolume.
  3. Сетка усреднения. При наличии открытых сделок стратегия отслеживает расстояние между последней ценой входа и текущим закрытием.
    • Для покупок новая сделка появляется, когда цена упала минимум на GridStepPips * PriceStep ниже последнего входа.
    • Для продаж добавление происходит после роста цены на ту же величину выше последнего входа.
    • Количество усреднений ограничено параметром MaxTrades на каждое направление.
  4. Динамический объём. Перед каждым новым ордером вычисляется плавающий убыток корзины, умножается на Power * 0.0001 и добавляется к BaseVolume. Получившийся объём округляется к шагу объёма площадки, ограничивается биржевыми пределами и параметром MaxVolume.
  5. Управление выходом. После каждого исполнения заново рассчитываются общие целевые уровни:
    • Для одной сделки тейк-профит находится на расстоянии TakeProfitFirstPips, стоп-лосс — на StopLossPips от цены входа.
    • Для двух и более сделок уровни привязываются к объёмно-взвешенной средней цене корзины с использованием TakeProfitNextPips и StopLossPips.
    • При достижении любого уровня вся корзина закрывается рыночной сделкой противоположного направления.
  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 инструмента и ограничиваются доступными MinVolume и MaxVolume, если площадка возвращает такие значения.
  • Лонговая и шортовая логики полностью независимы, что соответствует поведению 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;
		}
	}
}