Estrategia Viva Las Vegas
Descripción general
Viva Las Vegas es un divertido experto en administración de dinero que compra o vende aleatoriamente el instrumento adjunto y luego deja que uno de los cinco sistemas de apuestas decida el tamaño de la siguiente apuesta. El puerto StockSharp mantiene el comportamiento original MetaTrader al:
- Elegir una dirección comercial mediante un lanzamiento de moneda pseudoaleatorio en cada nuevo intento.
- Colocar inmediatamente protecciones simétricas de stop-loss y take-profit expresadas en pips.
- Actualizar la secuencia de progresión tan pronto como se cierre la posición anterior y abrir una nueva posición de inmediato.
Por lo tanto, la estrategia permanece constantemente expuesta (una posición abierta a la vez) y muestra cómo se comportan varios sistemas de apuestas clásicos dentro del marco comercial de StockSharp.
Módulos de administración de dinero
El parámetro MoneyManagement selecciona uno de los siguientes modelos de participación, todos los cuales utilizan BaseVolume como tamaño de lote ancla:
- Martingale: duplica el tamaño del lote después de cada operación perdedora y restablece el volumen base después de una operación rentable.
- Pirámide negativa: duplica el tamaño del lote después de una pérdida, pero reduce el volumen a la mitad después de una ganancia (nunca por debajo del volumen base).
- Labouchere: mantenga una secuencia numérica (predeterminada
1-2-3), apueste la suma del primer y último número, elimínelos después de una victoria y agregue su suma después de una pérdida.
- Oscar's Grind: aumenta la apuesta en el lote base después de cada victoria hasta que se haya acumulado un lote base de ganancias, luego reinicia; las pérdidas sólo disminuyen el resultado de la carrera.
- Sistema 31: recorre la serie
1,1,1,2,2,4,4,8,8, duplicando el elemento actual después de la primera victoria y reiniciando al principio después de la segunda victoria consecutiva.
Todos los módulos siguen de cerca la implementación original de MQL, incluida cómo reaccionan las progresiones de volumen a los empates (las operaciones sin ganancias se tratan como pérdidas).
Flujo de trabajo comercial
- Al iniciar, la estrategia genera el generador pseudoaleatorio (basado en el tiempo cuando
Seed = 0) y habilita el motor protector de StockSharp con paradas y objetivos simétricos.
- Cuando no hay ninguna posición abierta y no hay ninguna orden pendiente, la estrategia solicita al módulo de apuesta activo el siguiente tamaño de lote, lo redondea al
VolumeStep del instrumento y lanza una moneda para elegir entre BuyMarket y SellMarket.
- Una vez establecida la posición, el módulo de protección gestiona la salida utilizando la distancia de pips configurada.
- Cuando la posición vuelve a ser plana, se evalúa el delta de PnL realizado:
- Beneficio > 0 → el módulo recibe una notificación ganar.
- Beneficio ≤ 0 → el módulo recibe una notificación de pérdida.
- El proceso se repite inmediatamente, por lo que la cuenta siempre está en una operación o esperando a que se vuelva a llenar.
Debido a que solo existe una posición en un momento dado, la estrategia es fácil de seguir en un gráfico y refleja perfectamente el comportamiento de ticket único del asesor experto original.
Parámetros
| Nombre |
Tipo |
Predeterminado |
Descripción |
StopTakePips |
int |
50 |
Distancia (en pips) aplicada a las órdenes stop-loss y take-profit a través de StartProtection. |
BaseVolume |
decimal |
1 |
El tamaño del lote ancla influyó en la progresión de la gestión del dinero. |
MoneyManagement |
MoneyManagementMode |
Martingale |
Algoritmo de apuesta que controla cómo se calcula el tamaño del siguiente pedido. |
Seed |
int |
0 |
Semilla generadora pseudoaleatoria. Un valor de cero cambia a una semilla dependiente del tiempo, por lo que cada ejecución es diferente. |
Notas de implementación
- Los volúmenes se normalizan según el
VolumeStep del instrumento y se comparan con MinVolume / MaxVolume para evitar pedidos rechazados.
- Las distancias de parada/toma se convierten en pasos de precio utilizando la regla clásica MetaTrader (
Digits igual a 3 o 5 implica diez ticks por pip).
- Las ganancias obtenidas se miden a través de la propiedad
PnL de la estrategia, lo que garantiza que las salidas protectoras y los cierres manuales influyan en la secuencia de apuestas exactamente como en el código original.
- Los comentarios en línea en inglés resaltan los puntos de decisión, lo que facilita la adaptación de la plantilla con fines educativos o experimentos de riesgo controlado.
Consejos de uso
- Elija un conector de demostración o un entorno de reproducción; El algoritmo es intencionalmente arriesgado y está destinado a la experimentación.
- Ajuste
BaseVolume para que coincida con el tamaño del contrato del instrumento antes de comenzar la estrategia.
- Combine la estrategia con gráficos StockSharp para observar cómo cada sistema de apuestas aumenta o contrae el tamaño de la posición con el tiempo.
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()