Стратегия переносит советник MetaTrader «AH HM CCI» на StockSharp. Она отслеживает свечные паттерны «молот» и «висячий человек»,
а вход подтверждает индикатор Commodity Channel Index (CCI). Дополнительная проверка отсекает слабые паттерны и помогает
отрабатывать только те развороты, где импульс действительно меняется.
Обработка ведётся только по завершённым свечам. Короткая простая скользящая средняя (SMA) задаёт тренд: предыдущая свеча должна
быть молотом в нисходящем тренде с перепроданным CCI для входа в лонг либо висячим человеком в восходящем тренде с перекупленным
CCI для входа в шорт. Закрытия позиций происходят при пересечении CCI настроенных уровней, что повторяет «систему голосов»
оригинального эксперта.
Логика торговли
Трендовый фильтр. Средняя точка предыдущей свечи должна располагаться ниже (для лонга) или выше (для шорта) SMA по ценам
закрытия.
Тело полностью находится в верхней трети диапазона свечи.
Есть ценовой разрыв между открытиями/закрытиями двух последних свечей.
Контекст соответствует типу паттерна (молот внизу тренда, висячий человек вверху).
Подтверждение CCI. Значение CCI на предыдущей свече должно быть ниже порога для лонга или выше порога для шорта. По
умолчанию используются значения 40 и 60 как в шаблоне MetaTrader.
Выход из позиций. Текущие позиции закрываются, когда CCI пересекает один из уровней выхода. Пересечение снизу вверх
закрывает лонг, сверху вниз — шорт.
Параметры
Имя
Описание
Значение по умолчанию
CandleType
Тип и таймфрейм свечей для анализа.
TimeSpan.FromMinutes(15)
CciPeriod
Количество баров в расчёте CCI.
11
MaPeriod
Период SMA для трендового фильтра.
5
LongConfirmationThreshold
Максимальное значение CCI для подтверждения молота.
40
ShortConfirmationThreshold
Минимальное значение CCI для подтверждения висячего человека.
60
ExitUpperThreshold
Уровень CCI, при пересечении снизу которого закрывается позиция.
70
ExitLowerThreshold
Дополнительный уровень CCI для раннего выхода.
30
Все параметры доступны для оптимизации, а пороги допускают отрицательные значения, что позволяет адаптировать стратегию под
другие рынки и волатильность.
Управление заявками
Входы выполняются рыночными ордерами объёмом Volume + |Position|, чтобы переворот выполнялся одной сделкой.
Выходы основаны только на пересечениях CCI — так сохраняется близость к оригинальному советнику. При необходимости можно
добавить StartProtection со стопами и тейк-профитами.
Рекомендации
Используйте стратегию на ликвидных инструментах с чёткими хвостами свечей и ценовыми разрывами.
Для старших таймфреймов увеличивайте CciPeriod и MaPeriod, чтобы сгладить шум.
Уменьшение LongConfirmationThreshold или увеличение ShortConfirmationThreshold сократит число входов, но повысит их
избирательность.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Hammer/Hanging Man + CCI strategy.
/// Buys on hammer with negative CCI, sells on hanging man with positive CCI.
/// </summary>
public class HammerHangingManCciStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _cciLevel;
private readonly StrategyParam<int> _signalCooldownCandles;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
public decimal CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public HammerHangingManCciStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI period", "Indicators");
_cciLevel = Param(nameof(CciLevel), 100m)
.SetDisplay("CCI Level", "CCI threshold", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candlesSinceTrade = SignalCooldownCandles;
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(cci, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var range = candle.HighPrice - candle.LowPrice;
if (range <= 0 || body <= 0) return;
var upperShadow = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
var lowerShadow = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;
var isHammer = lowerShadow > body * 2.5m && upperShadow < body * 0.5m;
var isHangingMan = upperShadow > body * 2.5m && lowerShadow < body * 0.5m;
if (isHammer && cciValue < -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (isHangingMan && cciValue > CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
}
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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class hammer_hanging_man_cci_strategy(Strategy):
def __init__(self):
super(hammer_hanging_man_cci_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._cci_period = self.Param("CciPeriod", 14)
self._cci_level = self.Param("CciLevel", 100.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles_since_trade = 6
@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
@CciPeriod.setter
def CciPeriod(self, value):
self._cci_period.Value = value
@property
def CciLevel(self):
return self._cci_level.Value
@CciLevel.setter
def CciLevel(self, value):
self._cci_level.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(hammer_hanging_man_cci_strategy, self).OnReseted()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(hammer_hanging_man_cci_strategy, self).OnStarted2(time)
self._candles_since_trade = self.SignalCooldownCandles
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
cci_val = float(cci_value)
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
rng = float(candle.HighPrice) - float(candle.LowPrice)
if rng <= 0 or body <= 0:
return
upper_shadow = float(candle.HighPrice) - max(float(candle.OpenPrice), float(candle.ClosePrice))
lower_shadow = min(float(candle.OpenPrice), float(candle.ClosePrice)) - float(candle.LowPrice)
is_hammer = lower_shadow > body * 2.5 and upper_shadow < body * 0.5
is_hanging_man = upper_shadow > body * 2.5 and lower_shadow < body * 0.5
if is_hammer and cci_val < -self.CciLevel and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif is_hanging_man and cci_val > self.CciLevel and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return hammer_hanging_man_cci_strategy()