Viva Las Vegas-Strategie
Überblick
Viva Las Vegas ist ein spielerischer Experte für Geldmanagement, der das beigefügte Instrument nach dem Zufallsprinzip kauft oder verkauft und dann eines von fünf Wettsystemen über die Höhe des nächsten Einsatzes entscheiden lässt. Der Port StockSharp behält das ursprüngliche Verhalten von MetaTrader bei:
- Auswahl einer Handelsrichtung durch einen pseudozufälligen Münzwurf bei jedem neuen Versuch.
- Sofortige Platzierung symmetrischer Stop-Loss- und Take-Profit-Schutzmaßnahmen, ausgedrückt in Pips.
- Aktualisierung der Fortschrittssequenz, sobald die vorherige Position geschlossen wird, und Eröffnung einer neuen Position sofort.
Die Strategie bleibt daher ständig offen (jeweils eine offene Position) und zeigt, wie sich mehrere klassische Wettsysteme innerhalb des Handelsrahmens von StockSharp verhalten.
Module zur Geldverwaltung
Der Parameter MoneyManagement wählt eines der folgenden Absteckmodelle aus, die alle BaseVolume als Ankerlosgröße verwenden:
- Martingale – Verdoppelung der Lotgröße nach jedem verlorenen Trade und Zurücksetzen auf das Basisvolumen nach einem profitablen Trade.
- Negative Pyramide – Verdoppelung der Lotgröße nach einem Verlust, aber Halbierung des Volumens nach einem Gewinn (niemals Unterschreiten des Basisvolumens).
- Labouchere – Behalten Sie eine Zahlenfolge bei (Standard
1-2-3), setzen Sie die Summe der ersten und letzten Zahlen, entfernen Sie sie nach einem Gewinn und hängen Sie ihre Summe nach einer Niederlage an.
- Oscar’s Grind – Erhöhen Sie den Einsatz nach jedem Gewinn um das Basislos, bis ein Basislos Gewinn angesammelt wurde, und setzen Sie ihn dann zurück; Verluste mindern nur das laufende Ergebnis.
- 31-System – Durchlaufen Sie die Serie
1,1,1,2,2,4,4,8,8, verdoppeln Sie das aktuelle Element nach dem ersten Sieg und setzen Sie es nach dem zweiten Sieg in Folge auf den Anfang zurück.
Alle Module orientieren sich eng an der ursprünglichen MQL-Implementierung, einschließlich der Reaktion von Volumenverläufen auf Unentschieden (Null-Gewinn-Trades werden als Verluste behandelt).
Handelsablauf
- Beim Start aktiviert die Strategie den Pseudozufallsgenerator (zeitbasiert bei
Seed = 0) und aktiviert die Schutzmaschine von StockSharp mit symmetrischen Stopps und Zielen.
- Wenn keine Position offen ist und kein Auftrag aussteht, fragt die Strategie das aktive Einsatzmodul nach der nächsten Lotgröße, rundet sie auf den
VolumeStep des Instruments und wirft eine Münze, um zwischen BuyMarket und SellMarket zu wählen.
- Sobald die Position ermittelt ist, verwaltet das Schutzmodul den Ausgang mithilfe des konfigurierten Pip-Abstands.
- Wenn die Position wieder flach ist, wird das realisierte PnL-Delta ausgewertet:
- Gewinn > 0 → das Modul erhält eine Gewinnbenachrichtigung.
- Gewinn ≤ 0 → das Modul erhält eine Verlustmeldung.
- Der Vorgang wird sofort in einer Schleife ausgeführt, sodass sich das Konto immer entweder in einem Handel befindet oder auf eine neue Auffüllung wartet.
Da immer nur eine Position existiert, lässt sich die Strategie leicht auf einem Diagramm verfolgen und spiegelt perfekt das Single-Ticket-Verhalten des ursprünglichen Expertenberaters wider.
Parameter
| Name |
Typ |
Standard |
Beschreibung |
StopTakePips |
int |
50 |
Distanz (in Pips), angewendet auf Stop-Loss- und Take-Profit-Orders über StartProtection. |
BaseVolume |
decimal |
1 |
Die Ankerlosgröße floss in den Fortschritt des Geldmanagements ein. |
MoneyManagement |
MoneyManagementMode |
Martingale |
Absteckalgorithmus, der steuert, wie die nächste Ordergröße berechnet wird. |
Seed |
int |
0 |
Pseudozufallsgenerator-Seed. Ein Wert von Null schaltet auf einen zeitabhängigen Startwert um, sodass jeder Lauf anders ist. |
Hinweise zur Implementierung
- Die Volumina werden auf
VolumeStep des Instruments normalisiert und mit MinVolume / MaxVolume verglichen, um abgelehnte Bestellungen zu vermeiden.
- Stop/Take-Abstände werden mithilfe der klassischen MetaTrader-Regel in Preisschritte umgewandelt (
Digits gleich 3 oder 5 impliziert zehn Ticks pro Pip).
- Der realisierte Gewinn wird über die
PnL-Eigenschaft der Strategie gemessen, wodurch sichergestellt wird, dass Schutzexits und manuelle Schließungen die Abstecksequenz genau wie im Originalcode beeinflussen.
- Englische Inline-Kommentare heben die Entscheidungspunkte hervor und erleichtern so die Anpassung der Vorlage für Bildungszwecke oder kontrollierte Risikoexperimente.
Anwendungstipps
- Wählen Sie einen Demo-Connector oder eine Wiedergabeumgebung. Der Algorithmus ist absichtlich riskant und zum Experimentieren gedacht.
- Passen Sie
BaseVolume an die Kontraktgröße des Instruments an, bevor Sie mit der Strategie beginnen.
- Kombinieren Sie die Strategie mit StockSharp-Diagrammen, um zu beobachten, wie jedes Einsatzsystem die Positionsgröße im Laufe der Zeit erhöht oder verringert.
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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class viva_las_vegas_strategy(Strategy):
def __init__(self):
super(viva_las_vegas_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle type", "Timeframe for pacing trades.", "General")
self._stop_take_pips = self.Param("StopTakePips", 50) \
.SetDisplay("Stop/take distance", "Protective distance in pips.", "Risk")
self._base_volume = self.Param("BaseVolume", 1.0) \
.SetDisplay("Base volume", "Initial lot size.", "Risk")
self._seed = self.Param("Seed", 0) \
.SetDisplay("Random seed", "Seed for pseudo-random direction. Zero = time-based.", "General")
self._active_seed = 0
self._prev_position = 0.0
self._last_pnl = 0.0
self._next_volume = 0.0
self._entry_price = 0.0
self._best_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def StopTakePips(self):
return self._stop_take_pips.Value
@property
def BaseVolume(self):
return float(self._base_volume.Value)
@property
def SeedValue(self):
return self._seed.Value
def OnStarted2(self, time):
super(viva_las_vegas_strategy, self).OnStarted2(time)
import time as _time
self._active_seed = int(_time.time() * 1000) if self.SeedValue == 0 else self.SeedValue
self._prev_position = 0.0
self._last_pnl = 0.0
self._next_volume = self.BaseVolume
self._entry_price = 0.0
self._best_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def _get_pip_size(self):
sec = self.Security
if sec is not None:
ps = sec.PriceStep
if ps is not None and float(ps) > 0:
return float(ps)
return 0.0001
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
pip = self._get_pip_size()
stop_dist = self.StopTakePips * pip
# manage position exit
if self.Position > 0:
if close > self._best_price:
self._best_price = close
if stop_dist > 0 and (close <= self._entry_price - stop_dist or close >= self._entry_price + stop_dist):
won = close >= self._entry_price + stop_dist
self.SellMarket()
self._update_volume(won)
self._entry_price = 0.0
self._best_price = 0.0
return
elif self.Position < 0:
if close < self._best_price:
self._best_price = close
if stop_dist > 0 and (close >= self._entry_price + stop_dist or close <= self._entry_price - stop_dist):
won = close <= self._entry_price - stop_dist
self.BuyMarket()
self._update_volume(won)
self._entry_price = 0.0
self._best_price = 0.0
return
if self.Position == 0:
self._active_seed = (self._active_seed * 1103515245 + 12345) & 0x7FFFFFFF
is_buy = ((self._active_seed >> 16) & 1) == 0
self._entry_price = close
self._best_price = close
if is_buy:
self.BuyMarket()
else:
self.SellMarket()
def _update_volume(self, won):
if won:
self._next_volume = self.BaseVolume
else:
self._next_volume = self._next_volume * 2.0
def OnReseted(self):
super(viva_las_vegas_strategy, self).OnReseted()
self._active_seed = 0
self._prev_position = 0.0
self._last_pnl = 0.0
self._next_volume = 0.0
self._entry_price = 0.0
self._best_price = 0.0
def CreateClone(self):
return viva_las_vegas_strategy()