In this notebook I implement a Monte Carlo simulation applied to European roulette.
European roulette has 37 slots: the numbers from 0 to 36. We consider a fixed bet on red.
At each spin, the economic result can be represented by a random variable X.
If red comes up, the player wins one unit:
X = +1If red does not come up, the player loses one unit:
X = -1In European roulette there are 18 red numbers, 18 black numbers, and zero. The probability of winning by betting on red is therefore:
P(X = +1) = \frac{18}{37}The probability of losing is:
P(X = -1) = \frac{19}{37}The value 19 includes the 18 black numbers plus zero.
The theoretical average gain of one spin is:
E[X] = (+1)\frac{18}{37} + (-1)\frac{19}{37}therefore:
E[X] = \frac{18}{37} - \frac{19}{37}that is:
E[X] = -\frac{1}{37}Numerically:
-\frac{1}{37} \approx -0.027The expected value is negative. If many spins are repeated, the average of the simulated results tends toward this value.
The Monte Carlo simulation consists of generating many random spins and computing the average gain obtained.
If X_i is the result of spin number i, after N spins the total gain is:
S_N = \sum_{i=1}^{N} X_iThe average gain per spin is:
\overline{X}_N = \frac{S_N}{N}For a large number of spins, we expect:
\overline{X}_N \approx E[X]In this case:
\overline{X}_N \approx -\frac{1}{37}By simulating many independent players, each with the same number of spins, we obtain a distribution of final gains.
In the final part I compare this distribution with a Gaussian distribution generated using the Box-Muller transform.
Implementing Box-Muller
import numpy as np
def box_muller(n, seed=None):
rng=np.random.default_rng(seed)
u1=rng.random(n)
u2=rng.random(n)
r=np.sqrt(-2.0*np.log(u1))
theta=2.0*np.pi*u2
return r*np.cos(theta)
Roulette parameters
p_win = 18 / 37
p_loss = 19 / 37
expected_value = -1 / 37
print("Win probability =", p_win)
print("Loss probability =", p_loss)
print("Theoretical expected value =", expected_value)
Win probability = 0.4864864864864865 Loss probability = 0.5135135135135135 Theoretical expected value = -0.02702702702702703
Monte Carlo for European roulette
def monte_carlo_roulette(n_spins, seed=None):
rng = np.random.default_rng(seed)
results = rng.choice(
[1, -1],
size=n_spins,
p=[p_win, p_loss]
)
cumulative_gain = np.cumsum(results)
running_mean = cumulative_gain / np.arange(1, n_spins + 1)
return results, cumulative_gain, running_mean
Simulating n spins of roulette
n_spins = 200
results, cumulative_gain, running_mean = monte_carlo_roulette(
n_spins,
seed=1
)
expected_final_gain = n_spins * expected_value
difference = cumulative_gain[-1] - expected_final_gain
print("Final gain =", cumulative_gain[-1])
print("Simulated average gain =", running_mean[-1])
print("Theoretical expected value =", expected_value)
print("Theoretical average final gain =", expected_final_gain)
print("Difference =", difference)
Final gain = -4 Simulated average gain = -0.02 Theoretical expected value = -0.02702702702702703 Theoretical average final gain = -5.405405405405405 Difference = 1.4054054054054053
Evolution of the simulated average gain
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 5))
plt.plot(running_mean, label="Simulated average")
plt.axhline(expected_value, linestyle="--", label="Theoretical expected value")
plt.xlabel("Number of spins")
plt.ylabel("Average gain per spin")
plt.title("Monte Carlo convergence of the average gain")
plt.grid(True)
plt.legend()
plt.show()
Gaussian approximation of final gains
Consider many independent players.
Each player makes the same number of spins N, always betting on red.
The final gain of a single player is:
S_N = \sum_{i=1}^{N} X_iwhere each X_i can be either +1 or -1.
For a single spin we have:
E[X] = -\frac{1}{37}Since X only takes the values +1 and -1, we have:
X^2 = 1therefore:
E[X^2] = 1The variance of a single spin is:
Var(X) = E[X^2] - (E[X])^2that is:
Var(X) = 1 - \left(-\frac{1}{37}\right)^2For the sum of N independent spins:
E[S_N] = N E[X]and:
\sigma_{S_N} = \sqrt{N Var(X)}For a large number of spins, the distribution of final gains tends to have an approximately Gaussian shape.
We then use the Box-Muller transform to generate standard Gaussian numbers and convert them into Gaussian gains with the same theoretical mean and theoretical standard deviation as the roulette gains.
n_players = 10000
n_spins = 1000
rng = np.random.default_rng(2)
outcomes_many = rng.choice(
[1, -1],
size=(n_players, n_spins),
p=[18/37, 19/37]
)
final_gains = np.sum(outcomes_many, axis=1)
expected_value = -1 / 37
var_single = 1 - expected_value**2
mu_total = n_spins * expected_value
sigma_total = np.sqrt(n_spins * var_single)
print("Theoretical total mean =", mu_total)
print("Theoretical total standard deviation =", sigma_total)
print("Simulated average =", np.mean(final_gains))
print("Simulated standard deviation =", np.std(final_gains))
Theoretical total mean = -27.027027027027028 Theoretical total standard deviation = 31.611224902083126 Simulated average = -27.384 Simulated standard deviation = 31.153332791211923
z = box_muller(n_players, seed=3)
gaussian_gains = mu_total + sigma_total * z
plt.figure(figsize=(9, 5))
plt.hist(
final_gains,
bins=50,
density=True,
alpha=0.6,
label="Monte Carlo roulette"
)
plt.hist(
gaussian_gains,
bins=50,
density=True,
alpha=0.5,
label="Box-Muller Gaussian"
)
plt.axvline(mu_total, linestyle="--", label="Theoretical mean")
plt.xlabel("Final gain")
plt.ylabel("Density")
plt.title("Final gains and Gaussian approximation")
plt.grid(True)
plt.legend()
plt.show()
The histogram of final gains obtained with the Monte Carlo simulation has a shape similar to a Gaussian distribution.
The Gaussian distribution was generated with the Box-Muller transform and then rescaled using the theoretical mean and theoretical standard deviation of the total gain.
The similarity between the two histograms comes from the fact that the final gain is the sum of many independent random variables.
Roulette remains unfavorable to the player: the distribution is centered on a negative average value.
I WANT TO WIN!!!
Now we invent a clever strategy to win at roulette.
Strategy with stop-win and stop-loss
Consider a playing session with a fixed initial capital.
The player always bets one unit on red. After each spin, the capital is updated according to the result:
C_{k+1} = C_k + X_kwhere C_k is the capital after k spins and X_k is the result of the spin.
For a bet on red in European roulette:
X_k = \begin{cases} +1 & \text{with probability } \frac{18}{37} \\ -1 & \text{with probability } \frac{19}{37} \end{cases}Define three parameters of the session:
C_0 = 100initial capital,
C_{\text{win}} = 110exit threshold in profit,
C_{\text{loss}} = 80exit threshold in loss.
The session ends when one of the following conditions occurs:
C_k \ge C_{\text{win}}or:
C_k \le C_{\text{loss}}or when the maximum number of spins is reached.
This strategy changes how the risk is distributed. Wins are locked when the capital reaches the upper threshold, while losses are limited by the lower threshold.
The expected value of a single bet remains:
E[X] = -\frac{1}{37}For this reason, if a session contains many spins on average, the expected average gain remains linked to the total amount wagered.
Simulation of a single session
We simulate one complete session.
At each step, the result of one spin is generated. The capital is updated and stored in an array.
The resulting array represents the capital during the session:
C_0, C_1, C_2, \dots, C_mwhere m is the actual number of spins played before the session stops.
def roulette_session(
initial_capital=100,
base_bet=1,
stop_win=110,
stop_loss=80,
max_spins=1000,
seed=None
):
rng = np.random.default_rng(seed)
capital = initial_capital
capital_history = [capital]
for _ in range(max_spins):
result = rng.choice(
[1, -1],
p=[18/37, 19/37]
)
capital += base_bet * result
capital_history.append(capital)
if capital >= stop_win:
break
if capital <= stop_loss:
break
return np.array(capital_history)
capital_history = roulette_session(seed=1)
initial_capital = 100
stop_win = 110
stop_loss = 80
print("Final capital =", capital_history[-1])
print("Number of spins =", len(capital_history) - 1)
print("Profit =", capital_history[-1] - initial_capital)
Final capital = 110 Number of spins = 400 Profit = 10
plt.figure(figsize=(9, 5))
plt.plot(capital_history, label="Capital")
plt.axhline(initial_capital, linestyle="--", label="Initial capital")
plt.axhline(stop_win, linestyle="--", label="Stop-win")
plt.axhline(stop_loss, linestyle="--", label="Stop-loss")
plt.xlabel("Number of spins")
plt.ylabel("Capital")
plt.title("Roulette session with stop-win and stop-loss")
plt.grid(True)
plt.legend()
plt.show()
The figure shows the capital during a single session.
The session ends when the capital reaches the upper threshold, the lower threshold, or the maximum number of spins. It looks like a strategy that might at least be winning, but a single session is not enough to evaluate the strategy because the result depends on the particular random sequence generated.
We put it under pressure using Monte Carlo and simulate many sessions.
n_sessions = 2000
final_capitals = np.zeros(n_sessions)
n_spins_used = np.zeros(n_sessions)
for i in range(n_sessions):
history = roulette_session(seed=i)
final_capitals[i] = history[-1]
n_spins_used[i] = len(history) - 1
profits = final_capitals - initial_capital
print("Number of sessions =", n_sessions)
print("Average profit =", np.mean(profits))
print("Median profit =", np.median(profits))
print("Probability of closing in profit =", np.mean(profits > 0))
print("Probability of closing in loss =", np.mean(profits < 0))
print("Average number of spins =", np.mean(n_spins_used))
Number of sessions = 2000 Average profit = -6.072 Median profit = -20.0 Probability of closing in profit = 0.464 Probability of closing in loss = 0.536 Average number of spins = 200.492
Interpretation of the results
The simulation was repeated over many independent sessions.
The results obtained are:
- negative average profit;
- median profit equal to the loss threshold;
- probability of closing in loss greater than the probability of closing in profit.
In the simulated case, the player’s average profit is about:
-6.072This means that, under the same conditions, the Casino’s average profit is about:
+6.072per session.
The stop-win / stop-loss strategy can produce many sessions that look favorable, because some of them end with a small profit. However, when the session ends in loss, the loss is larger than the gain fixed by the stop-win.
In this example:
C_{\text{win}} - C_0 = 110 - 100 = 10while:
C_{\text{loss}} - C_0 = 80 - 100 = -20A winning session therefore gives a gain of 10 units, while a losing session gives a loss of 20 units.
The average result remains negative because European roulette gives the bet on red the expected value:
E[X] = -\frac{1}{37}Each unit wagered loses about 2.7% on average.
The average amount lost by the player is therefore linked to the average number of spins played:
E[\text{profit}] \approx -\frac{\text{average number of spins}}{37}In this case the average number of spins is about:
200.492so the expected loss is about:
-\frac{200.492}{37} \approx -5.42The simulated value, about -6.07, is compatible with this estimate, considering the statistical fluctuations due to the finite number of simulated sessions.
Over many players and many sessions, the fluctuations tend to compensate for each other. The total profit of the Casino grows with the total volume wagered.
I WANT TO BEAT THE CASINO!
Martingale strategy
We now study a more aggressive strategy: the martingale.
The player always bets on red. The initial bet is one unit.
After a win, the bet returns to the initial value. After a loss, the next bet is doubled.
If B_k is the bet at spin k, the rule is:
B_{k+1} = B_0after a win, while:
B_{k+1} = 2B_kafter a loss.
The idea of the strategy is to recover all previous losses at the first useful win.
For example, after three consecutive losses:
-1 -2 -4 = -7the next bet is:
8If this spin wins, the balance of the sequence becomes:
-1 -2 -4 + 8 = +1The strategy produces many sessions closed with a small profit. The risk is concentrated in long loss sequences, where the bet grows very quickly.
In the simulation we introduce two realistic constraints:
- limited available capital;
- maximum bet allowed at the table.
When the player can no longer cover the required bet, the session ends.
def martingale_session(
initial_capital=255,
base_bet=1,
target_profit=1,
table_limit=128,
max_spins=1000,
seed=None
):
rng = np.random.default_rng(seed)
capital = initial_capital
bet = base_bet
capital_history = [capital]
bet_history = []
for _ in range(max_spins):
# If the required bet exceeds the capital or the table limit, the session ends
if bet > capital:
break
if bet > table_limit:
break
result = rng.choice(
[1, -1],
p=[18/37, 19/37]
)
bet_history.append(bet)
if result == 1:
capital += bet
bet = base_bet
else:
capital -= bet
bet = 2 * bet
capital_history.append(capital)
# Target: chiudere appena si è guadagnata una unità
if capital >= initial_capital + target_profit:
break
return np.array(capital_history), np.array(bet_history)
capital_history, bet_history = martingale_session(seed=1)
print("Final capital =", capital_history[-1])
print("Number of spins =", len(capital_history) - 1)
print("Profit =", capital_history[-1] - 255)
Final capital = 256 Number of spins = 3 Profit = 1
plt.figure(figsize=(9, 5))
plt.plot(capital_history)
plt.axhline(255, linestyle="-", color="red", label="Initial capital")
plt.axhline(256, linestyle="--", color="green", label="Target")
plt.xlabel("Number of spins")
plt.ylabel("Capital")
plt.title("Session with martingale strategy")
plt.grid(True)
plt.legend()
plt.show()
n_sessions = 10000
final_capitals = np.zeros(n_sessions)
n_spins_used = np.zeros(n_sessions)
initial_capital = 255
for i in range(n_sessions):
history, bets = martingale_session(seed=i)
final_capitals[i] = history[-1]
n_spins_used[i] = len(history) - 1
profits = final_capitals - initial_capital
print("Number of sessions =", n_sessions)
print("Average profit =", np.mean(profits))
print("Median profit =", np.median(profits))
print("Probability of closing in profit =", np.mean(profits > 0))
print("Probability of closing in loss =", np.mean(profits < 0))
print("Average number of spins =", np.mean(n_spins_used))
print("Maximum loss =", np.min(profits))
Number of sessions = 10000 Average profit = -0.3056 Median profit = 1.0 Probability of closing in profit = 0.9949 Probability of closing in loss = 0.0051 Average number of spins = 2.0484 Maximum loss = -255.0
wins = np.sum(profits > 0)
losses = np.sum(profits < 0)
labels = ["Winning sessions\nprofit +1", "Losing sessions\nprofit -255"]
counts = [wins, losses]
plt.figure(figsize=(7, 5))
plt.bar(labels, counts)
plt.ylabel("Number of sessions")
plt.title("Session outcomes with martingale strategy")
plt.grid(axis="y")
for i, value in enumerate(counts):
percentage = 100 * value / len(profits)
plt.text(
i,
value,
f"{value}\n{percentage:.2f}%",
ha="center",
va="bottom"
)
plt.show()
average_profit = np.mean(profits)
print("Winning sessions =", wins)
print("Losing sessions =", losses)
print("Win probability =", wins / len(profits))
print("Loss probability =", losses / len(profits))
print("Average profit =", average_profit)
Winning sessions = 9949 Losing sessions = 51 Win probability = 0.9949 Loss probability = 0.0051 Average profit = -0.3056
The martingale produces many winning sessions, but each winning session earns only one unit.
Losing sessions are much rarer, but when they occur they produce a very large loss.
The graph therefore shows two main outcomes:
- many small wins;
- few catastrophic losses.
The average profit depends on the balance between these two effects. Even if winning sessions are much more frequent, rare losses can compensate for and exceed the accumulated wins.
D’Alembert strategy
We now examine a less aggressive strategy than the martingale: the D’Alembert system.
The player always bets on red.
The initial bet is one unit.
After a loss, the bet is increased by one unit:
B_{k+1} = B_k + 1After a win, the bet is decreased by one unit:
B_{k+1} = B_k - 1The bet cannot fall below the base bet.
Compared with the martingale, the growth of the bet is linear and not exponential.
The strategy tries to recover losses gradually, without producing very rapid doublings. Capital is limited, the maximum table bet cannot be exceeded, and there is a maximum number of spins.
def dalembert_session(
initial_capital=100,
base_bet=1,
table_limit=50,
max_spins=1000,
seed=None
):
rng = np.random.default_rng(seed)
capital = initial_capital
bet = base_bet
capital_history = [capital]
bet_history = []
for _ in range(max_spins):
if bet > capital:
break
if bet > table_limit:
break
result = rng.choice(
[1, -1],
p=[18/37, 19/37]
)
bet_history.append(bet)
if result == 1:
capital += bet
bet = max(base_bet, bet - base_bet)
else:
capital -= bet
bet = bet + base_bet
capital_history.append(capital)
if capital <= 0:
break
return np.array(capital_history), np.array(bet_history)
capital_history, bet_history = dalembert_session(seed=1)
print("Final capital =", capital_history[-1])
print("Number of spins =", len(capital_history) - 1)
print("Profit =", capital_history[-1] - 100)
print("Maximum bet used =", np.max(bet_history))
Final capital = 10 Number of spins = 70 Profit = -90 Maximum bet used = 15
plt.figure(figsize=(9, 5))
plt.plot(capital_history)
plt.axhline(100, linestyle="--", label="Initial capital")
plt.xlabel("Number of spins")
plt.ylabel("Capital")
plt.title("Session with D'Alembert strategy")
plt.grid(True)
plt.legend()
plt.show()
n_sessions = 1000
initial_capital = 100
final_capitals = np.zeros(n_sessions)
n_spins_used = np.zeros(n_sessions)
max_bets_used = np.zeros(n_sessions)
for i in range(n_sessions):
history, bets = dalembert_session(seed=i)
final_capitals[i] = history[-1]
n_spins_used[i] = len(history) - 1
if len(bets) > 0:
max_bets_used[i] = np.max(bets)
profits = final_capitals - initial_capital
print("Number of sessions =", n_sessions)
print("Average profit =", np.mean(profits))
print("Median profit =", np.median(profits))
print("Probability of closing in profit =", np.mean(profits > 0))
print("Probability of closing in loss =", np.mean(profits < 0))
print("Average number of spins =", np.mean(n_spins_used))
print("Average maximum bet =", np.mean(max_bets_used))
print("Maximum loss =", np.min(profits))
print("Maximum gain =", np.max(profits))
Number of sessions = 1000 Average profit = -65.597 Median profit = -89.0 Probability of closing in profit = 0.053 Probability of closing in loss = 0.946 Average number of spins = 259.557 Average maximum bet = 18.995 Maximum loss = -100.0 Maximum gain = 536.0
wins = np.sum(profits > 0)
losses = np.sum(profits < 0)
evens = np.sum(profits == 0)
labels = ["Positive", "Negative", "Break-even"]
counts = [wins, losses, evens]
plt.figure(figsize=(7, 5))
plt.bar(labels, counts)
plt.ylabel("Number of sessions")
plt.title("Session outcomes with D'Alembert strategy")
plt.grid(axis="y")
for i, value in enumerate(counts):
percentage = 100 * value / len(profits)
plt.text(
i,
value,
f"{value}\n{percentage:.2f}%",
ha="center",
va="bottom"
)
plt.show()
plt.figure(figsize=(9, 5))
plt.hist(profits, bins=40, edgecolor="black")
plt.axvline(0, linestyle="-", color="orange", label="Break-even")
plt.axvline(np.mean(profits), linestyle=":", color="green", label="Average profit")
plt.axvline(np.median(profits), linestyle="--", label="Median profit")
plt.xlabel("Final profit")
plt.ylabel("Number of sessions")
plt.title("Profit distribution with D'Alembert strategy")
plt.grid(True)
plt.legend()
plt.show()
The graphs show the behavior of the D’Alembert strategy over many independent sessions.
The strategy changes the bet progressively: after a loss it increases it by one unit, after a win it decreases it by one unit.
Compared with the martingale, the growth of the bet is slower. This reduces the risk of a sudden very large loss, but it does not remove the statistical disadvantage of European roulette.
The profit distribution shows that the results can vary a lot from one session to another. The position of the average profit allows us to evaluate the overall behavior of the strategy over many repetitions.
And now an “esoteric” strategy: Fibonacci
We now study a strategy based on the Fibonacci sequence. Why? Because someone decided that using esoteric number sequences could be a good idea, potentially useful for winning at roulette. This is how it works.
The player always bets on red.
The sequence of bets is built using the Fibonacci numbers:
1, 1, 2, 3, 5, 8, 13, 21, \dotsAfter a loss, the player moves forward by one position in the sequence.
After a win, the player moves back by two positions.
The rule can be described as:
i_{k+1} = i_k + 1after a loss, while:
i_{k+1} = \max(0, i_k - 2)after a win.
The bet at spin k is:
B_k = B_0 F_{i_k}where B_0 is the base bet and F_{i_k} is the Fibonacci number corresponding to the current index.
Compared with the martingale, the bet grows less rapidly. The strategy tries to recover losses more gradually.
def fibonacci_sequence(n):
fib = [1, 1]
for _ in range(2, n):
fib.append(fib[-1] + fib[-2])
return np.array(fib)
def fibonacci_session(
initial_capital=100,
base_bet=1,
table_limit=50,
max_spins=1000,
seed=None
):
rng = np.random.default_rng(seed)
fib = fibonacci_sequence(30)
capital = initial_capital
fib_index = 0
capital_history = [capital]
bet_history = []
index_history = []
for _ in range(max_spins):
bet = base_bet * fib[fib_index]
if bet > capital:
break
if bet > table_limit:
break
result = rng.choice(
[1, -1],
p=[18/37, 19/37]
)
bet_history.append(bet)
index_history.append(fib_index)
if result == 1:
capital += bet
fib_index = max(0, fib_index - 2)
else:
capital -= bet
fib_index += 1
if fib_index >= len(fib):
break
capital_history.append(capital)
if capital <= 0:
break
return (
np.array(capital_history),
np.array(bet_history),
np.array(index_history)
)
initial_capital = 100
capital_history, bet_history, index_history = fibonacci_session(seed=1)
print("Final capital =", capital_history[-1])
print("Number of spins =", len(capital_history) - 1)
print("Profit =", capital_history[-1] - initial_capital)
if len(bet_history) > 0:
print("Maximum bet used =", np.max(bet_history))
Final capital = 38 Number of spins = 140 Profit = -62 Maximum bet used = 34
plt.figure(figsize=(9, 5))
plt.plot(capital_history)
plt.axhline(initial_capital, linestyle="--", color="orange", label="Initial capital")
plt.xlabel("Number of spins")
plt.ylabel("Capital")
plt.title("Session with Fibonacci strategy")
plt.grid(True)
plt.legend()
plt.show()
n_sessions = 1000
final_capitals = np.zeros(n_sessions)
n_spins_used = np.zeros(n_sessions)
max_bets_used = np.zeros(n_sessions)
for i in range(n_sessions):
history, bets, indexes = fibonacci_session(seed=i)
final_capitals[i] = history[-1]
n_spins_used[i] = len(history) - 1
if len(bets) > 0:
max_bets_used[i] = np.max(bets)
profits = final_capitals - initial_capital
print("Number of sessions =", n_sessions)
print("Average profit =", np.mean(profits))
print("Median profit =", np.median(profits))
print("Probability of closing in profit =", np.mean(profits > 0))
print("Probability of closing in loss =", np.mean(profits < 0))
print("Probability of closing at break-even =", np.mean(profits == 0))
print("Average number of spins =", np.mean(n_spins_used))
print("Average maximum bet =", np.mean(max_bets_used))
print("Maximum loss =", np.min(profits))
print("Maximum gain =", np.max(profits))
Number of sessions = 1000 Average profit = -24.309 Median profit = -44.5 Probability of closing in profit = 0.242 Probability of closing in loss = 0.753 Probability of closing at break-even = 0.005 Average number of spins = 323.368 Average maximum bet = 33.883 Maximum loss = -88.0 Maximum gain = 237.0
wins = np.sum(profits > 0)
losses = np.sum(profits < 0)
evens = np.sum(profits == 0)
labels = ["Positive", "Negative", "Break-even"]
counts = [wins, losses, evens]
plt.figure(figsize=(7, 5))
plt.bar(labels, counts)
plt.ylabel("Number of sessions")
plt.title("Session outcomes with Fibonacci strategy")
plt.grid(axis="y")
for i, value in enumerate(counts):
percentage = 100 * value / len(profits)
plt.text(
i,
value,
f"{value}\n{percentage:.2f}%",
ha="center",
va="bottom"
)
plt.show()
Conclusions
The simulations show that European roulette is unfavorable to the player already at the level of a single bet.
When betting on red, the result of one spin is:
X = +1with probability:
\frac{18}{37}or:
X = -1with probability:
\frac{19}{37}The expected value is:
E[X] = (+1)\frac{18}{37} + (-1)\frac{19}{37}therefore:
E[X] = -\frac{1}{37}Each unit wagered loses on average about:
\frac{1}{37} \approx 0.027that is, about 2.7%.
The strategies analyzed do not change this basic probability. They only change how wins and losses are distributed across individual sessions.
The stop-win / stop-loss strategy can produce many sessions closed in profit, but negative sessions have larger losses. The martingale further increases the probability of closing a session with a small profit, but concentrates the risk into a few very large losses. The D’Alembert strategy makes the growth of the bet slower, but the average profit remains conditioned by the statistical disadvantage of roulette.
The central point is that the expected gain of the player depends on the total amount wagered:
E[\text{profit}] \approx -\frac{\text{total amount wagered}}{37}For the Casino the sign is reversed:
E[\text{Casino profit}] \approx +\frac{\text{total amount wagered}}{37}In a single session the player can win or lose because of randomness. Over many independent sessions, the fluctuations tend to compensate for each other and the average value converges toward the theoretical expected value.
The Monte Carlo simulation makes this behavior visible: a strategy may look effective after observing only a few sessions, but when it is repeated many times it shows its real average value.
The Casino’s advantage does not come from all players using the same strategy. It comes from the negative expected value of every bet. Different strategies produce different distributions of results, but they do not turn an unfavorable game into a favorable one.
In conclusion, European roulette can be won in a single session, but it cannot be made favorable in the long run using only a betting-management strategy.