GitHub で見る

スイングトレーダー戦略

概要

SwingTrader 戦略 は、MetaTrader 4 エキスパート アドバイザー SwingTrader.mq4 の StockSharp 移植です。元の EA は次のものを探します Bollinger バンドの反転: 価格が外側のバンドから反発し、次のバーが中央線を横切ると、アドバイザーは 位置を決め、マーチンゲール形式の平均グリッドの構築を開始します。翻訳された戦略は、同じ高レベルの動作を再現します。 StockSharp 個のキャンドル、StockSharp.Algo.Indicators の Bollinger 個のバンド、およびフレームワークの順序ヘルパー (BuyMarketSellMarket)。取引量を尊重しながら、ボリュームスケーリング、グリッドの幅、清算ルールはMT4コードを反映しています。 Security メタデータによって提供される制限。

取引ロジック

  1. 設定されたタイムフレーム (CandleType) をサブスクライブし、BollingerPeriod の長さと 標準偏差乗数 2 を固定しました。
  2. 完成したキャンドルのみを使用してください。インジケーターのコールバックは、MT4 IsNewCandle() を複製するために部分的に形成されたバーを無視します。 警備員。
  3. 前のローソク足が上部バンドまたは下部バンドに触れたかどうかを追跡します。ブール値ペア _upTouch / _downTouch が続きます。 反対側のバンドがタッチされるまで片側のみをアクティブに保つ独自のトグルロジック。
  4. 開いているバスケットがない場合:
    • 最後に完了したバーが、以前に下部バンドに触れた後、中央バンドの上を横切った場合、ロングポジションをオープンします。
    • バーがアッパーバンドに触れた後、ミドルバンドの下を横切った場合はショートポジションをオープンします。 最初の注文量は InitialVolume (為替四捨五入後) に等しく、初期グリッド幅は最新の距離に等しい 上部と下部の Bollinger バンドの間。
  5. バスケットが存在する場合は、最初のフィルから 1 バンド幅全体の逆方向の動きに注意してください。
    • ロングの場合、ローソク足の安値がアンカー価格より少なくとも 1 バンド幅下にある場合は、サイズを乗算した別のスライスを購入します。 新しいレベルごとに Multiplier ずつ。
    • ショートの場合、ローソク足の高値がアンカー価格を 1 バンド幅上回っている場合、同じ値を使用して追加のスライスを売ります。 乗算ロジック。
  6. 利益または最大許容損失の目標に達するまで、新しい注文を集計し続けます。

資金管理と出口

  • ヘルパー CalculateUnrealizedProfit は、価格差を価格に変換することで MT4 変動損益計算を再現します。 ステップ (Security.PriceStep) とステップ値 (Security.StepPrice)。
  • 投下資本プロキシでは元の式 Lots * Price / TickSize * TickValue / 30 が使用されます。ここで、Lots は合計になります。 グリッド ボリュームとティック パラメータは Security から供給されます。
  • 変動利益が TakeProfitFactor * invested capital を超えたら、バスケット全体を閉じます。
  • 浮動損失が 10 * TakeProfitFactor * invested capital に達した場合に緊急清算を強制します(浮動損失と同じ比率) MT4コード)。
  • すべての決済は反対方向の成行注文で実行されます。一度フラットになると、グリッドの状態がリセットされ、新しいタッチが必要になります。 別のエントリがトリガーされる前に検出されます。

パラメーター

名前 種類 デフォルト 説明
TakeProfitFactor decimal 0.05 利益目標を定義するために投資資本に適用される乗数。
Multiplier decimal 1.5 追加の平均化注文ごとのボリューム乗数。
BollingerPeriod int 20 Bollinger バンド インジケーターで使用されるローソク足の数。
InitialVolume decimal 1 新しいバスケットの最初の取引の基本ボリューム (会場制限に四捨五入)。
CandleType DataType 15分の時間枠 シグナル生成に使用されるタイムフレーム。

オリジナルの EA との違い

  • StockSharp はネット ポジションで機能します。この戦略は、MT4 のチケットベースの注文をエミュレートするために、グリッド エントリの明示的なリストを維持します。 取り扱い。
  • 代わりに、Exchange ボリューム フィルタ (Security.MinVolumeSecurity.VolumeStepSecurity.MaxVolume) が自動的に適用されます CheckVolumeValue を手動で呼び出す方法。
  • シグナルは閉じたローソク足で評価されます。 MT4 バージョンのバー内トリガーは、ローソク足の高値と安値を使用して近似されます。 決定を平均化するため。
  • 注文は常に市場指示として送信されますが、MT4 では明示的な買値/売値パラメーターを含む OrderSend が使用されていました。

使用上の注意

  • 取引商品の現実的なメタデータを提供します: PriceStepStepPriceMinVolumeVolumeStep、および MaxVolume は必須です MT4 の動作に一致するように、利益、損失、および出来高の計算用に設定されます。
  • 平均グリッドは幾何学的にスケールするため、履歴データで構成をテストし、ブローカーのマージンを考慮してください。 ライブで実行する前の要件。
  • グリッド幅は現在の Bollinger バンド幅と同じです。 BollingerPeriod を変更すると、エントリーのタイミングとグリッドの両方に直接影響します 間隔。最適化中に感度を検証します。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "SwingTrader" MetaTrader expert.
/// Uses Bollinger Band touches to detect swing direction, then enters on middle-band cross.
/// </summary>
public class SwingTraderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;

	private BollingerBands _bollinger;
	private bool _upTouch;
	private bool _downTouch;
	private decimal? _prevClose;
	private decimal? _prevMiddle;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	public SwingTraderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signals", "General");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 2m)
			.SetGreaterThanZero()
			.SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };
		_upTouch = false;
		_downTouch = false;
		_prevClose = null;
		_prevMiddle = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_bollinger, ProcessCandle)
			.Start();

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

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

		if (!bbValue.IsFinal)
			return;

		if (bbValue is not BollingerBandsValue bbVal)
			return;

		if (bbVal.UpBand is not decimal upper || bbVal.LowBand is not decimal lower || bbVal.MovingAverage is not decimal middle)
			return;

		if (!_bollinger.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevMiddle = middle;
			return;
		}

		var close = candle.ClosePrice;

		// Track Bollinger touches
		if (candle.HighPrice > upper)
		{
			_upTouch = true;
			_downTouch = false;
		}
		if (candle.LowPrice < lower)
		{
			_downTouch = true;
			_upTouch = false;
		}

		if (_prevClose is null || _prevMiddle is null)
		{
			_prevClose = close;
			_prevMiddle = middle;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Buy: had a lower band touch, now price crosses above middle
		var buySignal = _downTouch && _prevClose.Value < _prevMiddle.Value && close > middle;
		// Sell: had an upper band touch, now price crosses below middle
		var sellSignal = _upTouch && _prevClose.Value > _prevMiddle.Value && close < middle;

		if (buySignal)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));

			if (Position <= 0)
				BuyMarket(volume);

			_downTouch = false;
		}
		else if (sellSignal)
		{
			if (Position > 0)
				SellMarket(Position);

			if (Position >= 0)
				SellMarket(volume);

			_upTouch = false;
		}

		_prevClose = close;
		_prevMiddle = middle;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_bollinger = null;
		_upTouch = false;
		_downTouch = false;
		_prevClose = null;
		_prevMiddle = null;

		base.OnReseted();
	}
}