View on GitHub

Viva Las Vegas Strategy

Overview

Viva Las Vegas is a playful money-management expert that randomly buys or sells the attached instrument and then lets one of five staking systems decide the size of the next wager. The StockSharp port keeps the original MetaTrader behaviour by:

  • Choosing a trade direction through a pseudo-random coin toss on every new attempt.
  • Immediately placing symmetrical stop-loss and take-profit protections expressed in pips.
  • Updating the progression sequence as soon as the previous position is closed and opening a fresh position right away.

The strategy therefore stays constantly exposed (one open position at a time) and showcases how several classic betting systems behave inside StockSharp’s trading framework.

Money-management modules

The MoneyManagement parameter selects one of the following staking models, all of which use BaseVolume as their anchor lot size:

  1. Martingale – double the lot size after every losing trade and reset to the base volume after a profitable trade.
  2. Negative Pyramid – double the lot size after a loss, but cut the volume in half after a win (never going below the base volume).
  3. Labouchere – maintain a numeric sequence (default 1-2-3), stake the sum of the first and last numbers, remove them after a win, and append their sum after a loss.
  4. Oscar’s Grind – increase the bet by the base lot after each win until one base lot of profit has been accumulated, then reset; losses only decrease the running result.
  5. 31 System – cycle through the series 1,1,1,2,2,4,4,8,8, doubling the current element after the first win and resetting to the beginning after the second consecutive win.

All modules closely follow the original MQL implementation, including how volume progressions react to ties (zero-profit trades are treated as losses).

Trading workflow

  1. On start the strategy seeds the pseudo-random generator (time-based when Seed = 0) and enables StockSharp’s protective engine with symmetric stops and targets.
  2. When no position is open and no order is pending, the strategy asks the active staking module for the next lot size, rounds it to the instrument’s VolumeStep, and tosses a coin to choose between BuyMarket and SellMarket.
  3. Once the position is established, the protective module manages the exit using the configured pip distance.
  4. When the position returns to flat, the realized PnL delta is evaluated:
    • Profit > 0 → the module receives a win notification.
    • Profit ≤ 0 → the module receives a loss notification.
  5. The process loops immediately, so the account is always either in a trade or waiting for a fresh fill.

Because only one position exists at any time, the strategy is easy to follow on a chart and perfectly mirrors the single-ticket behaviour of the original expert advisor.

Parameters

Name Type Default Description
StopTakePips int 50 Distance (in pips) applied to both stop-loss and take-profit orders via StartProtection.
BaseVolume decimal 1 Anchor lot size fed into the money-management progression.
MoneyManagement MoneyManagementMode Martingale Staking algorithm controlling how the next order size is calculated.
Seed int 0 Pseudo-random generator seed. A value of zero switches to a time-dependent seed so every run differs.

Implementation notes

  • Volumes are normalized to the instrument’s VolumeStep and checked against MinVolume / MaxVolume to avoid rejected orders.
  • Stop/take distances are converted to price steps using the classic MetaTrader rule (Digits equal to 3 or 5 implies ten ticks per pip).
  • Realized profit is measured via the strategy’s PnL property, ensuring that protective exits and manual closes influence the staking sequence exactly like in the original code.
  • English inline comments highlight the decision points, making it easy to adapt the template for educational purposes or controlled risk experiments.

Usage tips

  • Pick a demo connector or replay environment; the algorithm is intentionally risky and meant for experimentation.
  • Adjust BaseVolume to match the instrument’s contract size before starting the strategy.
  • Combine the strategy with StockSharp charts to watch how each staking system escalates or contracts the position size over time.
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;
		}
	}
}