GitHub で見る

ビバ ラスベガス戦略

概要

Viva Las Vegas は遊び心のある資金管理の専門家で、付属の商品をランダムに売買し、5 つのステーキング システムの 1 つに次の賭け金のサイズを決定させます。 StockSharp ポートは、次のようにして元の MetaTrader の動作を維持します。

  • 新しい試みごとに疑似ランダムなコイントスを通じて取引の方向を選択します。
  • ピップで表される対称的なストップロスとテイクプロフィットのプロテクションを即座に配置します。
  • 前のポジションが決済されるとすぐに進行シーケンスを更新し、すぐに新しいポジションをオープンします。

したがって、戦略は常に公開され(一度に 1 つのオープン ポジション)、StockSharp の取引フレームワーク内でいくつかの古典的なベッティング システムがどのように動作するかを示します。

資金管理モジュール

MoneyManagement パラメータは、次のステーキング モデルのいずれかを選択します。これらはすべて、アンカー ロット サイズとして BaseVolume を使用します。

  1. Martingale – 取引に負けるたびにロットサイズが 2 倍になり、利益のある取引後には基本数量にリセットされます。
  2. ネガティブ ピラミッド – 負けた後はロット サイズが 2 倍になりますが、勝った後はボリュームが半分になります (基本ボリュームを下回ることはありません)。
  3. Labouchere – 数値シーケンス (デフォルトは 1-2-3) を維持し、最初と最後の数値の合計を賭け、勝利後にそれらを削除し、敗北後にその合計を追加します。
  4. オスカーズ グラインド – 1 つの基本ロットの利益が蓄積されるまで、勝利するたびに賭け金を基本ロットごとに増やし、その後リセットします。損失は​​実行結果を低下させるだけです。
  5. 31 システム – シリーズ 1,1,1,2,2,4,4,8,8 をサイクルし、最初の勝利後に現在の要素を 2 倍にし、2 回連続の勝利後に最初にリセットします。

すべてのモジュールは、出来高の推移がタイにどのように反応するか(利益ゼロの取引は損失として扱われる)など、元の MQL 実装に厳密に従っています。

取引ワークフロー

  1. 開始時に、この戦略は擬似ランダム ジェネレーター (Seed = 0 の場合は時間ベース) をシードし、対称的なストップとターゲットを備えた StockSharp の保護エンジンを有効にします。
  2. オープンなポジションがなく、保留中の注文もない場合、ストラテジーはアクティブなステーキング モジュールに次のロット サイズを問い合わせ、それを商品の VolumeStep に四捨五入し、コインを投げて BuyMarketSellMarket のどちらかを選択します。
  3. 位置が確立されると、保護モジュールは設定されたピップ距離を使用して出口を管理します。
  4. ポジションがフラットに戻ると、実現された損益デルタが評価されます。
    • 利益 > 0 → モジュールは 勝利 通知を受け取ります。
    • 利益 ≤ 0 → モジュールは 損失 通知を受け取ります。
  5. プロセスはすぐにループするため、口座は常に取引中か、新たな約定を待っています。

常に 1 つのポジションのみが存在するため、戦略はチャート上で追跡しやすく、元のエキスパートアドバイザーのシングルチケットの動作を完全に反映しています。

パラメーター

名前 種類 デフォルト 説明
StopTakePips int 50 StartProtection 経由でストップロス注文とテイクプロフィット注文の両方に適用される距離 (ピップ単位)。
BaseVolume decimal 1 アンカーロットのサイズは資金管理の進行に組み込まれます。
MoneyManagement MoneyManagementMode Martingale 次の注文サイズの計算方法を制御するステーキング アルゴリズム。
Seed int 0 擬似ランダムジェネレーターシード。値をゼロにすると、時間依存のシードに切り替わるため、実行ごとに異なります。

実装メモ

  • ボリュームは商品の VolumeStep に正規化され、注文の拒否を避けるために MinVolume / MaxVolume に対してチェックされます。
  • ストップ/テイク距離は、従来の MetaTrader ルールを使用して価格ステップに変換されます (Digits が 3 または 5 に等しいということは、1 ピップあたり 10 ティックを意味します)。
  • 実現利益はストラテジーの PnL プロパティを通じて測定され、保護的出口と手動クローズが元のコードとまったく同様にステーキング シーケンスに影響を与えることが保証されます。
  • 英語のインライン コメントは意思決定ポイントを強調表示するため、テンプレートを教育目的やリスク管理された実験に簡単に適用できます。

使い方のヒント

  • デモ コネクタまたは再生環境を選択します。このアルゴリズムは意図的に危険を伴い、実験を目的としています。
  • 戦略を開始する前に、商品の契約サイズに合わせて BaseVolume を調整します。
  • 戦略を StockSharp チャートと組み合わせて、各ステーキング システムが時間の経過とともにポジション サイズをどのように増減させるかを観察します。
namespace StockSharp.Samples.Strategies;

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

using StockSharp.Algo;

public class VivaLasVegasStrategy : Strategy
{
	private readonly StrategyParam<int> _stopTakePips;
	private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<MoneyManagementModes> _moneyManagementMode;
	private readonly StrategyParam<int> _seed;

	private int _activeSeed;
	private IMoneyManagement _management;
	private decimal _previousPosition;
	private decimal _lastRealizedPnL;
	private bool _orderInFlight;

	public enum MoneyManagementModes
	{
		Martingale,
		NegativePyramid,
		Labouchere,
		OscarsGrind,
		System31,
	}

	public VivaLasVegasStrategy()
	{
		_stopTakePips = Param(nameof(StopTakePips), 50)
			.SetGreaterThanZero()
			.SetDisplay("Stop/take distance", "Protective distance expressed in pips for both stop-loss and take-profit.", "Risk")
			
			.SetOptimize(10, 200, 10);

		_baseVolume = Param(nameof(BaseVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Base volume", "Initial lot size used as the anchor for all money management progressions.", "Risk")
			
			.SetOptimize(0.1m, 5m, 0.1m);

_moneyManagementMode = Param(nameof(MoneyManagement), MoneyManagementModes.Martingale)
			.SetDisplay("Money management", "Progression model that decides the next order volume.", "General");

		_seed = Param(nameof(Seed), 0)
			.SetDisplay("Random seed", "Seed for the pseudo-random trade direction. Zero switches to time-based seeding.", "General");
	}

	public int StopTakePips
	{
		get => _stopTakePips.Value;
		set => _stopTakePips.Value = value;
	}

	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

public MoneyManagementModes MoneyManagement
{
get => _moneyManagementMode.Value;
set
{
_moneyManagementMode.Value = value;
InitializeMoneyManagement();
}
}

	public int Seed
	{
		get => _seed.Value;
		set => _seed.Value = value;
	}

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

		_activeSeed = 0;
		_management = null;
		_previousPosition = 0m;
		_lastRealizedPnL = 0m;
		_orderInFlight = false;
	}

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

		Volume = BaseVolume;

		InitializeMoneyManagement();

		_activeSeed = Seed == 0 ? System.Environment.TickCount : Seed;

		var steps = StopTakePips * GetPipMultiplier();
		if (StopTakePips > 0 && steps > 0m)
		{
			var unit = new Unit(steps, UnitTypes.Absolute);
			StartProtection(unit, unit);
		}
		else
		{
			StartProtection(null, null);
		}

		// Use candle subscription to pace trades
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(ProcessCandle).Start();
	}

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

		if (Position == 0m && !_orderInFlight)
			TryOpenPosition();
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (Position != 0m)
		{
			// A fill confirmed our open position, so further market orders must wait.
			_orderInFlight = false;
		}
	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (_previousPosition == 0m && Position != 0m)
		{
			// A new position was just established; capture the realized PnL baseline.
			_lastRealizedPnL = PnL;
			_orderInFlight = false;
		}
		else if (_previousPosition != 0m && Position == 0m)
		{
			var tradePnL = PnL - _lastRealizedPnL;
			_lastRealizedPnL = PnL;

			var closedVolume = Math.Abs(_previousPosition);
			if (closedVolume > 0m && _management != null)
			{
				var result = tradePnL > 0m ? TradeResults.Win : TradeResults.Loss;
				_management.Update(result, closedVolume, BaseVolume);
			}

			_orderInFlight = false;
		}

		_previousPosition = Position;
	}

	private void TryOpenPosition()
	{
		if (ProcessState != ProcessStates.Started)
			return;

		if (Position != 0m || _orderInFlight)
			return;

		var volume = _management?.GetVolume(BaseVolume) ?? BaseVolume;
		volume = AdjustVolume(volume);

		if (volume <= 0m)
			return;

		_activeSeed = _activeSeed * 1103515245 + 12345;
		var isBuy = ((_activeSeed >> 16) & 1) == 0;

		_orderInFlight = true;

		if (isBuy)
		{
			// Coin toss favoured the bullish side.
			BuyMarket(volume);
		}
		else
		{
			// Bearish outcome - sell into the market.
			SellMarket(volume);
		}
	}

	private decimal AdjustVolume(decimal volume)
	{
		var security = Security;
		if (security == null)
			return volume;

		var step = security.VolumeStep ?? 0m;
		if (step > 0m)
		{
			var steps = Math.Max(1m, Math.Round(volume / step, MidpointRounding.AwayFromZero));
			volume = steps * step;
		}

		var minVolume = security.MinVolume ?? 0m;
		if (minVolume > 0m && volume < minVolume)
			volume = minVolume;

		var maxVolume = security.MaxVolume ?? 0m;
		if (maxVolume > 0m && volume > maxVolume)
			volume = maxVolume;

		return volume;
	}

	private decimal GetPipMultiplier()
	{
		var security = Security;
		if (security == null)
			return 1m;

		return security.Decimals is 3 or 5 ? 10m : 1m;
	}

	private void InitializeMoneyManagement()
	{
		_management = CreateMoneyManagement(MoneyManagement);
		_management.Reset(BaseVolume);
	}

private IMoneyManagement CreateMoneyManagement(MoneyManagementModes mode)
{
return mode switch
{
MoneyManagementModes.Martingale => new MartingaleManagement(),
MoneyManagementModes.NegativePyramid => new NegativePyramidManagement(),
MoneyManagementModes.Labouchere => new LabouchereManagement(),
MoneyManagementModes.OscarsGrind => new OscarsGrindManagement(),
MoneyManagementModes.System31 => new System31Management(),
_ => new MartingaleManagement(),
};
}

	private enum TradeResults
	{
		Win,
		Loss,
	}

	private interface IMoneyManagement
	{
		decimal GetVolume(decimal baseVolume);
		void Update(TradeResults result, decimal closedVolume, decimal baseVolume);
		void Reset(decimal baseVolume);
	}

	private sealed class MartingaleManagement : IMoneyManagement
	{
		private decimal _nextVolume;

		public decimal GetVolume(decimal baseVolume)
		{
			if (_nextVolume <= 0m)
				_nextVolume = baseVolume;

			return _nextVolume;
		}

		public void Update(TradeResults result, decimal closedVolume, decimal baseVolume)
		{
			_nextVolume = result == TradeResults.Win ? baseVolume : _nextVolume * 2m;
		}

		public void Reset(decimal baseVolume)
		{
			_nextVolume = 0m;
		}
	}

	private sealed class NegativePyramidManagement : IMoneyManagement
	{
		private decimal _nextVolume;

		public decimal GetVolume(decimal baseVolume)
		{
			if (_nextVolume <= 0m)
				_nextVolume = baseVolume;

			return _nextVolume;
		}

		public void Update(TradeResults result, decimal closedVolume, decimal baseVolume)
		{
			if (result == TradeResults.Win)
			{
				_nextVolume /= 2m;
				if (_nextVolume < baseVolume)
					_nextVolume = baseVolume;
			}
			else
			{
				_nextVolume *= 2m;
			}
		}

		public void Reset(decimal baseVolume)
		{
			_nextVolume = 0m;
		}
	}

	private sealed class LabouchereManagement : IMoneyManagement
	{
		private static readonly int[] _baseSeries = { 1, 2, 3 };
		private readonly List<decimal> _series = new();

		public decimal GetVolume(decimal baseVolume)
		{
			if (_series.Count == 0)
				Reset(baseVolume);

			if (_series.Count > 1)
				return (_series[0] + _series[^1]) * baseVolume;

			return _series[0] * baseVolume;
		}

		public void Update(TradeResults result, decimal closedVolume, decimal baseVolume)
		{
			if (_series.Count == 0)
				Reset(baseVolume);

			if (result == TradeResults.Win)
			{
				if (_series.Count > 2)
				{
					_series.RemoveAt(_series.Count - 1);
					_series.RemoveAt(0);
				}
				else
				{
					Reset(baseVolume);
				}
			}
			else
			{
				var first = _series[0];
				var last = _series[^1];
				_series.Add(first + last);
			}
		}

		public void Reset(decimal baseVolume)
		{
			_series.Clear();

			foreach (var value in _baseSeries)
				_series.Add(value);
		}
	}

	private sealed class OscarsGrindManagement : IMoneyManagement
	{
		private decimal _nextVolume;
		private decimal _currentResult;

		public decimal GetVolume(decimal baseVolume)
		{
			if (_nextVolume <= 0m)
				_nextVolume = baseVolume;

			return _nextVolume;
		}

		public void Update(TradeResults result, decimal closedVolume, decimal baseVolume)
		{
			if (result == TradeResults.Win)
			{
				_currentResult += closedVolume;

				if (_currentResult >= baseVolume)
				{
					_nextVolume = baseVolume;
					_currentResult = 0m;
					return;
				}

				_nextVolume += baseVolume;
				var cap = baseVolume + Math.Abs(_currentResult);
				if (_nextVolume > cap)
					_nextVolume = cap;
			}
			else
			{
				_currentResult -= closedVolume;
			}
		}

		public void Reset(decimal baseVolume)
		{
			_nextVolume = 0m;
			_currentResult = 0m;
		}
	}

	private sealed class System31Management : IMoneyManagement
	{
		private static readonly int[] _series = { 1, 1, 1, 2, 2, 4, 4, 8, 8 };
		private int _index;
		private bool _doubleUp;

		public decimal GetVolume(decimal baseVolume)
		{
			var multiplier = _series[_index];
			if (_doubleUp)
				multiplier *= 2;

			return multiplier * baseVolume;
		}

		public void Update(TradeResults result, decimal closedVolume, decimal baseVolume)
		{
			if (result == TradeResults.Win)
			{
				if (!_doubleUp)
				{
					_doubleUp = true;
				}
				else
				{
					_doubleUp = false;
					_index = 0;
				}
			}
			else
			{
				if (!_doubleUp)
				{
					_index = (_index + 1) % _series.Length;
				}
				else
				{
					_doubleUp = false;
				}
			}
		}

		public void Reset(decimal baseVolume)
		{
			_index = 0;
			_doubleUp = false;
		}
	}
}