ComFracti Fractal RSI — это порт советника MetaTrader ComFracti на платформу StockSharp. Алгоритм определяет направление с помощью фракталов Билла Уильямса на двух таймфреймах и фильтрует сигналы быстрым RSI, рассчитанным по дневным свечам. При появлении условий стратегия открывает единственную позицию, защищает её настраиваемыми уровнями стоп-лосса и тейк-профита и может дополнительно закрывать сделки при смене сигнала или по тайм-ауту удержания.
Параметры по умолчанию соответствуют оригинальному эксперту: торговый таймфрейм 15 минут, подтверждающий таймфрейм 1 час и RSI с периодом 3 по дневным свечам и цене открытия.
Логика торговли
Определение направления по фракталам
Завершённые свечи торгового и старшего таймфреймов попадают в скользящее окно из пяти баров.
Параметры Primary*Shift и Higher*Shift задают, на сколько баров назад нужно смотреть при поиске последнего подтверждённого фрактала (значение 3 соответствует фракталу, подтверждённому три свечи назад).
Наличие только нижнего фрактала трактуется как бычий сигнал (+1), наличие только верхнего — как медвежий сигнал (-1).
Фильтр дневным RSI
Индикатор RelativeStrengthIndex с периодом RsiPeriod (по умолчанию 3) работает на дневном таймфрейме и получает в качестве входа цену открытия, что повторяет настройку PRICE_OPEN в MetaTrader.
Для входа в лонг RSI должен быть ниже 50 - RsiBuyOffset, для входа в шорт — выше 50 + RsiSellOffset.
Условия входа
Покупка: фракталы обоих таймфреймов дают значение +1, а RSI удовлетворяет условиям на покупку. Если стратегия находится в шорте или без позиции, она покупает требуемый объём, чтобы стать в лонг.
Продажа: фракталы обоих таймфреймов дают -1, а RSI подтверждает продажу. Если стратегия в лонге или без позиции, она продаёт объём, достаточный для перехода в шорт.
Управление позицией
После изменения позиции рассчитываются уровни стоп-лосса и тейк-профита: StopLossPips и TakeProfitPips умножаются на размер пункта инструмента.
Позиция закрывается при достижении стопа/тейка, по истечении ExpiryMinutes или при включённом CloseOnOppositeSignal, если сигнал сменился на противоположный.
Параметры
Имя
Описание
Значение по умолчанию
Volume
Объём заявки при каждом входе.
0.1
TakeProfitPips
Дистанция тейк-профита в пунктах (0 — без тейка).
700
StopLossPips
Дистанция стоп-лосса в пунктах (0 — без стопа).
2500
ExpiryMinutes
Максимальное время удержания позиции в минутах (0 — без ограничения).
5555
CloseOnOppositeSignal
Закрывать ли позицию при смене торгового сигнала.
false
PrimaryBuyShift
Смещение по барам для поиска бычьего фрактала на торговом таймфрейме.
3
HigherBuyShift
Смещение по барам для поиска бычьего фрактала на старшем таймфрейме.
3
PrimarySellShift
Смещение по барам для поиска медвежьего фрактала на торговом таймфрейме.
3
HigherSellShift
Смещение по барам для поиска медвежьего фрактала на старшем таймфрейме.
3
RsiBuyOffset
Необходимое отклонение RSI ниже 50 для допуска лонгов.
3
RsiSellOffset
Необходимое отклонение RSI выше 50 для допуска шортов.
3
RsiPeriod
Период дневного RSI.
3
CandleType
Тип свечей торгового таймфрейма.
15-минутные свечи
HigherTimeFrame
Тип свечей старшего таймфрейма для подтверждения тренда.
1-часовые свечи
DailyTimeFrame
Тип свечей для расчёта дневного RSI.
Дневные свечи
Особенности реализации
Используется высокоуровневое API подписки на свечи (SubscribeCandles().Bind(...)); индикаторы ведутся внутри стратегии и не добавляются в Strategy.Indicators.
Фракталы рассчитываются вспомогательным классом, который хранит скользящее окно из пяти свечей и обновляет сигнал только после подтверждения экстремума.
Значения RSI получаются через RelativeStrengthIndex.Process(...) по цене открытия, что полностью повторяет режим PRICE_OPEN в MetaTrader.
Стратегия всегда держит только одну позицию; рыночные заявки автоматически переворачивают позицию при необходимости.
Размер пункта определяется по Security.PriceStep и Security.Decimals; для инструментов с тремя и более знаками после запятой используется множитель ×10, имитирующий перевод Point в пункты (pip) в MetaTrader.
Рекомендации по использованию
Значения смещения фракталов должны обеспечивать наличие достаточного количества завершённых свечей. При смещении 3 требуется минимум пять свечей на каждом таймфрейме.
Для инструментов с нестандартным шагом цены (индексы, акции) корректируйте TakeProfitPips и StopLossPips в соответствии с реальным пунктом.
Отключённый параметр CloseOnOppositeSignal полностью повторяет поведение оригинального советника — выход выполняется только по стопу, тейку или таймеру удержания.
Оригинальная логика расчёта лота на основе свободной маржи в StockSharp недоступна. При необходимости динамического управления объёмом используйте внешние модули или настраивайте параметр Volume вручную.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ComFracti Fractal RSI: Fractal breakout with RSI filter and ATR stops.
/// </summary>
public class ComFractiFractalRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private decimal _prevHigh5;
private decimal _prevLow5;
private decimal _high1, _high2, _high3, _high4, _high5;
private decimal _low1, _low2, _low3, _low4, _low5;
private int _barCount;
public ComFractiFractalRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_barCount = 0;
_prevHigh5 = 0;
_prevLow5 = 0;
_high1 = _high2 = _high3 = _high4 = _high5 = 0;
_low1 = _low2 = _low3 = _low4 = _low5 = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_barCount = 0;
_prevHigh5 = 0;
_prevLow5 = 0;
_high1 = _high2 = _high3 = _high4 = _high5 = 0;
_low1 = _low2 = _low3 = _low4 = _low5 = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
// Shift fractal window
_high5 = _high4; _high4 = _high3; _high3 = _high2; _high2 = _high1;
_high1 = candle.HighPrice;
_low5 = _low4; _low4 = _low3; _low3 = _low2; _low2 = _low1;
_low1 = candle.LowPrice;
_barCount++;
if (_barCount < 5 || atrVal <= 0)
return;
var close = candle.ClosePrice;
// Detect fractal high (center bar _high3 is highest)
var fractalUp = _high3 > _high1 && _high3 > _high2 && _high3 > _high4 && _high3 > _high5;
// Detect fractal low (center bar _low3 is lowest)
var fractalDown = _low3 < _low1 && _low3 < _low2 && _low3 < _low4 && _low3 < _low5;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || fractalDown)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || fractalUp)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fractalDown && rsiVal < 45)
{
_entryPrice = close;
BuyMarket();
}
else if (fractalUp && rsiVal > 55)
{
_entryPrice = close;
SellMarket();
}
}
_prevHigh5 = _high5;
_prevLow5 = _low5;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex, AverageTrueRange
class com_fracti_fractal_rsi_strategy(Strategy):
def __init__(self):
super(com_fracti_fractal_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._high5 = 0.0
self._low1 = 0.0
self._low2 = 0.0
self._low3 = 0.0
self._low4 = 0.0
self._low5 = 0.0
self._bar_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(com_fracti_fractal_rsi_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._bar_count = 0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = self._high2 = self._high3 = self._high4 = self._high5 = 0.0
self._low1 = self._low2 = self._low3 = self._low4 = self._low5 = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
av = float(atr_val)
# Shift fractal window
self._high5 = self._high4
self._high4 = self._high3
self._high3 = self._high2
self._high2 = self._high1
self._high1 = float(candle.HighPrice)
self._low5 = self._low4
self._low4 = self._low3
self._low3 = self._low2
self._low2 = self._low1
self._low1 = float(candle.LowPrice)
self._bar_count += 1
if self._bar_count < 5 or av <= 0:
return
close = float(candle.ClosePrice)
# Detect fractal high (center bar _high3 is highest)
fractal_up = (self._high3 > self._high1 and self._high3 > self._high2 and
self._high3 > self._high4 and self._high3 > self._high5)
# Detect fractal low (center bar _low3 is lowest)
fractal_down = (self._low3 < self._low1 and self._low3 < self._low2 and
self._low3 < self._low4 and self._low3 < self._low5)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or fractal_down:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or fractal_up:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high5 = self._high5
self._prev_low5 = self._low5
return
if self.Position == 0:
if fractal_down and rv < 45:
self._entry_price = close
self.BuyMarket()
elif fractal_up and rv > 55:
self._entry_price = close
self.SellMarket()
self._prev_high5 = self._high5
self._prev_low5 = self._low5
def OnReseted(self):
super(com_fracti_fractal_rsi_strategy, self).OnReseted()
self._entry_price = 0.0
self._bar_count = 0
self._prev_high5 = 0.0
self._prev_low5 = 0.0
self._high1 = self._high2 = self._high3 = self._high4 = self._high5 = 0.0
self._low1 = self._low2 = self._low3 = self._low4 = self._low5 = 0.0
def CreateClone(self):
return com_fracti_fractal_rsi_strategy()