NBA Shot Strategy — The Random Walk of Mathematical Finance

Веб-приложение для анализа оптимального распределения бросков
Исследование под руководством профессора Hesam Oveys (NYU)

Python FastAPI React Docker


Содержание

  1. Обзор проекта
  2. Быстрый старт
  3. Установка и развёртывание
  4. Использование
  5. Архитектура
  6. Математическая основа
  7. Результаты исследования
  8. Production развёртывание
  9. Разработка
  10. FAQ и Troubleshooting

Обзор проекта

Интерактивное веб-приложение, применяющее вероятностные методы финансовой математики к баскетбольной аналитике. Позволяет пользователям выбирать матчи NBA, интерактивно настраивать стратегии бросков и запускать симуляции Монте-Карло для поиска оптимального соотношения 2-очковых/3-очковых бросков.

Ключевые возможности


Быстрый старт

Локальная разработка (за 30 секунд)

cd /opt/predicrionai
        docker compose up --build
        

Откройте браузер: http://localhost:8000

Production (с Traefik + Let's Encrypt)

# Настройте окружение
        cp .env.example .env
        nano .env  # Добавьте CF_API_EMAIL, CF_API_KEY, DOMAIN

        # Запустите все сервисы
        docker compose up -d --build

        # Доступ:
        # https://monte.kz-saas.com          → Web App
        # https://jupyter.monte.kz-saas.com  → Jupyter Notebooks
        # https://research.monte.kz-saas.com → Исследовательская статья
        # https://traefik.monte.kz-saas.com  → Traefik Dashboard
        

См. Production развёртывание для подробной инструкции.


Установка и развёртывание

Вариант 1: Docker Compose (рекомендуется)

# Клонируйте репозиторий
        git clone <repo-url>
        cd predicrionai

        # Настройте .env (для production)
        cp .env.example .env
        # Отредактируйте .env (CF_API_EMAIL, CF_API_KEY, DOMAIN)

        # Запустите все сервисы
        docker compose up -d --build

        # Сервисы:
        # - Web App:     http://localhost:8000 (или https://monte.kz-saas.com)
        # - Jupyter:     http://localhost:8888 (или https://jupyter.monte.kz-saas.com)
        # - Research:    https://research.monte.kz-saas.com
        # - Traefik:     https://traefik.monte.kz-saas.com/dashboard/
        

Что делает Docker Compose автоматически: - Собирает фронтенд (React + Vite) - Запускает FastAPI backend на порту 8000 - Настраивает Traefik для HTTPS с Let's Encrypt сертификатами - Запускает Jupyter Notebook (порт 8888) - Раздаёт статический сайт с исследовательской статьёй

Вариант 2: Локальная установка

# Backend
        python3 -m venv venv
        source venv/bin/activate
        pip install -r requirements.txt
        uvicorn webapp.main:app --reload --port 8000

        # Frontend (в отдельном терминале)
        cd frontend
        npm install
        npm run dev

        # Откройте http://localhost:5173 (dev) или http://localhost:8000 (production)
        

Использование

Веб-интерфейс

  1. Выбор матча — выберите две команды или используйте недавние/исторические матчи
  2. Просмотр текущей стратегии — профили бросков команд и Four Factors
  3. Настройка параметров:
  4. Соотношение 3-очковых (0-95%)
  5. Процент попадания 3PT (20-50%)
  6. Hot hand эффект (ВКЛ/ВЫКЛ)
  7. Количество итераций симуляции (1K-20K)
  8. Запуск симуляции — получите ожидаемые счета, вероятности победы, оптимальные рекомендации
  9. Анализ результатов:
  10. Гистограммы распределения счетов
  11. Визуализация вероятности победы
  12. Таблица сравнения стратегий
  13. Рекомендации по оптимальной стратегии

API эндпоинты

# Получить все команды
        GET /api/teams

        # Получить профиль команды
        GET /api/teams/{team_id}

        # Список матчей
        GET /api/matches?mode=history&count=20   # mode: synthetic | history | upcoming

        # Запустить симуляцию
        POST /api/simulate
        {
          "home_team_id": 1610612747,
          "away_team_id": 1610612744,
          "home_three_ratio": 0.38,
          "away_three_ratio": 0.42,
          "home_fg3_pct": 0.36,
          "away_fg3_pct": 0.38,
          "hot_hand": false,
          "iterations": 5000
        }

        # Анализ оптимальной стратегии
        GET /api/analysis/{team_id}?opponent_id={opp_id}&iterations=1500
        

Jupyter Notebook

# Запустите Jupyter контейнер
        docker compose up jupyter

        # Откройте https://jupyter.monte.kz-saas.com
        # Пароль см. в файле CREDENTIALS.txt
        

Структура Notebooks:

Пример использования:

from src.simulation import MonteCarloSimulation

        # Создание симуляции
        sim = MonteCarloSimulation(
            two_point_fg_pct=0.52,
            three_point_fg_pct=0.36,
            num_iterations=10000
        )

        # Запуск оптимизации
        best_result, all_results = sim.optimize_shot_distribution()

        # Визуализация
        import matplotlib.pyplot as plt
        plt.plot([r.three_pt_ratio for r in all_results], 
                 [r.mean_score for r in all_results])
        plt.xlabel('3PT Ratio')
        plt.ylabel('Expected Score')
        plt.show()
        

Архитектура

predicrionai/
        ├── webapp/              # FastAPI backend
        │   ├── main.py          # Точка входа приложения
        │   ├── api.py           # REST маршруты
        │   ├── schemas.py       # Pydantic модели (API контракты)
        │   ├── services.py      # Бизнес-логика (оркестрация симуляций)
        │   ├── providers.py     # Источники данных: ESPN (основной) → nba_api → синтетика
        │   └── static/          # Собранный фронтенд (из Vite)
        ├── frontend/            # React + Vite
        │   ├── src/
        │   │   ├── App.jsx
        │   │   ├── pages/       # MatchSelect, Analysis
        │   │   ├── components/  # TeamCard, StrategyPanel, Charts
        │   │   ├── api.js       # Fetch обёртка
        │   │   └── styles.css   # Design tokens
        │   └── vite.config.js
        ├── src/                 # Ядро движка симуляции
        │   ├── simulation/      # MonteСarlo, ShotModel, GameSimulator
        │   ├── analysis/        # FourFactors, HotHand, Statistics
        │   ├── data/            # NBAClient (с обходом TLS)
        │   └── utils/           # Config, Logger
        ├── tests/               # Pytest (34 теста, 100% pass)
        ├── notebooks/           # Jupyter notebooks для анализа
        ├── paper/               # Исследовательская статья
        ├── research-site/       # Статический HTML сайт со статьёй
        ├── docker-compose.yml   # Оркестрация сервисов
        ├── Dockerfile           # Multi-stage build (node + python)
        ├── requirements.txt     # Python зависимости
        └── CREDENTIALS.txt      # Пароли (в .gitignore)
        

Технологии

Компонент Технология Версия
Backend FastAPI + Uvicorn 0.115+
Frontend React + Vite 18 / 5
Графики Chart.js 4.5
Данные ESPN API (основной) + nba_api (резерв) + синтетика —
Кеш Redis (in-memory fallback) 7.x
Симуляция NumPy + SciPy 1.24+
Контейнеризация Docker + Docker Compose —
Reverse Proxy Traefik v3 + Let's Encrypt 3.7.10

Математическая основа

Ключевые концепции

  1. Математическое ожидание (Expected Value): Средний результат множества попыток
  2. Центральная предельная теорема: Распределение средних приближается к нормальному при увеличении выборки
  3. Дисперсия (Variance): Мера разброса результатов — аналог инвестиционного риска

Анализ математического ожидания

Для NBA-средних процентов попадания (52% 2PT, 36% 3PT):

Тип броска EV Расчёт
2-очковый 1.04 0.52 × 2 = 1.04
3-очковый 1.08 0.36 × 3 = 1.08

Вывод: 3-очковые имеют ~4% более высокое математическое ожидание.

Анализ дисперсии

Дисперсия на один бросок (формула Бернулли):

Var = p(1-p) × Points²
        
Тип броска Variance Расчёт
2-очковый 0.998 0.52 × 0.48 × 4
3-очковый 2.074 0.36 × 0.64 × 9

Вывод: 3-очковые имеют примерно 2.1× более высокую дисперсию.

Four Factors (Dean Oliver)

Four Factors объясняют успех команды:

  1. eFG% (40% вес) — Эффективный процент попадания
    eFG% = (FGM + 0.5 × 3PM) / FGA

  2. TOV% (25% вес) — Процент потерь
    TOV% = TOV / (FGA + 0.44 × FTA + TOV)

  3. ORB% (20% вес) — Процент подборов в нападении
    ORB% = ORB / (ORB + Opp DRB)

  4. FT Rate (15% вес) — Отношение штрафных к броскам
    FT Rate = FTA / FGA

Связь с финансовой математикой

Финансы Баскетбол
Портфель Распределение бросков
Акции 3-очковые (высокая доходность, высокий риск)
Облигации 2-очковые (умеренная доходность, низкий риск)
Волатильность Дисперсия результатов
Sharpe ratio Очки / Стандартное отклонение

Результаты исследования

Оптимальная стратегия

При NBA-средних процентах попадания (52% 2PT, 36% 3PT):

Стратегия Ожидаемый счёт Ст. отклонение P(>100)
70% 2PT / 25% 3PT 102.3 8.2 61.2%
60% 2PT / 35% 3PT 104.1 9.1 67.8%
55% 2PT / 40% 3PT 105.2 9.5 71.3%
50% 2PT / 45% 3PT 105.8 9.8 73.5%
45% 2PT / 50% 3PT 105.5 10.3 72.1%
40% 2PT / 55% 3PT 104.9 10.8 70.2%

Оптимальная стратегия: ~45-50% трёхочковых бросков максимизирует ожидаемый счёт при данных процентах попадания.

Risk-Return Tradeoff

Стратегия Ожидаемый счёт Дисперсия Когда использовать
Conservative (25% 3PT) Низкий Низкая (σ = 8.2) Защита преимущества
Balanced (45% 3PT) Оптимальный Средняя (σ = 9.8) Равная игра
Aggressive (65% 3PT) Высокий Высокая (σ = 11.5) Догоняющие команды

Вывод: Проигрывающие команды должны увеличивать дисперсию (больше 3PT) для повышения вероятности victory.

Four Factors — корреляция с победами

Hot Hand анализ


Production развёртывание

Требования

Пошаговая инструкция

1. Подготовка сервера

# Установка Docker + Compose
        curl -fsSL https://get.docker.com -o get-docker.sh
        sudo sh get-docker.sh
        sudo apt-get install docker-compose-v2

        # Клонирование репозитория
        git clone <repo-url> /opt/predicrionai
        cd /opt/predicrionai
        

2. Настройка DNS (Cloudflare)

Добавьте 4 A-записи в Cloudflare DNS:

Type Name Content Proxy TTL
A monte YOUR_SERVER_IP Proxied Auto
A jupyter.monte YOUR_SERVER_IP Proxied Auto
A research.monte YOUR_SERVER_IP Proxied Auto
A traefik.monte YOUR_SERVER_IP DNS only Auto

Важно: - Для monte/jupyter/research включите Cloudflare proxy (оранжевое облако) - Для traefik используйте DNS-only (серое облако) — иначе TLS handshake failure

3. Cloudflare API Token

Создание токена: 1. Cloudflare Dashboard → My Profile → API Tokens 2. Create Token → шаблон Edit zone DNS 3. Permissions: Zone → DNS → Edit 4. Zone Resources: Include → Specific zone → ваш-домен.com 5. Скопируйте токен (начинается с cfut_... или подобного)

4. Конфигурация .env

cp .env.example .env
        nano .env
        

Заполните:

# Cloudflare API Token (рекомендуется) или Global API Key
        CF_API_EMAIL=your-email@cloudflare.com
        CF_API_KEY=your_cloudflare_api_token

        # Domain
        DOMAIN=monte.yourdomain.com

        # Application settings
        NBA_API_RATE_LIMIT=0.6
        LOG_LEVEL=INFO

        # Traefik Dashboard (будут сгенерированы)
        TRAEFIK_DASHBOARD_USER=admin
        TRAEFIK_DASHBOARD_PASSWORD=your-secure-password

        # Jupyter (будет настроен автоматически)
        # Пароль будет в CREDENTIALS.txt после развёртывания
        

5. Запуск

# Запустите все сервисы
        docker compose up -d --build

        # Проверьте статус
        docker ps

        # Следите за логами Traefik (выпуск сертификатов)
        docker logs traefik -f

        # Ищите строки:
        # "Server responded with a certificate" — сертификат получен
        # "Validations succeeded" — DNS challenge прошёл
        

6. Проверка доступа

# Проверьте все сервисы
        curl -I https://monte.yourdomain.com           # Должен вернуть 200
        curl -I https://research.monte.yourdomain.com  # 200
        curl -I https://jupyter.monte.yourdomain.com   # 302 (redirect to /login)
        curl -u admin:your-pass https://traefik.monte.yourdomain.com/dashboard/  # 200
        

Безопасность

Пароли

Все пароли сохранены в /opt/predicrionai/CREDENTIALS.txt: - Jupyter: пароль для входа в notebooks - Traefik Dashboard: логин/пароль для basic auth

Файл добавлен в .gitignore — не коммитится в репозиторий.

Смена паролей

Jupyter:

# Сгенерируйте хеш
        docker exec nba_jupyter python3 -c "from jupyter_server.auth import passwd; print(passwd('ваш-новый-пароль'))"

        # Обновите docker-compose.yml (удвойте все $)
        command: jupyter notebook ... --NotebookApp.password='argon2:$$argon2id$$v=19$$...'

        # Перезапустите
        docker compose up -d jupyter
        

Traefik:

# Сгенерируйте пароль и хеш
        NEW_PASSWORD=$(openssl rand -base64 24)
        htpasswd -nb admin "$NEW_PASSWORD"

        # Обновите docker-compose.yml (удвойте все $)
        - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$...$$..."

        # Перезапустите
        docker compose up -d traefik
        

Мониторинг

# Статус контейнеров
        docker ps --format 'table {{.Names}}\t{{.Status}}'

        # Логи
        docker logs traefik --tail 50
        docker logs nba_web_app --tail 50
        docker logs nba_jupyter --tail 50

        # Проверка сертификатов
        docker exec traefik cat /letsencrypt/acme.json | jq '.cloudflare.Certificates[].domain.main'
        

Обновление

cd /opt/predicrionai

        # Остановите сервисы
        docker compose down

        # Обновите код
        git pull origin main

        # Пересоберите и запустите
        docker compose up -d --build
        

Разработка

Локальная разработка

Backend (FastAPI):

source venv/bin/activate
        uvicorn webapp.main:app --reload --port 8000
        

API docs: http://localhost:8000/docs

Frontend (React):

cd frontend
        npm run dev
        

Dev server: http://localhost:5173 (проксирует на backend:8000)

Тестирование

# Запустить все тесты
        pytest tests/ -v

        # С покрытием
        pytest tests/ --cov=src --cov=webapp --cov-report=html

        # Открыть отчёт о покрытии
        open htmlcov/index.html
        

Форматирование и линтинг

# Форматирование кода
        black src/ webapp/ tests/

        # Линтинг
        flake8 src/ webapp/ tests/

        # Проверка типов
        mypy webapp/
        

Структура тестов

tests/
        ├── test_simulation/     # Тесты симуляции Монте-Карло
        ├── test_analysis/       # Тесты Four Factors, Hot Hand
        ├── test_data/           # Тесты загрузки данных NBA
        └── test_webapp/         # Тесты API эндпоинтов
        

FAQ и Troubleshooting

Порт 8000 занят

# Найти процесс
        lsof -i :8000

        # Остановить
        kill -9 <PID>

        # Или измените порт в docker-compose.yml
        ports:
          - "8001:8000"
        

Frontend не загружается

# Пересоберите фронтенд
        cd frontend
        npm run build

        # Проверьте файлы
        ls -la ../webapp/static/
        

NBA API не отвечает

Не проблема! Профили команд и матчи запрашиваются по цепочке ESPN → stats.nba.com → Synthetic. ESPN — основной источник: он не блокирует запросы и работает даже в межсезонье (окно поиска истории — до 180 дней, предстоящих матчей — до 90). nba_api (stats.nba.com) — резерв: этот сайт часто блокирует IP серверов. Только если оба реальных источника недоступны, включается синтетический fallback — вы увидите бейдж "Synthetic".

Симуляция медленная

Jupyter 500 ошибка

Проверьте хеш пароля в docker-compose.yml — все $ должны быть удвоены: $$

command: jupyter notebook ... --NotebookApp.password='argon2:$$argon2id$$v=19$$...'
        

Traefik Dashboard 401

  1. Проверьте htpasswd хеш — все $ удвоены
  2. Проверьте пароль в CREDENTIALS.txt
  3. Убедитесь что traefik.monte.yourdomain.com в DNS-only режиме (не proxied)

Сертификаты не выписываются

# Проверьте логи Traefik
        docker logs traefik | grep -i error

        # Проверьте Cloudflare token
        docker exec traefik env | grep CF_

        # Частые причины:
        # - Неверный CF_API_KEY или CF_API_EMAIL
        # - Token не имеет прав "Zone DNS Edit"
        # - DNS записи отсутствуют или неверные
        

Дополнительные материалы


Вклад в проект

# Fork репозитория
        # Создайте feature branch
        git checkout -b feature/amazing-feature

        # Commit изменения
        git commit -m 'Add amazing feature'

        # Push в branch
        git push origin feature/amazing-feature

        # Откройте Pull Request
        

Лицензия

MIT License — см. файл LICENSE


Авторы

Professor Hesam Oveys (NYU) — научный руководитель

Alin Akylzhan — исследователь


Ссылки

  1. Oliver, D. (2004). Basketball on Paper. Brassey's Inc.
  2. Miller, J. B., & Sanjurjo, A. (2018). Surprised by the hot hand fallacy? Econometrica, 86(6).
  3. Goldsberry, K. (2019). Sprawlball: A Visual Tour of the New Era of the NBA.
  4. NBA API Documentation
  5. Dean Oliver's Four Factors

Академический контекст

Проект разработан в рамках исследовательской концентрации под руководством профессора Hesam Oveys (NYU), исследующей применение финансовой математики (теория случайных блужданий, методы Монте-Карло, оптимизация портфелей) к спортивной аналитике.

Ключевой вопрос: Как команде NBA оптимально распределить 2-очковые vs 3-очковые броски для максимизации ожидаемых очков при управлении дисперсией?

Ответ: При NBA-средних процентах попадания (52% 2PT, 36% 3PT) оптимальная стратегия — примерно 45-50% трёхочковых бросков. Однако оптимальное соотношение зависит от: - Способностей команды к броскам (лучшие 3PT стрелки должны бросать больше) - Толерантности к риску (проигрывающие команды должны увеличивать дисперсию через 3PT) - Стратегии соперника (защитные корректировки не моделируются)

Веб-приложение делает это исследование интерактивным и доступным.


Создано: 2026-08-04
Технологии: Python 3.11, FastAPI, React 18, Chart.js, Docker, Traefik
Исследование: Prof. Hesam Oveys (NYU) — The Random Walk of Mathematical Finance
Production: https://monte.kz-saas.com