Ver en GitHub

Estrategia SVM Trader

Descripción general

La Estrategia SVM Trader demuestra cómo una combinación de indicadores técnicos clásicos puede aproximar el comportamiento de un modelo de máquinas de vectores de soporte (SVM) para generar señales de trading. El ejemplo MQL original entrenaba dos SVMs separados para decisiones de compra y venta. En esta conversión a StockSharp, emulamos el proceso de decisión con un simple sistema de puntuación derivado de siete indicadores:

  • Bears Power y Bulls Power – miden el equilibrio entre vendedores y compradores.
  • Average True Range (ATR) – captura la volatilidad actual.
  • Momentum – verifica la aceleración del precio.
  • Moving Average Convergence Divergence (MACD) – identifica la dirección de la tendencia.
  • Stochastic Oscillator – detecta niveles de sobrecompra y sobreventa.
  • Force Index – combina el movimiento del precio y el volumen.

Cada indicador contribuye a una puntuación acumulada. Cuando la puntuación supera un umbral, la estrategia abre una posición larga; cuando la puntuación cae por debajo del umbral opuesto, se abre una posición corta. Esta configuración refleja el paso de clasificación del enfoque SVM original manteniendo la implementación ligera y transparente.

Parámetros

Nombre Descripción
CandleType Marco temporal de velas para los cálculos.
Volume Volumen de orden para nuevas operaciones.
TakeProfit Distancia para el take-profit en unidades absolutas de precio.
StopLoss Distancia para el stop-loss en unidades absolutas de precio.
RiskExposure Volumen máximo de posición acumulada permitido.

Lógica de Trading

  1. Suscribirse a velas del tipo especificado y enlazar todos los indicadores usando la API de alto nivel.
  2. Para cada vela terminada, recuperar los valores del indicador desde la devolución de llamada de enlace.
  3. Calcular una puntuación:
    • Bulls Power mayor que Bears Power
    • Momentum por encima de cero
    • Línea MACD por encima de su línea de señal
    • Estocástico %K por encima de %D
    • Force Index por encima de cero
  4. Si al menos tres condiciones son verdaderas y la posición actual no es positiva, se coloca una orden de compra de mercado.
  5. Si dos o menos condiciones son verdaderas y la posición actual no es negativa, se coloca una orden de venta de mercado.
  6. StartProtection aplica tanto el stop-loss como el take-profit para cada posición abierta.

Notas

  • Los períodos de los indicadores están fijos a los valores del ejemplo MQL original (principalmente 13 para simetría y suavidad).
  • El sistema de puntuación es un proxy simplificado de la clasificación SVM y puede reemplazarse con un modelo más avanzado si es necesario.
  • RiskExposure evita la sobreasignación limitando el tamaño total de posición.
  • La estrategia usa tabulaciones para la indentación y comentarios en inglés según las convenciones del proyecto.

Descargo de responsabilidad

Esta estrategia se proporciona con fines educativos. Demuestra el enlace de indicadores y la gestión básica de riesgos en StockSharp. Úsela y modifíquela bajo su propio riesgo.

using System;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// SVM Trader Strategy using multiple indicators to approximate SVM classification.
/// </summary>
public class SvmTraderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _riskExposure;
	private readonly StrategyParam<int> _buyThreshold;
	private readonly StrategyParam<int> _sellThreshold;
	private readonly StrategyParam<int> _cooldownBars;

	private BearPower _bears;
	private BullPower _bulls;
	private Momentum _momentum;
	private MovingAverageConvergenceDivergenceSignal _macd;
	private StochasticOscillator _stochastic;
	private ForceIndex _force;
	private int _previousScore;
	private bool _hasPreviousScore;
	private int _barsSinceTrade;

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

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

	/// <summary>
	/// Stop loss in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Maximum allowed cumulative position volume.
	/// </summary>
	public decimal RiskExposure
	{
		get => _riskExposure.Value;
		set => _riskExposure.Value = value;
	}

	/// <summary>
	/// Minimum score required for a long signal.
	/// </summary>
	public int BuyThreshold
	{
		get => _buyThreshold.Value;
		set => _buyThreshold.Value = value;
	}

	/// <summary>
	/// Maximum score allowed for a short signal.
	/// </summary>
	public int SellThreshold
	{
		get => _sellThreshold.Value;
		set => _sellThreshold.Value = value;
	}

	/// <summary>
	/// Bars to wait after a position is closed.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="SvmTraderStrategy"/> class.
	/// </summary>
	public SvmTraderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");

		_takeProfit = Param(nameof(TakeProfit), 1400m)
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

		_stopLoss = Param(nameof(StopLoss), 900m)
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_riskExposure = Param(nameof(RiskExposure), 1m)
			.SetDisplay("Risk Exposure", "Max cumulative position", "Risk");

		_buyThreshold = Param(nameof(BuyThreshold), 4)
			.SetDisplay("Buy Threshold", "Score required for a long signal", "Signal");

		_sellThreshold = Param(nameof(SellThreshold), 1)
			.SetDisplay("Sell Threshold", "Score required for a short signal", "Signal");

		_cooldownBars = Param(nameof(CooldownBars), 2)
			.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
	}

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

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

		_previousScore = 0;
		_hasPreviousScore = false;
		_barsSinceTrade = CooldownBars;
	}

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

		_bears = new BearPower { Length = 13 };
		_bulls = new BullPower { Length = 13 };
		_momentum = new Momentum { Length = 13 };
		_macd = new MovingAverageConvergenceDivergenceSignal();
		_stochastic = new StochasticOscillator();
		_force = new ForceIndex { Length = 13 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_bears, _bulls, _momentum, _macd, _stochastic, _force, ProcessIndicators)
			.Start();

		StartProtection(
			new Unit(TakeProfit, UnitTypes.Absolute),
			new Unit(StopLoss, UnitTypes.Absolute));

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

	private void ProcessIndicators(
		ICandleMessage candle,
		IIndicatorValue bearsValue,
		IIndicatorValue bullsValue,
		IIndicatorValue momentumValue,
		IIndicatorValue macdValue,
		IIndicatorValue stochasticValue,
		IIndicatorValue forceValue)
	{
		if (candle.State != CandleStates.Finished ||
			!bearsValue.IsFinal || !bullsValue.IsFinal || !momentumValue.IsFinal ||
			!macdValue.IsFinal || !stochasticValue.IsFinal || !forceValue.IsFinal)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_barsSinceTrade < CooldownBars)
			_barsSinceTrade++;

		var score = 0;
		var bears = bearsValue.ToDecimal();
		var bulls = bullsValue.ToDecimal();
		var momentum = momentumValue.ToDecimal();
		var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		var stochasticTyped = (StochasticOscillatorValue)stochasticValue;
		var force = forceValue.ToDecimal();

		if (bulls > bears)
			score++;
		if (momentum > 100m)
			score++;
		if (macdTyped.Macd > macdTyped.Signal)
			score++;
		if (stochasticTyped.K > stochasticTyped.D && stochasticTyped.K > 55m)
			score++;
		if (force > 0m)
			score++;

		if (!_hasPreviousScore)
		{
			_previousScore = score;
			_hasPreviousScore = true;
			return;
		}

		var longSignal = _previousScore < BuyThreshold && score >= BuyThreshold;
		var shortSignal = _previousScore > SellThreshold && score <= SellThreshold;
		var openVolume = Math.Abs(Position);

		if (_barsSinceTrade >= CooldownBars && openVolume + Volume <= RiskExposure)
		{
			if (longSignal && Position <= 0)
			{
				BuyMarket(Volume + (Position < 0 ? -Position : 0m));
				_barsSinceTrade = 0;
			}
			else if (shortSignal && Position >= 0)
			{
				SellMarket(Volume + (Position > 0 ? Position : 0m));
				_barsSinceTrade = 0;
			}
		}

		_previousScore = score;
	}
}