Стратегия Cronex DeMarker Crossover
Обзор
Стратегия Cronex DeMarker повторяет индикатор Cronex DeMarker из MetaTrader и превращает его в полностью автоматизированную торговую систему. Исходный индикатор строит осциллятор DeMarker и две линейно взвешенные скользящие средние (LWMA). В стратегии воспроизводится та же связка: пересечения сглаженных линий интерпретируются как сигналы на покупку или продажу, что позволяет быстро реагировать на смену импульса между покупателями и продавцами.
Построение индикаторов
- Осциллятор DeMarker — оценивает отношение текущей свечи к предыдущей:
- Если текущий максимум выше предыдущего, положительное давление равно разнице максимумов, иначе берётся ноль.
- Если текущий минимум ниже предыдущего, отрицательное давление равно расстоянию между минимумами, иначе берётся ноль.
- Суммы положительного и отрицательного давления за
DeMarkerPeriod свечей формируют значение осциллятора deMax / (deMax + deMin).
- Быстрая LWMA — линейно взвешенная скользящая среднего с периодом
FastMaPeriod применяется к значениям DeMarker, подчёркивая последние изменения осциллятора.
- Медленная LWMA — ещё одна линейно взвешенная средняя с периодом
SlowMaPeriod сглаживает тот же поток значений DeMarker и служит подтверждающей линией.
Каждая завершённая свеча передаётся через эту цепочку индикаторов, полностью повторяя вычисления буферов из исходного MQ4-файла.
Торговая логика
- Дождаться, пока осциллятор DeMarker и обе LWMA полностью сформируются.
- После закрытия каждой свечи вычислить новое значение DeMarker и обновить обе скользящие средние.
- Определить пересечения между быстрой и медленной LWMA:
- Бычье пересечение — быстрая LWMA переходит снизу вверх через медленную. Стратегия закрывает короткую позицию и открывает длинную.
- Медвежье пересечение — быстрая LWMA опускается сверху вниз ниже медленной. Стратегия закрывает длинную позицию и открывает короткую.
- Заявки не отправляются, если стратегия ещё не сформирована, находится офлайн или торговля запрещена.
При смене сигнала позиция разворачивается немедленно: объём рыночного ордера увеличивается, чтобы закрыть противоположную позицию и открыть новую.
Параметры
| Параметр |
Описание |
Значение по умолчанию |
DeMarkerPeriod |
Количество свечей в расчёте осциллятора DeMarker. |
25 |
FastMaPeriod |
Период быстрой линейно взвешенной скользящей средней. |
14 |
SlowMaPeriod |
Период медленной линейно взвешенной скользящей средней. |
25 |
CandleType |
Тип свечей (таймфрейм или другой DataType), с которым работает стратегия. |
Таймфрейм 1 час |
Особенности реализации
- Используется высокоуровневый метод
SubscribeCandles. Индикаторы обновляются только после закрытия свечи (Finished), чтобы исключить перерисовку.
- Применяются встроенные индикаторы StockSharp:
DeMarker и WeightedMovingAverage, что гарантирует соответствие MQ4 версии.
- Автоматически создаётся область графика с ценой, осциллятором и обеими LWMA для визуального контроля сигналов.
- Метод
StartProtection() вызывается при запуске один раз, соблюдая правила проекта по защите позиций.
Использование
- Подключите стратегию к нужному инструменту и задайте желаемый тип свечей (например, часовые).
- Настройте периоды DeMarker и скользящих средних, чтобы воспроизвести оригинальный индикатор или подготовить параметры для оптимизации.
- Запустите стратегию. Торговля начнётся после полной формировки индикаторов и разрешения сделок.
- Отслеживайте график: на нём отображаются свечи, осциллятор и LWMA, по которым генерируются входы.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that replicates the Cronex DeMarker indicator setup and trades crossovers of its smoothed values.
/// </summary>
public class CronexDeMarkerCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _deMarkerPeriod;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<DataType> _candleType;
private DeMarker _deMarker;
private WeightedMovingAverage _fastMa;
private WeightedMovingAverage _slowMa;
private decimal? _previousFast;
private decimal? _previousSlow;
/// <summary>
/// DeMarker indicator period.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// Fast linear weighted moving average period.
/// </summary>
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
/// <summary>
/// Slow linear weighted moving average period.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CronexDeMarkerCrossoverStrategy"/>.
/// </summary>
public CronexDeMarkerCrossoverStrategy()
{
_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 25)
.SetRange(2, 150)
.SetDisplay("DeMarker Period", "Length of the DeMarker oscillator", "Indicators")
;
_fastMaPeriod = Param(nameof(FastMaPeriod), 14)
.SetRange(2, 100)
.SetDisplay("Fast LWMA Period", "Length of the fast linear weighted moving average", "Indicators")
;
_slowMaPeriod = Param(nameof(SlowMaPeriod), 25)
.SetRange(2, 150)
.SetDisplay("Slow LWMA Period", "Length of the slow linear weighted moving average", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame of processed candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_deMarker = null;
_fastMa = null;
_slowMa = null;
_previousFast = null;
_previousSlow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Instantiate indicators matching the original MetaTrader logic.
_deMarker = new DeMarker
{
Length = DeMarkerPeriod
};
_fastMa = new WeightedMovingAverage
{
Length = FastMaPeriod
};
_slowMa = new WeightedMovingAverage
{
Length = SlowMaPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _deMarker);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle)
{
// Only act on completed candles to avoid repainting effects.
if (candle.State != CandleStates.Finished)
return;
if (_deMarker is null || _fastMa is null || _slowMa is null)
return;
// Update the DeMarker oscillator with the full candle data.
var deMarkerResult = _deMarker.Process(new CandleIndicatorValue(_deMarker, candle));
if (deMarkerResult.IsEmpty)
{
return;
}
var deMarkerValue = deMarkerResult.GetValue<decimal>();
// Smooth the oscillator with linear weighted moving averages.
var fastResult = _fastMa.Process(new DecimalIndicatorValue(_fastMa, deMarkerValue, candle.OpenTime) { IsFinal = true });
if (fastResult.IsEmpty) return;
var fastValue = fastResult.GetValue<decimal>();
var slowResult = _slowMa.Process(new DecimalIndicatorValue(_slowMa, deMarkerValue, candle.OpenTime) { IsFinal = true });
if (slowResult.IsEmpty) return;
var slowValue = slowResult.GetValue<decimal>();
// Ensure all indicators accumulated enough samples.
if (!_deMarker.IsFormed || !_fastMa.IsFormed || !_slowMa.IsFormed)
{
_previousFast = fastValue;
_previousSlow = slowValue;
return;
}
var previousFast = _previousFast;
var previousSlow = _previousSlow;
_previousFast = fastValue;
_previousSlow = slowValue;
if (!previousFast.HasValue || !previousSlow.HasValue)
return;
// Check readiness and trading permissions before sending orders.
// indicators formed check removed
var crossUp = previousFast.Value <= previousSlow.Value && fastValue > slowValue;
var crossDown = previousFast.Value >= previousSlow.Value && fastValue < slowValue;
if (crossUp)
{
// Close short exposure and establish a long position.
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (crossDown)
{
// Close long exposure and establish a short position.
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DeMarker, WeightedMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class cronex_de_marker_crossover_strategy(Strategy):
"""Cronex DeMarker crossover strategy. Smooths the DeMarker oscillator with
fast and slow WMA and trades on their crossover."""
def __init__(self):
super(cronex_de_marker_crossover_strategy, self).__init__()
self._de_marker_period = self.Param("DeMarkerPeriod", 25) \
.SetDisplay("DeMarker Period", "Length of the DeMarker oscillator", "Indicators")
self._fast_ma_period = self.Param("FastMaPeriod", 14) \
.SetDisplay("Fast LWMA Period", "Length of the fast linear weighted moving average", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 25) \
.SetDisplay("Slow LWMA Period", "Length of the slow linear weighted moving average", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame of processed candles", "General")
self._de_marker = None
self._fast_ma = None
self._slow_ma = None
self._previous_fast = None
self._previous_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DeMarkerPeriod(self):
return self._de_marker_period.Value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
def OnReseted(self):
super(cronex_de_marker_crossover_strategy, self).OnReseted()
self._de_marker = None
self._fast_ma = None
self._slow_ma = None
self._previous_fast = None
self._previous_slow = None
def OnStarted2(self, time):
super(cronex_de_marker_crossover_strategy, self).OnStarted2(time)
self._de_marker = DeMarker()
self._de_marker.Length = self.DeMarkerPeriod
self._fast_ma = WeightedMovingAverage()
self._fast_ma.Length = self.FastMaPeriod
self._slow_ma = WeightedMovingAverage()
self._slow_ma.Length = self.SlowMaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._de_marker is None or self._fast_ma is None or self._slow_ma is None:
return
dm_input = CandleIndicatorValue(self._de_marker, candle)
dm_input.IsFinal = True
dm_result = self._de_marker.Process(dm_input)
if dm_result.IsEmpty:
return
dm_value = float(dm_result)
fast_result = process_float(self._fast_ma, dm_value, candle.OpenTime, True)
if fast_result.IsEmpty:
return
fast_value = float(fast_result)
slow_result = process_float(self._slow_ma, dm_value, candle.OpenTime, True)
if slow_result.IsEmpty:
return
slow_value = float(slow_result)
if not self._de_marker.IsFormed or not self._fast_ma.IsFormed or not self._slow_ma.IsFormed:
self._previous_fast = fast_value
self._previous_slow = slow_value
return
previous_fast = self._previous_fast
previous_slow = self._previous_slow
self._previous_fast = fast_value
self._previous_slow = slow_value
if previous_fast is None or previous_slow is None:
return
cross_up = previous_fast <= previous_slow and fast_value > slow_value
cross_down = previous_fast >= previous_slow and fast_value < slow_value
if cross_up:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif cross_down:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return cronex_de_marker_crossover_strategy()