Открыть на GitHub

Стратегия PLC

Описание

PLC — портирование эксперта MetaTrader PLC (barabashkakvn's edition) на высокоуровневый API StockSharp. Стратегия работает на таймфрейме, указанном в параметре «Входной таймфрейм», и выставляет стоп-заявки на пробой за пределами последней завершённой свечи. Дополнительно используется анализ фракталов на M5 и H1 для динамического изменения объёма заявок. Когда плавающая прибыль открытых позиций превышает заданный порог, позиции закрываются и начинается ожидание следующего сигнала.

Логика работы

  1. Обработка закрытой свечи — вычисления выполняются только после закрытия очередной свечи на основном таймфрейме, что исключает перерисовку сигналов.
  2. Сервисные действия — перед анализом новых условий стратегия отменяет стоп-заявки, отмеченные на удаление, и закрывает позиции, для которых ранее была достигнута прибыльная цель.
  3. Смещение уровней — максимум и минимум последней свечи смещаются на величину «Shift OHLC» (в пунктах). Размер пункта автоматически корректируется для инструментов с 3 или 5 знаками после запятой.
  4. Фракталы — отдельные подписки на M5 и H1 отслеживают классические фрактальные паттерны из пяти баров. После формирования паттерна сохраняются последние значения верхнего и нижнего фракталов.
  5. Контроль расстояния — покупка через стоп оформляется только если новый уровень выше самого «дорогого» открытия длинной позиции как минимум на «Shift Position» пунктов, либо при отсутствии длинных позиций и активных стопов. Для продаж условия зеркальны.
  6. Масштабирование объёма — базовый объём (Buy Volume или Sell Volume) умножается на коэффициенты M5/H1, если уровень стопа пробивает соответствующий фрактал. Значение 0 отключает усиление объёма.
  7. Регистрация заявок — стоп-заявки отправляются методами BuyStop/SellStop, а созданные объекты сохраняются для дальнейшего контроля и отмены.
  8. Контроль прибыли — суммарная плавающая прибыль (с учётом StepPrice) сравнивается с параметром «Minimum Profit». При превышении планки стратегия переводится в режим закрытия, и на следующем шаге выставляется рыночная заявка на ликвидацию позиции.
  9. Обработка сделок — при исполнении стоп-заявки все остальные отложенные стопы удаляются, что полностью повторяет поведение оригинального советника.

Параметры

Параметр Назначение
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;
	}
}