Открыть на GitHub

Стратегия Pinball Machine

Обзор

Стратегия представляет собой прямую конвертацию советника MetaTrader 4 Pinball_machine.mq4 в среду StockSharp. В исходном советнике на каждом тике генерировались случайные числа и при совпадении пары значений открывалась рыночная позиция. В версии для StockSharp логика сохранена: на каждой закрывшейся свече выбранного таймфрейма выполняются две серии случайных бросков, и при совпадении чисел стратегия открывает длинную или короткую позицию. Дистанции стоп-лосса и тейк-профита также выбираются случайным образом при каждом проходе, что сохраняет “пинбольный” характер торговли.

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

  • Подписаться на свечи типа CandleType и ждать их завершения.
  • Для каждой закрывшейся свечи сгенерировать четыре равномерно распределённых целых числа в диапазоне [0, RandomMaxValue]. Первая пара относится к потенциальной покупке, вторая — к потенциальной продаже.
  • Дополнительно получить два случайных значения в пределах MinStopLossPoints/MaxStopLossPoints и MinTakeProfitPoints/ MaxTakeProfitPoints, определяющие расстояние до защитных ордеров (в шагах цены). Эти расстояния используются обеими сторонами одновременно.
  • Если первое и второе значения совпали — отправить рыночную заявку на покупку объёмом TradeVolume. Если совпали третье и четвёртое значения — отправить рыночную заявку на продажу тем же объёмом. Оба условия могут выполниться в рамках одной свечи, как и в MQL-версии.
  • При наличии положительных защитных расстояний немедленно повесить тейк-профит и стоп-лосс. Значения интерпретируются как множители PriceStep, аналогично тому, как оригинал умножал на Point.

Управление ордерами и рисками

  • При запуске вызывается StartProtection(), чтобы StockSharp автоматически сопровождал защитные заявки.
  • После открытия позиции вычисляется результирующий размер позиции (Position ± TradeVolume), и именно он передаётся в методы SetStopLoss и SetTakeProfit, что позволяет объединять защитные ордера даже при одновременных сделках.
  • Если минимальные или максимальные значения дистанций равны нулю либо отрицательны, соответствующие защитные ордера в текущем цикле не выставляются.

Параметры

Параметр Описание
TradeVolume Объём рыночного ордера для каждого случайного входа.
CandleType Тип свечей, по закрытию которых выполняются случайные броски. Короткие интервалы имитируют тиковый режим исходного советника.
RandomMaxValue Верхняя граница (включительно) для случайных чисел. Чем больше значение, тем реже совпадения и тем ниже частота сделок.
MinStopLossPoints Нижняя граница дистанции стоп-лосса (в шагах цены).
MaxStopLossPoints Верхняя граница дистанции стоп-лосса.
MinTakeProfitPoints Нижняя граница дистанции тейк-профита.
MaxTakeProfitPoints Верхняя граница дистанции тейк-профита.
RandomSeed Зерно генератора псевдослучайных чисел. Ноль — инициализация по текущему времени, любое другое значение делает последовательность повторяемой.

Особенности реализации

  • Исходный советник реагировал на каждый тик, поэтому в StockSharp используется закрытие свечей — так работает высокоуровневый API. Выбор очень короткого таймфрейма (секундные или тиковые свечи) позволяет приблизиться к оригинальному темпу.
  • Значения стоп-лосса и тейк-профита генерируются один раз на проход и применяются одновременно для покупок и продаж — как в MQL-версии.
  • Убедитесь, что для выбранного инструмента задан корректный PriceStep, иначе дистанции, выраженные в пунктах, потребуют ручной корректировки.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Pinball Machine: Pseudo-random entry with ATR-based risk management.
/// Uses candle hash to generate deterministic random signals.
/// </summary>
public class PinballMachineRandomDrawStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private int _candleCount;

	public PinballMachineRandomDrawStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");
	}

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

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_candleCount = 0;
	}

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

		_entryPrice = 0;
		_candleCount = 0;

		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (atrVal <= 0)
			return;

		_candleCount++;
		var close = candle.ClosePrice;

		// Exit management
		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 1.5m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 1.5m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Pseudo-random entry based on candle price hash
		if (Position == 0)
		{
			var hash = (int)(close * 100m) ^ _candleCount;
			var mod = Math.Abs(hash) % 10;

			if (mod < 3)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (mod > 6)
			{
				_entryPrice = close;
				SellMarket();
			}
		}
	}
}