GitHub で見る

VIDYA N バーボーダー Martingale

概要

オリジナルの MetaTrader 戦略は、「VIDYA N Bars Borders」チャネル インジケーターとマーチンゲール ポジション サイジング モジュールを組み合わせています。 StockSharp ポートは、価格が適応下限バンドを下回ったときに買い、価格が上限バンドを超えて上昇したときに売るというアイデアを維持しています。チャネルの中心は適応移動平均 (VIDYA アナログ) によって生成され、その幅は Average True Range エンベロープによって制御されます。資金管理ブロックは、最大ポジションとエクスポージャーの制限を守りながら、取引を失った後に取引サイズを増加させます。

取引ロジック

  1. 選択したタイムフレームのローソク足を購読します。
  2. VIDYA の代替としてカウフマン適応移動平均とその周囲の ATR チャネルを計算します。
  3. 終了したローソク足の終値が下側のバンドを下回った場合、ロングポジションにオープンまたはリバースします(Reverseフラグが有効な場合を除き、その場合ショートポジションがオープンされます)。
  4. 終値が上部バンドを上抜けたら、オープンまたは反転してショートポジション (または、Reverse が true の場合はロングポジション) にします。
  5. 前回の約定値に近づきすぎて再エントリーすることを避けるために、連続するエントリー間の最小価格距離を強制します。
  6. オープンポジション全体の変動利益が指定された金額目標に達した場合は、すべてをフラットにして次のシグナルを待ちます。
  7. 各取引が終了した後、次の基本ボリュームは初期サイズにリセットされるか (利益のある取引後)、マーチンゲール比が乗算されます (負けた取引後)。結果の出来高は商品ステップに合わせて調整され、取引ごとの出来高と総出来高の両方の上限が適用されます。

パラメーター

名前 説明
Candle Type 取引するキャンドルのデータ型。
CMO Period 適応移動平均の効率比ウィンドウ。
EMA Period 適応移動平均の平滑化期間。
ATR Period ATR チャンネルの半幅のバーの数。
Profit Target 完全なExitをトリガーする金銭的利益のしきい値。
Increase Ratio 損失取引後の次の取引高に適用される乗数。
Max Position Volume 単一注文/ポジション量の上限は厳しい。
Max Total Volume 戦略によって開かれる合計エクスポージャの上限。
Max Positions 同時ポジションの最大数 (ポートは 1 つのネット ポジションを保持します)。
Minimum Step 2 つの連続するエントリ間の最小距離 (ポイント単位で測定)。
Base Volume マーチンゲール調整前の開始注文サイズ。
Reverse Signals チャネルブレイクアウトのロング/ショート解釈を反転します。

実装メモ

  • StockSharp には、直接の VIDYA 実装は含まれていません。この戦略では、構成可能な効率性と平滑化ウィンドウを備えた KaufmanAdaptiveMovingAverage を使用して、VIDYA の適応動作を模倣します。これにより、内蔵コンポーネントに依存しながら、元のインジケーターに近い応答性が維持されます。
  • 一度に管理できるネットポジションは 1 つだけです。 MetaTrader バージョンは複数の保留中のエントリをキューに入れました。 StockSharp では、各シグナルが新しいポジションをオープンするか、現在のポジションを反転します。 Martingale スケーリングは、新しいレイヤーをすぐに追加するのではなく、次のエントリ サイズに適用されます。
  • 最小ステップと音量の調整は、楽器メタデータ (PriceStepVolumeStepMinVolumeMaxVolume) に依存します。正確な実行制限の戦略を構成する場合は、これらの値を指定します。
  • 利益追跡は戦略 PnL と最新のローソク足の終値に基づいており、高レベルのバックテストには十分です。ライブ取引の場合、実現損益値を更新するポートフォリオに戦略を接続します。

ファイル

  • CS/VidyaNBarsBordersMartingaleStrategy.cs — 戦略の C# 実装。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "VIDYA N Bars Borders Martingale" MetaTrader expert.
/// Uses EMA as adaptive MA proxy and a range-based channel from recent N bars.
/// Buys when price closes below lower band, sells when above upper band.
/// Includes simple martingale volume increase on losing trades.
/// </summary>
public class VidyaNBarsBordersMartingaleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _rangePeriod;
	private readonly StrategyParam<decimal> _martingaleMultiplier;

	private ExponentialMovingAverage _ema;
	private readonly Queue<decimal> _highHistory = new();
	private readonly Queue<decimal> _lowHistory = new();
	private decimal _currentVolume;
	private decimal _entryPrice;

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

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int RangePeriod
	{
		get => _rangePeriod.Value;
		set => _rangePeriod.Value = value;
	}

	public decimal MartingaleMultiplier
	{
		get => _martingaleMultiplier.Value;
		set => _martingaleMultiplier.Value = value;
	}

	public VidyaNBarsBordersMartingaleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Trading candle type", "General");

		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "Smoothing period for adaptive MA proxy", "Indicators");

		_rangePeriod = Param(nameof(RangePeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Range Period", "Number of bars for high/low range channel", "Indicators");

		_martingaleMultiplier = Param(nameof(MartingaleMultiplier), 1.25m)
			.SetDisplay("Martingale Multiplier", "Volume multiplier after losing trade", "Risk");
	}

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

		_ema = new ExponentialMovingAverage { Length = EmaPeriod };
		_highHistory.Clear();
		_lowHistory.Clear();
		_currentVolume = Volume > 0 ? Volume : 1;
		_entryPrice = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_ema, ProcessCandle)
			.Start();

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

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

		// Track high/low for range calculation
		_highHistory.Enqueue(candle.HighPrice);
		_lowHistory.Enqueue(candle.LowPrice);

		if (_highHistory.Count > RangePeriod)
		{
			_highHistory.Dequeue();
			_lowHistory.Dequeue();
		}

		if (!_ema.IsFormed || _highHistory.Count < RangePeriod)
			return;

		// Compute range from recent bars
		decimal highest = decimal.MinValue;
		decimal lowest = decimal.MaxValue;
		var highs = _highHistory.ToArray();
		var lows = _lowHistory.ToArray();
		foreach (var h in highs)
			if (h > highest) highest = h;
		foreach (var l in lows)
			if (l < lowest) lowest = l;

		var range = (highest - lowest) * 0.75m;
		if (range <= 0)
			return;

		var upper = emaValue + range;
		var lower = emaValue - range;
		var close = candle.ClosePrice;
		var baseVolume = Volume > 0 ? Volume : 1;
		var maxVolume = baseVolume * 8;
		var nextVolume = _currentVolume;

		if (close < lower)
		{
			// Price below lower band -> buy signal
			if (Position < 0)
			{
				var wasLoss = close > _entryPrice;
				nextVolume = wasLoss
					? Math.Min(_currentVolume * MartingaleMultiplier, maxVolume)
					: baseVolume;
			}

			if (Position <= 0)
			{
				BuyMarket(Position < 0 ? Math.Abs(Position) + nextVolume : nextVolume);
				_currentVolume = nextVolume;
				_entryPrice = close;
			}
		}
		else if (close > upper)
		{
			// Price above upper band -> sell signal
			if (Position > 0)
			{
				var wasLoss = close < _entryPrice;
				nextVolume = wasLoss
					? Math.Min(_currentVolume * MartingaleMultiplier, maxVolume)
					: baseVolume;
			}

			if (Position >= 0)
			{
				SellMarket(Position > 0 ? Math.Abs(Position) + nextVolume : nextVolume);
				_currentVolume = nextVolume;
				_entryPrice = close;
			}
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_ema = null;
		_highHistory.Clear();
		_lowHistory.Clear();
		_currentVolume = 0;
		_entryPrice = 0;

		base.OnReseted();
	}
}