Веб-приложение для анализа оптимального распределения бросков
Исследование под руководством профессора Hesam Oveys (NYU)
Интерактивное веб-приложение, применяющее вероятностные методы финансовой математики к баскетбольной аналитике. Позволяет пользователям выбирать матчи NBA, интерактивно настраивать стратегии бросков и запускать симуляции Монте-Карло для поиска оптимального соотношения 2-очковых/3-очковых бросков.
cd /opt/predicrionai
docker compose up --build
Откройте браузер: http://localhost:8000
# Настройте окружение
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 развёртывание для подробной инструкции.
# Клонируйте репозиторий
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) - Раздаёт статический сайт с исследовательской статьёй
# 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)
# Получить все команды
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 контейнер
docker compose up jupyter
# Откройте https://jupyter.monte.kz-saas.com
# Пароль см. в файле CREDENTIALS.txt
Структура Notebooks:
notebooks/01_monte_carlo_simulation.ipynb — основная симуляция и анализПример использования:
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 |
Для 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 объясняют успех команды:
eFG% (40% вес) — Эффективный процент попадания
eFG% = (FGM + 0.5 × 3PM) / FGA
TOV% (25% вес) — Процент потерь
TOV% = TOV / (FGA + 0.44 × FTA + TOV)
ORB% (20% вес) — Процент подборов в нападении
ORB% = ORB / (ORB + Opp DRB)
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% трёхочковых бросков максимизирует ожидаемый счёт при данных процентах попадания.
| Стратегия | Ожидаемый счёт | Дисперсия | Когда использовать |
|---|---|---|---|
| Conservative (25% 3PT) | Низкий | Низкая (σ = 8.2) | Защита преимущества |
| Balanced (45% 3PT) | Оптимальный | Средняя (σ = 9.8) | Равная игра |
| Aggressive (65% 3PT) | Высокий | Высокая (σ = 11.5) | Догоняющие команды |
Вывод: Проигрывающие команды должны увеличивать дисперсию (больше 3PT) для повышения вероятности victory.
# Установка 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
Добавьте 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
Создание токена:
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_... или подобного)
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 после развёртывания
# Запустите все сервисы
docker compose up -d --build
# Проверьте статус
docker ps
# Следите за логами Traefik (выпуск сертификатов)
docker logs traefik -f
# Ищите строки:
# "Server responded with a certificate" — сертификат получен
# "Validations succeeded" — DNS challenge прошёл
# Проверьте все сервисы
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 эндпоинтов
# Найти процесс
lsof -i :8000
# Остановить
kill -9 <PID>
# Или измените порт в docker-compose.yml
ports:
- "8001:8000"
# Пересоберите фронтенд
cd frontend
npm run build
# Проверьте файлы
ls -la ../webapp/static/
Не проблема! Профили команд и матчи запрашиваются по цепочке ESPN → stats.nba.com → Synthetic. ESPN — основной источник: он не блокирует запросы и работает даже в межсезонье (окно поиска истории — до 180 дней, предстоящих матчей — до 90). nba_api (stats.nba.com) — резерв: этот сайт часто блокирует IP серверов. Только если оба реальных источника недоступны, включается синтетический fallback — вы увидите бейдж "Synthetic".
Проверьте хеш пароля в docker-compose.yml — все $ должны быть удвоены: $$
command: jupyter notebook ... --NotebookApp.password='argon2:$$argon2id$$v=19$$...'
$ удвоены# Проверьте логи 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 записи отсутствуют или неверные
README.mddocs/README_RU.mddocs/PROJECT_SUMMARY_RU.mddocs/RESEARCH_SITE_RU.mddocs/TRAEFIK_SETUP_RU.md# 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 — исследователь
Проект разработан в рамках исследовательской концентрации под руководством профессора 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