Полнофункциональное приложение для оценки справедливой цены акций MOEX с поддержкой нескольких таблиц аналитиков, автоматическим обновлением цен, мониторингом и развёртыванием через Docker Compose / Minikube.
- Ведение таблиц аналитиков (до 10 таблиц).
- Для каждой строки по тикеру:
- текущая цена,
- количество акций (млрд),
- средний P/E,
- прогнозная чистая прибыль на 4 года,
- расчёт прогнозной цены и Upside на 4 года.
- Автоматический пересчёт производных полей при изменении входных данных.
- Автосинхронизация строк с одинаковыми тикерами между таблицами.
- Поддержка «основной» таблицы:
- только в основной таблице редактируются
Кол-во акцийиP/E; - можно выбрать любую таблицу как основную.
- только в основной таблице редактируются
- Backend получает цену по ISS API MOEX.
- При отсутствии сделок применяется fallback на
PREVPRICE. - Фоновое обновление цен раз в 10 минут.
- Русскоязычный интерфейс.
- Sticky-header, сортировки по тикеру/капитализации/upside.
- Автосохранение редактирования.
- Сравнение по тикеру: при наведении на тикер показываются дополнительные строки из других таблиц (внутри основной таблицы), с визуальным выделением группы.
frontend(Nginx + static SPA):- отдаёт
index.html,app.js,styles.css; - проксирует
/apiи/metricsв backend.
- отдаёт
backend(FastAPI + SQLAlchemy + Alembic):- API и бизнес-логика,
- фоновая задача обновления цен,
- экспорт метрик Prometheus.
db(PostgreSQL 16):- хранение таблиц аналитиков и строк тикеров.
monitoring:prometheus,grafana,loki,promtail,node-exporter.
pgbackup:- периодические бэкапы БД.
- Пользователь редактирует строку во frontend.
- Frontend отправляет
PUT /api/rows/{id}. - Backend сохраняет данные, пересчитывает производные поля, при необходимости синхронизирует данные в других таблицах.
- Frontend обновляет отображение.
backend/app/— API, модели, сервисы.backend/alembic/— миграции.backend/tests/— unit-тесты.frontend/— статический клиент.k8s/— манифесты Kubernetes.scripts/— сценарии запуска/остановки.monitoring/— конфиги Prometheus/Grafana/Loki.deploy/nginx/— шаблоны reverse-proxy.
analyst_tablesid,analyst_name,year_offset,sort_order,created_at.
stock_rowstable_id,ticker,current_price,shares_billion,pe_avg_5y,forecast_profit_year1..4_billion_rub,forecast_price_year1..4,upside_percent_year1..4,net_profit_year_map,status_message, timestamps.
- Используется Alembic, миграции в
backend/alembic/versions. - Важно: revision-id в Alembic должен помещаться в
alembic_version.version_num(VARCHAR(32)).
GET /api/tables— список таблиц в текущем порядке (основная = №1).POST /api/tables— создать таблицу.PATCH /api/tables/{table_id}— изменить имя/сдвиг лет.DELETE /api/tables/{table_id}— удалить таблицу (нельзя удалить текущую основную).POST /api/tables/{table_id}/make-primary— сделать таблицу основной.
GET /api/rows?table_id=...POST /api/rowsPUT /api/rows/{row_id}DELETE /api/rows/{row_id}POST /api/rows/refresh?table_id=...
GET /api/healthGET /metricsGET /api/ticker-comparison?ticker=...
- Поля
shares_billionиpe_avg_5y:- редактируются только в основной таблице (№1);
- в остальных таблицах — readonly.
- При создании/изменении строки тикер синхронизируется между таблицами.
- Изменения
shares_billionиpe_avg_5yв основной таблице автоматически распространяются в остальные таблицы для того же тикера.
- При наведении на тикер:
- в таблицу временно вставляются строки из других таблиц по этому же тикеру;
- группа строк выделяется цветом.
- При уходе курсора/blur — таблица возвращается в исходный вид.
./scripts/compose-up.shСкрипт также автоматически переключает хостовый Nginx reverse-proxy в compose-режим
(scripts/configure-nginx-compose-proxy.sh --reload), чтобы URL в домашней сети оставался тем же: http://junibox/.
./scripts/compose-down.shПри остановке автоматически сохраняется актуальный snapshot БД в
backups/mode-sync/latest.sql.gz для последующего переноса между режимами.
- Frontend: http://localhost:8080
- Backend health: http://localhost:8000/api/health
- Metrics (через proxy): http://localhost:8080/metrics
- Prometheus: http://localhost:9090
- Grafana: http://localhost:3000
- Loki readiness: http://localhost:3100/ready
- При
compose-downиminikube-downвыполняется экспорт snapshot БД. - При
compose-upиminikube-upвыполняется импорт последнего snapshot (если он есть). - Общий путь snapshot:
backups/mode-sync/latest.sql.gzbackups/mode-sync/latest.meta
- Это позволяет не терять актуальные данные при переключении способа развёртывания.
./scripts/minikube-up.shСкрипт автоматически переключает тот же хостовый Nginx reverse-proxy в Minikube-режим
(scripts/configure-nginx-k8s-proxy.sh --reload), сохраняя единый внешний URL http://junibox/.
Также скрипт поднимает kubectl port-forward для frontend на 127.0.0.1:30080
и пишет PID/лог в:
/tmp/moex-k8s-port-forward.pid/tmp/moex-k8s-port-forward.log/tmp/moex-k8s-prometheus-port-forward.pid/tmp/moex-k8s-prometheus-port-forward.log/tmp/moex-k8s-grafana-port-forward.pid/tmp/moex-k8s-grafana-port-forward.log/tmp/moex-k8s-loki-port-forward.pid/tmp/moex-k8s-loki-port-forward.log
В Minikube-режиме также поднимается мониторинг (prometheus, grafana, loki) и
он доступен через тот же внешний хост:
http://junibox/prometheus/http://junibox/grafana/http://junibox/loki/
Опции:
./scripts/minikube-up.sh --skip-nginx./scripts/minikube-down.sh
# или оставить кластер:
./scripts/minikube-down.sh --keep-minikubeminikube start
minikube addons enable ingress
eval "$(minikube docker-env)"
docker build -t krapa666/moex-backend:latest backend
docker build -t krapa666/moex-frontend:latest frontend
kubectl apply -k k8ssudo apt update
sudo apt install -y git docker.io docker-compose-plugin nginxcd /opt
sudo git clone https://gitlab.com/krapa/moex.git
sudo chown -R $USER:$USER /opt/moex
cd /opt/moex
./scripts/compose-up.sh# Compose-режим:
sudo ./scripts/configure-nginx-compose-proxy.sh --reload
# Minikube-режим:
sudo ./scripts/configure-nginx-k8s-proxy.sh --reload
# Ручная установка шаблона (fallback):
sudo cp deploy/nginx/home-server.conf /etc/nginx/conf.d/moex.conf
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx- Prometheus собирает метрики backend + инфраструктуры.
- Grafana datasource provisioning настраивается автоматически.
- Loki + Promtail собирают логи контейнеров.
- Готовые dashboard/alerts находятся в
monitoring/.
Полезные проверки:
curl -s http://localhost:3100/ready
curl -s http://localhost:8000/api/health
curl -s http://localhost:8080/metrics | head- Бэкапы в
./backups. - Данные PostgreSQL в volume
postgres_data.
docker compose exec db pg_dump -U postgres -d fair_price > ./backups/manual_$(date +%F_%H-%M-%S).sqlcat ./backups/<backup_file>.sql | docker compose exec -T db psql -U postgres -d fair_priceruff check backend
PYTHONPATH=backend pytest -q backend/testscd backend
alembic upgrade head- При старте backend выполняется defensive-проверка
sort_orderдля legacy БД. - Рекомендуется всё равно поддерживать БД в актуальном состоянии через Alembic.
Файл .gitlab-ci.yml:
lint—ruff check backendtest—pytest -q backend/testsbuild— docker build backend/frontend
Проверить backend:
docker compose ps
docker compose logs -f backend
curl http://localhost:8000/api/healthПричина: слишком длинный revision ID.
Решение: использовать сокращённый revision (в проекте уже исправлено для миграции 0007).
Перегенерируйте proxy-конфиг:
sudo ./scripts/configure-nginx-k8s-proxy.sh --reloadЕсли Minikube-профиль временно не поднят, но 127.0.0.1:30080 доступен,
configure-nginx-k8s-proxy.sh всё равно сгенерирует рабочий конфиг.
Compose-режим:
sudo ./scripts/configure-nginx-compose-proxy.sh --reloadMinikube-режим:
sudo ./scripts/configure-nginx-k8s-proxy.sh --reload- Не храните токены (
GITHUB_TOKEN,GITLAB_TOKEN) в репозитории. - Для production ограничьте CORS и доступ к служебным endpoint.
- Регулярно проверяйте алерты Prometheus и ротацию бэкапов.
- Формат:
MAJOR.MINOR.PATCH. MAJOR— несовместимые изменения API/данных.MINOR— новый функционал без поломки обратной совместимости.PATCH— исправления.
./scripts/compose-up.sh- Открыть
http://localhost:8080 - Проверить
http://localhost:8000/api/health - Проверить Grafana/Prometheus
- Выполнить пробное добавление тикера и проверить авторасчёты
- Проверить сравнение между таблицами
Если хотите, следующим шагом могу сделать отдельные разделы в README с примерами API-запросов (curl) для каждого endpoint и отдельный runbook для production-аварий (что проверять в каком порядке).