Flat 001a — скальперская система для часового графика EURUSD. Она анализирует три последние завершённые свечи и измеряет расстояние между максимальным максимумом и минимальным минимумом. Если диапазон укладывается в заданное количество пунктов, стратегия предполагает, что рынок остаётся во флэте, и ищет сделки против крайних движений внутри этого канала. Входы происходят, когда цена закрытия оказывается в верхней или нижней четверти диапазона, после чего сразу выставляются защитные уровни.
Исходный советник MQL4 работал только с EURUSD на таймфрейме H1 и отказывался торговать при несоответствии символа или периода. В портированной версии сохранены эти настройки по умолчанию (EURUSD, 60-минутные свечи), а логика входов, стоп-лоссов, тейк-профитов и трейлинг-стопов полностью воспроизведена на StockSharp.
Данные и индикаторы
Индикаторы Highest и Lowest с периодом 3 отслеживают верхнюю и нижнюю границы трёх последних свечей.
Параметр типа свечей по умолчанию задаёт 60-минутные бары, что повторяет требование исходного эксперта.
Дополнительные индикаторы не используются: стратегия опирается только на экстремумы цены.
Логика входа
Стратегия обрабатывает только завершённые свечи подписки.
Проверяется соответствие кода инструмента параметру SecurityCode (по умолчанию EURUSD). При несовпадении стратегия остаётся в режиме ожидания.
Применяется опциональный фильтр торговых часов. По умолчанию входы разрешены в двухчасовом окне, начиная с полуночи (часы 0 и 1). Фильтр можно отключить.
Рассчитывается диапазон трёх свечей range = highest - lowest и переводится в пункты с помощью PriceStep инструмента.
Продолжение возможно только при условии, что диапазон находится между DiffMinPoints и DiffMaxPoints.
Если цена закрытия попадает в нижнюю четверть диапазона и позиций нет, открывается длинная позиция.
Если цена закрытия попадает в верхнюю четверть диапазона и позиций нет, открывается короткая позиция.
Управление позицией
Начальный стоп-лосс
Для покупок: lowest - range / 3.
Для продаж: highest + range / 3.
Тейк-профит
Для покупок: цена входа + TakeProfitPoints * PriceStep.
Для продаж: цена входа − TakeProfitPoints * PriceStep.
Трейлинг-стоп
Как только нереализованная прибыль превышает TrailingStopPoints * PriceStep, стоп-лосс подтягивается по закрытиям свечей.
Для покупок стоп переносится на closePrice - TrailingDistance, если новое значение выше текущего.
Для продаж стоп переносится на closePrice + TrailingDistance, если новое значение ниже текущего.
Выходы выполняются рыночными ордерами. Как только очередная свеча пробивает стоп или тейк, позиция закрывается полностью.
Параметры
Группа
Имя
Описание
Значение по умолчанию
Общие
CandleType
Тип свечей для расчётов. Для повторения оригинала используйте 60-минутные бары.
TimeFrame(60m)
Общие
SecurityCode
Ожидаемый код инструмента. Оставьте пустым, чтобы торговать любым инструментом.
EURUSD
Фильтр диапазона
DiffMinPoints
Минимальный диапазон трёх свечей в пунктах, допускающий торговлю.
18
Фильтр диапазона
DiffMaxPoints
Максимальный диапазон трёх свечей в пунктах, при превышении которого сделки запрещены.
28
Торговое окно
EnableTimeFilter
Включение/отключение фильтра по часам.
true
Торговое окно
OpenHour
Начальный час (0–23) торгового окна. Стратегия также допускает следующий час.
0
Риск-менеджмент
TakeProfitPoints
Дистанция тейк-профита в пунктах. Ноль отключает тейк.
8
Риск-менеджмент
TrailingStopPoints
Дистанция трейлинг-стопа в пунктах. Ноль отключает подтягивание.
6
Практические рекомендации
Объём сделок задаётся свойством Strategy.Volume. Убедитесь, что он соответствует контракту брокера.
Инструмент должен предоставлять корректный PriceStep. При его отсутствии стратегия использует значение 1 и записывает предупреждение в лог.
В оригинале присутствовал простой мани-менеджмент, рассчитывающий лоты от свободной маржи. В версии для StockSharp объём фиксированный; при необходимости добавьте собственную логику.
Перед запуском на реальном счёте протестируйте стратегию в симуляторе. При резких движениях возможен проскальзывание относительно рассчитанных уровней защиты.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Flat 001a: RSI reversal with EMA filter and ATR stops.
/// </summary>
public class Flat001aStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevRsi;
private decimal _entryPrice;
public Flat001aStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Trend filter.", "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 EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0; _entryPrice = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevRsi == 0 || atrVal <= 0) { _prevRsi = rsiVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 70) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 30) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (rsiVal > 55 && _prevRsi <= 55 && close > emaVal) { _entryPrice = close; BuyMarket(); }
else if (rsiVal < 45 && _prevRsi >= 45 && close < emaVal) { _entryPrice = close; SellMarket(); }
}
_prevRsi = rsiVal;
}
}
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, ExponentialMovingAverage, AverageTrueRange
class flat001a_strategy(Strategy):
def __init__(self):
super(flat001a_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_rsi = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(flat001a_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
ev = float(ema_val)
av = float(atr_val)
if self._prev_rsi == 0 or av <= 0:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or rv > 70:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or rv < 30:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self.Position == 0:
if rv > 55 and self._prev_rsi <= 55 and close > ev:
self._entry_price = close
self.BuyMarket()
elif rv < 45 and self._prev_rsi >= 45 and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_rsi = rv
def OnReseted(self):
super(flat001a_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._entry_price = 0.0
def CreateClone(self):
return flat001a_strategy()