尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Личный трейд-журнал с риск-менеджментом. Вход по почте и паролю (Better Auth), у каждого пользователя свой торговый счёт. Next.js (App Router) + Postgres (Neon) через Prisma, деплой на Vercel.
npm install
cp .env.example .env # заполнить DATABASE_URL, BETTER_AUTH_*, OWNER_EMAIL, APP_PASSWORD
npx prisma migrate dev
npm run dev
Потом открыть /setup и создать учётку владельца (см. «Вход и пользователи»).
| Переменная | Назначение |
|---|---|
DATABASE_URL | строка подключения к Postgres (Neon) |
BETTER_AUTH_SECRET | секрет подписи сессий, свой на каждое окружение |
BETTER_AUTH_URL | адрес сайта; на превью не нужен — берутся адреса деплоя |
OWNER_EMAIL | почта владельца: роль admin и существующие сделки |
APP_PASSWORD | старый общий пароль, нужен один раз для /setup |
TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET | бот журнала (только прод): привязка Telegram, сигналы безопасности |
DATABASE_URL.BETTER_AUTH_SECRET, BETTER_AUTH_URL, OWNER_EMAIL
и APP_PASSWORD в Environment Variables.npm run build прогоняет prisma migrate deploy перед сборкой./setup и создать владельца — торговый счёт создастся или
привяжется сам.prisma/schema.prisma Account / BalanceEvent / Trade / Fix
src/lib/trading-math.ts формулы: risk, position size, закрытие сделки
src/lib/risk-budget.ts бюджет риска и его зоны
src/app/api/** route handlers (zod-валидация, транзакции)
src/app/(pages) дашборд, /trades/new, /trades/[id], /trades, /settings
lib/trading-math.ts — логика не продублирована.Trade хранит снапшоты depositAtEntry и ставок комиссии (makerFeePct,
takerFeePct), поэтому изменение настроек не меняет уже открытые сделки.позиция = риск / (d + fвх + fвых × стоп/вход), где d — расстояние до стопа в долях входа. Комиссия берётся с
номинала: лимитная заявка — maker, рыночная — taker; стоп всегда считается
рынком. Поэтому выбитая по стопу сделка — ровно −1R. Без этого на коротком стопе
(0,1–0,2%) комиссия добавляла к риску 50–100%.market / limit) хранится у каждого входа и каждой фиксации и
задаёт ставку. По умолчанию: вход — как в прошлый раз на этом устройстве,
ручная фиксация — лимит, стоп и б/у — рынок. Комиссия выхода — с номинала по
цене выхода, как на бирже.sizePct фиксаций достигает 100:
в одной транзакции считается результат, создаётся BalanceEvent типа
trade_settlement и обновляется баланс аккаунта.PATCH /api/account — только через
BalanceEvent или закрытие сделки.PATCH /api/trades/:id): riskAmount и
positionSize пересчитываются, а снапшоты депозита и комиссии остаются прежними.
У закрытой сделки правка запрещена.realizedRR — знаковый R-мультипликатор netPnL / riskAmount. Это осознанное
отступление от формулы раздела 4 ТЗ, которая брала модуль хода цены и поэтому
давала убыточной сделке положительный R:R.Trade.leverage, по умолчанию из Account.defaultLeverage, 5×) на риск
не влияет: убыток при стопе считается от номинала позиции. Плечо определяет
только маржу — positionSize / leverage, ту часть депозита, что реально уходит
в сделку. На дашборде под бюджетом риска отдельная шкала «Маржа в сделках»,
в форме — предупреждение, если маржа больше баланса.Account.riskLimitPct (потолок суммарного риска
открытых позиций в процентах от баланса, по умолчанию 3%), редактируется в
. Жёлтая зона начинается после 2/3 лимита.У сделки может быть несколько входов (TradeEntry), как у позиции на бирже в
одностороннем режиме: одна позиция, средний вход, один стоп. entryPrice,
positionSize и riskAmount сделки — производные от входов и стопа
(src/lib/entries-math.ts, покрыто тестами):
Добор — на странице открытой сделки, пока нет фиксаций; последний добор можно снять. Две открытые сделки по одной паре и в одну сторону (без фиксаций) можно объединить: более поздняя становится добором к более ранней, стоп берётся у поздней, теги объединяются, заметки склеиваются. Объединение необратимо и не оптимистично — ждёт ответа сервера.
У сделки с несколькими входами правка параметров переносит стоп с сохранением количества монет (риск пересчитывается); цену входа, риск и направление там не правят.
Страница сделки после первого SSR живёт в кеше TanStack Query (ключ
['trade', id], src/lib/query/). Фиксации добавляются и удаляются
оптимистично: onMutate патчит кеш и делает снимок, onError откатывает,
onSuccess кладёт серверный ответ, onSettled сверяется. Локальный расчёт —
src/lib/trade-local.ts на той же lib/trading-math, поэтому после
подтверждения цифры не меняются. Новая сделка открывается как карточка на
месте формы: DTO считается локально, URL подменяется через replaceState,
POST уходит в фоне. Id сделок и фиксаций генерирует клиент (uuid), сервер
идемпотентен по ним. Запросы, зависящие от ещё не сохранённой сделки, ждут её
через src/lib/query/pending.ts. Ошибки не молчат: полоска с причиной,
«Повторить» и «Скрыть». Devtools Query открываются кнопкой слева внизу.
Блок «Разбор» (теги причин входа и заметка) правится и у закрытых сделок.
Теги сделки лежат в кеше рядом со сделкой ({ trade, fixes, tagIds }), а не
внутри: ответы на правку сделки тегов не содержат и иначе затирали бы их.
Тег включается оптимистично, а запросы одной сделки уходят строго по очереди
(src/lib/query/serial.ts), чтобы быстрые тапы дошли до сервера в том же
порядке. Встроенный scope у мутаций не подошёл: он продолжает очередь только
на видимой странице. Заметка сохраняется сама — через паузу в наборе и при
уходе с поля.
Под заметкой — «Похоже на: + cme»: теги, упомянутые в тексте, но не отмеченные
(src/lib/tag-suggest.ts, без ИИ). Короткие теги (до 3 символов) ищутся только
целым словом, длинные — по основе, чтобы не мешали окончания: «дивергенция»
находится в «дивергенции». Правила покрыты тестами: npm test (встроенный
node:test, TypeScript через tsx).
user, session, authAccount в той же базе. Торговый Account
привязан к пользователю через userId, getAccount() берёт счёт по сессии.src/proxy.ts (в Next 16 так называется middleware) проверяет сессию на
каждом запросе целиком, а не только наличие cookie: сессия ищется в базе.
Кеш сессии в cookie выключен, поэтому выход на других устройствах и смена
пароля действуют сразу./api/login, эндпоинт Better Auth
/sign-in/email закрыт (disabledPaths), чтобы защиту ниже нельзя было обойти./setup: почта из OWNER_EMAIL, новый пароль и старый APP_PASSWORD как
подтверждение. Владелец получает роль admin и
существующий торговый счёт. После этого /setup отдаёт 404./invite/<токен>, пересылает её сам (писем приложение не
шлёт). Ссылка одноразовая, живёт 7 дней, её можно отозвать; новое
приглашение на ту же почту отзывает прежнее. В Invite хранится только
SHA-256 токена. По ссылке заводится учётка ровно на эту почту, с ролью user
и своим пустым торговым счётом./reset/<токен>, которую выписывает
админ: одноразовая, 24 часа, хранится только хеш. По ней задаётся новый
пароль, все прежние сессии пользователя удаляются. Админам ссылку не выписать —
иначе один админ мог бы войти в учётку другого; свой пароль админ меняет в
настройках.LoginAttempt до проверки пароля — так
пачка параллельных запросов не проскакивает проверку лимита.429 с
Retry-After. Во время блокировки даже верный пароль получает отказ, а новые
попытки продлевают блокировку. Неверный пароль отвечает с задержкой 0,5 с.APP_PASSWORD на /setup сравнивается
за постоянное время (timingSafeEqual по SHA-256).after(), вход не тормозят.SecurityAlert с уникальным индексом через ON CONFLICT DO NOTHING, сигнал
отправляет только тот запрос, чья вставка прошла.x-real-ip и
x-vercel-ip-country/-city — клиент их подделать не может. Журналы старше
90 дней чистятся при успешном входе.Бот живёт на тех же функциях Vercel: Telegram присылает сообщения на
/api/telegram/webhook с секретом TELEGRAM_WEBHOOK_SECRET в заголовке —
без него запрос отклоняется. Отдельный сервер не нужен.
BETTER_AUTH_URL), и если нет —
регистрирует его. Ручной запасной путь — «Пользователи → Бот Telegram →
Подключить заново».t.me/<бот>?start=link_<токен> (10 минут, в базе хеш). Telegram становится
вторым входом в ту же учётку: сделки и пароль остаются. Один Telegram — одна
учётка./?tgapp=1) — с живой сессией без промежуточного экрана. Без сессии proxy
ведёт на /login, и та внутри Telegram входит сама: Telegram передаёт
подписанные данные пользователя (initData),
плагин Better Auth (src/lib/telegram/auth-plugin.ts) проверяет подпись
токеном бота и свежесть (сутки), находит привязанную учётку и ставит cookie
сессии так же, как при входе по паролю. Повторное открытие с живой сессией
новую не создаёт. Проверка подписи покрыта тестами.TELEGRAM_CHAT_ID, если задан, перекрывает).extension/tradingview — пункт «Открыть сделку в журнале» в контекстном меню
инструмента «Длинная/Короткая позиция». Открывает форму новой сделки по
ссылке /trades/new?pair=…&side=…&entry=…&stop=…&source=tv. Установка —
в extension/tradingview/README.md.
Разовая миграция старого журнала. Таблица должна быть открыта по ссылке — экспорт CSV идёт без авторизации.
npx tsx scripts/import-sheet.ts <ссылка-на-таблицу> # показать, что будет
npx tsx scripts/import-sheet.ts <ссылка-на-таблицу> --apply # записать в БД
Результаты пересчитываются формулами приложения, а не берутся из колонок таблицы, поэтому расходятся с ней в пределах копейки на округлении. Повторный запуск безопасен: сделка с теми же входом, стопом и направлением пропускается. Незакрытые сделки (сумма фиксаций не равна 100%) не переносятся.
Интеграция с API биржи — не реализована. Разбор скриншотов моделью был и
убран: им не пользовались, а на насыщенных графиках он ошибался. Вход и стоп
теперь приходят из TradingView через расширение (extension/tradingview) или
вводятся руками.
/settings