Ver en GitHub

Estrategia de Ejecución de Órdenes OCO

Esta estrategia replica un ticket de orden "Una Cancela la Otra" escrito originalmente para MetaTrader. Permite al trader definir hasta cuatro disparadores de precio independientes:

  • Buy Limit Price
  • Sell Limit Price
  • Buy Stop Price
  • Sell Stop Price

La estrategia se suscribe a datos Level1 para monitorear continuamente la mejor oferta y demanda. Cuando se alcanza un precio disparador, envía una orden de mercado en la dirección correspondiente. Después de ejecutarse una orden, se aplican protecciones de stop-loss y take-profit usando distancias medidas en pips. Estas distancias se convierten automáticamente a precios absolutos basándose en el PriceStep del instrumento.

Cuando el modo OCO está habilitado, alcanzar cualquier disparador desactivará automáticamente todos los demás, implementando eficazmente el comportamiento clásico de una-cancela-la-otra. Si el modo OCO está deshabilitado, los demás disparadores permanecen activos y pueden abrir posiciones adicionales cuando el precio continúa moviéndose.

Detalles

  • Criterios de entrada:
    • Largo cuando Ask <= BuyLimitPrice (disparador Buy Limit).
    • Largo cuando Ask >= BuyStopPrice (disparador Buy Stop).
    • Corto cuando Bid >= SellLimitPrice (disparador Sell Limit).
    • Corto cuando Bid <= SellStopPrice (disparador Sell Stop).
  • Largo/Corto: Ambos.
  • Criterios de salida:
    • Las posiciones se cierran automáticamente por niveles de stop-loss o take-profit predefinidos.
  • Stops: Sí, stop-loss y take-profit en pips.
  • Valores predeterminados:
    • StopLossPips = 300.
    • TakeProfitPips = 300.
    • OCO Mode = habilitado.
  • Filtros:
    • Categoría: Ejecución de órdenes.
    • Dirección: Ambos.
    • Indicadores: Ninguno.
    • Stops: Sí.
    • Complejidad: Simple.
    • Marco temporal: Basado en ticks.
    • Estacionalidad: No.
    • Redes neuronales: No.
    • Divergencia: No.
    • Nivel de riesgo: Medio.
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>
/// OCO-style breakout strategy. Calculates dynamic buy-stop and sell-stop levels
/// from recent high/low and enters on breakout, with StdDev-based stop-loss and take-profit.
/// </summary>
public class OcoOrderStrategy : Strategy
{
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<decimal> _stdMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _recentHigh;
	private decimal _recentLow;
	private decimal _entryPrice;
	private int _barCount;

	public int LookbackPeriod { get => _lookbackPeriod.Value; set => _lookbackPeriod.Value = value; }
	public decimal StdMultiplier { get => _stdMultiplier.Value; set => _stdMultiplier.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public OcoOrderStrategy()
	{
		_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Lookback", "Bars for high/low calculation", "General");

		_stdMultiplier = Param(nameof(StdMultiplier), 1.5m)
			.SetDisplay("StdDev Multiplier", "Multiplier for SL/TP distance", "Risk");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_recentHigh = 0;
		_recentLow = decimal.MaxValue;
		_entryPrice = 0;
		_barCount = 0;
	}

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

		var stdDev = new StandardDeviation { Length = LookbackPeriod };

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

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

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

		_barCount++;

		// Track rolling high/low
		if (_barCount <= LookbackPeriod)
		{
			if (candle.HighPrice > _recentHigh)
				_recentHigh = candle.HighPrice;
			if (candle.LowPrice < _recentLow)
				_recentLow = candle.LowPrice;
			return;
		}

		if (stdValue <= 0)
			return;

		var close = candle.ClosePrice;
		var slDistance = stdValue * StdMultiplier;

		// Exit logic
		if (Position > 0)
		{
			if (close <= _entryPrice - slDistance || close >= _entryPrice + slDistance * 2)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + slDistance || close <= _entryPrice - slDistance * 2)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: breakout above recent high or below recent low
		if (Position == 0)
		{
			if (close > _recentHigh)
			{
				BuyMarket();
				_entryPrice = close;
			}
			else if (close < _recentLow)
			{
				SellMarket();
				_entryPrice = close;
			}
		}

		// Update high/low
		if (candle.HighPrice > _recentHigh)
			_recentHigh = candle.HighPrice;
		if (candle.LowPrice < _recentLow)
			_recentLow = candle.LowPrice;
	}
}