Small Inside Bar Strategy ищет компактную фигуру «внутренний бар» и после её формирования реагирует на изменение настроения между двумя последовательными свечами. Изначальный советник MetaTrader 5 был перенесён на высокоуровневый API StockSharp, поэтому расчёты ведутся исключительно по закрытым свечам. Подход подойдёт трейдерам, предпочитающим входы на пробой после фаз низкой волатильности.
Определение паттерна
Стратегия анализирует две последние закрытые свечи:
Условие внутреннего бара – текущая завершённая свеча должна полностью находиться внутри диапазона предыдущей.
Фильтр по размеру – диапазон «материнской» свечи (две свечи назад) обязан как минимум в заданное число раз превышать диапазон внутреннего бара. Значение по умолчанию – 2:1.
Направленные фильтры –
Для покупки требуется бычий внутренний бар, расположенный в нижней половине материнской свечи, и медвежья материнская свеча.
Для продажи требуется медвежий внутренний бар, расположенный в верхней половине материнской свечи, и бычья материнская свеча.
Опция реверса меняет местами трактовку длинных и коротких сигналов, сохраняя геометрию свечей.
AnySignal – отправлять рыночную заявку при каждом сигнале. При наличии противоположной позиции она частично перекрывается, поскольку в StockSharp используется неттинг.
SwingWithRefill – перед входом закрывать противоположное направление и разрешать добавления к текущему тренду.
SingleSwing – держать не более одной позиции в рынке; новые сигналы игнорируются, пока активна позиция в ту же сторону.
Длинные и короткие входы можно включать отдельно. Реверс просто меняет, какая комбинация условий генерирует покупку или продажу.
Параметры
Имя
Значение по умолчанию
Описание
CandleType
Таймфрейм 1 час
Тип свечей, по которым ищется паттерн.
RangeRatioThreshold
2.0
Минимальное отношение диапазона материнской свечи к внутреннему бару.
EnableLong
true
Разрешить длинные сделки.
EnableShort
true
Разрешить короткие сделки.
ReverseSignals
false
Инвертировать направления сигналов.
OpenMode
SwingWithRefill
Сценарий обработки существующей позиции при новом сигнале.
Логика торговли
Подписаться на выбранную свечную серию и ждать закрытия баров.
Хранить две последние завершённые свечи для оценки паттерна.
При совпадении всех условий определить направление входа, при необходимости применив реверс.
Проверить доступность торговли через IsFormedAndOnlineAndAllowTrading и разрешение нужного направления.
Рассчитать объём заявки с учётом OpenMode и отправить рыночную сделку с базовым объёмом стратегии.
Обновить внутреннюю историю свечей, чтобы следующая проверка использовала актуальные значения.
Особенности реализации
Вызывается StartProtection() без параметров, что активирует стандартный риск-менеджер StockSharp (уровни SL/TP можно добавить отдельно).
В памяти хранятся только две свечи, дополнительные коллекции не используются.
Вся логика строится на закрытых свечах, что соответствует рекомендациям по использованию высокоуровневого API.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Implements the "Small Inside Bar" pattern strategy converted from MetaTrader 5.
/// The strategy searches for an inside bar with a small range compared to the mother bar
/// and opens positions following the direction of the pattern conditions.
/// </summary>
public class SmallInsideBarStrategy : Strategy
{
/// <summary>
/// Defines how the strategy manages simultaneous entries.
/// </summary>
public enum SmallInsideBarOpenModes
{
/// <summary>
/// Open a new position on every signal without forcing opposite positions to close.
/// </summary>
AnySignal,
/// <summary>
/// Close opposite positions first and allow adding to the current swing direction.
/// </summary>
SwingWithRefill,
/// <summary>
/// Maintain a single position in the market and ignore additional entries while it is active.
/// </summary>
SingleSwing
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _rangeRatioThreshold;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<bool> _reverseSignals;
private readonly StrategyParam<SmallInsideBarOpenModes> _openMode;
private ICandleMessage _previousCandle;
private ICandleMessage _twoBackCandle;
/// <summary>
/// Type of candles used for pattern detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum ratio between the mother bar range and the inside bar range.
/// </summary>
public decimal RangeRatioThreshold
{
get => _rangeRatioThreshold.Value;
set => _rangeRatioThreshold.Value = value;
}
/// <summary>
/// Allow long trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Allow short trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Reverse long and short signals.
/// </summary>
public bool ReverseSignals
{
get => _reverseSignals.Value;
set => _reverseSignals.Value = value;
}
/// <summary>
/// Mode for handling position entries.
/// </summary>
public SmallInsideBarOpenModes OpenMode
{
get => _openMode.Value;
set => _openMode.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public SmallInsideBarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame used for pattern detection", "General");
_rangeRatioThreshold = Param(nameof(RangeRatioThreshold), 2.25m)
.SetGreaterThanZero()
.SetDisplay("Range Ratio", "Minimum mother-to-inside bar range ratio", "Pattern")
.SetOptimize(1.5m, 3m, 0.25m);
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow bullish trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow bearish trades", "Trading");
_reverseSignals = Param(nameof(ReverseSignals), false)
.SetDisplay("Reverse Signals", "Invert long and short signals", "Trading");
_openMode = Param(nameof(OpenMode), SmallInsideBarOpenModes.SwingWithRefill)
.SetDisplay("Open Mode", "Position management mode", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousCandle = null;
_twoBackCandle = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_previousCandle == null)
{
_previousCandle = candle;
return;
}
if (_twoBackCandle == null)
{
_twoBackCandle = _previousCandle;
_previousCandle = candle;
return;
}
var insideHigh = _previousCandle.HighPrice;
var insideLow = _previousCandle.LowPrice;
var motherHigh = _twoBackCandle.HighPrice;
var motherLow = _twoBackCandle.LowPrice;
if (insideHigh <= insideLow || motherHigh <= motherLow)
{
ShiftHistory(candle);
return;
}
if (!(insideHigh < motherHigh && insideLow > motherLow))
{
ShiftHistory(candle);
return;
}
var insideRange = insideHigh - insideLow;
var motherRange = motherHigh - motherLow;
var ratio = insideRange == 0 ? decimal.MaxValue : motherRange / insideRange;
if (ratio <= RangeRatioThreshold)
{
ShiftHistory(candle);
return;
}
var midpoint = (motherHigh + motherLow) / 2m;
var bullishInside = _previousCandle.ClosePrice > _previousCandle.OpenPrice && insideHigh < midpoint && _twoBackCandle.ClosePrice < _twoBackCandle.OpenPrice;
var bearishInside = _previousCandle.ClosePrice < _previousCandle.OpenPrice && insideLow < midpoint && _twoBackCandle.ClosePrice > _twoBackCandle.OpenPrice;
if (ReverseSignals)
{
(bullishInside, bearishInside) = (bearishInside, bullishInside);
}
var shouldOpenLong = bullishInside && EnableLong;
var shouldOpenShort = bearishInside && EnableShort;
if (shouldOpenLong)
{
var volume = CalculateOrderVolume(true);
if (volume > 0)
BuyMarket(volume);
}
if (shouldOpenShort)
{
var volume = CalculateOrderVolume(false);
if (volume > 0)
SellMarket(volume);
}
ShiftHistory(candle);
}
private decimal CalculateOrderVolume(bool isLong)
{
var baseVolume = Volume;
if (baseVolume <= 0)
return 0;
var position = Position;
if (isLong)
{
if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position > 0)
return 0;
if (position < 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
baseVolume += Math.Abs(position);
}
else
{
if (OpenMode == SmallInsideBarOpenModes.SingleSwing && position < 0)
return 0;
if (position > 0 && OpenMode != SmallInsideBarOpenModes.AnySignal)
baseVolume += Math.Abs(position);
}
return baseVolume;
}
private void ShiftHistory(ICandleMessage candle)
{
// Keep track of the last two finished candles for pattern evaluation.
_twoBackCandle = _previousCandle;
_previousCandle = candle;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class small_inside_bar_strategy(Strategy):
def __init__(self):
super(small_inside_bar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._range_ratio_threshold = self.Param("RangeRatioThreshold", 2.25)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._reverse_signals = self.Param("ReverseSignals", False)
self._prev_candle_high = None
self._prev_candle_low = None
self._prev_candle_open = None
self._prev_candle_close = None
self._two_back_high = None
self._two_back_low = None
self._two_back_open = None
self._two_back_close = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(small_inside_bar_strategy, self).OnStarted2(time)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._prev_candle_high is None:
self._cache_prev(candle)
return
if self._two_back_high is None:
self._shift_history(candle)
return
inside_high = self._prev_candle_high
inside_low = self._prev_candle_low
mother_high = self._two_back_high
mother_low = self._two_back_low
if inside_high <= inside_low or mother_high <= mother_low:
self._shift_history(candle)
return
if not (inside_high < mother_high and inside_low > mother_low):
self._shift_history(candle)
return
inside_range = inside_high - inside_low
mother_range = mother_high - mother_low
ratio = mother_range / inside_range if inside_range != 0 else 1e18
if ratio <= self._range_ratio_threshold.Value:
self._shift_history(candle)
return
midpoint = (mother_high + mother_low) / 2.0
bullish_inside = (self._prev_candle_close > self._prev_candle_open and
inside_high < midpoint and
self._two_back_close < self._two_back_open)
bearish_inside = (self._prev_candle_close < self._prev_candle_open and
inside_low < midpoint and
self._two_back_close > self._two_back_open)
if self._reverse_signals.Value:
bullish_inside, bearish_inside = bearish_inside, bullish_inside
should_open_long = bullish_inside and self._enable_long.Value
should_open_short = bearish_inside and self._enable_short.Value
if should_open_long:
volume = self._calc_order_volume(True)
if volume > 0:
self.BuyMarket(volume)
if should_open_short:
volume = self._calc_order_volume(False)
if volume > 0:
self.SellMarket(volume)
self._shift_history(candle)
def _calc_order_volume(self, is_long):
base_volume = float(self.Volume)
if base_volume <= 0:
return 0
position = self.Position
if is_long:
if position < 0:
base_volume += abs(position)
else:
if position > 0:
base_volume += abs(position)
return base_volume
def _cache_prev(self, candle):
self._prev_candle_high = float(candle.HighPrice)
self._prev_candle_low = float(candle.LowPrice)
self._prev_candle_open = float(candle.OpenPrice)
self._prev_candle_close = float(candle.ClosePrice)
def _shift_history(self, candle):
self._two_back_high = self._prev_candle_high
self._two_back_low = self._prev_candle_low
self._two_back_open = self._prev_candle_open
self._two_back_close = self._prev_candle_close
self._cache_prev(candle)
def OnReseted(self):
super(small_inside_bar_strategy, self).OnReseted()
self._prev_candle_high = None
self._prev_candle_low = None
self._prev_candle_open = None
self._prev_candle_close = None
self._two_back_high = None
self._two_back_low = None
self._two_back_open = None
self._two_back_close = None
def CreateClone(self):
return small_inside_bar_strategy()