Открыть на GitHub

Стратегия Up3x1 Investor

Перенос советника Up3x1 Investor из MetaTrader. Стратегия отслеживает последний закрытый бар на выбранном таймфрейме и при достаточной ширине диапазона и тела свечи открывает позицию в направлении закрытия на следующей свече.

По умолчанию стратегия рассчитана на часовики по основным валютным парам, но пороги можно адаптировать под любой инструмент. Одновременно поддерживается только одна позиция, объем сделки задаётся свойством Volume стратегии.

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

  • Источник сигналов – свечи типа CandleType (по умолчанию H1).
  • Условия входа
    • Рассчитывается диапазон High–Low и модуль тела предыдущей свечи.
    • Длинная позиция открывается, если свеча закрылась выше открытия и оба значения превышают порог в пунктах.
    • Короткая позиция открывается, если свеча закрылась ниже открытия и оба значения превышают порог.
    • Пока позиция открыта, новые входы игнорируются.
  • Сопровождение позиции
    • Стоп-лосс и тейк-профит задаются в пунктах, переводятся в цену через Security.PriceStep. Нулевое значение отключает уровень.
    • Трейлинг-стоп активируется, когда цена проходит расстояние TrailingStopPips + TrailingStepPips от входа.
    • Трейлинг сдвигается только если новый уровень ближе к цене минимум на TrailingStepPips.
    • Выход из позиции происходит при достижении стоп-лосса, тейк-профита или трейлинг-стопа.

Параметры

Параметр Описание
CandleType Тип свечей для сигналов (по умолчанию 1 час).
RangeThresholdPips Минимальный диапазон High–Low предыдущей свечи в пунктах.
BodyThresholdPips Минимальный размер тела предыдущей свечи в пунктах.
StopLossPips Стоп-лосс в пунктах, 0 отключает уровень.
TakeProfitPips Тейк-профит в пунктах, 0 отключает уровень.
TrailingStopPips Расстояние трейлинг-стопа от цены, 0 отключает трейлинг.
TrailingStepPips Дополнительное движение цены для сдвига трейлинг-стопа.

Важно: Пункты пересчитываются через Security.PriceStep. Убедитесь, что у инструмента корректно задан шаг цены.

Рекомендации по использованию

  1. Перед запуском назначьте нужный Security и подключение к торговому шлюзу.
  2. Настройте пороги в пунктах под волатильность вашего рынка (для 5-значных котировок форекса 10 пунктов = 0.0010).
  3. Задайте Volume стратегии для управления размером позиции. Алгоритм управления риском исходного советника намеренно упрощён.
  4. Сигналы формируются на закрытии свечи, заявки отправляются сразу после подтверждения импульсной свечи.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Range breakout strategy based on the Up3x1 Investor expert advisor.
/// </summary>
public class Up3x1InvestorStrategy : Strategy
{
	private readonly StrategyParam<decimal> _rangeThresholdPips;
	private readonly StrategyParam<decimal> _bodyThresholdPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _hasPreviousCandle;
	private decimal? _entryPrice;
	private decimal _highestPrice;
	private decimal _lowestPrice;
	private decimal? _trailingStopPrice;

	public decimal RangeThresholdPips { get => _rangeThresholdPips.Value; set => _rangeThresholdPips.Value = value; }
	public decimal BodyThresholdPips { get => _bodyThresholdPips.Value; set => _bodyThresholdPips.Value = value; }
	public decimal StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public decimal TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
	public decimal TrailingStopPips { get => _trailingStopPips.Value; set => _trailingStopPips.Value = value; }
	public decimal TrailingStepPips { get => _trailingStepPips.Value; set => _trailingStepPips.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Up3x1InvestorStrategy()
	{
		_rangeThresholdPips = Param(nameof(RangeThresholdPips), 2m)
			.SetDisplay("Range Threshold (pips)", "Minimum previous candle range in pips", "Signals");

		_bodyThresholdPips = Param(nameof(BodyThresholdPips), 1m)
			.SetDisplay("Body Threshold (pips)", "Minimum previous candle body in pips", "Signals");

		_stopLossPips = Param(nameof(StopLossPips), 5m)
			.SetDisplay("Stop Loss (pips)", "Distance of protective stop in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 5m)
			.SetDisplay("Take Profit (pips)", "Distance of profit target in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 3m)
			.SetDisplay("Trailing Stop (pips)", "Distance kept behind price when trailing", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetDisplay("Trailing Step (pips)", "Increment required to move trailing stop", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for signals", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		_hasPreviousCandle = false;
		ResetPositionTracking();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Subscribe to the configured timeframe and process finished candles.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Work only with fully formed candles to keep logic aligned with the original EA.
		if (candle.State != CandleStates.Finished)
			return;

		// If position was closed externally, reset tracking.
		if (Position == 0 && _entryPrice != null)
			ResetPositionTracking();

		var pipSize = GetPipSize();
		var stopLossDistance = StopLossPips > 0 ? StopLossPips * pipSize : 0m;
		var takeProfitDistance = TakeProfitPips > 0 ? TakeProfitPips * pipSize : 0m;
		var trailingStopDistance = TrailingStopPips > 0 ? TrailingStopPips * pipSize : 0m;
		var trailingStepDistance = TrailingStepPips > 0 ? TrailingStepPips * pipSize : 0m;

		// Manage existing trades before searching for a new signal.
		if (Position != 0 && _entryPrice != null)
		{
			if (ManageOpenPosition(candle, stopLossDistance, takeProfitDistance, trailingStopDistance, trailingStepDistance))
			{
				_prevOpen = candle.OpenPrice;
				_prevClose = candle.ClosePrice;
				_prevHigh = candle.HighPrice;
				_prevLow = candle.LowPrice;
				_hasPreviousCandle = true;
				return;
			}
		}

		// no indicators bound, skip IsFormedAndOnlineAndAllowTrading

		if (Position != 0)
		{
			_prevOpen = candle.OpenPrice;
			_prevClose = candle.ClosePrice;
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return;
		}

		var refOpen = _hasPreviousCandle ? _prevOpen : candle.OpenPrice;
		var refClose = _hasPreviousCandle ? _prevClose : candle.ClosePrice;
		var refHigh = _hasPreviousCandle ? _prevHigh : candle.HighPrice;
		var refLow = _hasPreviousCandle ? _prevLow : candle.LowPrice;

		var range = refHigh - refLow;
		var body = Math.Abs(refClose - refOpen);
		var rangeThreshold = RangeThresholdPips * pipSize;
		var bodyThreshold = BodyThresholdPips * pipSize;

		// Bullish setup: strong bullish candle with large range and body.
		if (range > rangeThreshold && body > bodyThreshold && refClose > refOpen)
		{
			BuyMarket();
			InitializePositionTracking(candle.ClosePrice);
		}
		// Bearish setup: strong bearish candle with large range and body.
		else if (range > rangeThreshold && body > bodyThreshold && refClose < refOpen)
		{
			SellMarket();
			InitializePositionTracking(candle.ClosePrice);
		}

		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPreviousCandle = true;
	}

	private bool ManageOpenPosition(ICandleMessage candle, decimal stopLossDistance, decimal takeProfitDistance, decimal trailingStopDistance, decimal trailingStepDistance)
	{
		if (_entryPrice == null)
			return false;

		if (Position > 0)
		{
			// Update the highest price reached by the long position.
			_highestPrice = Math.Max(_highestPrice, candle.HighPrice);

			// Check stop loss.
			if (stopLossDistance > 0m && candle.LowPrice <= _entryPrice.Value - stopLossDistance)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}

			// Check take profit.
			if (takeProfitDistance > 0m && candle.HighPrice >= _entryPrice.Value + takeProfitDistance)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}

			// Update trailing stop level when the move is large enough.
			if (trailingStopDistance > 0m && _highestPrice - _entryPrice.Value >= trailingStopDistance + trailingStepDistance)
			{
				var candidate = _highestPrice - trailingStopDistance;
				if (_trailingStopPrice == null || candidate - _trailingStopPrice.Value >= trailingStepDistance)
				_trailingStopPrice = candidate;
			}

			// Exit if price returned to the trailing stop.
			if (_trailingStopPrice != null && candle.LowPrice <= _trailingStopPrice.Value)
			{
				SellMarket();
				ResetPositionTracking();
				return true;
			}
		}
		else if (Position < 0)
		{
			// Update the lowest price reached by the short position.
			_lowestPrice = Math.Min(_lowestPrice, candle.LowPrice);

			// Check stop loss for short trades.
			if (stopLossDistance > 0m && candle.HighPrice >= _entryPrice.Value + stopLossDistance)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}

			// Check take profit for short trades.
			if (takeProfitDistance > 0m && candle.LowPrice <= _entryPrice.Value - takeProfitDistance)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}

			// Update trailing stop for the short side.
			if (trailingStopDistance > 0m && _entryPrice.Value - _lowestPrice >= trailingStopDistance + trailingStepDistance)
			{
				var candidate = _lowestPrice + trailingStopDistance;
				if (_trailingStopPrice == null || _trailingStopPrice.Value - candidate >= trailingStepDistance)
				_trailingStopPrice = candidate;
			}

			// Exit once the trailing stop is touched.
			if (_trailingStopPrice != null && candle.HighPrice >= _trailingStopPrice.Value)
			{
				BuyMarket();
				ResetPositionTracking();
				return true;
			}
		}

		return false;
	}

	private void InitializePositionTracking(decimal entryPrice)
	{
		// Store entry information to evaluate stops and trailing logic.
		_entryPrice = entryPrice;
		_highestPrice = entryPrice;
		_lowestPrice = entryPrice;
		_trailingStopPrice = null;
	}

	private void ResetPositionTracking()
	{
		_entryPrice = null;
		_highestPrice = 0m;
		_lowestPrice = 0m;
		_trailingStopPrice = null;
	}

	private decimal GetPipSize()
	{
		var step = Security?.PriceStep;
		if (step == null || step == 0m)
			return 1m;

		return step.Value;
	}
}