在 GitHub 上查看

SVM Trader 策略

概述

SVM Trader 策略展示如何使用多个经典技术指标来模拟支持向量机(SVM)模型的交易信号。在原始的 MQL 示例中,买入和卖出分别由两个 SVM 模型决定。在本转换版本中,我们通过七个指标的评分系统来模仿该分类过程:

  • Bears PowerBulls Power:衡量空头与多头的力量对比。
  • Average True Range (ATR):描述当前波动性。
  • Momentum:检查价格动量。
  • MACD:识别趋势方向。
  • Stochastic Oscillator:检测超买和超卖状态。
  • Force Index:综合价格变动与成交量。

每个指标都会增加累计得分。当得分超过阈值时开多仓;当得分低于相反阈值时开空仓。这种方式在保持代码透明的同时,实现了原始 SVM 分类逻辑的效果。

参数

名称 描述
CandleType 用于计算的K线类型。
Volume 每次下单的数量。
TakeProfit 以价格单位表示的止盈距离。
StopLoss 以价格单位表示的止损距离。
RiskExposure 允许的最大总持仓量。

交易逻辑

  1. 订阅指定类型的K线,并使用高层 API 绑定所有指标。
  2. 对于每根完成的K线,在回调中获取所有指标值。
  3. 计算评分:
    • Bulls Power 大于 Bears Power;
    • Momentum 大于 0;
    • MACD 线高于信号线;
    • 随机指标 %K 高于 %D;
    • Force Index 大于 0。
  4. 若至少满足三个条件且当前无多头持仓,则以市价买入。
  5. 若最多满足两个条件且当前无空头持仓,则以市价卖出。
  6. StartProtection 为每笔交易设置止损和止盈。

注意事项

  • 指标周期固定为原始示例中的值(大多为 13)。
  • 评分系统是 SVM 分类的简化版本,可根据需要替换为更高级的模型。
  • RiskExposure 限制总持仓量,防止过度交易。
  • 代码遵循项目规范,使用制表符缩进并以英文注释。

免责声明

本策略仅用于教育目的,展示如何在 StockSharp 中绑定指标并进行基本风险管理。使用和修改时请自行承担风险。

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;
	}
}