Double Up — это точный порт советника MetaTrader DoubleUp.mq4. В основе лежит совместный анализ осциллятора CCI и основной линии MACD. Как только оба индикатора достигают одинаковой экстремальной зоны, стратегия «заряжается» на сделку в противоположном направлении. После возвращения CCI к центральной зоне алгоритм либо закрывает все короткие позиции и открывает новую покупку, либо закрывает все длинные позиции и открывает продажу.
Объём новой позиции рассчитывается по экспоненциальной схеме (базовый_объём * 2^счётчикУбыточных). Последовательность убыточных закрытий увеличивает показатель степени, а прибыльное закрытие переносит накопленный буфер ожидания в счётчик и тем самым задаёт стартовый множитель для следующего входа. Такой же механизм использовался в исходном коде через переменные pos и wait.
Логика торговли
Подписка на поток свечей одного таймфрейма и вычисление CCI (по умолчанию период 8) и основной линии MACD (быстрая EMA 13, медленная 33, сигнальная 2).
Значение MACD умножается на один миллион, чтобы сопоставить его масштаб с порогом CCI.
Если обе величины превышают +Threshold, активируется сигнал для будущей продажи. Если обе величины ниже -Threshold, активируется сигнал для будущей покупки.
Ожидаемая покупка исполняется, когда CCI возвращается ниже +Threshold. Ожидаемая продажа исполняется, когда CCI опускается ниже -Threshold при активном флаге продажи — порядок полностью повторяет оригинал.
Перед открытием новой позиции стратегия принудительно закрывает противоположное направление и дожидается завершения всех сделок на выход.
Выходы осуществляются рыночными заявками при смене сигнала. Фактический результат сделки используется для корректировки мартингейла.
Управление объёмом
Убыточное закрытие увеличивает счётчик убыточных сделок. Как только он достигает PreWait, значение добавляется в буфер ожидания, а сам счётчик сбрасывается.
Прибыльное закрытие переносит (усечённое) значение буфера ожидания в счётчик убыточных сделок и очищает буфер. Следующая позиция будет открыта с объёмом базовый_объём * 2^счётчик.
Буфер ожидания инициализируется параметром InitialWait и далее изменяется описанными правилами.
Параметры
Имя
Значение по умолчанию
Описание
CciPeriod
8
Период индикатора Commodity Channel Index.
Threshold
230
Абсолютный порог для определения экстремумов.
MacdFastPeriod
13
Период быстрой EMA в расчёте MACD.
MacdSlowPeriod
33
Период медленной EMA в расчёте MACD.
MacdSignalPeriod
2
Период сигнальной EMA, нужен для совместимости с оригиналом.
BaseVolume
0.01
Базовый объём до применения степени мартингейла.
InitialWait
0
Стартовое значение буфера ожидания (wait в исходном скрипте).
PreWait
2
Минимальное число подряд убыточных выходов перед переносом счётчика в буфер.
BackShift
0
Сдвиг по истории для индикаторов. В порте поддерживается только ноль.
CandleType
15-минутные свечи
Тип свечей для подписки; при необходимости измените под рабочий таймфрейм.
Примечания
Поддерживается только значение BackShift = 0, что соответствует типичной настройке оригинального советника.
Все входы и выходы выполняются рыночными ордерами; при необходимости подключите защитные стопы и тейк-профиты.
Стратегия удваивает объём после убыточных сделок, поэтому заранее рассчитайте требования к марже и ограничения по риску.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class DoubleUpStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
private int _cooldownRemaining;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DoubleUpStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 100).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = default;
_prevSlow = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevFast = fast;
_prevSlow = slow;
}
}