Стратегия повторяет работу советника MetaTrader «Contrarian trade MA», но реализована на высокоуровневом API StockSharp. Она комбинирует недельный контекст и фильтр входа по понедельникам, чтобы торговать против экстремальных движений. Система ждёт открытия новой торговой недели, измеряет, насколько предыдущая неделя закрылась выше максимума или ниже минимума за выбранный период, и проверяет, располагается ли предыдущая величина скользящей средней по другую сторону от текущего недельного открытия. Если после закрытия первой дневной свечи недели выполняется хотя бы одно из условий, открывается контртрендовая позиция.
Логика работает только с закрытыми свечами. Дневной ряд (по умолчанию) управляет входами и выходами, а недельный ряд поставляет уровни экстремумов и сигнал скользящей средней. Каждый раз, когда закрывается свеча понедельника, стратегия оценивает, завершилась ли прошлая неделя выше диапазона максимумов/минимумов или же предыдущая MA оказалась выше/ниже текущего открытия недели. Предполагается, что такие растянутые движения склонны к возврату к среднему в течение недели.
Принцип работы
Недельные свечи питают два индикатора:
Highest и Lowest находят максимум и минимум за CalcPeriod недель.
Настраиваемая скользящая средняя (MaPeriod, MaMethod, MaShift, AppliedPrice) рассчитывается по тем же свечам.
Дневные свечи (или любой выбранный TradeCandleType) запускают торговые решения после закрытия.
При закрытии первой свечи недели с OpenTime.DayOfWeek == Monday проверяются условия входа:
Покупка, если предыдущее недельное закрытие выше найденного максимума или если предыдущая MA больше текущего недельного открытия (цена открылась ниже средней).
Продажа, если предыдущее недельное закрытие ниже найденного минимума или если предыдущая MA меньше текущего недельного открытия (цена открылась выше средней).
Заявки выставляются по рынку (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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class contrarian_trade_ma_monday_strategy(Strategy):
def __init__(self):
super(contrarian_trade_ma_monday_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20) \
.SetDisplay("Fast Period", "Fast SMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 100) \
.SetDisplay("Slow Period", "Slow SMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(contrarian_trade_ma_monday_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(contrarian_trade_ma_monday_strategy, self).OnStarted2(time)
self._fast = SimpleMovingAverage()
self._fast.Length = self.fast_period
self._slow = SimpleMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return contrarian_trade_ma_monday_strategy()