Ver en GitHub

Estrategia Stop Loss Take Profit

Este port replica el asesor experto MetaTrader «Stop Loss Take Profit». La estrategia lanza una moneda cada vez que la cuenta está plana y abre una orden de mercado en la dirección elegida. Cada posición recibe inmediatamente órdenes de stop-loss y take-profit basadas en pips. Si se toca el stop, la siguiente operación duplica su tamaño (limitado por los límites de volumen del valor). Un take-profit reinicia el volumen a la cantidad inicial. El comportamiento refleja el dimensionamiento de posición estilo martingala original mientras usa la API de alto nivel de StockSharp.

Lógica de Trading

  • Datos de Mercado: Usa el parámetro CandleType (por defecto marco temporal de 1 minuto) para impulsar los puntos de decisión.
  • Reglas de Entrada:
    • Cuando Position == 0 y no hay orden de entrada pendiente, la estrategia genera un booleano pseudoaleatorio.
    • true abre una posición larga con BuyMarket(volume); false abre una corta con SellMarket(volume).
  • Reglas de Salida:
    • Las órdenes de stop-loss y take-profit protectores se colocan tan pronto como se recibe el fill de entrada.
    • Una salida por stop duplica el tamaño para la siguiente operación, mientras que un take-profit lo reinicia.
    • Si la distancia de stop o take-profit se establece en 0, se omite la orden protectora respectiva.
  • Gestión de Dinero:
    • InitialVolume define el tamaño base de la orden.
    • Después de una operación perdedora, el tamaño se duplica pero se recorta a Security.MaxVolume cuando ese valor está disponible.
    • El volumen se normaliza al VolumeStep, MinVolume y MaxVolume del instrumento para que las órdenes sean válidas.
  • Manejo de Pips:
    • Por defecto, la estrategia infiere un pip del PriceStep y Decimals del instrumento (los símbolos FX de 5 dígitos se asignan a 0.0001).
    • Establezca PipSize a un valor positivo para anular la detección automática del tamaño de pip.

Parámetros

Nombre Por defecto Descripción
CandleType Velas de 1 minuto Marco temporal usado para activar lanzamientos de moneda y entradas.
StopLossPips 1 Distancia de stop-loss expresada en pips. 0 deshabilita el stop.
TakeProfitPips 1 Distancia de take-profit expresada en pips. 0 deshabilita el take-profit.
InitialVolume 0.01 Volumen de operación inicial. Duplicado después de eventos de stop-loss y reiniciado después de ganancias.
PipSize 0 (auto) Anulación opcional del tamaño de pip en unidades de precio absolutas.

Notas de Uso

  • Funciona tanto en el lado largo como corto y es intencionalmente neutral en dirección.
  • Las órdenes protectoras se cancelan siempre que la posición se cierra para evitar órdenes obsoletas.
  • El generador aleatorio se siembra con Environment.TickCount, lo que significa que cada sesión produce diferentes secuencias de operaciones.
  • Adecuado para demostrar la estratificación de riesgo y el comportamiento martingala en lugar de para trading en producción sin controles de riesgo adicionales.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Random direction strategy with pip-based stop loss and take profit that doubles the volume after losses.
/// </summary>
public class StopLossTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossDistance;
	private readonly StrategyParam<decimal> _takeProfitDistance;
	private readonly StrategyParam<decimal> _initialVolume;

	private decimal _currentVolume;
	private decimal _entryPrice;
	private int _tradeCount;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal StopLossDistance
	{
		get => _stopLossDistance.Value;
		set => _stopLossDistance.Value = value;
	}

	public decimal TakeProfitDistance
	{
		get => _takeProfitDistance.Value;
		set => _takeProfitDistance.Value = value;
	}

	public decimal InitialVolume
	{
		get => _initialVolume.Value;
		set => _initialVolume.Value = value;
	}

	public StopLossTakeProfitStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for evaluating entries", "General");

		_stopLossDistance = Param(nameof(StopLossDistance), 5m)
			.SetDisplay("Stop Loss Distance", "Stop loss distance in price units", "Risk");

		_takeProfitDistance = Param(nameof(TakeProfitDistance), 5m)
			.SetDisplay("Take Profit Distance", "Take profit distance in price units", "Risk");

		_initialVolume = Param(nameof(InitialVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Initial Volume", "Starting order volume", "Risk");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_currentVolume = 0;
		_entryPrice = 0;
		_tradeCount = 0;
	}

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

		_currentVolume = InitialVolume;
		_entryPrice = 0m;

		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;

		// Check SL/TP for open position
		if (Position != 0 && _entryPrice > 0)
		{
			if (Position > 0)
			{
				var hitStop = StopLossDistance > 0 && candle.LowPrice <= _entryPrice - StopLossDistance;
				var hitTake = TakeProfitDistance > 0 && candle.HighPrice >= _entryPrice + TakeProfitDistance;

				if (hitStop)
				{
					SellMarket();
					HandleStopLoss();
					return;
				}

				if (hitTake)
				{
					SellMarket();
					HandleTakeProfit();
					return;
				}
			}
			else if (Position < 0)
			{
				var hitStop = StopLossDistance > 0 && candle.HighPrice >= _entryPrice + StopLossDistance;
				var hitTake = TakeProfitDistance > 0 && candle.LowPrice <= _entryPrice - TakeProfitDistance;

				if (hitStop)
				{
					BuyMarket();
					HandleStopLoss();
					return;
				}

				if (hitTake)
				{
					BuyMarket();
					HandleTakeProfit();
					return;
				}
			}
		}

		// Enter new position when flat
		if (Position == 0)
		{
			_tradeCount++;

			// Use candle direction as a deterministic signal
			if (candle.ClosePrice < candle.OpenPrice)
			{
				SellMarket();
			}
			else
			{
				BuyMarket();
			}

			_entryPrice = candle.ClosePrice;
		}
	}

	private void HandleStopLoss()
	{
		// Double volume on loss (martingale)
		_currentVolume *= 2m;

		var maxVol = Security?.MaxVolume;
		if (maxVol.HasValue && maxVol.Value > 0 && _currentVolume > maxVol.Value)
			_currentVolume = maxVol.Value;

		Volume = _currentVolume;
		_entryPrice = 0m;
	}

	private void HandleTakeProfit()
	{
		// Reset volume on profit
		_currentVolume = InitialVolume;
		Volume = _currentVolume;
		_entryPrice = 0m;
	}
}