Ver en GitHub

Estrategia 20/200 Expert

Esta estrategia abre operaciones basándose en la diferencia entre los precios de apertura de dos barras pasadas. Entra largo cuando la apertura en shift2 menos la apertura en shift1 supera un umbral y entra corto en la condición opuesta. Las posiciones se abren solo en una hora especificada y se cierran por take profit, stop loss o después de un tiempo máximo de mantenimiento.

Detalles

  • Criterios de entrada:
    • Largo: open[Shift2] - open[Shift1] > DeltaLong puntos.
    • Corto: open[Shift1] - open[Shift2] > DeltaShort puntos.
  • Largo/Corto: Ambos.
  • Criterios de salida: take profit, stop loss o tiempo máximo de mantenimiento.
  • Stops: Stop loss y take profit fijos en puntos.
  • Valores predeterminados:
    • Shift1 = 6
    • Shift2 = 2
    • DeltaLong = 6 puntos
    • DeltaShort = 21 puntos
    • TakeProfitLong = 390 puntos
    • StopLossLong = 1470 puntos
    • TakeProfitShort = 320 puntos
    • StopLossShort = 2670 puntos
    • TradeHour = 14
    • MaxOpenTime = 504 horas
    • Volume = 0.1
    • Marco temporal de velas = 1 hora
  • Filtros:
    • Categoría: Momentum
    • Dirección: Largo y Corto
    • Indicadores: Ninguno
    • Stops: Sí
    • Complejidad: Moderado
    • Marco temporal: Por hora
    • Estacionalidad: Basada en tiempo
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
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>
/// Strategy that compares the opening prices of two past bars.
/// Goes long if the second shifted open exceeds the first by a threshold
/// and short if the opposite condition is met. Trades only at a specified hour
/// and exits via fixed take profit, stop loss or timeout.
/// </summary>
public class Twenty200ExpertStrategy : Strategy
{
	private readonly StrategyParam<int> _shift1;
	private readonly StrategyParam<int> _shift2;
	private readonly StrategyParam<int> _deltaLong;
	private readonly StrategyParam<int> _deltaShort;
	private readonly StrategyParam<int> _takeProfitLong;
	private readonly StrategyParam<int> _stopLossLong;
	private readonly StrategyParam<int> _takeProfitShort;
	private readonly StrategyParam<int> _stopLossShort;
	private readonly StrategyParam<int> _tradeHour;
	private readonly StrategyParam<int> _maxOpenTime;
	private readonly StrategyParam<decimal> _volume;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _opens = new();
	private decimal _entryPrice;
	private DateTimeOffset _entryTime;

	/// <summary>
	/// First bar shift.
	/// </summary>
	public int Shift1 { get => _shift1.Value; set => _shift1.Value = value; }

	/// <summary>
	/// Second bar shift.
	/// </summary>
	public int Shift2 { get => _shift2.Value; set => _shift2.Value = value; }

	/// <summary>
	/// Difference for long entry in points.
	/// </summary>
	public int DeltaLong { get => _deltaLong.Value; set => _deltaLong.Value = value; }

	/// <summary>
	/// Difference for short entry in points.
	/// </summary>
	public int DeltaShort { get => _deltaShort.Value; set => _deltaShort.Value = value; }

	/// <summary>
	/// Take profit for long positions in points.
	/// </summary>
	public int TakeProfitLong { get => _takeProfitLong.Value; set => _takeProfitLong.Value = value; }

	/// <summary>
	/// Stop loss for long positions in points.
	/// </summary>
	public int StopLossLong { get => _stopLossLong.Value; set => _stopLossLong.Value = value; }

	/// <summary>
	/// Take profit for short positions in points.
	/// </summary>
	public int TakeProfitShort { get => _takeProfitShort.Value; set => _takeProfitShort.Value = value; }

	/// <summary>
	/// Stop loss for short positions in points.
	/// </summary>
	public int StopLossShort { get => _stopLossShort.Value; set => _stopLossShort.Value = value; }

	/// <summary>
	/// Hour when new positions can be opened.
	/// </summary>
	public int TradeHour { get => _tradeHour.Value; set => _tradeHour.Value = value; }

	/// <summary>
	/// Maximum time to hold a position in hours.
	/// </summary>
	public int MaxOpenTime { get => _maxOpenTime.Value; set => _maxOpenTime.Value = value; }

	/// <summary>
	/// Order volume.
	/// </summary>
	public decimal OrderVolume { get => _volume.Value; set => _volume.Value = value; }

	/// <summary>
	/// Candle type for calculations.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>
	/// Initializes a new instance of the <see cref="Twenty200ExpertStrategy"/>.
	/// </summary>
	public Twenty200ExpertStrategy()
	{
		_shift1 = Param(nameof(Shift1), 6)
			.SetGreaterThanZero()
			.SetDisplay("Shift 1", "Shift of first bar", "Signals")
			;

		_shift2 = Param(nameof(Shift2), 2)
			.SetGreaterThanZero()
			.SetDisplay("Shift 2", "Shift of second bar", "Signals")
			;

		_deltaLong = Param(nameof(DeltaLong), 20)
			.SetGreaterThanZero()
			.SetDisplay("Delta Long", "Price difference for long in points", "Signals")
			;

		_deltaShort = Param(nameof(DeltaShort), 40)
			.SetGreaterThanZero()
			.SetDisplay("Delta Short", "Price difference for short in points", "Signals")
			;

		_takeProfitLong = Param(nameof(TakeProfitLong), 390)
			.SetDisplay("Take Profit Long", "Take profit for long in points", "Risk")
			;

		_stopLossLong = Param(nameof(StopLossLong), 1470)
			.SetDisplay("Stop Loss Long", "Stop loss for long in points", "Risk")
			;

		_takeProfitShort = Param(nameof(TakeProfitShort), 320)
			.SetDisplay("Take Profit Short", "Take profit for short in points", "Risk")
			;

		_stopLossShort = Param(nameof(StopLossShort), 2670)
			.SetDisplay("Stop Loss Short", "Stop loss for short in points", "Risk")
			;

		_tradeHour = Param(nameof(TradeHour), 12)
			.SetRange(0, 23)
			.SetDisplay("Trade Hour", "Hour to open positions", "Signals")
			;

		_maxOpenTime = Param(nameof(MaxOpenTime), 504)
			.SetGreaterThanZero()
			.SetDisplay("Max Open Time", "Maximum position time in hours", "Risk")
			;

		_volume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_opens.Clear();
		_entryPrice = 0m;
		_entryTime = default;
	}

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

		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;

		_opens.Enqueue(candle.OpenPrice);
		var maxShift = Math.Max(Shift1, Shift2) + 1;
		if (_opens.Count > maxShift)
			_opens.Dequeue();

		var priceStep = Security.PriceStep ?? 1m;

		if (Position > 0)
		{
			var tp = _entryPrice + TakeProfitLong * priceStep;
			var sl = _entryPrice - StopLossLong * priceStep;
			var timedOut = MaxOpenTime > 0 && (candle.OpenTime - _entryTime).TotalHours >= MaxOpenTime;
			if (candle.HighPrice >= tp || candle.LowPrice <= sl || timedOut)
			{
				SellMarket();
				_entryPrice = 0m;
				_entryTime = default;
			}
		}
		else if (Position < 0)
		{
			var tp = _entryPrice - TakeProfitShort * priceStep;
			var sl = _entryPrice + StopLossShort * priceStep;
			var timedOut = MaxOpenTime > 0 && (candle.OpenTime - _entryTime).TotalHours >= MaxOpenTime;
			if (candle.LowPrice <= tp || candle.HighPrice >= sl || timedOut)
			{
				BuyMarket();
				_entryPrice = 0m;
				_entryTime = default;
			}
		}

		if (_opens.Count < maxShift)
			return;

		if (Position != 0)
			return;

		var arr = _opens.ToArray();
		var openShift1 = arr[arr.Length - 1 - Shift1];
		var openShift2 = arr[arr.Length - 1 - Shift2];

		var diffLong = openShift2 - openShift1;
		var diffShort = openShift1 - openShift2;
		var thLong = DeltaLong * priceStep;
		var thShort = DeltaShort * priceStep;

		if (candle.OpenTime.Hour != TradeHour)
			return;

		if (diffLong > thLong && diffShort <= thShort)
		{
			BuyMarket();
			_entryPrice = candle.ClosePrice;
			_entryTime = candle.OpenTime;
		}
		else if (diffShort > thShort && diffLong <= thLong)
		{
			SellMarket();
			_entryPrice = candle.ClosePrice;
			_entryTime = candle.OpenTime;
		}
	}
}