PLC — портирование эксперта MetaTrader PLC (barabashkakvn's edition) на высокоуровневый API StockSharp. Стратегия работает на таймфрейме, указанном в параметре «Входной таймфрейм», и выставляет стоп-заявки на пробой за пределами последней завершённой свечи. Дополнительно используется анализ фракталов на M5 и H1 для динамического изменения объёма заявок. Когда плавающая прибыль открытых позиций превышает заданный порог, позиции закрываются и начинается ожидание следующего сигнала.
Логика работы
Обработка закрытой свечи — вычисления выполняются только после закрытия очередной свечи на основном таймфрейме, что исключает перерисовку сигналов.
Сервисные действия — перед анализом новых условий стратегия отменяет стоп-заявки, отмеченные на удаление, и закрывает позиции, для которых ранее была достигнута прибыльная цель.
Смещение уровней — максимум и минимум последней свечи смещаются на величину «Shift OHLC» (в пунктах). Размер пункта автоматически корректируется для инструментов с 3 или 5 знаками после запятой.
Фракталы — отдельные подписки на M5 и H1 отслеживают классические фрактальные паттерны из пяти баров. После формирования паттерна сохраняются последние значения верхнего и нижнего фракталов.
Контроль расстояния — покупка через стоп оформляется только если новый уровень выше самого «дорогого» открытия длинной позиции как минимум на «Shift Position» пунктов, либо при отсутствии длинных позиций и активных стопов. Для продаж условия зеркальны.
Масштабирование объёма — базовый объём (Buy Volume или Sell Volume) умножается на коэффициенты M5/H1, если уровень стопа пробивает соответствующий фрактал. Значение 0 отключает усиление объёма.
Регистрация заявок — стоп-заявки отправляются методами BuyStop/SellStop, а созданные объекты сохраняются для дальнейшего контроля и отмены.
Контроль прибыли — суммарная плавающая прибыль (с учётом StepPrice) сравнивается с параметром «Minimum Profit». При превышении планки стратегия переводится в режим закрытия, и на следующем шаге выставляется рыночная заявка на ликвидацию позиции.
Обработка сделок — при исполнении стоп-заявки все остальные отложенные стопы удаляются, что полностью повторяет поведение оригинального советника.
Параметры
Параметр
Назначение
Shift OHLC
Смещение максимумов/минимумов последней свечи для расчёта уровней стоп-заявок (в пунктах).
Minimum Profit
Порог прибыли, при достижении которого закрываются все открытые позиции.
Shift Position
Минимальная дистанция между новым стоп-уровнем и крайними ценами открытия существующих позиций.
Buy Volume / Sell Volume
Базовый объём заявок до применения фрактальных множителей.
M5 Multiplier / H1 Multiplier
Коэффициенты увеличения объёма при пробое соответствующего фрактала. Значение 0 отключает увеличение.
Entry Timeframe
Основной таймфрейм, на котором формируются сигналы.
M5 Fractal Timeframe
Таймфрейм для расчёта «младших» фракталов (по умолчанию 5 минут).
H1 Fractal Timeframe
Таймфрейм для расчёта «старших» фракталов (по умолчанию 1 час).
Управление позицией
Отмена стопов — стратегия хранит ссылки на все выставленные стоп-заявки. После срабатывания любой из них остальные стопы отменяются при следующей проверке условий.
Принудительное закрытие — при превышении Minimum Profit выполняется рыночная сделка (SellMarket для лонга, BuyMarket для шорта), после чего флаг закрытия сбрасывается при обнулении позиции.
Учёт сделок — каждая исполненная заявка записывается как отдельный лот. Это позволяет корректно определять самый высокий вход в лонг и самый низкий вход в шорт, как в MetaTrader.
Дополнительно
Значения параметров по умолчанию подобраны в соответствии с оригинальным советником и могут быть изменены под конкретный инструмент.
Объём заявок округляется вниз до допустимого шага объёма. Если после округления значение равно нулю, заявка не отправляется.
Для расчёта прибыли используется PriceStep и StepPrice, что обеспечивает корректность на инструментах с нестандартным тик-вэлью.
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// PLC (Price Level Channel) strategy.
/// Buys when price breaks above the previous candle's high plus an offset,
/// sells when price breaks below the previous candle's low minus an offset.
/// Uses ATR to dynamically adjust the offset.
/// </summary>
public class PlcStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _shiftPips;
private readonly StrategyParam<int> _atrPeriod;
private decimal _prevHigh;
private decimal _prevLow;
private bool _initialized;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int ShiftPips
{
get => _shiftPips.Value;
set => _shiftPips.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public PlcStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_shiftPips = Param(nameof(ShiftPips), 15)
.SetGreaterThanZero()
.SetDisplay("Shift Pips", "Offset added to candle high/low for breakout", "Trading");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for volatility measurement", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0m;
_prevLow = 0m;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_initialized = false;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_initialized)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_initialized = true;
return;
}
var shift = atrValue * ShiftPips / 100m;
var buyLevel = _prevHigh + shift;
var sellLevel = _prevLow - shift;
// Buy breakout: close above previous high + shift
if (candle.ClosePrice > buyLevel && Position <= 0)
{
BuyMarket();
}
// Sell breakout: close below previous low - shift
else if (candle.ClosePrice < sellLevel && Position >= 0)
{
SellMarket();
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class plc_strategy(Strategy):
def __init__(self):
super(plc_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for calculations", "General")
self._shift_pips = self.Param("ShiftPips", 15) \
.SetDisplay("Shift Pips", "Offset added to candle high/low for breakout", "Trading")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for volatility measurement", "Indicators")
self._prev_high = 0.0
self._prev_low = 0.0
self._initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def ShiftPips(self):
return self._shift_pips.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
def OnReseted(self):
super(plc_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._initialized = False
def OnStarted2(self, time):
super(plc_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._initialized = False
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(atr, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def _on_process(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
av = float(atr_value)
if not self._initialized:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._initialized = True
return
shift = av * self.ShiftPips / 100.0
buy_level = self._prev_high + shift
sell_level = self._prev_low - shift
close = float(candle.ClosePrice)
if close > buy_level and self.Position <= 0:
self.BuyMarket()
elif close < sell_level and self.Position >= 0:
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return plc_strategy()