Ver en GitHub

Estrategia Genie Pivot

Esta estrategia implementa la idea de scalping de reversión Genie Pivot originalmente escrita en MQL4. Espera un patrón de pivote formado por siete velas consecutivas y gestiona la posición abierta con un take profit fijo y un stop trailing.

Lógica de la Estrategia

  1. Detección del patrón – una señal larga aparece cuando los siete mínimos anteriores son estrictamente decrecientes y la última vela completada hace un mínimo más alto con un cierre por encima del máximo anterior. Una señal corta se genera por la condición espejo en los máximos.
  2. Ejecución de órdenes – una vez confirmada la señal, la estrategia abre una orden de mercado con el volumen calculado a partir del patrimonio de la cuenta y los parámetros de riesgo configurados.
  3. Gestión de operaciones – tras la entrada se establecen un take profit y un stop trailing. El stop trailing se activa solo una vez que el beneficio supera la distancia de trailing. Si el precio revierte en la siguiente vela (bajista para largo, alcista para corto), la posición se cierra inmediatamente.
  4. Reducción de volumen – las operaciones perdedoras consecutivas reducen el volumen negociado según el parámetro Decrease Factor.

Parámetros

Nombre Descripción
TakeProfit Objetivo de ganancia en pasos de precio desde el precio de entrada.
TrailingStop Distancia del stop trailing en pasos de precio.
MaximumRisk Fracción del valor de la cuenta utilizada para dimensionar la posición.
DecreaseFactor Reduce el volumen tras pérdidas consecutivas.
BaseVolume Volumen de respaldo cuando el valor del portafolio es desconocido.
CandleType Marco temporal de las velas a analizar.

Notas

La estrategia procesa únicamente velas completadas. No se ha proporcionado versión en Python todavía.

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>
/// Pivot point reversal scalping strategy.
/// </summary>
public class GeniePivotStrategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _maximumRisk;
	private readonly StrategyParam<decimal> _decreaseFactor;
	private readonly StrategyParam<decimal> _baseVolume;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private readonly decimal[] _lows = new decimal[8];
	private readonly decimal[] _highs = new decimal[8];
	private int _filled;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private decimal _targetPrice;
	private int _lossCount;
	private int _cooldownRemaining;

	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in price steps.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Maximum risk used to calculate volume.
	/// </summary>
	public decimal MaximumRisk
	{
		get => _maximumRisk.Value;
		set => _maximumRisk.Value = value;
	}

	/// <summary>
	/// Factor to decrease volume after consecutive losses.
	/// </summary>
	public decimal DecreaseFactor
	{
		get => _decreaseFactor.Value;
		set => _decreaseFactor.Value = value;
	}

	/// <summary>
	/// Base volume used when account value is unknown.
	/// </summary>
	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

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

	/// <summary>
	/// Number of completed candles to wait after closing a position.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="GeniePivotStrategy"/>.
	/// </summary>
	public GeniePivotStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 500m).SetDisplay("Take Profit", "Profit target in points", "Risk");

		_trailingStop =
			Param(nameof(TrailingStop), 200m).SetDisplay("Trailing Stop", "Trailing distance in points", "Risk");

		_maximumRisk = Param(nameof(MaximumRisk), 0.02m).SetDisplay("Maximum Risk", "Risk per trade", "Risk");

		_decreaseFactor =
			Param(nameof(DecreaseFactor), 3m).SetDisplay("Decrease Factor", "Volume reduction after losses", "Risk");

		_baseVolume = Param(nameof(BaseVolume), 0.1m).SetDisplay("Base Volume", "Fallback volume", "General");

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

		_cooldownBars = Param(nameof(CooldownBars), 4)
			.SetDisplay("Cooldown Bars", "Completed candles to wait after closing a position", "Risk");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_filled = 0;
		_entryPrice = default;
		_stopPrice = default;
		_targetPrice = default;
		_lossCount = 0;
		_cooldownRemaining = 0;

		Array.Clear(_lows);
		Array.Clear(_highs);
	}

	/// <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 decimal GetVolume()
	{
		var lot = BaseVolume;

		if (MaximumRisk > 0m && Portfolio.CurrentValue is decimal value)
			lot = Math.Round(value * MaximumRisk / 1000m, 1);

		if (DecreaseFactor > 0m && _lossCount > 1)
			lot -= lot * _lossCount / DecreaseFactor;

		return lot < 0.1m ? 0.1m : lot;
	}

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var step = Security.PriceStep ?? 1m;
		var close = candle.ClosePrice;
		var open = candle.OpenPrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		// shift history
		for (var i = _lows.Length - 1; i > 0; i--)
		{
			_lows[i] = _lows[i - 1];
			_highs[i] = _highs[i - 1];
		}
		_lows[0] = low;
		_highs[0] = high;

		if (_filled < 4)
		{
			_filled++;
			return;
		}

		if (Position == 0)
		{
			if (_cooldownRemaining > 0)
				return;

			// Buy when 3 consecutive declining lows followed by a higher low (reversal)
			var buyCond = _lows[4] > _lows[3] && _lows[3] > _lows[2] && _lows[2] > _lows[1] &&
						  _lows[1] < _lows[0] && close > _highs[1] && close > open;

			// Sell when 3 consecutive rising highs followed by a lower high (reversal)
			var sellCond = _highs[4] < _highs[3] && _highs[3] < _highs[2] && _highs[2] < _highs[1] &&
						   _highs[1] > _highs[0] && close < _lows[1] && close < open;

			if (buyCond)
			{
				BuyMarket();
				_entryPrice = close;
				_stopPrice = _entryPrice - TrailingStop * step;
				_targetPrice = _entryPrice + TakeProfit * step;
			}
			else if (sellCond)
			{
				SellMarket();
				_entryPrice = close;
				_stopPrice = _entryPrice + TrailingStop * step;
				_targetPrice = _entryPrice - TakeProfit * step;
			}
		}
		else if (Position > 0)
		{
			if (close >= _targetPrice)
			{
				SellMarket();
				_lossCount = 0;
				_cooldownRemaining = CooldownBars;
			}
			else
			{
				if (close - _entryPrice > TrailingStop * step)
				{
					var newStop = close - TrailingStop * step;
					if (newStop > _stopPrice)
						_stopPrice = newStop;
				}

				if (low <= _stopPrice)
				{
					SellMarket();
					_lossCount++;
					_cooldownRemaining = CooldownBars;
				}
			}
		}
		else if (Position < 0)
		{
			if (close <= _targetPrice)
			{
				BuyMarket();
				_lossCount = 0;
				_cooldownRemaining = CooldownBars;
			}
			else
			{
				if (_entryPrice - close > TrailingStop * step)
				{
					var newStop = close + TrailingStop * step;
					if (newStop < _stopPrice)
						_stopPrice = newStop;
				}

				if (high >= _stopPrice)
				{
					BuyMarket();
					_lossCount++;
					_cooldownRemaining = CooldownBars;
				}
			}
		}
	}
}