Finaim
Finaim Умные инвестиции
Документация

Техническая записка

Высокоуровневое описание архитектуры платформы 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 открывался мгновенно.

Социальный граф

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). Для публичных портфелей пользователь выдаёт неисключительную лицензию на отображение в ленте (см. Условия использования).

API Endpoints

Метод Маршрут Описание
GET/Главная страница
GET/portfolioОптимизатор портфеля (UI)
POST/portfolio/optimizeЗапуск оптимизации (JSON in/out)
GET/scoreСкоринг непубличных компаний (UI)
POST/score/companySHAP-скоринг по 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/feedJSON-лента (trending / latest)
POST/api/community/follow/<handle>Подписка / отписка (toggle)
GET/api/portfoliosПортфели текущего пользователя
POST/api/portfoliosСоздание / обновление портфеля
GET/healthHealth-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). Модели загружаются один раз при первом обращении и остаются в памяти на протяжении всей жизни процесса, исключая повторную инициализацию при каждом запросе.