- TypeScript 95.8%
- CSS 3.4%
- JavaScript 0.5%
- HTML 0.3%
|
Some checks failed
Deploy to GitHub Pages / deploy (push) Has been cancelled
Reviewed-on: #3 |
||
|---|---|---|
| .devcontainer | ||
| .github/workflows | ||
| public | ||
| src | ||
| .gitignore | ||
| bun.lock | ||
| components.json | ||
| eslint.config.js | ||
| index.html | ||
| package.json | ||
| README.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
Portfolio Rebalancer
Одностраничное веб-приложение для управления и ребалансировки инвестиционного портфеля. Работает полностью в браузере, развёртывается на GitHub Pages как статический сайт без бэкенда.
Все данные хранятся только у пользователя на компьютере.
Возможности
- Управление позициями — акции и кэш в произвольных валютах
- Актуальные котировки — через Finnhub.io (бесплатный API)
- Выбор типа цены — цена закрытия предыдущего дня или цена последней сделки
- Конвертация валют — через frankfurter.app (официальные курсы ЕЦБ)
- Ребалансировка — расчёт сколько акций купить/продать и сколько кэша добавить/вывести
- Порог шума — мелкие сделки (< 0.1% от стоимости портфеля) автоматически игнорируются
- Portfolio Drift — интегральная оценка отклонения от целевых весов
- Быстрое выравнивание — клик по «Факт %» копирует текущую долю позиции в целевой процент
- Контроль суммы — бейдж показывает суммарный целевой % и отклонение от 100%; блок ребалансировки предупреждает, если сумма некорректна
- Портфель в файле — сохранение/загрузка JSON; API-ключ и настройки сохраняются вместе с портфелем
- Двуязычный интерфейс — русский и английский
- Светлая и тёмная тема
- Встроенная справка — инструкция по использованию, описание алгоритма, форматы тикеров
Стек
| Инструмент | Версия | Назначение |
|---|---|---|
| Bun | latest | Runtime, пакетный менеджер, сборка |
| React | 19 | UI |
| TypeScript | ~5.9 | Типизация |
| Vite | 7 | Dev-сервер и бандлер |
| Tailwind CSS | v4 | Стили |
| shadcn/ui | latest | UI-компоненты (Neutral theme) |
| Zustand | 5 | Управление состоянием |
| lucide-react | latest | Иконки |
Источники данных
| Сервис | Данные | Ограничения |
|---|---|---|
| Finnhub.io | Котировки акций | 60 запросов/мин, требует бесплатный API-ключ |
| frankfurter.app | Курсы валют (ЕЦБ) | ~33 валюты, RUB не поддерживается с 2022 |
Настройка API-ключа Finnhub
Вариант 1 — через интерфейс (рекомендуется):
- Зарегистрироваться на finnhub.io
- Скопировать API Key из Dashboard
- Открыть приложение → кнопка ⚙ → вставить ключ
- Сохранить портфель — ключ запишется в JSON-файл и будет восстановлен при следующей загрузке
Вариант 2 — через переменную окружения (для локальной разработки):
# .env
VITE_FINNHUB_API_KEY=your_key_here
Режим цены акции
Настраивается в ⚙ → «Цена акции». Сохраняется в JSON-файл.
| Режим | Поле Finnhub | Описание |
|---|---|---|
| Цена закрытия | pc |
Официальная цена закрытия предыдущего торгового дня. Стабильна весь день |
| Последняя сделка | c |
Цена последней совершённой сделки. Актуальна при высокой волатильности |
При недоступности основного поля (рынок закрыт, значение = 0) — автоматический fallback на альтернативное поле.
Формат портфеля (JSON)
{
"baseCurrency": "USD",
"positions": [
{ "ticker": "AAPL", "quantity": 10, "targetPercent": 25 },
{ "ticker": "BMW.DE", "quantity": 5, "targetPercent": 20 }
],
"cash": [
{ "currency": "USD", "amount": 5000, "targetPercent": 30 },
{ "currency": "EUR", "amount": 2000, "targetPercent": 25 }
],
"finnhubApiKey": "your_key_here",
"priceMode": "previousClose"
}
Сумма всех targetPercent (акции + кэш) должна равняться 100%.
Поля finnhubApiKey и priceMode опциональны — сохраняются автоматически при сохранении портфеля.
Формат тикеров
| Тикер | Биржа | Валюта |
|---|---|---|
AAPL, MSFT |
NASDAQ / NYSE | USD |
BMW.DE |
XETRA | EUR |
HSBA.L |
LSE | GBP |
7203.T |
TSE (Tokyo) | JPY |
0700.HK |
HKEX | HKD |
CBA.AX |
ASX | AUD |
RY.TO |
TSX | CAD |
Алгоритм ребалансировки
Расчёт текущего состояния
currentValue[i] = price[i] × quantity[i] (в базовой валюте)
totalValue = Σ currentValue[i] (акции + кэш)
currentPercent[i] = currentValue[i] / totalValue × 100
delta[i] = targetPercent[i] − currentPercent[i]
Рекомендации по акциям
targetQuantity[i] = floor(targetPercent[i] / 100 × totalValue / priceBase[i])
diff[i] = targetQuantity[i] − currentQuantity[i]
Используется Math.floor — не выходить за бюджет целевой аллокации.
Порог шума
Сделка игнорируется (показывается «Держать»), если:
|tradeValue[i]| / totalValue < 0.1%
Это устраняет ложные рекомендации, возникающие из-за округления целевых процентов.
Остаток распределяется в кэш
После выделения средств под акции оставшаяся сумма распределяется между кэш-позициями пропорционально их targetPercent.
Portfolio Drift
Drift = 0.5 × Σ |currentPercent[i] − targetPercent[i]|
Показывает, какую долю портфеля нужно переложить для достижения цели.
| Drift | Интерпретация |
|---|---|
| < 5% | В норме, ребалансировка не нужна |
| 5–10% | Стоит рассмотреть ребалансировку |
| > 10% | Ребалансировка оправдана |
Разработка
Требования
- Docker
- VSCode с расширением Dev Containers
Запуск
# 1. Клонировать репозиторий, открыть в VSCode
# 2. Command Palette → "Reopen in Container"
# 3. В терминале контейнера:
bun run dev
Приложение доступно на http://localhost:5173.
Сборка
bun run build
Структура проекта
src/
├── components/
│ ├── ui/ # shadcn/ui (сгенерировано)
│ ├── Header.tsx # шапка: навигация, настройки, тема, язык
│ ├── HelpModal.tsx # встроенная справка (RU/EN)
│ ├── PortfolioTable.tsx # таблица позиций с inline-редактированием
│ ├── RebalanceTable.tsx # таблица рекомендаций по ребалансировке
│ └── PortfolioChart.tsx # круговая диаграмма портфеля (SVG)
├── store/
│ ├── portfolioStore.ts # Zustand: портфель, котировки, ребалансировка
│ └── settingsStore.ts # Zustand: тема, язык, режим цены
├── services/
│ ├── finnhub.ts # котировки акций (Finnhub.io)
│ └── frankfurter.ts # курсы валют (ECB / frankfurter.app)
├── lib/
│ ├── rebalance.ts # логика ребалансировки + Portfolio Drift
│ ├── i18n.ts # переводы RU/EN
│ ├── types.ts # TypeScript типы
│ └── utils.ts # shadcn утилиты (cn)
├── App.tsx
└── main.tsx
Deploy pipeline
Forgejo (self-hosted) → GitHub (mirror) → GitHub Pages
Push в main автоматически запускает GitHub Actions workflow (.github/workflows/deploy.yml):
bun install → bun run build → deploy dist/ на GitHub Pages.
Лицензия
MIT