Открыть на GitHub

Стратегия Spearman Rank Correlation Histogram с торговым окном

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

Стратегия повторяет эксперт Exp_SpearmanRankCorrelation_Histogram_TimeWeekPeriod на платформе StockSharp, используя высокоуровневый API. Она подписывается на один поток свечей (по умолчанию четырёхчасовых) и пересчитывает гистограмму ранговой корреляции Спирмена из оригинального индикатора. Цвет гистограммы определяет направление локального тренда (значения выше нуля — рост, ниже нуля — падение). Отдельный торговый интервал ограничивает сделки заданными днями недели и временем, что воспроизводит логику TimeTrade из MQL.

Логика торговли

  1. Расчёт индикатора

    • После закрытия каждой свечи сохраняется цена закрытия и вычисляется корреляция Спирмена по RangeLength последним закрытиям.
    • Цвет гистограммы присваивается так же, как в индикаторе: 4 — выше HighLevel, 3 — между 0 и HighLevel, 1 — между LowLevel и 0, 0 — ниже LowLevel, 2 — ровно ноль.
    • Сигналы считываются по свече с номером SignalBar (по умолчанию последняя закрытая). Для выявления переходов используется предыдущий закрытый бар.
  2. Режимы торговли (TradeMode)

    • Mode1 — вход в покупки при переходе цвета выше 2 (после значений < 3); вход в продажи при падении цвета ниже 2 (после значений > 1). При каждом бычьем цвете запрашивается закрытие шорта, при медвежьем — закрытие лонга.
    • Mode2 — покупки открываются при цвете 4, продажи — при цвете 0. Цвета > 2 закрывают шорты, цвета < 2 закрывают лонги.
    • Mode3 — цвет 4 одновременно закрывает шорт и открывает лонг, цвет 0 закрывает лонг и открывает шорт.
    • После успешного входа действует «кулдаун» длиной в одну свечу — следующая заявка в ту же сторону будет зарегистрирована только после времени, когда в MetaTrader закрылась бы следующая свеча.
  3. Управление капиталом и объём

    • Параметры MoneyManagement и MarginMode переводят долю капитала или риск в объём заявки. Положительные значения повторяют логику МQL, ноль использует стандартное свойство Volume, отрицательные — фиксированный лот.
    • Рискованные режимы (LossFreeMargin, LossBalance) требуют положительного StopLossPoints. При нулевом стопе стратегия возвращается к Volume, что эквивалентно отказу от сделки в исходном коде.
  4. Защита позиции

    • StopLossPoints и TakeProfitPoints пересчитываются в цены через Security.PriceStep. На каждой закрытой свече проверяется достижение уровней по максимуму/минимуму свечи; при срабатывании позиция полностью закрывается.
    • DeviationPoints сохранён для совместимости и отображения, но на рыночные заявки StockSharp не влияет.
  5. Торговое окно

    • При TimeTrade = true текущее время должно находиться между (StartDay, StartHour, StartMinute, StartSecond) и (EndDay, EndHour, EndMinute, EndSecond). Вне интервала все позиции по инструменту немедленно закрываются, как и в оригинальном советнике.
    • Предполагается, что StartDay не позже EndDay. Для окна, пересекающего выходные, подберите соответствующие значения вручную.
  6. Прочие особенности

    • Для появления сигналов требуется минимум RangeLength + SignalBar + 1 закрытых свечей.
    • Параметр Direction оставлен для совместимости, но на расчёты не влияет.

Параметры

Параметр Описание Значение по умолчанию
MoneyManagement Доля капитала или фиксированный лот для расчёта объёма. 0.1
MarginMode Интерпретация MoneyManagement (FreeMargin, Balance, LossFreeMargin, LossBalance, Lot). Lot
StopLossPoints Размер стоп-лосса в пунктах цены. 1000
TakeProfitPoints Размер тейк-профита в пунктах цены. 2000
DeviationPoints Допустимое проскальзывание (информационно). 10
BuyOpen / SellOpen Разрешение на открытие длинных/коротких позиций. true
BuyClose / SellClose Разрешение закрывать лонги/шорты по сигналам. true
TradeMode Режим трактовки цветов (Mode1, Mode2, Mode3). Mode1
TimeTrade Включение недельного торгового окна. true
StartDay, StartHour, StartMinute, StartSecond Начало окна (день недели и время). Вторник, 8, 0, 0
EndDay, EndHour, EndMinute, EndSecond Конец окна (день недели и время). Пятница, 20, 59, 40
CandleType Рабочий таймфрейм свечей. H4
RangeLength Количество закрытий в расчёте корреляции. 14
MaxRange Максимально допустимый RangeLength (ограничитель). 30
Direction Зарезервированный флаг индикатора, не используется. true
HighLevel, LowLevel Верхний и нижний пороги гистограммы. 0.5, -0.5
SignalBar Номер закрытого бара для чтения сигнала. 1

Остальные настройки (портфель, инструмент, базовый Volume, риск-профиль) задаются стандартными средствами StockSharp.

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;

public class SpearmanRankCorrelationHistogramTimeWeekPeriodStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public SpearmanRankCorrelationHistogramTimeWeekPeriodStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}