Перенос советника MetaTrader 4 "Anubis" на высокоуровневый API StockSharp.
Использует фильтр CCI на 4-часовых свечах в сочетании с пересечениями MACD на 15-минутных данных.
Реализует адаптивный объём, виртуальный стоп-лосс, перевод в безубыток, выходы по ATR и по стандартному отклонению.
Логика стратегии
Данные
Основной таймфрейм: 15 минут (SignalCandleType) для MACD и ATR.
Старший таймфрейм: 4 часа (TrendCandleType) для CCI и стандартного отклонения.
Индикаторы
CommodityChannelIndex с настраиваемым периодом на 4H свечах.
StandardDeviation длиной 30 на 4H закрытиях для расчёта дистанции тейк-профита.
MovingAverageConvergenceDivergenceSignal на 15M свечах с настраиваемыми периодами EMA.
AverageTrueRange длиной 12 на 15M свечах для волатильностных выходов.
Входы
Short: CCI выше CciThreshold, MACD за предыдущие две свечи формирует мёртвый крест (переход MACD ниже сигнальной линии) при положительном значении, нет открытых лонгов, и цена отошла от последнего входа не менее чем на PriceFilterPoints пунктов.
Long: зеркальное условие с CCI ниже -CciThreshold, бычьим пересечением MACD в отрицательной зоне, отсутствием шортов и соблюдением фильтра по дистанции.
Управление рисками
Базовый объём задаётся VolumeValue, масштабируется по величине капитала (×2 выше 14k, ×3.2 выше 22k) и умножается на LossFactor после убыточной сделки.
Максимальное число одновременных сделок в каждом направлении ограничено MaxLongTrades и MaxShortTrades.
Жёсткий стоп контролируется виртуально на расстоянии StopLossPoints * PriceStep от средней цены входа.
Безубыток активируется после прибыли в BreakevenPoints пунктов и закрывает позицию при возврате цены к входу.
Выходы
Тейк-профит по стандартному отклонению закрывает позицию при движении StdDevMultiplier * StdDev в нужную сторону.
Агрессивный выход срабатывает, если предыдущая свеча превышает CloseAtrMultiplier * ATR.
Выход по замедлению MACD требует и достаточной прибыли (ProfitThresholdPoints), и разворота наклона MACD (сравнение значений одной и двух свечей назад).
Дополнительно позиция закрывается при срабатывании виртуального стопа или возврате к цене входа после активации безубытка.
Параметры
Имя
Описание
VolumeValue
Базовый объём заявки.
CciThreshold
Абсолютный порог фильтра CCI на 4H.
CciPeriod
Период индикатора CCI на 4H.
StopLossPoints
Дистанция стоп-лосса в пунктах.
BreakevenPoints
Прибыль в пунктах для перевода в безубыток.
MacdFastPeriod
Быстрая EMA MACD.
MacdSlowPeriod
Медленная EMA MACD.
MacdSignalPeriod
Сигнальная EMA MACD.
LossFactor
Множитель объёма после убыточной сделки.
MaxShortTrades
Максимум одновременных коротких позиций.
MaxLongTrades
Максимум одновременных длинных позиций.
CloseAtrMultiplier
Множитель ATR для агрессивного выхода.
ProfitThresholdPoints
Дополнительная прибыль в пунктах для выхода по MACD.
StdDevMultiplier
Множитель стандартного отклонения для тейк-профита.
PriceFilterPoints
Минимальная дистанция между повторными входами.
SignalCandleType
Основной таймфрейм для MACD и ATR.
TrendCandleType
Старший таймфрейм для CCI и стандартного отклонения.
Примечания
Требуется корректно заполненный Security.PriceStep, иначе пунктовые параметры нельзя перевести в цену.
Стратегия использует виртуальные стопы и тейки, не размещая реальные отложенные заявки, что соответствует оригинальному советнику.
Версия на Python намеренно опущена по условиям задачи.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Anubis CCI + MACD strategy.
/// Buys when CCI crosses above 0 and MACD histogram is positive.
/// Sells when CCI crosses below 0 and MACD histogram is negative.
/// </summary>
public class AnubisCciMacdStrategy : Strategy
{
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevCci;
private bool _hasPrev;
public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AnubisCciMacdStrategy()
{
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetDisplay("CCI Period", "CCI lookback period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = 0m;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var cci = new CommodityChannelIndex { Length = CciPeriod };
var macd = new MovingAverageConvergenceDivergenceSignal();
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(cci, macd, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue cciValue, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!cciValue.IsFinal || !macdValue.IsFinal)
return;
var cci = cciValue.ToDecimal();
var macdVal = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdVal.Macd is not decimal macd || macdVal.Signal is not decimal signal)
return;
var histogram = macd - signal;
if (!_hasPrev)
{
_prevCci = cci;
_hasPrev = true;
return;
}
// CCI crosses above 0 with bullish MACD
if (_prevCci <= 0 && cci > 0 && histogram > 0 && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// CCI crosses below 0 with bearish MACD
else if (_prevCci >= 0 && cci < 0 && histogram < 0 && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevCci = cci;
}
}
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 CommodityChannelIndex, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class anubis_cci_macd_strategy(Strategy):
"""Anubis CCI + MACD strategy.
Buys when CCI crosses above 0 and MACD histogram is positive.
Sells when CCI crosses below 0 and MACD histogram is negative."""
def __init__(self):
super(anubis_cci_macd_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 14) \
.SetDisplay("CCI Period", "CCI lookback period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_cci = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciPeriod(self):
return self._cci_period.Value
def OnReseted(self):
super(anubis_cci_macd_strategy, self).OnReseted()
self._prev_cci = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(anubis_cci_macd_strategy, self).OnStarted2(time)
self._has_prev = False
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
macd = MovingAverageConvergenceDivergenceSignal()
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(cci, macd, self._process_candle).Start()
def _process_candle(self, candle, cci_value, macd_value):
if candle.State != CandleStates.Finished:
return
if not cci_value.IsFinal or not macd_value.IsFinal:
return
cci = float(cci_value)
macd_raw = macd_value.Macd if hasattr(macd_value, 'Macd') else None
signal_raw = macd_value.Signal if hasattr(macd_value, 'Signal') else None
if macd_raw is None or signal_raw is None:
return
histogram = float(macd_raw) - float(signal_raw)
if not self._has_prev:
self._prev_cci = cci
self._has_prev = True
return
# CCI crosses above 0 with bullish MACD
if self._prev_cci <= 0 and cci > 0 and histogram > 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# CCI crosses below 0 with bearish MACD
elif self._prev_cci >= 0 and cci < 0 and histogram < 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_cci = cci
def CreateClone(self):
return anubis_cci_macd_strategy()