Открыть на GitHub

Стратегия FatPanel Visual Builder

FatPanel Visual Builder — перевод эксперта FAT Panel с MetaTrader на платформу StockSharp. В оригинальном MQL-проекте пользователь собирал стратегию, перетаскивая блоки сигналов, логики, состояний и приказов на панель. В версии для StockSharp вся схема описывается единственным JSON-документом, который стратегия читает при запуске.

Подход к конверсии

  • Интерфейсные элементы (кнопки, вкладки, таймеры) удалены. Стратегия анализирует параметр Configuration и на его основе создает индикаторы и логические блоки.
  • Все вычисления выполняются на закрытии свечи указанного типа CandleType. Для скользящих средних используются встроенные индикаторы StockSharp (SMA, EMA, SMMA, WMA).
  • Блоки приказов в MQL принимали объем в “пунктах” и позволяли выбирать инструмент. Здесь используется базовый инструмент стратегии, а стоп-лосс и тейк-профит задаются параметрами StopLossPoints и TakeProfitPoints, которые умножаются на PriceStep.
  • Временные и недельные фильтры повторяют поведение FAT Panel. Подписка на лучшую цену покупки (Bid) активируется только при наличии соответствующего сигнала в конфигурации.

Параметры

Параметр Описание
CandleType Тип и таймфрейм свечей, с которыми работает стратегия.
Configuration JSON-строка с описанием правил, условий и действий. По умолчанию задан пример с пересечением EMA и SMA.
Volume Базовый объем приказов, используемый если правило не указало собственный.
StopLossPoints Дистанция стоп-лосса в шагах цены (0 — выключено).
TakeProfitPoints Дистанция тейк-профита в шагах цены (0 — выключено).

Обе дистанции активируют защиту только при положительном значении и наличии корректного PriceStep у инструмента.

Формат JSON

{
  "rules": [
    {
      "name": "Произвольное имя",
      "all": [ /* условия, которые должны выполниться все */ ],
      "any": [ /* условия, из которых достаточно одного */ ],
      "none": [ /* условия, которые должны быть ложными */ ],
      "action": { "type": "Buy" | "SellShort" | "Close", "volume": 1.0 }
    }
  ]
}

Допустимые типы условий:

Тип Поля Назначение
comparison operator, left, right, threshold Сравнение двух сигналов. Операторы: Greater, Less, Equal, CrossAbove, CrossBelow. Порог threshold задается в абсолютном выражении. Пересечение фиксируется, если на предыдущей свече сигналы находились по разные стороны, а текущая разница превысила порог.
position required Контроль позиции: Any, FlatOnly, FlatOrShort, FlatOrLong, LongOnly, ShortOnly.
time start, end Торговое окно внутри дня (HH:mm). Если start больше end, окно считается ночным, как в MQL.
dayOfWeek days Список допустимых дней недели. При отсутствии берутся будние дни.

Описание сигналов:

{ "type": "MovingAverage", "period": 20, "method": "Exponential", "price": "Close" }
{ "type": "Bid" }
{ "type": "Constant", "level": 1.2345 }
  • MovingAverage поддерживает методы Simple, Exponential, Smoothed, LinearWeighted и любой из цен OHLC.
  • Bid использует последнюю цену лучшей покупки; до прихода стакана подставляется цена закрытия свечи.
  • Constant — аналог блока HLINE, возвращает фиксированный уровень.

Доступные действия:

  • Buy — открытие или разворот в длинную позицию (при отсутствии длинной позиции).
  • SellShort — открытие или разворот в короткую позицию.
  • Close — закрытие текущей позиции вызовом ClosePosition().

Каждое действие может задать собственное поле volume, переопределяя общий параметр Volume.

Последовательность работы

  1. При старте конфигурация JSON разбирается; в случае ошибки стратегия пишет сообщение в лог и прекращает запуск.
  2. Для каждого уникального сигнала создается один экземпляр индикатора, который может переиспользоваться в нескольких правилах.
  3. После закрытия свечи обновляются значения сигналов, затем проверяются правила: блок all должен пройти целиком, any — хотя бы один элемент, none — ни одного истинного элемента.
  4. При выполнении условий срабатывает соответствующее действие, о чем выводится информационный лог.
  5. Защитные стопы включаются один раз в методе OnStarted при положительных значениях StopLossPoints/TakeProfitPoints.

Ограничения

  • Поддерживается только основной инструмент стратегии; перекрестные связки из FAT Panel требуют нескольких экземпляров стратегии.
  • JSON-модель (all/any/none) покрывает большинство комбинаций AND/OR/NOT, однако сложные графы могут потребовать ручного упрощения.
  • Операторы CrossAbove/CrossBelow анализируют только предыдущую свечу. В оригинале можно было задавать буфер и допуск в пунктах — используйте поле threshold, чтобы добиться нужной чувствительности.
  • Элементы интерфейса FAT Panel (перетаскивание, диалоги, цветовые схемы) сознательно не воспроизводились.

Пример конфигурации

{
  "rules": [
    {
      "name": "EMA crosses above SMA",
      "all": [
        {
          "type": "comparison",
          "operator": "CrossAbove",
          "left": { "type": "MovingAverage", "period": 20, "method": "Exponential", "price": "Close" },
          "right": { "type": "MovingAverage", "period": 50, "method": "Simple", "price": "Close" }
        },
        { "type": "dayOfWeek", "days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] },
        { "type": "time", "start": "09:00", "end": "17:00" },
        { "type": "position", "required": "FlatOrShort" }
      ],
      "action": { "type": "Buy" }
    },
    {
      "name": "EMA crosses below SMA",
      "all": [
        {
          "type": "comparison",
          "operator": "CrossBelow",
          "left": { "type": "MovingAverage", "period": 20, "method": "Exponential", "price": "Close" },
          "right": { "type": "MovingAverage", "period": 50, "method": "Simple", "price": "Close" }
        },
        { "type": "position", "required": "LongOnly" }
      ],
      "action": { "type": "Close" }
    }
  ]
}

При пересечении EMA(20) выше SMA(50) в рабочее время открывается длинная позиция; обратное пересечение закрывает её, если стратегия все еще находится в лонге.

using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// FatPanel Visual Builder strategy. Uses SMA crossover for signal generation.
/// </summary>
public class FatPanelVisualBuilderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public FatPanelVisualBuilderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast SMA", "Fast SMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA", "Slow SMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = null;
		_prevSlow = null;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fastVal > slowVal;

		_prevFast = fastVal;
		_prevSlow = slowVal;

		if (!prevAbove && currAbove && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (prevAbove && !currAbove && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}