Стратегия «Три типичные свечи» переносит одноимённого эксперта MetaTrader в инфраструктуру StockSharp. Алгоритм анализирует типичную цену (среднее арифметическое High, Low и Close) трёх последних завершённых свечей и реагирует на строго монотонные последовательности. Если типичные цены трёх свечей подряд возрастают, стратегия открывает длинную позицию, а при последовательном снижении — короткую.
Основные особенности портированной версии:
Сигналы оцениваются только в момент закрытия свечи, что полностью совпадает с логикой исходного MQL5-советника.
Гибкий фильтр торговых часов позволяет ограничить работу алгоритма заданным диапазоном и принудительно закрывает позиции вне разрешённого окна.
Перед открытием новой сделки закрывается позиция противоположного направления, поэтому одновременно удерживается только один сценарий.
Объём заявки задаётся фиксированным значением, но автоматически корректируется с учётом шага объёма и ограничений инструмента (минимум/максимум), как и в оригинальном советнике.
Правила торговли
Формирование сигнала
Для каждой завершённой свечи вычисляется типичная цена Tp = (High + Low + Close) / 3.
Хранятся два предыдущих значения. Когда доступны три точки, проверяется наличие строго возрастающей или убывающей последовательности.
Вход в длинную позицию
Если выполнено условие Tp[-2] < Tp[-1] < Tp[0] и открытых лонгов нет, стратегия закрывает шорт (если он есть) и отправляет рыночную заявку на покупку.
Вход в короткую позицию
При условии Tp[-2] > Tp[-1] > Tp[0] закрывается лонг (если присутствует) и выставляется рыночная заявка на продажу.
Контроль времени
При включённом фильтре времени (UseTimeControl) проверяется час открытия свечи. Если свеча сформировалась вне допустимого диапазона [StartHour; EndHour), позиция немедленно закрывается и новые заявки не подаются. Когда StartHour > EndHour, временное окно «перетекает» через полночь.
Управление позицией
Штатные уровни стоп-лосс и тейк-профит не используются. Управление рисками следует реализовать дополнительными защитными механизмами.
Параметры
Имя
Тип
Значение по умолчанию
Описание
Volume
decimal
1
Фиксированный торговый объём. Значение автоматически нормализуется по шагу объёма инструмента и ограничивается допустимыми минимальными/максимальными границами.
UseTimeControl
bool
true
Включает фильтр торговых часов. При отключении стратегия работает круглосуточно.
StartHour
int
11
Начало торгового окна (включительно, 0-23).
EndHour
int
17
Конец торгового окна (не включается в диапазон, 0-23). Если меньше StartHour, окно охватывает вечер и утро следующего дня.
CandleType
DataType
TimeFrame(1h)
Тип (таймфрейм) свечей, используемых для анализа.
Особенности реализации
Подписка на свечи организована через высокоуровневый метод SubscribeCandles, обработчик ProcessCandle получает уже завершённые свечи.
При смене направления алгоритм сначала закрывает текущую позицию вызовом BuyMarket или SellMarket, а затем открывает новую в противоположную сторону.
В OnStarted вызывается StartProtection(), что позволяет подключать стандартные защитные стратегии StockSharp (глобальный стоп, трейлинг и т.п.).
Метод GetTradeVolume повторяет нормализацию лота MetaTrader: учитывается шаг объёма, а также минимальные и максимальные ограничения инструмента.
Для принятия решения достаточно хранить всего два предыдущих значения типичной цены, что делает реализацию компактной и производительной.
Рекомендации по использованию
Стратегия хорошо подходит для ликвидных инструментов, где свечи формируются без существенных разрывов. Помимо форекса можно использовать фьючерсы, акции или криптовалюты.
Настройка таймфрейма через CandleType позволяет адаптировать стратегию к разным торговым стилям. Часовые свечи повторяют оригинальный алгоритм, но можно протестировать и другие интервалы.
Рекомендуется дополнительно ограничивать риски на уровне портфеля (максимальная просадка, лимиты по количеству сделок и т.д.).
Перед запуском на реальных счетах следует провести многостороннее тестирование: разные инструменты, исторические периоды и параметры временного фильтра.
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;
public class ThreeTypicalCandlesStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public ThreeTypicalCandlesStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 60).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class three_typical_candles_strategy(Strategy):
def __init__(self):
super(three_typical_candles_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 15) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 60) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(three_typical_candles_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(three_typical_candles_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return three_typical_candles_strategy()