Техническая записка
Высокоуровневое описание архитектуры платформы Finaim: технологический стек, потоки данных, API-эндпоинты и оптимизации производительности.
Технологический стек
Бэкенд
- Flask 3.0 + Jinja2 — WSGI-фреймворк и шаблонизатор
- SQLAlchemy 2.0 + SQLite — ORM и хранилище данных пользователей
- PyPortfolioOpt — оптимизация портфеля (CLA + EfficientFrontier)
- CatBoost + SHAP — ML-скоринг непубличных компаний
- sentence-transformers — семантические эмбеддинги описаний
Фронтенд и данные
- d3.js v7 — интерактивные графики (эффективная граница, heatmap, radar)
- Tailwind CSS CDN — utility-first стилизация
- yfinance — котировки NYSE/NASDAQ, фундаменталы
- MOEX ISS API — котировки и история российских акций
- Clearbit — логотипы публичных компаний по домену
Слои бэкенда
┌─────────────────────────────────────────────┐
│ Flask Application (src/server/app.py) │ HTTP / Jinja2 rendering
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Page routes │ │ REST API /api/* │ │
│ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │
│ ┌──────▼────────────────────▼──────────┐ │
│ │ Business Logic │ │
│ │ src/portfolio/optimizer.py (CLA) │ │
│ │ src/portfolio/profiler.py (quiz) │ │
│ │ src/portfolio/company_info.py │ │
│ │ src/ml/predict.py (SHAP) │ │
│ │ src/ml/semantic.py (embeddings) │ │
│ └──────────────────┬───────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────┐ │
│ │ Data / Storage Layer │ │
│ │ src/portfolio/data.py (yfinance) │ │
│ │ src/portfolio/moex.py (MOEX ISS) │ │
│ │ src/storage/db.py (SQLite) │ │
│ │ src/storage/community.py │ │
│ │ src/storage/portfolio_metrics.py │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Поток данных (рынок → кэш → API → фронтенд)
Запрос пользователя (POST /portfolio/optimize)
│
▼
fetch_prices(tickers, period)
├── yfinance.download() US акции (NYSE/NASDAQ)
└── MOEX ISS API Российские акции (MOEX)
│
▼
TTL in-memory cache (30 мин) ← MD5-ключ по тикерам+весам
│
▼
optimize_portfolio(prices, bounds)
├── expected_returns.mean_historical_return(prices)
├── risk_models.sample_cov(prices)
├── EfficientFrontier.max_sharpe() макс. Sharpe
├── CLA.efficient_frontier(40 points) граница
└── Sortino / MaxDD / Correlations
│
▼
JSON response → d3.js visualization (frontend)
├── scatter frontier + random cloud
├── stacked-area (transition map)
└── heatmap (correlations)
Для страницы рынка используется фоновый прогрев кэша через requestIdleCallback:
при каждой загрузке любой страницы (кроме /market) браузер в режиме простоя делает запрос
GET /api/market/prewarm?regions=us, чтобы /market открывался мгновенно.
API Endpoints
| Метод | Маршрут | Описание |
|---|---|---|
| GET | / | Главная страница |
| GET | /portfolio | Оптимизатор портфеля (UI) |
| POST | /portfolio/optimize | Запуск оптимизации (JSON in/out) |
| GET | /score | Скоринг непубличных компаний (UI) |
| POST | /score/company | SHAP-скоринг по 11 признакам |
| GET | /market | Рынок и анализ компаний (UI) |
| GET | /api/market/snapshot | Индексы + топ-муверы (RU + US) |
| GET | /api/market/quotes | Котировки по группе тикеров |
| GET | /api/market/prewarm | Прогрев кэша котировок |
| GET | /api/market/search | Поиск тикеров по строке |
| GET | /api/market/info/<ticker> | Snowflake + описание компании |
| GET | /api/market/sectors | Данные по секторам рынка |
| GET | /profile | Риск-профилирование инвестора (UI) |
| GET | /api/profile/questions | Список вопросов анкеты |
| POST | /api/profile/score | Расчёт риск-балла по ответам |
| POST | /api/score/semantic | Семантический анализ описания |
| POST | /api/waitlist | Регистрация / вход |
| GET | /community | Лента публичных портфелей |
| GET | /api/community/feed | JSON-лента (trending / latest) |
| POST | /api/community/follow/<handle> | Подписка / отписка (toggle) |
| GET | /api/portfolios | Портфели текущего пользователя |
| POST | /api/portfolios | Создание / обновление портфеля |
| GET | /health | Health-check (version, status) |
| GET | /docs/methodology | Методика расчётов |
| GET | /docs/architecture | Техническая записка |
| GET | /legal/privacy | Политика конфиденциальности |
| GET | /legal/terms | Условия использования |
| GET | /legal/disclaimer | Уведомление об инвестиционных рисках |
Оптимизации производительности
In-memory TTL Cache
Все сетевые запросы (котировки yfinance, MOEX history) кешируются в памяти Python-процесса.
TTL для котировок — 30 минут, для информации о компаниях — 7 дней.
Реализовано в src/portfolio/market_data.py и src/portfolio/company_info.py.
requestIdleCallback Prewarm
На каждой странице (кроме /market) фронтенд в режиме простоя браузера делает фоновый запрос
GET /api/market/prewarm?regions=us. Это заполняет кэш котировок заранее,
так что страница /market открывается без задержки на загрузку данных.
ThreadPoolExecutor для MOEX history
Исторические данные по нескольким MOEX-тикерам загружаются параллельно через
concurrent.futures.ThreadPoolExecutor, что сокращает время ожидания
при построении графиков истории российских акций.
lru_cache для ML-моделей
Загрузка CatBoost-модели и sentence-transformers кешируется через @lru_cache(maxsize=1).
Модели загружаются один раз при первом обращении и остаются в памяти на протяжении всей жизни процесса,
исключая повторную инициализацию при каждом запросе.
Социальный граф
User ──────follows──────► User │ │ ├──owns──► Portfolio │ │ │ │ │ ├── Like │ │ │ │ │ └── Comment │ │ │ └──────────seen in Feed ◄──┘ SQLite таблицы: users (id, email, name, handle, avatar_url, ...) portfolios (id, user_id, name, tickers_json, weights_json, is_public, description, ...) follows (follower_id, followee_id) likes (user_id, portfolio_id) comments (id, user_id, portfolio_id, body, created_at)Публичные портфели агрегируются в ленту «Сообщество» с сортировкой по популярности (лайки) или свежести. Подписки реализованы симметрично: один запрос к
POST /api/community/follow/<handle>переключает состояние (toggle). Для публичных портфелей пользователь выдаёт неисключительную лицензию на отображение в ленте (см. Условия использования).