Разворот по свечам "Молот"
Свеча типа "молот" часто отмечает внутридневной разворот после ослабления давления продаж. Стратегия ищет эту формацию и открывает длинную позицию в ожидании отскока.
Тестирование показывает среднегодичную доходность около 64%. Стратегию лучше запускать на рынке Форекс.
Для входа требуется нижняя тень не менее чем вдвое длиннее тела и практически отсутствие верхней тени. После обнаружения паттерна система покупает заданным объёмом и ждёт профита или стоп-лосса.
Детали
- Условия входа: Обнаружена свеча-молот.
- Длинные/короткие позиции: Только длинные.
- Условия выхода: Стоп-лосс или произвольный выход.
- Стопы: Да.
- Значения по умолчанию:
CandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Pattern
- Направление: Long
- Индикаторы: Candlestick
- Стопы: Да
- Сложность: Базовая
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hammer Candle strategy.
/// Enters long on hammer pattern (long lower shadow, small upper shadow).
/// Enters short on inverted hammer (long upper shadow, small lower shadow).
/// Exits via SMA crossover.
/// </summary>
public class HammerCandleStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private int _cooldown;
/// <summary>
/// MA Period for exit.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Candle type and timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HammerCandleStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var bodySize = Math.Abs(candle.OpenPrice - candle.ClosePrice);
var lowerShadow = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;
var upperShadow = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
var isHammer = bodySize > 0 && lowerShadow > bodySize * 2m && upperShadow < bodySize * 0.5m;
var isInvertedHammer = bodySize > 0 && upperShadow > bodySize * 2m && lowerShadow < bodySize * 0.5m;
if (Position == 0 && isHammer && candle.ClosePrice < smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && isInvertedHammer && candle.ClosePrice > smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class hammer_candle_strategy(Strategy):
"""
Hammer Candle strategy.
Enters long on hammer pattern (long lower shadow, small upper shadow).
Enters short on inverted hammer (long upper shadow, small lower shadow).
Exits via SMA crossover.
"""
def __init__(self):
super(hammer_candle_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hammer_candle_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(hammer_candle_strategy, self).OnStarted2(time)
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
return
body_size = abs(float(candle.OpenPrice) - float(candle.ClosePrice))
lower_shadow = min(float(candle.OpenPrice), float(candle.ClosePrice)) - float(candle.LowPrice)
upper_shadow = float(candle.HighPrice) - max(float(candle.OpenPrice), float(candle.ClosePrice))
is_hammer = body_size > 0 and lower_shadow > body_size * 2.0 and upper_shadow < body_size * 0.5
is_inverted_hammer = body_size > 0 and upper_shadow > body_size * 2.0 and lower_shadow < body_size * 0.5
close = float(candle.ClosePrice)
sv = float(sma_val)
cd = self._cooldown_bars.Value
if self.Position == 0 and is_hammer and close < sv:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and is_inverted_hammer and close > sv:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return hammer_candle_strategy()