Ver en GitHub

Estrategia SAW System 1

Esta estrategia de ruptura coloca órdenes stop al inicio de cada día de trading. Mide el rango diario promedio durante un número configurable de días y utiliza ese valor para derivar los niveles de stop-loss y take-profit. Las órdenes se posicionan en ambos lados del precio actual y solo se espera que uno de ellos se active.

En el OpenHour especificado, la estrategia calcula los precios de buy stop y sell stop a la mitad de la distancia de stop-loss desde el precio de mercado actual. Los niveles de stop-loss y take-profit se definen como porcentajes del rango promedio. Cuando se ejecuta una orden stop, la orden opuesta puede cancelarse o mantenerse para revertir la posición. Una función opcional de martingala multiplica el volumen de la orden restante tras una ejecución.

Las órdenes de entrada pendientes que no se ejecuten antes de CloseHour se eliminan para evitar exposición durante la noche. Tras una entrada, la estrategia coloca inmediatamente órdenes protectoras de stop-loss y take-profit relativas al precio de ejecución.

Detalles

  • Criterios de entrada:
    • Calcular el rango diario promedio usando un ATR durante VolatilityDays días.
    • Calcular las distancias de stop-loss y take-profit como StopLossRate y TakeProfitRate por ciento de ese rango.
    • En OpenHour colocar órdenes buy y sell stop a offset = stopLoss/2 del precio de mercado.
  • Criterios de salida:
    • Las órdenes protectoras de stop-loss y take-profit cierran posiciones.
    • Las órdenes de entrada pendientes se cancelan en CloseHour.
  • Modo inverso:
    • Si Reverse es verdadero, la orden stop opuesta permanece para revertir la posición.
    • Si UseMartingale también es verdadero, la orden restante se re-registra con el volumen multiplicado por MartingaleMultiplier.
  • Largo/Corto: Ambas direcciones.
  • Stops: Stop-loss y take-profit fijos basados en el rango diario.
  • Valores predeterminados:
    • VolatilityDays = 5
    • OpenHour = 7
    • CloseHour = 10
    • StopLossRate = 15%
    • TakeProfitRate = 30%
    • Reverse = false
    • UseMartingale = false
    • MartingaleMultiplier = 2.0

Este enfoque intenta capturar rupturas tras sesiones nocturnas tranquilas, limitando el riesgo mediante objetivos ajustados a la volatilidad.

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>
/// SAW System breakout strategy.
/// Uses ATR to calculate volatility range, then enters on breakout above/below
/// the open price offset by a fraction of ATR.
/// </summary>
public class SawSystem1Strategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _breakoutMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevAtr;
	private decimal _sessionOpen;
	private bool _traded;
	private DateTime _currentDate;

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public decimal BreakoutMultiplier
	{
		get => _breakoutMultiplier.Value;
		set => _breakoutMultiplier.Value = value;
	}

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

	public SawSystem1Strategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators");

		_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 0.5m)
			.SetDisplay("Breakout Multiplier", "Fraction of ATR for breakout offset", "Parameters");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevAtr = null;
		_sessionOpen = 0;
		_traded = false;
		_currentDate = default;
	}

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

		_prevAtr = null;
		_sessionOpen = 0;
		_traded = false;
		_currentDate = default;

		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var date = candle.OpenTime.Date;

		// New day: record open price and reset
		if (date != _currentDate)
		{
			_currentDate = date;
			_sessionOpen = candle.OpenPrice;
			_traded = false;

			// Close any open position at start of new day
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			_prevAtr = atrValue;
			return;
		}

		if (_traded || _prevAtr is null || _sessionOpen == 0)
		{
			_prevAtr = atrValue;
			return;
		}

		var offset = _prevAtr.Value * BreakoutMultiplier;
		var upperBreak = _sessionOpen + offset;
		var lowerBreak = _sessionOpen - offset;

		if (candle.ClosePrice > upperBreak && Position <= 0)
		{
			BuyMarket();
			_traded = true;
		}
		else if (candle.ClosePrice < lowerBreak && Position >= 0)
		{
			SellMarket();
			_traded = true;
		}

		_prevAtr = atrValue;
	}
}