Открыть на GitHub

Стратегия OneHrStocTrader

Обзор

OneHrStocTrader переносит советник MetaTrader 4 OneHrStocTrader.mq4 в экосистему StockSharp и использует высокоуровневый API платформы. Базовый таймфрейм — часовые свечи (настраивается), а входы формируются комбинацией стохастического осциллятора и фильтра по ширине полос Боллинджера. Сделка открывается только тогда, когда волатильность (расстояние между полосами) лежит в допустимом диапазоне, а стохастик выходит из зоны перекупленности или перепроданности в заданный час.

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

  1. Данные
    • Подписка на часовые свечи, работа только с завершёнными барами.
  2. Фильтр полос Боллинджера
    • Расчёт спрэда между верхней и нижней полосами, пересчитанного в пункты.
    • Если значение вне диапазона [BollingerSpreadLower, BollingerSpreadUpper], сигналы игнорируются.
  3. Триггер стохастика
    • Используются значения %K для двух последних закрытых свечей.
    • Покупка: текущий %K ниже StochasticLower, предыдущий %K растёт (prev < current), текущий час равен BuyHourStart.
    • Продажа: текущий %K выше StochasticUpper, предыдущий %K снижается (prev > current), текущий час равен SellHourStart.
  4. Управление позициями
    • Перед открытием новой позиции закрывается встречная.
    • Параметр MaxOrdersPerDirection ограничивает количество последовательных входов в одном направлении.
  5. Риск-менеджмент
    • Фиксированные стоп-лосс и тейк-профит в пунктах.
    • Дополнительный трейлинг-стоп, который подтягивается, когда цена проходит заданное расстояние.
    • На каждом закрытом баре проверяются защитные уровни; при их достижении позиция закрывается рыночным ордером.

Параметры

Имя Описание Значение по умолчанию
TradeVolume Объём сделки в лотах. 0.01
CandleType Таймфрейм для расчётов. 1h
BollingerPeriod Период полос Боллинджера. 20
BollingerSigma Множитель сигмы полос Боллинджера. 2.0
BollingerSpreadLower Минимальная допустимая ширина полос (в пунктах). 56
BollingerSpreadUpper Максимальная допустимая ширина полос (в пунктах). 158
BuyHourStart Час (0-23) для поиска длинных сигналов. 4
SellHourStart Час (0-23) для поиска коротких сигналов. 0
StochasticKPeriod Период %K стохастика. 5
StochasticDPeriod Период %D стохастика. 3
StochasticSlowing Параметр замедления стохастика. 5
StochasticLower Уровень перепроданности. 36
StochasticUpper Уровень перекупленности. 70
TakeProfitPips Дистанция тейк-профита (пункты). 200
StopLossPips Дистанция стоп-лосса (пункты). 95
TrailingStopPips Дистанция трейлинг-стопа (пункты, 0 = отключено). 40
MaxOrdersPerDirection Максимум последовательных входов в одном направлении. 1

Отображение на графике

При наличии графического интерфейса стратегия строит:

  • свечной график цены;
  • полосы Боллинджера;
  • стохастический осциллятор на отдельной панели;
  • метки совершённых сделок.

Дополнительно

  • Размер пункта вычисляется на основе PriceStep и количества знаков инструмента, что повторяет логику исходного советника.
  • Защитные уровни округляются через Security.ShrinkPrice, чтобы соответствовать шагу цены биржи.
  • Трейлинг-стоп передвигается только тогда, когда новая цена улучшает стоп не менее чем на один пункт.
  • Все операции выполняются рыночными ордерами, без установки отложенных заявок, что полностью соответствует оригинальной реализации.
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 OneHrStocTraderStrategy : 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 OneHrStocTraderStrategy()
	{
		_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;
	}
}