Открыть на GitHub

Стратегия Sudoku UI

Общее описание

Стратегия является портом скрипта MetaTrader 5 SudokuUI.mq5. Оригинальная MQL-программа предоставляла графический интерфейс для работы с головоломкой судоку: можно было генерировать новые задачи, перемешивать цифры и включать автоматическую помощь. В среде StockSharp приоритет отдан алгоритмической торговле, поэтому функциональность была переосмыслена: статистика судоку используется для построения сетки уровней вокруг скользящей средней и реализации торговой стратегии возврата к среднему.

Доска судоку трактуется как матрица 9x9. Средние значения по колонкам формируют симметричные пороги отклонения относительно простой скользящей средней (SMA). Когда цена уходит за эти уровни, открывается позиция в противоположном направлении с расчётом на возврат. Возврат цены в нейтральную зону приводит к закрытию позиции, что аналогично сбросу состояния в исходном скрипте.

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

  1. Подготовка головоломки

    • Стратегия загружает описание судоку из файла или строки. Используются только цифры 1-9, прочие символы игнорируются, как и нули.
    • Если корректного описания нет, формируется псевдослучайная доска. При генерации учитываются два исходных параметра — shuffling и composition seed — что позволяет воспроизводить нужную последовательность.
    • При необходимости можно исключить выбранную цифру до расчёта статистики, имитируя скрытие метки в интерфейсе MQL и сокращая активную сетку.
  2. Построение уровней

    • Для каждой колонки вычисляется среднее значение (после исключения выбранной цифры), нормируется в диапазон [-1; 1] и умножается на ThresholdRange. Получаем пороги отклонения, выраженные в долях цены относительно SMA.
    • Если матрица даёт уровни только с одной стороны, автоматически добавляются недостающие отрицательные или положительные пороги, чтобы в стратегии были и покупки, и продажи.
  3. Генерация сигналов

    • Стратегия подписывается на указанный тип свечей и привязывает их к индикатору SMA. Обрабатываются только полностью сформированные свечи (CandleStates.Finished).
    • Пересечение отрицательного порога вниз инициирует открытие длинной позиции (после закрытия шортов). Аналогично, пересечение верхнего порога вверх открывает короткую позицию.
    • Параметр NeutralBand задаёт окрестность нулевого отклонения. Попадание в эту зону закрывает все позиции, что играет роль авто-помощника из оригинала.
  4. Автоматическое обновление

    • При включенном EnableAutoUpdate сетка уровней пересчитывается в начале каждого торгового дня. На новые уровни влияют оба seed-параметра, число циклов перемешивания и исключаемая цифра, что сочетает предсказуемость и адаптивность.

Параметры

Параметр Описание
PuzzleDefinition Путь к файлу или строка из 81 цифры, описывающие доску судоку.
ShufflingRandomSeed Основной seed для генерации. Значение -1 берёт дату торгового дня.
CompositionRandomSeed Дополнительный seed, меняющий конфигурацию при сохранении воспроизводимости.
ShufflingCycles Количество дополнительных циклов перемешивания.
EliminateLabel Цифра (1-9), исключаемая из расчёта. 0 оставляет все цифры.
EnableAutoUpdate Пересчитывать сетку при смене торгового дня.
SmaPeriod Период SMA, выступающей целевым уровнем возврата.
ThresholdRange Максимальное абсолютное отклонение в долях цены, создаваемое на основе судоку.
NeutralBand Ширина нейтральной зоны, в которой позиции закрываются.
Volume Объём рыночных ордеров.
CandleType Тип свечей, используемый для обновления индикатора.

Рекомендации по использованию

  • Обрабатываются только завершённые свечи, а нулевые цены игнорируются. Это гарантирует стабильную работу с разными источниками данных.
  • Для точного повторения исходного судоку используйте строку из 81 цифры (без нулей) или файл с таким содержимым.
  • Чтобы получить «фиксированную» сетку, отключите EnableAutoUpdate и задайте конкретные seed значения. Включённый режим имитирует автоматического помощника из MQL и обновляет уровни ежедневно.
  • Пороговые значения зависят от распределения цифр по колонкам. Если сетка даёт сильный перекос, исключите доминирующую цифру, чтобы сбалансировать длинные и короткие сигналы.

Отличия от оригинального скрипта

  • Интерфейсные элементы (диалог, кнопки, события графика) удалены. Управление ведётся через параметры стратегии.
  • Судоку используется не для ручного решения головоломки, а для формирования торговых порогов. Те же параметры случайности определяют агрессивность стратегии.
  • Стратегия полностью автономна: автообновление привязано к датам, а управление позициями выполняется стандартными методами BuyMarket, SellMarket, ClosePosition.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Sudoku UI strategy: SMA mean reversion.
/// Buys when close < SMA - offset. Sells when close > SMA + offset.
/// </summary>
public class SudokuUiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaPeriod;

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

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public SudokuUiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_smaPeriod = Param(nameof(SmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period", "Indicators");
	}

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

		var sma = new SimpleMovingAverage { Length = SmaPeriod };

		decimal? prevClose = null;
		decimal? prevSma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, (candle, smaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevSma.HasValue)
				{
					var crossBelow = prevClose.Value >= prevSma.Value && close < smaVal;
					var crossAbove = prevClose.Value <= prevSma.Value && close > smaVal;

					if (crossBelow && Position <= 0)
						BuyMarket();
					else if (crossAbove && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevSma = smaVal;
			})
			.Start();

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