GitHub で見る

SVMトレーダー戦略

概要

SVMトレーダー戦略は、古典的なテクニカルインジケーターの組み合わせが、取引シグナルを生成するサポートベクターマシン(SVM)モデルの動作をどのように近似できるかを示しています。元のMQL例では買いと売りの決定に2つの別々のSVMを訓練していました。このStockSharpへの変換では、7つのインジケーターから導出されたシンプルなスコアリングシステムで意思決定プロセスをエミュレートします。

  • Bears PowerBulls Power – 売り手と買い手のバランスを測定。
  • Average True Range (ATR) – 現在のボラティリティを捉える。
  • Momentum – 価格の加速度を確認。
  • Moving Average Convergence Divergence (MACD) – トレンド方向を識別。
  • Stochastic Oscillator – 買われすぎ・売られすぎレベルを検出。
  • Force Index – 価格動向とボリュームを組み合わせる。

各インジケーターが累積スコアに貢献します。スコアが閾値を超えるとロングポジションを開き、スコアが反対の閾値を下回るとショートポジションを開きます。この設定は、実装を軽量で透明に保ちながら、元のSVMアプローチの分類ステップを反映しています。

パラメーター

名前 説明
CandleType 計算に使用するローソク足の時間軸。
Volume 新規取引の注文量。
TakeProfit 絶対価格単位でのテイクプロフィット距離。
StopLoss 絶対価格単位でのストップロス距離。
RiskExposure 許可される最大累積ポジションボリューム。

取引ロジック

  1. 指定されたタイプのローソク足を購読し、高レベルAPIを使用してすべてのインジケーターをバインドする。
  2. 各完了ローソク足で、バインディングコールバックからインジケーター値を取得する。
  3. スコアを計算する:
    • Bulls PowerがBears Powerより大きい
    • Momentumがゼロ以上
    • MACDラインがシグナルラインを上回る
    • ストキャスティック%Kが%Dを上回る
    • Force Indexがゼロを上回る
  4. 少なくとも3つの条件が真で現在のポジションが非正の場合、成行買い注文を発注する。
  5. 2つ以下の条件が真で現在のポジションが非負の場合、成行売り注文を発注する。
  6. StartProtectionは開かれた各ポジションにストップロスとテイクプロフィットの両方を適用する。

注記

  • インジケーター期間は元のMQL例の値(対称性と平滑性のために主に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;
	}
}