openportfolio
openportfolio
Каждый счёт — одна книга. Каждый прогноз — на записи.
Открытый, самостоятельно размещаемый трекер портфеля. Он сводит все брокерские счета, пенсионные накопления, кошельки и банковские счета в единый показатель чистого состояния, хранит потоки инвестора за ценой и оценивает по Брайеру прогнозы, которые вы зарегистрировали до того, как они сбылись. Ни одного ключа API провайдера — нигде в нём.
Веб-сайт · Документация · Живое демо
Панель управления и бэкенд — в один клик. Синхронизирующий воркер работает на вашей машине, по замыслу: см. Развёртывание.
Скриншоты показывают демонстрационную книгу. Все цифры в них вымышлены.
Статус: предрелизный. Он работает, и описанная ниже настройка работает. Интерфейсы ещё будут меняться.
Это не торговый бот. В бэкенде нет ни одной функции, которая размещает ордер, поставляемые адаптеры объявляют canPlaceOrders: false, а PlaceOrderRequest требует OrderConfirmation, у которого нет значения по умолчанию. Что он делает — агрегирует, наблюдает и ведёт счёт.
Зачем
Две проблемы, которые выглядят несвязанными, а на деле — одна и та же.
Портфель разрознен по своей конструкции. Брокер здесь, пенсия там, ISA, биржевой счёт, банковский баланс, актив, который не вернёт ни один API. Каждое из этих приложений показывает вам число, и ни одно не показывает ваше. Поэтому цифры, которые на самом деле решают, — итог, концентрация в одном имени, доля книги в валюте, которой вы не тратите, — это цифры, которых нет ни у кого. Их оценивают на глаз, и оценка щедра в ту сторону, которая позволяет избежать решения.
Рыночные комментарии безответственны — и стали ещё более безответственными с того момента, как модель стала выдавать уверенный направленный прогноз на любой ваш вопрос. Проблема не в том, что прогнозы ошибочны. Проблема в том, что ошибиться ничего не стоит и не оставляет следов, поэтому прогнозист, которого стоит читать, и прогнозист, который просто красноречив, неотличимы снаружи — и изнутри тоже.
Обе проблемы — это провалы учёта, поэтому openportfolio относится к ним как к учёту.
Одно чистое состояние | Счета, подтянутые через адаптеры площадок в единую базовую валюту, с курсом, сохранённым на той строке, которую он конвертировал, так что снимок фиксирует, сколько стоила книга тогда, а не сколько говорят сегодняшние курсы. Позиции, удерживаемые в трёх местах, — это одна экспозиция. |
Потоки, а не только цены | Цена — это результат того, кто покупал, а кого заставили продавать. Чистые покупки по типу инвестора, оборачиваемость и календарь датированных будущих событий хранятся как первоклассные ряды, а не вычисляются, когда кто-то вспомнит спросить. Вынужденный продавец — по расписанию, и расписание публично. Позиционирование по классам участников приходит без ключей из еженедельного отчёта CFTC Commitments of Traders. |
Оценённый послужной список | Прогноз регистрируется до факта с вероятностью, горизонтом и условием, которое его разрешает. Когда горизонт проходит, те прогнозы, что разрешаются машинно, разрешаются сами и получают оценку по Брайеру. Диаграмма надёжности — это и есть продукт: она показывает, что вы сказали, что произошло и каков разрыв между ними. |
Есть и четвёртая таблица, которая существует по одной причине. Рекомендация, сформулированная как «дождитесь публикации, потом решайте», испаряется в момент, когда её произносят вслух. decisions — это очередь таких решений, каждое с условием-триггером и исходом, и они остаются на доске, пока одно из них не изменится.
Related MCP server: Hamara Hisaab
Ни одного ключа API провайдера
Наблюдение за книгой полезно только если кто-то действительно наблюдает: сверяет после закрытия, разрешает прогноз в день, когда истекает его горизонт, замечает, что отложенное решение созрело три недели назад.
Тарифицируемый по объёму вывод — неправильная форма для этого. Когда каждый запуск списывает деньги за токены, каждая автономная проверка становится покупкой, а продукт, который тратит деньги оператора без запроса, должен сначала спрашивать, либо пакетировать, либо нормировать. Все три варианта превращают портфель, который следит за собой, в портфель, который просит разрешения посмотреть.
Поэтому каждый вызов модели вместо этого направляется в агентный CLI, в который вы уже вошли: codex, antigravity или claude, с порядком резервного переключения по задачам. В этом репозитории нет ключа провайдера и нет поля, куда его можно вставить. Это не делает запуск бесплатным: у подписочных планов есть лимиты скорости, и цепочка резервирования существует отчасти потому, что один провайдер исчерпывается раньше других. Меняется тип лимита. Работа агента ограничена квотой и настенными часами, а не расходами, поэтому её не нужно оправдывать по одному вызову за раз.
Следствие: openportfolio по замыслу размещается самостоятельно. Ваше развёртывание выполняет ваши синхронизации на вашей машине под вашими логинами, против ваших собственных счетов.
Что он делает
Поверхность | |
Чистое состояние | счета, балансы, разбивка по площадкам и классам активов, снимки в одной базовой валюте, FX без ключей |
Площадки | контракт адаптера с объявленными возможностями; котировочные адаптеры без ключей для листингуемых инструментов и для монет, плюс ручной |
Потоки | чистые покупки и оборачиваемость по типу инвестора за сессию, по рынку или по символу |
Прогнозы | вероятность, горизонт и критерий разрешения; авторазрешение по истечении горизонта; балл Брайера и корзины надёжности |
Решения | очередь отложенных решений, с условиями-триггерами и исходами |
Катализаторы | датированные будущие события и активы, которых они касаются |
Аудит | добавляемая-только запись каждой изменяющей состояние мутации, включая то, что крон сделал без присмотра |
MCP | 25 инструментов, чтобы |
Мультитенантность | каждая таблица ограничена тенантом, каждый индекс начинается с него, один сервисный ключ на тенанта |
Быстрый старт
Node 22+, pnpm и аккаунт Convex. Бесплатного тарифа достаточно.
git clone https://github.com/seonglae/openportfolio.git
cd openportfolio
pnpm install
cp .env.example .env.local
npx convex dev --once # creates the deployment
# create the first book
npx convex env set OPENPORTFOLIO_DEV_TENANT home
npx convex run tenants:create '{"slug":"home","name":"Home","baseCurrency":"GBP"}'
# the UI, then the sync loop
pnpm --filter openportfolio-browser dev # http://localhost:6101
npx tsx sync-worker.mts --onceПока ничего не подключено, он регистрирует площадки, которые может обслуживать, и фиксирует чистое состояние в ноль, что корректно. Добавьте файл ручных активов, чтобы получить настоящее:
[
{ "accountKey": "isa", "symbol": "VWRL", "assetClass": "etf", "qty": 40, "price": 118.2, "currency": "GBP" },
{ "accountKey": "wallet", "symbol": "BTC", "assetClass": "crypto", "qty": 0.15, "price": 0, "currency": "USD" }
]export OPENPORTFOLIO_MANUAL_HOLDINGS=$PWD/holdings.json
npx convex run accounts:link '{"accountKey":"isa","venue":"manual","kind":"brokerage","label":"ISA","currency":"GBP"}'
npx convex run accounts:link '{"accountKey":"wallet","venue":"manual","kind":"wallet","label":"Wallet","currency":"USD"}'
npx tsx sync-worker.mts --onceЦены в файле — отправная точка, а не запись: воркер заново котирует каждую строку, которую может, через источник без ключей, маршрутизируя по классу активов, конвертирует их в GBP и записывает один итог. Акции, ETF и фонды идут в Yahoo, монеты — в CoinGecko. Строка, чья записанная цена и есть запись, — пенсия или недвижимость — получает класс other, о котором ни один источник котировок не спрашивается.
Полное руководство: openportfolio.app/docs/quickstart
Или разверните его
Этот поток клонирует этот репозиторий в ваш собственный Git-аккаунт, устанавливает интеграцию Convex из Vercel Marketplace и выделяет проект Convex под вашей собственной командой Convex, запрашивает у вас одно значение и собирает обе половины одной командой:
npx convex deploy --cmd-url-env-var-name VITE_CONVEX_URL --cmd 'pnpm --filter openportfolio-browser build'Шаг Marketplace — единственная причина, по которой это один клик, а не два: Vercel может создать бэкенд во время импорта, вместо того чтобы отправлять вас создавать его сначала, и передаёт сборке ключ развёртывания. vercel.json защищает команду условием CONVEX_DEPLOY_KEY и откатывается к обычной браузерной сборке, так что тот же файл покрывает и развёртывание, которое вы подготовили сами, а теперь хотите размещённую страницу. Без защиты этот случай провалил бы сборку.
Он не спрашивает у вас ничего. Вход не требует ключа, потому что он работает внутри только что созданного развёртывания: аутентификация — это Convex Auth с провайдером пароля, так что ваше развёртывание само выпускает и проверяет свои токены, и вход никогда его не покидает. Никакой компании-аутентификатора на пути, никакого аккаунта, который нужно создавать где-то ещё. Convex Auth находится в бете у вышестоящего проекта, что и есть честная цена этого выбора.
Затем одна команда на стороне Convex для генерации ключей подписи развертывания и создания первого портфеля. Первый зарегистрировавшийся становится его владельцем:
npx @convex-dev/auth
npx convex run tenants:create '{"slug":"home","name":"Home","baseCurrency":"GBP"}'Регистрации затем закрываются сами: вызывающий, не принадлежащий ни одному тенанту, может создать только самый первый портфель, поэтому публичный URL не становится чьим-то чужим бэкендом. OPENPORTFOLIO_OPEN_SIGNUP=1 снова открывает её.
Воркер синхронизации не является частью этого и не может быть. Он читает ваши счета через адаптеры и отправляет модельную работу в CLI агента, в который вы вошли, а внутри бессерверной функции нет вошедшего CLI. Развернутая половина по-прежнему вычисляет прогнозы и оценивает их на собственных кронах Convex; запускайте воркер, когда хотите обновить балансы. Подробности: Развертывание.
Кнопки Cloudflare нет: эта кнопка поддерживает только Workers, и её режим монорепозитория требует полной изоляции приложения в его подкаталоге, чем browser/ не является.
Перед тем как открыть доступ
Две вещи открыты на localhost и должны быть закрыты, прежде чем развертывание станет доступным из интернета.
Dev-тенант. Пока установлен
OPENPORTFOLIO_DEV_TENANT, любой неаутентифицированный вызывающий ограничен этим тенантом. Сбросьте его. Вход уже есть и не требует настройки.Сервисные ключи. У Workers и MCP-сервера нет браузерной сессии, поэтому они предъявляют ключ. Сгенерируйте его локально и отправьте только его хэш.
npx @convex-dev/auth # once, generates this deployment's signing keys
npx convex env unset OPENPORTFOLIO_DEV_TENANT
KEY="$(openssl rand -hex 32)"
npx convex run tenants:issueServiceKey "{\"key\":\"$KEY\",\"label\":\"sync-worker\",\"role\":\"member\"}"
echo "OPENPORTFOLIO_SERVICE_KEY=$KEY" >> .env.localМультитенантность
Одно развертывание содержит много портфелей. Инвариант: вызывающий никогда не сообщает, какой он тенант.
tenantId выводится из строк членства вызывающего или из собственной строки сервисного ключа, поэтому нет аргумента, который клиент может установить, чтобы получить доступ к другому портфелю. Публичный API принимает tenantSlug, и только как дискриминатор для вызывающего, принадлежащего нескольким тенантам; членство по-прежнему решает. Идентификатор документа, принадлежащий другому тенанту, читается как отсутствующий, а не запрещённый, потому что «запрещено» подтверждает существование строки, что само по себе является межтенантным чтением.
Каждый индекс начинается с tenantId, поэтому запрос, забывающий область, вообще не может использовать индекс. Одно исключение намеренно и отмечено: крон резолвера просматривает просроченные вызовы каждого портфеля через индекс без тенанта и является internalMutation именно по этой причине. Он недоступен ни одному клиенту.
Подробности: openportfolio.app/docs/multi-tenancy
Адаптеры площадок
Адаптер объявляет, что он умеет, и реализует только это:
type VenueAdapter = {
venue: string;
kind: AccountKind;
capabilities: { canReadBalances: boolean; canReadQuotes: boolean; canPlaceOrders: boolean };
readBalances(request: ReadBalancesRequest): Promise<AdapterBalance[]>;
readQuote(request: ReadQuoteRequest): Promise<AdapterQuote>;
placeOrder?(request: PlaceOrderRequest): Promise<OrderReceipt>;
};Поставляются четыре, ни один из них не требует ключа. yahoo оценивает всё, что где-либо котируется, в той валюте, в которой торгуется листинг, поэтому портфель из акций США, ETF LSE и наименований KRX остаётся актуальным без счёта ни в одном из них. coingecko оценивает монеты. Оба отказываются от балансов, потому что источник цен не знает, что у вас есть, и возврат пустого списка читался бы как «у вас ничего нет». manual читает JSON-файл, который вы ведёте, — так пенсия или некотируемая позиция попадает в итог, а не остаётся за его пределами. csv читает собственный экспорт брокера: укажите OPENPORTFOLIO_CSV_DIR на папку и поместите в неё <accountKey>.csv. Столбцы сопоставляются по имени, поэтому большинство экспортов работают без правок, и он покрывает счета, у которых вообще никогда не будет API.
Какой источник оценивает какую строку, решает уже указанный на ней класс актива, и ничто не переключается на другой. Оба отвечают на неправильный инструмент с HTTP 200: спросите Yahoo про BTC — и он вернёт траст Grayscale около $30 вместо биткоина около $68 000, а у CoinGecko есть токен с id aapl стоимостью около 18 центов. Неправильное число в итоге хуже, чем отсутствующее, поэтому класс, о котором не следует спрашивать ни один источник, просто не переоценивается.
Ни один адаптер брокера с ключом не поставляется. Добавление такого означает написание модуля в packages/node/src/adapters/, получение его учётных данных из окружения воркера и регистрацию в defaultRegistry(). Держите учётные данные в процессе воркера: бэкенд их никогда не видит, и этот репозиторий тоже.
Подробности: openportfolio.app/docs/adapters
Требования
Node 22+, pnpm
Учётная запись Convex (бесплатного тарифа достаточно)
Как минимум один вошедший CLI агента, если вы хотите агентский воркер:
codex,antigravity(agy) илиclaudeБольше ничего. Аутентификация — это Convex Auth, работающий в вашем собственном развертывании, поэтому нет поставщика удостоверений, для которого нужно регистрироваться.
Разработка
pnpm typecheck # every workspace, src and test alike
pnpm test # vitest across packages, convex handlers, browser helpers
# the demo build used for the screenshots and the hosted demo
pnpm --filter openportfolio-browser exec vite build --config vite.demo.config.ts
# the marketing site and docs are static; regenerate the docs pages after editing
python3 site/build-docs.pyСоглашения, полный инвариант тенанта и заметки для агентских CLI, работающих в этом репозитории, находятся в AGENTS.md.
Лицензия
Apache-2.0. См. LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Personal finance for AI agents — onboard, import statements, categorize & budget over MCP.
Real SEC, 13F, insider, congress & macro data your AI agent can cite. Hosted MCP, 24 tools.
Household net worth and portfolio tracker your AI keeps: priced daily, previewed and undoable.
- MoflowOAuthapp.moflow
Net worth tracker: ask your AI what you're worth, and have it update your ledger.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to manage and analyze personal investment portfolios, including fund and stock holdings, net value tracking, XIRR calculations, penetration analysis, and backtesting.Apache 2.0
- FlicenseNot gradedqualityBmaintenanceSelf-hosted household finance app for shared expenses, budgets, investments, loans, and zakat, exposing MCP tools for AI agents to manage finances via natural language.3-
- FlicenseNot gradedqualityCmaintenanceAn MCP server that exposes personal financial data — transaction ledger, portfolio holdings, live/historical market prices, and quantitative risk metrics — as standardized tools, resources, and prompts, enabling natural language reasoning over real computed numbers.-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to operate a self-hosted financial platform over MCP, managing books, expenses, revenues, settlements, payments, accounts, categories, transfers, positions, and cash flow with idempotent and audited writes.MIT