CCI MACD Scalper — это порт советника MetaTrader 5 «CCI + MACD Scalper» на высокоуровневый API StockSharp. В конверсии сохранён исходный набор индикаторов: EMA в качестве фильтра тренда, CCI для нулевого пробоя и MACD для подтверждения импульса. Логика управления капиталом адаптирована к StockSharp: объём рассчитывается от капитала портфеля, слишком короткие стопы отклоняются, а опциональный трейлинг-стоп может зафиксировать часть прибыли при первом переносе. Пятиизначный «таймер» реализован через паузу в пять свечей после каждой сделки — точно так же, как EventSetTimer в MQL.
Логика стратегии
Индикаторы и подготовка данных
Свечи — все вычисления ведутся по одной настраиваемой серии свечей. Стратегия анализирует только закрытые свечи, чтобы исключить перерисовку сигналов.
EMA(34) — экспоненциальная средняя закрытия выступает трендовым фильтром. Для покупки требуется закрытие выше предыдущего значения EMA, для продажи — ниже.
CCI(50) — служит триггером импульса. Ожидается пересечение нулевой линии, которое произошло на двух последних завершённых свечах (текущая свеча лишь подтверждает условия и не участвует в сравнении).
MACD(12,26,9) — главная линия и сигнальная линия должны оставаться по одну сторону от нуля на двух предыдущих свечах. Дополнительно требуется пересечение сигнальной линии через основную между этими свечами (снизу вверх для лонга, сверху вниз для шорта).
Буферы экстремумов — последние пять завершённых максимумов и минимумов определяют уровни стоп-лосса. Для лонга используется минимум, для шорта — максимум, что полностью повторяет вызовы iLowest/iHighest со смещением в одну свечу.
Условия входа
Торговое окно — сделки открываются только если время закрытия свечи попадает в диапазон [MinHour, MaxHour] локального времени терминала.
Пауза после сделки — после открытия позиции стратегия ждёт пять длительностей выбранного таймфрейма перед новой попыткой входа, полностью повторяя MQL-таймер.
Длинные позиции
Чистая позиция не должна быть длиннее нуля (Position <= 0).
Цена закрытия выше предыдущего значения EMA.
CCI пересёк ноль снизу вверх на двух последних закрытых свечах.
MACD зафиксировал «бычье» пересечение сигнальной линии под нулём за те же две свечи.
Стоп-лосс на последнем минимуме удовлетворяет ограничению по минимальной дистанции.
Короткие позиции
Чистая позиция не должна быть короче нуля (Position >= 0).
Цена закрытия ниже предыдущего значения EMA.
CCI пересёк ноль сверху вниз на двух последних свечах.
MACD показал «медвежье» пересечение сигнальной линии над нулём.
Стоп-лосс на последнем максимуме проходит проверку минимальной дистанции.
Управление риском и сделками
Динамический объём — размер позиции вычисляется от параметра RiskPercent и текущей стоимости портфеля. Денежный риск на единицу объёма находится через расстояние до стопа, шаг цены и стоимость шага. Итог округляется к шагу объёма инструмента и ограничивается минимальными/максимальными значениями площадки.
Стоп и тейк-профит — стоп-лосс располагается на выбранном экстремуме и отклоняется, если расстояние меньше MinimalStopLossPoints. Тейк-профит рассчитывается как entry ± RiskReward × stopDistance, аналогично оригинальному советнику.
Трейлинг-стоп (опционально) — при включении стоп смещается на TrailingStopPoints, как только цена закрытия проходит дальше предыдущего стопа. Первый перенос сопровождается частичной фиксацией половины исходного объёма, что повторяет поведение функции PositionClosePartial в MetaTrader.
Принудительные выходы — в лонге позиция закрывается при пробое стопа (минимум свечи) или достижении тейка (максимум). Для шорта используется симметричная проверка максимумов и минимумов.
Параметры
Параметр
Описание
Значение по умолчанию
CandleType
Таймфрейм, по которому ведутся расчёты.
Свечи 15 минут
RiskPercent
Процент капитала портфеля, задействованный в расчёте объёма.
2%
RiskReward
Коэффициент «прибыль/риск» для тейк-профита.
1.5
EmaPeriod
Период EMA для фильтра тренда.
34
CciPeriod
Период Commodity Channel Index.
50
MinHour
Нижняя граница часа (включительно) для открытия сделок.
0
MaxHour
Верхняя граница часа (включительно) для открытия сделок.
24
MinimalStopLossPoints
Минимально допустимое расстояние от входа до стопа в пунктах.
100
UseTrailingStop
Включает модуль трейлинг-стопа и частичную фиксацию.
Выкл.
TrailingStopPoints
Дистанция трейлинг-стопа в пунктах.
100
Дополнительные замечания
Пересчёт пунктов в цену использует PriceStep инструмента. Если шаг цены неизвестен, применяется единичное значение.
Стоимость портфеля берётся из Portfolio.CurrentValue с резервом на BeginValue. При отсутствии обеих величин стратегия возвращается к базовому значению Volume.
Python-реализация не предусмотрена — в пакете присутствует только версия на C#.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "CCI MACD Scalper" MetaTrader expert.
/// Uses CCI zero-line crossover with EMA trend filter for scalping entries.
/// </summary>
public class CciMacdScalperStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cciPeriod;
private ExponentialMovingAverage _ema;
private CommodityChannelIndex _cci;
private decimal? _prevCci;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public CciMacdScalperStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for scalping", "General");
_emaPeriod = Param(nameof(EmaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI period for zero-line crosses", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
_cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_cci.IsFormed)
{
_prevCci = cciValue;
return;
}
if (_prevCci is null)
{
_prevCci = cciValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var close = candle.ClosePrice;
// CCI crosses back above the oversold zone with trend confirmation -> buy
var cciCrossUp = _prevCci.Value <= -50m && cciValue > -50m;
// CCI crosses back below the overbought zone with trend confirmation -> sell
var cciCrossDown = _prevCci.Value >= 50m && cciValue < 50m;
if (cciCrossUp && close > emaValue)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (cciCrossDown && close < emaValue)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevCci = cciValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_ema = null;
_cci = null;
_prevCci = null;
base.OnReseted();
}
}
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 ExponentialMovingAverage, CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_macd_scalper_strategy(Strategy):
def __init__(self):
super(cci_macd_scalper_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._ema_period = self.Param("EmaPeriod", 21)
self._cci_period = self.Param("CciPeriod", 14)
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def CciPeriod(self):
return self._cci_period.Value
@CciPeriod.setter
def CciPeriod(self, value):
self._cci_period.Value = value
def OnReseted(self):
super(cci_macd_scalper_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(cci_macd_scalper_strategy, self).OnStarted2(time)
self._prev_cci = None
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, cci, self._process_candle).Start()
def _process_candle(self, candle, ema_value, cci_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
cci_val = float(cci_value)
close = float(candle.ClosePrice)
if self._prev_cci is None:
self._prev_cci = cci_val
return
# CCI crosses back above oversold zone with trend confirmation -> buy
cci_cross_up = self._prev_cci <= -50.0 and cci_val > -50.0
# CCI crosses back below overbought zone with trend confirmation -> sell
cci_cross_down = self._prev_cci >= 50.0 and cci_val < 50.0
if cci_cross_up and close > ema_val:
if self.Position <= 0:
self.BuyMarket()
elif cci_cross_down and close < ema_val:
if self.Position >= 0:
self.SellMarket()
self._prev_cci = cci_val
def CreateClone(self):
return cci_macd_scalper_strategy()