Рулетка — идеальный полигон для изучения случайных процессов. В отличие от абстрактных примеров из учебников, здесь мы сразу видим, как теоретические вероятности превращаются в осязаемые выигрыши и убытки. В этой статье мы построим первую симуляцию европейской рулетки на Python, используя генераторы псевдослучайных чисел. Вы получите не просто работающий код, а инструмент для тестирования ставок, сбора статистики и осознания того, почему «вероятность» и «реальность» в короткой серии часто расходятся. Это фундамент, на котором позже вырастут более сложные проекты: от моделирования финансовых временных рядов до обучения нейросетей с добавлением шума.
Мы начнём с базовой логики игры, реализуем несколько типов ставок, а затем добавим сбор и визуализацию статистики. Никакой воды — только практика, код и объяснения, которые будут понятны даже тем, кто только начинает программировать.
Почему моделирование рулетки важно для data science?
Симуляция рулетки на Python — это не просто проверка интуиции игрока. Это первый шаг к пониманию стохастических процессов — процессов, в которых случайность играет ключевую роль. В data science и машинном обучении мы постоянно сталкиваемся с данными, содержащими шум: колебания цен на бирже, погрешности датчиков умного дома, случайные флуктуации в A/B-тестах. Рулетка предлагает контролируемую среду, где этот шум можно «пощупать» и измерить.
Чем она удобна:
- Простая математика: 37 чисел в европейской версии, фиксированные вероятности.
- Полный контроль: можно запустить миллион итераций и увидеть, как эмпирические частоты сходятся к теоретическим — классическая иллюстрация закона больших чисел.
- Наглядность: графики накопленного результата, распределения выигрышей и убытков сразу показывают, где заканчивается удача и начинается математика.
Ключевые концепции, которые вы освоите
- Генераторы псевдослучайных чисел (ГПСЧ): как Python создаёт «случайность» и почему она не идеальна. В криптографии для таких задач применяют аппаратные источники энтропии, а в ML мы часто фиксируем seed, чтобы эксперименты были воспроизводимыми.
- Теорема больших чисел: почему при достаточном количестве испытаний результаты неумолимо приближаются к математическому ожиданию.
- Математическое ожидание (Expected Value): как рассчитать средний результат одной ставки и почему в рулетке оно всегда отрицательное — аналог систематической ошибки (bias) в модели, которую нельзя устранить никакой стратегией.
- Вариативность и дисперсия: почему даже «правильная» с точки зрения вероятностей ставка может привести к убыткам на коротком горизонте — точно так же, как хорошая торговая стратегия может показывать просадки.
Эти концепции применимы далеко за пределами игорного стола:
- Алгоритмический трейдинг (моделирование цен, расчёт рисков).
- Клинические испытания (формирование случайных выборок).
- Оптимизация логистики (учёт случайных задержек).
- Обучение нейросетей (стохастический градиентный спуск, dropout как источник шума).
Моделирование рулетки — это ваша «песочница», где можно безопасно экспериментировать с алгоритмами, не рискуя реальным капиталом.
Европейская рулетка: математическая база
Прежде чем писать код, важно чётко зафиксировать правила. Мы будем использовать европейскую рулетку — самую распространённую в онлайн-казино и самую простую для анализа.
Структура колеса
Колесо содержит 37 секторов:
- 18 красных чисел: 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36.
- 18 чёрных чисел: 2, 4, 6, 8, 10, 11, 13, 15, 17, 20, 22, 24, 26, 28, 29, 31, 33, 35.
- 1 зелёное число: 0.
Американская версия с двумя нулями (0 и 00) увеличивает количество секторов до 38 и ощутимо ухудшает шансы игрока, но для старта мы возьмём европейскую.
Типы ставок и их шансы
Для первой симуляции сосредоточимся на трёх базовых типах ставок, которые легко реализовать в коде:
| Тип ставки | Описание | Числа, на которые ставится | Шанс выигрыша | Коэффициент (выплата) |
|---|---|---|---|---|
| На число (Straight Up) | Ставка на одно конкретное число | 1 число | 1/37 ≈ 2.70% | 35 к 1 |
| На цвет (Color) | Ставка на красный или чёрный | 18 чисел | 18/37 ≈ 48.65% | 1 к 1 |
| На половину (Even/Odd) | Ставка на чётное или нечётное | 18 чисел (без 0) | 18/37 ≈ 48.65% | 1 к 1 |
Важно: Ноль (зелёный) не считается ни чётным, ни нечётным, ни красным, ни чёрным. При выпадении 0 все ставки на цвет и чёт/нечет сгорают.
Математическое ожидание (Expected Value)
Математическое ожидание — это средний результат одной ставки на бесконечной дистанции. В рулетке оно всегда отрицательно, что и формирует преимущество казино.
Формула: EV = (Pwin × Выплата) − (Plose × Ставка)
Для ставки на цвет (1 к 1):
Pwin = 18/37, Plose = 19/37 (включая 0)
EV = (18/37 × 1) − (19/37 × 1) = −1/37 ≈ −0.027
То есть с каждого поставленного евро вы в среднем теряете 2.7 евроцента. За 1000 ставок по €1 ожидаемый убыток — около €27.
Для ставки на число (35 к 1):
Pwin = 1/37, Plose = 36/37
EV = (1/37 × 35) − (36/37 × 1) = −1/37 ≈ −0.027
Результат тот же. В европейской рулетке математическое ожидание одинаково для всех ставок. Это ключевой момент, который часто упускают новички: не существует «правильной» ставки, которая дала бы положительное ожидание. Ситуация напоминает эффективный рынок, где нет арбитража — любая стратегия обречена на отрицательный результат в долгосроке.
Установка Python и необходимых библиотек
Для симуляции нам не нужны громоздкие фреймворки — хватит стандартной библиотеки и matplotlib для визуализации. Но перед стартом убедимся, что окружение настроено правильно.
Шаг 1: Установка Python
Скачайте Python с официального сайта python.org. Рекомендую версию Python 3.10 или новее — старые ветки 2.x давно не поддерживаются.
При установке на Windows обязательно отметьте галочку «Add Python to PATH». Это избавит от проблем с запуском из командной строки. После установки проверьте версию:
python --version
Вы должны увидеть что-то вроде Python 3.10.x.
Шаг 2: Установка библиотек
Нам потребуется только matplotlib для графиков. В реальных проектах я обычно создаю виртуальное окружение, но для первого знакомства можно установить глобально:
pip install matplotlib
Если вы работаете в Linux/Mac и получаете ошибку прав доступа, добавьте --user или используйте sudo (хотя виртуальное окружение — более чистый путь).
Шаг 3: Создание проекта
Создайте папку roulette_simulation и внутри неё файл main.py. Вся дальнейшая работа пойдёт в этом файле.
Генерация псевдослучайных чисел в Python
Сердце любой симуляции — генератор случайных чисел. В Python за это отвечает модуль random.
Что такое псевдослучайность?
ГПСЧ не создаёт истинную случайность. Он использует детерминированный алгоритм, который по заданному начальному значению (seed) выдаёт последовательность, выглядящую случайной. Если знать seed, всю последовательность можно воспроизвести. В криптографии это недопустимо, поэтому там применяют криптографически стойкие ГПСЧ (CSPRNG) или аппаратные источники энтропии. В казино для онлайн-рулетки часто используют физические шумы или квантовые эффекты. Но для учебной симуляции стандартного random более чем достаточно.
Фиксация seed — полезный приём не только для отладки, но и для воспроизводимости экспериментов в data science. Когда вы обучаете нейросеть, seed инициализации весов гарантирует, что при повторном запуске вы получите те же результаты.
Основные функции модуля random
random.randint(a, b)— случайное целое отaдоbвключительно.random.choice(list)— случайный элемент из списка.random.seed(value)— устанавливает начальное значение ГПСЧ.
Для нашей рулетки идеально подходит random.randint(0, 36).
Пример: генерация одного числа
import random
number = random.randint(0, 36)
print(number)
При каждом запуске вы будете получать разные числа. Но если задать seed:
random.seed(42)
print(random.randint(0, 36)) # всегда одно и то же число
Это позволяет тестировать симуляцию в строго контролируемых условиях.
Почему не использовать numpy.random?
numpy.random предлагает более богатый функционал и быстрее на больших объёмах, но для первых шагов стандартный random предпочтительнее: он не требует установки дополнительных пакетов, легковесен и полностью покрывает наши нужды. Если позже вы захотите симулировать миллионы спинов за секунду, перейти на numpy будет несложно.
Создание базовой симуляции одной ставки
Теперь, когда математика и инструменты понятны, напишем первую функцию, которая обрабатывает один раунд.
Шаг 1: Определение цветов и четности
Сначала зададим списки красных и чёрных чисел, а также функцию для проверки чётности с учётом особенностей рулетки.
RED_NUMBERS = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36}
BLACK_NUMBERS = {2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35}
def get_color(number):
if number == 0:
return 'green'
elif number in RED_NUMBERS:
return 'red'
else:
return 'black'
def is_even(number):
return number != 0 and number % 2 == 0
Шаг 2: Функция для одной ставки
Функция bet_one_round принимает тип ставки и её размер, генерирует случайное число и возвращает чистый результат (профит).
def bet_one_round(bet_type, stake):
number = random.randint(0, 36)
if bet_type == 'color':
# для простоты считаем, что ставка всегда на красный
win = (get_color(number) == 'red')
payout = 1 # 1 к 1
elif bet_type == 'even':
win = is_even(number)
payout = 1
elif bet_type == 'number':
# ставка на конкретное число, для примера — на 7
win = (number == 7)
payout = 35
else:
raise ValueError("Unknown bet type")
if win:
return stake * payout
else:
return -stake
Шаг 3: Тестирование функции
Запустим несколько раз и посмотрим на результат:
for _ in range(5):
print(bet_one_round('color', 1))
Вывод будет прыгать между 1 и -1. Если нужно зафиксировать результаты для отладки, добавьте random.seed(123) перед циклом — точно так же, как фиксируют random state в scikit-learn.
Расширение симуляции: запуск множества ставок
Одна ставка — лишь точка. Чтобы увидеть закономерности, нужна серия из тысяч испытаний.
Шаг 1: Функция для запуска симуляции
def simulate_roulette(bet_type, stake, n_rounds):
results = []
for _ in range(n_rounds):
profit = bet_one_round(bet_type, stake)
results.append(profit)
total_profit = sum(results)
win_count = sum(1 for p in results if p > 0)
return {
'total_profit': total_profit,
'win_rate': win_count / n_rounds,
'results': results,
'n_rounds': n_rounds
}
Шаг 2: Запуск симуляции
stats = simulate_roulette('color', 1, 10000)
print(f"Общий результат: {stats['total_profit']}")
print(f"Процент выигрышей: {stats['win_rate']:.2%}")
Вы увидите отрицательный итог (например, -270) и частоту выигрышей около 48.65%.
Шаг 3: Анализ результатов
Посчитаем математическое ожидание на основе симуляции и сравним с теорией:
n = stats['n_rounds']
avg_profit = stats['total_profit'] / n
print(f"Эмпирическое мат. ожидание: {avg_profit:.4f}")
print(f"Теоретическое: {-1/37:.4f}")
Дисперсия здесь — мера риска. В финансовом моделировании аналогом служит волатильность, и часто смотрят на коэффициент Шарпа. В рулетке любая стратегия даст отрицательное отношение доходности к риску на длинной дистанции.
Визуализация результатов: графики выигрышей и убытков
Цифры полезны, но график накопленного результата даёт интуитивное понимание процесса.
Шаг 1: Импорт matplotlib
import matplotlib.pyplot as plt
Шаг 2: Функция для построения графика
def plot_cumulative(results):
cumulative = [sum(results[:i+1]) for i in range(len(results))]
plt.figure(figsize=(10,5))
plt.plot(cumulative)
plt.title('Накопленный результат')
plt.xlabel('Номер ставки')
plt.ylabel('Прибыль')
plt.grid(True)
plt.show()
Шаг 3: Запуск визуализации
stats = simulate_roulette('color', 1, 1000)
plot_cumulative(stats['results'])
График будет напоминать случайное блуждание с отрицательным сносом. В начале он может уйти в плюс — точно так же, как кривая обучения модели может показывать ложные улучшения на малых выборках. Но чем длиннее серия, тем явственнее проявляется нисходящий тренд.
Анализ статистики: распределение выигрышей и убытков
Помимо общего итога, полезно посмотреть на распределение результатов отдельных раундов.
Шаг 1: Функция для анализа
def analyze_results(results):
wins = [p for p in results if p > 0]
losses = [p for p in results if p < 0]
print(f"Количество выигрышей: {len(wins)}")
print(f"Количество проигрышей: {len(losses)}")
if wins:
print(f"Средний выигрыш: {sum(wins)/len(wins):.2f}")
print(f"Максимальный выигрыш: {max(wins)}")
if losses:
print(f"Средний проигрыш: {sum(losses)/len(losses):.2f}")
print(f"Максимальный проигрыш: {min(losses)}")
Шаг 2: Запуск анализа
stats = simulate_roulette('color', 1, 10000)
analyze_results(stats['results'])
При большом числе испытаний распределение выигрышей/проигрышей стремится к нормальному благодаря центральной предельной теореме. Это позволяет строить доверительные интервалы и оценивать вероятность экстремальных просадок — методы, широко используемые в риск-менеджменте.
Чек-лист: как проверить свою симуляцию
Когда вы пишете симуляцию, относитесь к ней как к программному продукту, который нужно тестировать. Вот минимальный набор проверок:
- Случайность: десять запусков с одинаковыми параметрами должны давать разные итоги (если не зафиксирован seed).
- Вероятности: для ставки на цвет процент выигрышей должен колебаться около 48.65%, для числа — около 2.70%.
- Мат. ожидание: общий результат должен быть отрицательным и составлять примерно −2.7% от суммы всех ставок.
- Дисперсия: на 100 ставках результат может быть положительным, на 10 000 — практически гарантированно отрицательный.
- Визуализация: график накопленного результата должен демонстрировать нисходящий тренд в долгосроке.
Если все пункты выполняются, ваша симуляция корректна. Такой подход напоминает валидацию модели машинного обучения: проверка метрик на тренировочных и тестовых данных, анализ кривых обучения.
Типовые ошибки и как их избежать
За годы работы с симуляциями я выделил несколько граблей, на которые регулярно наступают новички.
Ошибка 1: Неправильное определение цвета
Проблема: забывают, что 0 — зелёный, и учитывают его в ставках на цвет.
Решение: всегда использовать функцию get_color, которая явно возвращает 'green' для нуля.
Ошибка 2: Неправильная проверка четности
Проблема: считают 0 чётным, но по правилам рулетки он не относится ни к чётным, ни к нечётным.
Решение: в функции is_even первой же строкой отсекать number == 0.
Ошибка 3: Неправильный расчет выплаты
Проблема: используют коэффициент 36 для ставки на число, тогда как реальная выплата — 35 к 1.
Решение: в bet_one_round для bet_type == 'number' умножать ставку на 35.
Ошибка 4: Использование seed без необходимости
Проблема: фиксируют seed для каждой симуляции, лишая результаты вариативности.
Решение: применять seed только для отладки и тестов, а в «боевых» запусках полагаться на системную энтропию.
Ошибка 5: Неправильное количество ставок
Проблема: делают выводы на основе 10–20 раундов.
Решение: минимум 1000 итераций для получения статистически значимых результатов. В A/B-тестах тоже не принимают решения на малых выборках.
Практический пример: тестирование стратегии Мартингейла
Мартингейл — классика жанра: после каждого проигрыша ставка удваивается, чтобы первый же выигрыш перекрыл все предыдущие убытки. Звучит заманчиво, но давайте проверим на симуляции.
Шаг 1: Модификация функции для Мартингейла
def martingale_simulation(bet_type, initial_stake, n_rounds, max_stake=float('inf')):
stake = initial_stake
results = []
for _ in range(n_rounds):
if stake > max_stake:
break # достигнут лимит стола или капитала
profit = bet_one_round(bet_type, stake)
results.append(profit)
if profit > 0:
stake = initial_stake
else:
stake *= 2
return {
'total_profit': sum(results),
'results': results,
'n_rounds': len(results)
}
Шаг 2: Запуск симуляции
stats = martingale_simulation('color', 1, 1000, max_stake=256)
print(f"Общий результат: {stats['total_profit']}")
print(f"Сыграно раундов: {stats['n_rounds']}")
Шаг 3: Анализ результатов
На коротком отрезке Мартингейл может показать плюс, но рано или поздно серия убытков приведёт к экспоненциальному росту ставки. Вы либо упрётесь в лимит стола, либо исчерпаете банкролл. Математическое ожидание остаётся неизменным — −2.7% с каждой ставки. Это напоминает стратегии усреднения убыточных позиций в трейдинге: временные выигрыши сменяются катастрофическими потерями при наступлении «чёрного лебедя».
Важно: Мартингейл не меняет отрицательное ожидание. В долгосрочной перспективе он лишь ускоряет разорение.
FAQ: ответы на частые вопросы
1. Почему в рулетке всегда отрицательное математическое ожидание?
Из-за наличия зеро. Для ставок на равные шансы вероятность выигрыша 18/37, а проигрыша — 19/37. Эта разница в 1/37 и создаёт преимущество казино. В американской рулетке с двумя нулями разница ещё больше.
2. Можно ли использовать симуляцию для поиска «правильной» ставки?
Нет. Все ставки имеют одинаковое отрицательное ожидание. Симуляция лишь подтверждает этот факт, но не может его обойти.
3. Почему Мартингейл не работает в долгосрочной перспективе?
Потому что ставка растёт экспоненциально, а капитал и лимиты стола конечны. Рано или поздно серия проигрышей приведёт к невозможности сделать следующую ставку. Это классический пример «стратегии разорения».
4. Как проверить, что моя симуляция работает корректно?
Воспользуйтесь чек-листом из соответствующего раздела: проверьте случайность, частоты, мат. ожидание и визуализацию. При любых сомнениях увеличивайте число итераций — закон больших чисел всё расставит по местам.
5. Можно ли использовать симуляцию для обучения data science?
Безусловно. Это отличный проект для освоения ГПСЧ, расчёта вероятностей, дисперсии, визуализации и базового статистического анализа. Многие концепции, заложенные здесь, напрямую переносятся на анализ реальных данных.
6. Почему в американской рулетке шансы хуже?
В американской версии два зелёных сектора: 0 и 00. Общее количество ячеек — 38. Для ставки на цвет вероятность выигрыша становится 18/38 ≈ 47.37%, а преимущество казино возрастает до 5.26%. Это вдвое выше, чем в европейской рулетке. В некоторых вариантах правил (например, «La Partage» или «En Prison») при выпадении нуля игрок теряет только половину ставки на равные шансы, что снижает преимущество до 1.35%, но в стандартной американской рулетке таких послаблений нет.