Открыть на GitHub

Стратегия Contrarian Trade MA Monday

Стратегия повторяет работу советника MetaTrader «Contrarian trade MA», но реализована на высокоуровневом API StockSharp. Она комбинирует недельный контекст и фильтр входа по понедельникам, чтобы торговать против экстремальных движений. Система ждёт открытия новой торговой недели, измеряет, насколько предыдущая неделя закрылась выше максимума или ниже минимума за выбранный период, и проверяет, располагается ли предыдущая величина скользящей средней по другую сторону от текущего недельного открытия. Если после закрытия первой дневной свечи недели выполняется хотя бы одно из условий, открывается контртрендовая позиция.

Логика работает только с закрытыми свечами. Дневной ряд (по умолчанию) управляет входами и выходами, а недельный ряд поставляет уровни экстремумов и сигнал скользящей средней. Каждый раз, когда закрывается свеча понедельника, стратегия оценивает, завершилась ли прошлая неделя выше диапазона максимумов/минимумов или же предыдущая MA оказалась выше/ниже текущего открытия недели. Предполагается, что такие растянутые движения склонны к возврату к среднему в течение недели.

Принцип работы

  1. Недельные свечи питают два индикатора:
    • Highest и Lowest находят максимум и минимум за CalcPeriod недель.
    • Настраиваемая скользящая средняя (MaPeriod, MaMethod, MaShift, AppliedPrice) рассчитывается по тем же свечам.
  2. Дневные свечи (или любой выбранный TradeCandleType) запускают торговые решения после закрытия.
  3. При закрытии первой свечи недели с OpenTime.DayOfWeek == Monday проверяются условия входа:
    • Покупка, если предыдущее недельное закрытие выше найденного максимума или если предыдущая MA больше текущего недельного открытия (цена открылась ниже средней).
    • Продажа, если предыдущее недельное закрытие ниже найденного минимума или если предыдущая MA меньше текущего недельного открытия (цена открылась выше средней).
  4. Заявки выставляются по рынку (BuyMarket/SellMarket) с объёмом стратегии, усреднение не применяется. Одновременно может быть открыта только одна позиция.

Управление выходом

  • Фиксированный стоп рассчитывается как StopLossPips * Security.PriceStep. Когда значение больше нуля, стратегия контролирует максимумы и минимумы дневных свечей и закрывает позицию по рынку, если уровень стопа был достигнут внутри дня.
  • Временной фильтр закрывает любую открытую позицию через семь дней после входа (604800 секунд, как в оригинале). Проверка выполняется на каждой закрытой дневной свече.
  • Новая сделка не открывается, пока предыдущая полностью не закрыта.

Индикаторы и данные

  • Недельные экстремумы: индикаторы Highest и Lowest, подключённые к ряду MaCandleType (по умолчанию недельные свечи).
  • Недельная скользящая: поддерживаются методы Simple, Exponential, Smoothed, LinearWeighted. Сдвиг MaShift позволяет имитировать параметр MetaTrader, а AppliedPrice задаёт источник цены.
  • Основной таймфрейм: TradeCandleType определяет свечи, по которым оцениваются входы и стопы; по умолчанию используется дневной ряд, поэтому решение принимается после закрытия понедельника.

Параметры

Имя Тип Значение по умолчанию Описание
CalcPeriod int 4 Количество недельных свечей для расчёта максимумов и минимумов.
StopLossPips int 300 Размер стопа в шагах цены. При 0 стоп отключён.
MaPeriod int 7 Длина недельной скользящей средней.
MaShift int 0 Сдвиг скользящей средней вперёд (в барах).
MaMethod MovingAverageMethod LinearWeighted Метод сглаживания (Simple, Exponential, Smoothed, LinearWeighted).
AppliedPrice AppliedPriceType Weighted Источник цены для скользящей (Close, Open, High, Low, Median, Typical, Weighted).
TradeCandleType DataType TimeSpan.FromMinutes(5).TimeFrame() Таймфрейм, по которому принимаются решения и ведётся контроль стопа.
MaCandleType DataType TimeSpan.FromDays(7).TimeFrame() Старший таймфрейм для расчёта экстремумов и MA.

Примечания

  • Размер стопа адаптируется к инструменту: количество пунктов умножается на Security.PriceStep. Если шаг цены не задан, стоп фактически не используется.
  • Поскольку учитываются только закрытые свечи, вход происходит по цене закрытия понедельника, а не по первому тика недели, что делает результаты воспроизводимыми.
  • Стратегия работает с одной позицией: новая сделка появляется только после закрытия текущей по стопу или по сроку удержания.
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;

/// <summary>
/// Contrarian Trade MA Monday strategy using SMA crossover with mean reversion.
/// Buys when fast SMA crosses above slow SMA, sells on reverse.
/// </summary>
public class ContrarianTradeMaMondayStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private SimpleMovingAverage _fast;
	private SimpleMovingAverage _slow;

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

	/// <summary>
	/// Fast SMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow SMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ContrarianTradeMaMondayStrategy"/> class.
	/// </summary>
	public ContrarianTradeMaMondayStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast SMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow SMA 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");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fast = new SimpleMovingAverage { Length = FastPeriod };
		_slow = new SimpleMovingAverage { 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;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

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

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// SMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

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

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}