Ver en GitHub

Estrategia de Reversión VRS Vegas

Estrategia de reversión que utiliza las mechas de las velas.

Las pruebas indican un rendimiento anual promedio de aproximadamente el 37%. Funciona mejor en el mercado de criptomonedas.

El sistema busca grandes picos relativos al precio de cierre. Una mecha inferior grande activa una entrada larga mientras que una mecha superior grande activa una entrada corta. Las posiciones se cierran cuando el precio se mueve el doble del tamaño del pico en beneficio.

Detalles

  • Criterios de entrada:
    • Largo: mecha inferior ≥ Spike% * close y sin pico superior.
    • Corto: mecha superior ≥ Spike% * close y sin pico inferior.
  • Largo/Corto: Ambos lados.
  • Criterios de salida: Objetivo en entrada ± (pico * 2).
  • Stops: No.
  • Valores predeterminados:
    • SpikePercent = 0.025
    • CandleType = TimeSpan.FromMinutes(5)
  • Filtros:
    • Categoría: Reversión
    • Dirección: Ambos
    • Indicadores: Price action
    • Stops: No
    • Complejidad: Básico
    • Marco temporal: Intradía
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Alto
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>
/// Vegas Reversal strategy based on spike percentage.
/// Enters long on large lower wick and short on large upper wick.
/// Exits when price moves twice the spike length in favor.
/// </summary>
public class VrsVegasReversalStrategy : Strategy
{
    private readonly StrategyParam<decimal> _spikePercent;
    private readonly StrategyParam<DataType> _candleType;

    private decimal _entryPrice;
    private decimal _spikeSize;
    private bool _isLong;

    /// <summary>
    /// Spike percentage relative to close price.
    /// </summary>
    public decimal SpikePercent
    {
	get => _spikePercent.Value;
	set => _spikePercent.Value = value;
    }

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

    /// <summary>
    /// Initializes a new instance of <see cref="VrsVegasReversalStrategy"/>.
    /// </summary>
    public VrsVegasReversalStrategy()
    {
	_spikePercent = Param(nameof(SpikePercent), 0.025m)
	    .SetGreaterThanZero()
	    .SetDisplay("Spike %", "Spike percentage threshold", "Reversal")
	    
	    .SetOptimize(0.01m, 0.05m, 0.005m);

	_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).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();
	_entryPrice = 0;
	_spikeSize = 0;
	_isLong = false;
    }

    /// <inheritdoc />
    protected override void OnStarted2(DateTime time)
    {
	base.OnStarted2(time);
	Volume = NormalizeVolume(1m);

	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;

	var upperSpike = candle.HighPrice - Math.Max(candle.ClosePrice, candle.OpenPrice);
	var lowerSpike = Math.Min(candle.ClosePrice, candle.OpenPrice) - candle.LowPrice;

	var validUpper = upperSpike >= candle.ClosePrice * SpikePercent;
	var validLower = lowerSpike >= candle.ClosePrice * SpikePercent;
	var valid = (validUpper && !validLower) || (validLower && !validUpper);

	var enterLong = valid && validLower;
	var enterShort = valid && validUpper;

	if (enterLong && Position <= 0)
	{
	    _entryPrice = candle.ClosePrice;
	    _spikeSize = lowerSpike;
	    _isLong = true;
	    BuyMarket(NormalizeVolume(Volume + Math.Abs(Position)));
	}
	else if (enterShort && Position >= 0)
	{
	    _entryPrice = candle.ClosePrice;
	    _spikeSize = upperSpike;
	    _isLong = false;
	    SellMarket(NormalizeVolume(Volume + Math.Abs(Position)));
	}

	if (Position > 0 && _isLong)
	{
	    var target = _entryPrice + _spikeSize * 2m;
	    if (candle.ClosePrice >= target)
		SellMarket(Position);
	}
	else if (Position < 0 && !_isLong)
	{
	    var target = _entryPrice - _spikeSize * 2m;
	    if (candle.ClosePrice <= target)
		BuyMarket(-Position);
	}
    }

    private decimal NormalizeVolume(decimal volume)
    {
	var step = Security?.VolumeStep ?? 1m;
	if (step <= 0m)
	    step = 1m;

	var minimum = Security?.MinVolume ?? step;
	if (volume < minimum)
	    volume = minimum;

	var rounded = Math.Ceiling(volume / step) * step;
	return rounded < minimum ? minimum : rounded;
    }
}