Открыть на GitHub

Стратегия уведомлений TenKijun Cross (ID 3562)

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

Стратегия представляет собой перенос эксперта MetaTrader TenKijun.mq4 на высокоуровневый API StockSharp. В оригинале советник лишь отслеживал пересечения линий Tenkan-sen и Kijun-sen индикатора Ишимоку и отправлял уведомления, не совершая сделок. Перенос полностью сохраняет уведомительный характер, дополняя его инфраструктурой StockSharp: параметрами, подпиской на свечи и автоматической визуализацией.

Алгоритм работает на завершённых свечах выбранного таймфрейма. Когда новая свеча закрывается в пределах разрешённого торгового интервала, рассчитывается индикатор Ichimoku с классическими периодами 9/26/52 и фиксируются текущие значения Tenkan/Kijun. Если Tenkan пересекает Kijun снизу вверх — в журнал добавляется запись о бычьем сигнале, при пересечении сверху вниз — запись о медвежьем сигнале. Сделки не открываются: стратегия предназначена для сигнализации или подключения внешней автоматики.

Индикаторы и поток данных

  • Индикатор — стандартный Ichimoku из StockSharp с независимыми параметрами Tenkan, Kijun и Senkou Span B. Для принятия решений используются только линии Tenkan и Kijun, как в исходном советнике.
  • ПодпискаSubscribeCandles с настраиваемым CandleType. По умолчанию используется 30-минутный таймфрейм.
  • Связка — применяется BindEx, чтобы получать типизированный IchimokuValue без вызовов GetValue.
  • График — при наличии окна графика автоматически отображаются свечи и линия Ichimoku, что упрощает проверку сигналов.

Фильтр торговой сессии

Как и в MetaTrader, пользователь задаёт временной интервал в часах:

  • StartHour — начало активного окна (включительно), по умолчанию 0.
  • LastHour — окончание активного окна (включительно), по умолчанию 20.

Если StartHourLastHour, уведомления формируются только внутри этого диапазона. Если StartHour > LastHour, интервал считается ночным и покрывает конец суток и начало следующего дня (например, 20 → 6).

Параметры

Параметр Описание Значение по умолчанию Примечание
StartHour Старт часа, с которого разрешены уведомления 0 Целое число 0-23
LastHour Финальный час разрешённого интервала 20 Целое число 0-23
TenkanPeriod Длина расчёта линии Tenkan 9 Допускает оптимизацию
KijunPeriod Длина расчёта линии Kijun 26 Допускает оптимизацию
SenkouSpanBPeriod Длина Senkou Span B 52 Добавлена для полноты, в логике не участвует
CandleType Тип свечей для индикатора 30-минутные свечи Можно выбрать любой таймфрейм на основе TimeSpan

Логика сигналов

  1. Дождаться первой завершённой свечи, чтобы инициализировать предыдущие значения Tenkan и Kijun.
  2. На каждой последующей завершённой свече внутри торгового окна:
    • Извлечь текущие значения Tenkan и Kijun из IchimokuValue.
    • Зафиксировать бычий сигнал, если раньше Tenkan ≤ Kijun, а теперь Tenkan > Kijun.
    • Зафиксировать медвежий сигнал, если раньше Tenkan ≥ Kijun, а теперь Tenkan < Kijun.
    • Записать информационное сообщение в журнал с направлением сигнала, ценой закрытия и отметкой времени.

Рекомендации по применению

  • Подпишитесь на журнал стратегии или расширьте метод ProcessCandle, чтобы пересылать уведомления по email, в мессенджеры или проигрывать звук.
  • Для автоматической торговли унаследуйте стратегию и добавьте в обработчик размещение заявок (BuyMarket, SellMarket и т.п.).
  • Выберите таймфрейм, соответствующий используемому в MetaTrader графику, чтобы получать те же пересечения.

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

  • Уведомления пишутся через систему логирования StockSharp вместо SendNotification MetaTrader.
  • Все параметры снабжены метаданными (SetDisplay, диапазоны, признаки оптимизации), что упрощает работу в Designer/Optimizer.
  • Свечи и индикатор автоматически выводятся на график StockSharp.

Файлы

  • CS/TenKijunCrossStrategy.cs — реализация стратегии на C#.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Tenkan/Kijun cross strategy based on Ichimoku indicator.
/// Buys when Tenkan crosses above Kijun, sells when Tenkan crosses below Kijun.
/// Uses SMA proxies for Tenkan (short) and Kijun (long) since Ichimoku complex type
/// requires BindEx and special value handling.
/// </summary>
public class TenKijunCrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;

	private readonly Queue<decimal> _highsTenkan = new();
	private readonly Queue<decimal> _lowsTenkan = new();
	private readonly Queue<decimal> _highsKijun = new();
	private readonly Queue<decimal> _lowsKijun = new();
	private decimal? _prevTenkan;
	private decimal? _prevKijun;

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

	public int TenkanPeriod
	{
		get => _tenkanPeriod.Value;
		set => _tenkanPeriod.Value = value;
	}

	public int KijunPeriod
	{
		get => _kijunPeriod.Value;
		set => _kijunPeriod.Value = value;
	}

	public TenKijunCrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for Ichimoku calculations", "General");

		_tenkanPeriod = Param(nameof(TenkanPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Tenkan Period", "Tenkan-sen conversion line period", "Indicators");

		_kijunPeriod = Param(nameof(KijunPeriod), 34)
			.SetGreaterThanZero()
			.SetDisplay("Kijun Period", "Kijun-sen base line period", "Indicators");
	}

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

		_prevTenkan = null;
		_prevKijun = null;
		_highsTenkan.Clear();
		_lowsTenkan.Clear();
		_highsKijun.Clear();
		_lowsKijun.Clear();

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

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

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

		// Compute Tenkan-sen = (highest high + lowest low) / 2 over TenkanPeriod
		_highsTenkan.Enqueue(candle.HighPrice);
		_lowsTenkan.Enqueue(candle.LowPrice);
		if (_highsTenkan.Count > TenkanPeriod)
		{
			_highsTenkan.Dequeue();
			_lowsTenkan.Dequeue();
		}

		// Compute Kijun-sen = (highest high + lowest low) / 2 over KijunPeriod
		_highsKijun.Enqueue(candle.HighPrice);
		_lowsKijun.Enqueue(candle.LowPrice);
		if (_highsKijun.Count > KijunPeriod)
		{
			_highsKijun.Dequeue();
			_lowsKijun.Dequeue();
		}

		if (_highsTenkan.Count < TenkanPeriod || _highsKijun.Count < KijunPeriod)
			return;

		var highsTenkan = _highsTenkan.ToArray();
		var lowsTenkan = _lowsTenkan.ToArray();
		var highsKijun = _highsKijun.ToArray();
		var lowsKijun = _lowsKijun.ToArray();
		var tenkan = (Max(highsTenkan) + Min(lowsTenkan)) / 2;
		var kijun = (Max(highsKijun) + Min(lowsKijun)) / 2;

		if (_prevTenkan is null || _prevKijun is null)
		{
			_prevTenkan = tenkan;
			_prevKijun = kijun;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var crossUp = _prevTenkan.Value <= _prevKijun.Value && tenkan > kijun;
		var crossDown = _prevTenkan.Value >= _prevKijun.Value && tenkan < kijun;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevTenkan = tenkan;
		_prevKijun = kijun;
	}

	private static decimal Max(IEnumerable<decimal> values)
	{
		decimal max = decimal.MinValue;

		foreach (var v in values)
			if (v > max) max = v;

		return max;
	}

	private static decimal Min(IEnumerable<decimal> values)
	{
		decimal min = decimal.MaxValue;

		foreach (var v in values)
			if (v < min) min = v;

		return min;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_prevTenkan = null;
		_prevKijun = null;
		_highsTenkan.Clear();
		_lowsTenkan.Clear();
		_highsKijun.Clear();
		_lowsKijun.Clear();

		base.OnReseted();
	}
}