Auf GitHub ansehen

Sudoku UI Strategy

Overview

This strategy is a StockSharp port of the MetaTrader 5 script SudokuUI.mq5. The original MQL program exposes a graphical Sudoku puzzle interface with parameters that control puzzle generation, shuffling and automatic updates. Because the StockSharp environment focuses on automated trading instead of interactive chart widgets, the port repurposes the underlying concepts into a grid-based mean reversion strategy driven by puzzle statistics.

The Sudoku board is interpreted as a 9x9 matrix of digits. Column averages define symmetric deviation thresholds around a simple moving average (SMA). When price deviates from the SMA beyond these Sudoku-derived levels the strategy enters a position in the opposite direction, seeking a reversion back toward the mean. Returning into a neutral zone closes the position, mimicking the original tool's ability to reset the board.

Trading Logic

  1. Puzzle preparation

    • The strategy can load an 81-digit Sudoku specification from a file or a raw string. Non-digit characters are ignored and zeros are skipped, matching the Sudoku digit requirements.
    • When no valid puzzle is supplied, a pseudo-random board is generated by repeatedly shuffling digit pools. The logic honors both the shuffling and composition seeds that were exposed in the MQL version so that traders can obtain reproducible layouts.
    • A specific digit can be eliminated before statistics are computed. This mimics the original GUI option that hid certain labels and provides an easy way to shrink the active grid.
  2. Level construction

    • Each puzzle column is averaged after the elimination step. The average is normalised to the range [-1, 1] and multiplied by ThresholdRange, yielding price deviation levels expressed as fractions of the SMA value.
    • Fallback negative or positive levels are inserted if the puzzle only produces values on one side of the SMA, guaranteeing that both long and short triggers exist.
  3. Signal generation

    • The strategy subscribes to the configured candle type and binds it to an SMA indicator. Only finished candles are processed, following StockSharp best practices.
    • When the percentage distance between the close price and the SMA crosses below the most negative level, a long position is opened (after flattening shorts). Crossing above the highest positive level opens a short position in the same manner.
    • A neutral band around zero deviation (NeutralBand) forces flat exposure. This replaces the Sudoku "assistant" that automatically adjusted the puzzle state.
  4. Auto-update

    • Setting EnableAutoUpdate to true causes the Sudoku grid to regenerate at the start of every trading day. The shuffling seeds, elimination settings and shuffle count all influence the recalculated thresholds, providing a dynamic yet reproducible grid.

Parameters

Parameter Description
PuzzleDefinition File path or inline digits that describe the Sudoku puzzle used for level calculations.
ShufflingRandomSeed Primary seed for puzzle generation. -1 derives the seed from the trading day.
CompositionRandomSeed Secondary seed that perturbs the shuffling process to create alternative layouts.
ShufflingCycles Number of additional shuffling passes applied to the digit pool. Higher values create more randomized boards.
EliminateLabel Digit (1-9) removed from the board prior to computing averages. 0 keeps all digits.
EnableAutoUpdate Rebuild the puzzle levels when the trading date changes.
SmaPeriod Length of the SMA indicator used as the reversion anchor.
ThresholdRange Maximum absolute deviation (expressed as a fraction of price) produced by the puzzle.
NeutralBand Deviation zone that triggers position flattening when price re-enters it.
Volume Order volume for market entries.
CandleType Candle subscription used for indicator updates.

Usage Notes

  • The strategy only reacts to fully formed candles and ignores zero prices, ensuring stable behaviour across data providers.
  • Provide an 81-character digit string (without zeros) or a text file containing such digits to exactly reproduce a Sudoku board from the MetaTrader version.
  • If you need a stationary grid, disable EnableAutoUpdate and set explicit seeds. Enabling the option mirrors the MQL "auto assistant" that kept the board in sync with user actions.
  • Thresholds are derived from column statistics. For asymmetric puzzles consider eliminating the dominant digit to maintain balanced buy/sell coverage.

Differences from the Original Script

  • All user interface features (dialog windows, buttons, chart events) are removed. Their functional equivalents are exposed as strategy parameters.
  • Instead of solving Sudoku puzzles manually, the board influences algorithmic trading levels. The same randomness controls determine how aggressive or conservative those levels become.
  • The StockSharp version runs autonomously. Auto-update now reacts to trading days rather than button clicks, and position management happens via standard BuyMarket/SellMarket/ClosePosition calls.
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);
		}
	}
}