neuroalpha 0.5.13 → 0.5.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ # Развёртывание центрального сервера
2
+
3
+ Центральный сервер (`NEUROALPHA_ROLE=central`) — единственная точка, которую видит клиент до подключения. На нём работают:
4
+
5
+ - лендинг, регистрация и вход (email, OAuth, Telegram/MAX), оплата ЮKassa, оферта;
6
+ - личный кабинет клиента с блоком «Где работать»;
7
+ - консоль администратора `/admin`;
8
+ - ИИ-прокси для машин клиентов (`/api/llm/proxy/v1`);
9
+ - реестр машин клиентов, выпуск подписанных лицензий и автоматическое развёртывание облачных машин;
10
+ - боты Telegram и MAX: уведомления с машин клиентов уходят через центр.
11
+
12
+ Торговый терминал на центре тоже открывается (для владельца), но клиенты торгуют на своих машинах.
13
+
14
+ ## 1. Требования
15
+
16
+ | Ресурс | Значение |
17
+ |---|---|
18
+ | Образ | собирается из `Dockerfile` в корне репозитория (`node:22-bookworm-slim`, запуск не от root) |
19
+ | Реплики | **строго 1**, стратегия `Recreate` — база SQLite не допускает двух процессов |
20
+ | Том | PVC от 10 ГБ, монтируется в `/data` (база, ключи, лицензии) |
21
+ | CPU / память | от 1 vCPU / 2 ГБ (обучение модели для собственного терминала владельца — до 4 ГБ) |
22
+ | Сеть | входящий HTTPS через ingress на `neuroalpha.odant.org`; исходящий доступ к Мосбирже, брокерам, моделям ИИ, Telegram/MAX, ЮKassa |
23
+ | WebSocket | путь `/ws`, тайм-ауты прокси не меньше 3600 с |
24
+ | Проверка живости | `GET /healthz` → `{"ok":true,"role":"central"}` |
25
+
26
+ ## 2. Обязательные переменные
27
+
28
+ | Переменная | Значение |
29
+ |---|---|
30
+ | `NEUROALPHA_ROLE` | `central` |
31
+ | `NEUROALPHA_DATA_DIR` | `/data` (в образе уже задано) |
32
+ | `CENTRAL_PUBLIC_URL` | `https://neuroalpha.odant.org` — публичный адрес центра, его получают машины клиентов |
33
+ | `MASTER_KEY` | случайная строка 32+ символов: шифрует токены ботов, ключи ЮKassa, OAuth и LLM в базе |
34
+ | `LICENSE_SIGNING_KEY` | закрытый ключ подписи лицензий Ed25519 (PEM), см. раздел 3 |
35
+
36
+ Полный список — в документе «Справочник переменных окружения».
37
+
38
+ > **Если `MASTER_KEY` не задан**, ключ создаётся в `/data/.master.key`. Потеря этого файла означает потерю всех сохранённых в базе секретов (их придётся ввести заново в админке).
39
+
40
+ ## 3. Ключ подписи лицензий — самое ценное на центре
41
+
42
+ Им центр подписывает лицензии и одноразовые ссылки входа. Машины клиентов проверяют подпись открытым ключом.
43
+
44
+ **Первый запуск.** Если `LICENSE_SIGNING_KEY` не задан, центр создаёт ключ в `/data/.license-ed25519.pem` и пишет об этом в журнал. Сразу после первого запуска:
45
+
46
+ ```
47
+ kubectl cp <namespace>/<pod>:/data/.license-ed25519.pem ./license-ed25519.pem
48
+ kubectl create secret generic neuroalpha-central-keys --from-file=LICENSE_SIGNING_KEY=./license-ed25519.pem
49
+ ```
50
+
51
+ Подключите Secret к переменной `LICENSE_SIGNING_KEY`, удалите локальную копию и сохраните резервную копию ключа в хранилище секретов (не в git).
52
+
53
+ **Открытый ключ** отдаётся всем: `GET https://neuroalpha.odant.org/api/public/license-key`. Он нужен при сборке версии для компьютера и вписывается в Secret каждой облачной машины.
54
+
55
+ | Что случилось | Последствия | Действия |
56
+ |---|---|---|
57
+ | Ключ потерян | новые лицензии не проверяются ни одной машиной; через 72 ч все машины приостановят новые заявки | создать новый ключ, обновить `LICENSE_PUBLIC_KEY` во всех облачных машинах («Новый ключ» в админке при включённом развёртывании), пересобрать и разослать версию для компьютера |
58
+ | Ключ утёк | злоумышленник может подписать себе лицензию на любой тариф | те же действия, что при потере, — немедленно |
59
+
60
+ ## 4. Вход администратора
61
+
62
+ Администратор платформы — пользователь №1. На центре вход без пароля отключён, поэтому после первого запуска задайте email и пароль:
63
+
64
+ ```
65
+ kubectl exec -it deploy/neuroalpha-central -- env ADMIN_PASS='длинный-пароль' node scripts/set-admin-password.mjs --email admin@neuroalpha.odant.org
66
+ ```
67
+
68
+ Скрипт завершает все прежние сессии администратора. Пароль — не короче 10 символов; после 8 неудачных попыток вход по этому email и IP блокируется на 15 минут.
69
+
70
+ ## 5. Ingress и адрес клиента
71
+
72
+ - Центр считает адресом клиента **последний** IP в `X-Forwarded-For` (его добавляет ваш ingress). Ingress должен добавлять этот заголовок; иначе ограничение попыток входа будет общим для всех.
73
+ - Заголовок `Host` должен передаваться без изменений: по нему проверяется `Origin` запросов (защита от CSRF).
74
+ - Cookie сессии ставится с флагом `Secure`, если ingress передаёт `X-Forwarded-Proto: https`.
75
+
76
+ ## 6. Боты, оплата и вход через OAuth
77
+
78
+ Настраиваются в `/admin` (значения шифруются `MASTER_KEY`) или переменными `TELEGRAM_BOT_TOKEN`, `MAX_BOT_TOKEN`, `YOOKASSA_SHOP_ID`, `YOOKASSA_SECRET_KEY`. Webhook ЮKassa: `https://neuroalpha.odant.org/api/billing/yookassa/webhook`.
79
+
80
+ Вебхук MAX по умолчанию **выключен** (обновления забираются опросом). Чтобы включить, задайте `MAX_WEBHOOK_SECRET` и передайте этот же секрет при подписке бота — центр проверяет заголовок `X-Max-Bot-Api-Secret`.
81
+
82
+ ## 7. Модель ИИ для клиентов
83
+
84
+ Машины клиентов обращаются к ИИ только через прокси центра по API-ключу клиента из лицензии. Апстрим-модель центра задаётся переменными `LLM_PROVIDER`, `LLM_BASE_URL`, `LLM_MODEL`, `LLM_API_KEY` или в разделе «Модели ИИ» терминала администратора. Дневные лимиты запросов — по тарифу (`maxChatPerDay`).
85
+
86
+ ## 8. Резервное копирование
87
+
88
+ Ежедневно копируйте том `/data` целиком: `trading.db`, `trading.db-wal`, `.master.key`, `.license-ed25519.pem` (если ключ ещё не вынесен в Secret). Для согласованной копии SQLite делайте снимок тома или `sqlite3 trading.db ".backup /backup/trading.db"`.
89
+
90
+ ## 9. Проверка после развёртывания
91
+
92
+ 1. `GET /healthz` отвечает `role: central`.
93
+ 2. `GET /api/public/license-key` отдаёт `-----BEGIN PUBLIC KEY-----`.
94
+ 3. Вход в `/admin` по паролю из шага 4.
95
+ 4. Вкладка «Машины клиентов» открывается; в журнале центра строка «Развёртывание машин клиентов: автоматически (PAAS_DRIVER=…)» или «вручную».
96
+ 5. Тестовый клиент с тарифом «Профи» в течение 2–3 минут получает облачную машину (см. документ «Облачные машины клиентов»).
@@ -0,0 +1,130 @@
1
+ # Облачные машины клиентов
2
+
3
+ Облачная машина — отдельный namespace в Kubernetes с одним подом НейроАльфа в режиме `NEUROALPHA_ROLE=instance`, `INSTANCE_KIND=cloud`. Адрес — `naXXXXXX.odant.org`. На машине хранятся база клиента, его зашифрованные токены брокера, считается прогноз и исполняются заявки. С центром машина связывается только исходящими запросами.
4
+
5
+ ## 1. Жизненный цикл
6
+
7
+ 1. Клиент оплачивает тариф «Инвестор» или «Профи» (или получает его промокодом либо в админке).
8
+ 2. Не позже чем через 2 минуты центр создаёт запись машины `naXXXXXX` и вызывает драйвер развёртывания.
9
+ 3. Под стартует, раз в минуту отправляет в центр техническое состояние и получает подписанную лицензию на 72 часа.
10
+ 4. Клиент в кабинете нажимает «Перейти в терминал»: центр выдаёт одноразовую ссылку входа (60 с), машина ставит сессию на 7 дней.
11
+
12
+ Автоматически машина **не создаётся**:
13
+
14
+ - администраторам;
15
+ - тарифу VIP: облако или компьютер назначаются по договору вручную в админке;
16
+ - клиенту, у которого уже есть любая машина, в том числе с завершённой арендой.
17
+
18
+ Состояния развёртывания в админке:
19
+
20
+ | Состояние | Значение |
21
+ |---|---|
22
+ | ожидает развёртывания | запись создана, драйвер ещё не вызван |
23
+ | разворачивается… | идёт вызов драйвера |
24
+ | развёрнута, ждём связь | драйвер ответил успешно, под ещё не вышел на связь |
25
+ | (пусто) | машина на связи |
26
+ | ошибка развёртывания | текст ошибки — во всплывающей подсказке; центр повторит через 2 минуты с новым ключом |
27
+
28
+ ## 2. Драйвер развёртывания: `PAAS_DRIVER`
29
+
30
+ ### `none` (по умолчанию) — вручную
31
+
32
+ 1. В `/admin` → «Машины клиентов» выберите клиента и тип «☁ Облако», нажмите «Создать машину».
33
+ 2. Скопируйте переменные (показываются **один раз**) в Secret и примените шаблон `deploy/k8s/instance.yaml`, заменив `na235412` на ID машины:
34
+
35
+ ```
36
+ sed 's/na235412/naXXXXXX/g' deploy/k8s/instance.yaml | kubectl apply -f -
37
+ ```
38
+
39
+ ### `k8s` — напрямую через API Kubernetes
40
+
41
+ Центр применяет объекты методом server-side apply (`fieldManager=neuroalpha-central`). Внутри кластера используется токен сервисного аккаунта пода центра; снаружи — `K8S_API_URL`, `K8S_TOKEN`, `K8S_CA_FILE`.
42
+
43
+ Права сервисного аккаунта центра (ClusterRole):
44
+
45
+ ```yaml
46
+ apiVersion: rbac.authorization.k8s.io/v1
47
+ kind: ClusterRole
48
+ metadata: { name: neuroalpha-provisioner }
49
+ rules:
50
+ - apiGroups: [""]
51
+ resources: [namespaces]
52
+ verbs: [get, create, patch, delete]
53
+ - apiGroups: [""]
54
+ resources: [secrets, persistentvolumeclaims, services]
55
+ verbs: [get, create, patch, delete]
56
+ - apiGroups: [apps]
57
+ resources: [deployments]
58
+ verbs: [get, create, patch, delete]
59
+ - apiGroups: [networking.k8s.io]
60
+ resources: [ingresses, networkpolicies]
61
+ verbs: [get, create, patch, delete]
62
+ ```
63
+
64
+ ### `webhook` — ваш PaaS
65
+
66
+ Центр отправляет `POST` на `PAAS_WEBHOOK_URL`:
67
+
68
+ ```json
69
+ {
70
+ "action": "apply", // или "delete"
71
+ "instanceId": "na235412",
72
+ "host": "na235412.odant.org",
73
+ "image": "registry.odant.org/neuroalpha:0.4.0",
74
+ "env": { "NEUROALPHA_ROLE": "instance", "INSTANCE_KIND": "cloud", "INSTANCE_ID": "…",
75
+ "INSTANCE_KEY": "na_inst_…", "CENTRAL_URL": "…", "CLOUD_URL": "…", "LICENSE_PUBLIC_KEY": "-----BEGIN…" },
76
+ "manifests": [ /* 7 объектов Kubernetes, как в deploy/k8s/instance.yaml */ ],
77
+ "ts": 1792679474163
78
+ }
79
+ ```
80
+
81
+ Заголовок `X-NA-Signature: sha256=<hex>` — HMAC-SHA256 от **сырого тела** запроса с ключом `PAAS_WEBHOOK_SECRET`. Проверка на стороне PaaS (Node.js):
82
+
83
+ ```js
84
+ const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
85
+ const ok = expected.length === sig.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
86
+ // дополнительно: отклонять запросы, у которых |now - ts| > 5 минут
87
+ ```
88
+
89
+ Требования к обработчику:
90
+
91
+ - ответ 2xx — успех, любой другой код — ошибка (будет повтор);
92
+ - `apply` идемпотентен: создать или обновить все объекты. При смене `INSTANCE_KEY` под должен перезапуститься: в шаблоне пода для этого есть аннотация `neuroalpha/key-rev`;
93
+ - `delete` удаляет namespace целиком **вместе с томом данных клиента**.
94
+
95
+ ## 3. Состав машины
96
+
97
+ | Объект | Назначение |
98
+ |---|---|
99
+ | Namespace `naXXXXXX` | изоляция клиента |
100
+ | Secret `neuroalpha-instance` | переменные машины, в том числе ключ машины и открытый ключ центра |
101
+ | PVC `neuroalpha-data` | `/data`: база, ключ шифрования токенов брокера, лицензия (`INSTANCE_STORAGE`, по умолчанию 5Gi) |
102
+ | Deployment `neuroalpha` | 1 реплика, `Recreate`, запуск от UID 1000, `/healthz` для проверок живости и готовности |
103
+ | Service + Ingress | `naXXXXXX.odant.org`, TLS из `INSTANCE_TLS_SECRET` (wildcard `*.odant.org`), тайм-ауты WebSocket 3600 с |
104
+ | NetworkPolicy | входящие соединения — только из namespace ingress-контроллера |
105
+
106
+ Исходящий доступ машине нужен к центру, `iss.moex.com`, API брокеров (`invest-public-api.tinkoff.ru` и др.) и источникам новостей.
107
+
108
+ Ресурсы пода: запрос 0,5 CPU / 1 ГБ, предел 2 CPU / 3 ГБ. За первые сутки база вырастает примерно до 1 ГБ: загружается история котировок.
109
+
110
+ ## 4. Управление из админки
111
+
112
+ | Кнопка | Что происходит |
113
+ |---|---|
114
+ | ⏸ Пауза | новые заявки не отправляются, защитные стопы работают; применяется при следующей связи (до 1 мин) |
115
+ | ⛔ Стоп | запрещены любые заявки, включая защитные стопы (аварийная остановка) |
116
+ | ▶ Возобновить | обычная работа |
117
+ | 🔑 Новый ключ | новый ключ машины; при включённом развёртывании сразу передаётся в Secret, и под перезапускается |
118
+ | Завершить аренду | лицензия выдаётся без тарифа: новые заявки остановлены, защитные стопы работают, данные сохраняются |
119
+
120
+ Удаление машины (`DELETE /api/admin/instances/<id>`) при включённом развёртывании удаляет namespace и **все данные клиента**. В интерфейсе отдельной кнопки нет, это сделано намеренно.
121
+
122
+ ## 5. Обновление версии
123
+
124
+ Новый образ задаётся переменной центра `INSTANCE_IMAGE` и применяется к новым машинам и при «Новом ключе». Массовое обновление существующих машин пока выполняется средствами кластера или PaaS, например:
125
+
126
+ ```
127
+ kubectl -n naXXXXXX set image deploy/neuroalpha neuroalpha=registry.odant.org/neuroalpha:0.4.1
128
+ ```
129
+
130
+ Обновление всех машин из админки запланировано отдельно.
@@ -0,0 +1,92 @@
1
+ # Сборка и выпуск версии для компьютера клиента
2
+
3
+ Версия для компьютера выдаётся клиентам тарифа VIP по индивидуальному договору. Это один исполняемый файл (Node.js Single Executable Application):
4
+
5
+ - код сервера внутри собран в один файл и обфусцирован;
6
+ - адрес центра и открытый ключ центра вшиты при сборке;
7
+ - файлы интерфейса вшиты в исполняемый файл и распаковываются в профиль пользователя.
8
+
9
+ ## 1. Что защищено и что нет
10
+
11
+ | Попытка | Результат |
12
+ |---|---|
13
+ | Запустить в режиме разработчика через `.env` или `NEUROALPHA_ROLE=dev` | игнорируется: режим вшит (`instance`, `desktop`) |
14
+ | Подложить свой открытый ключ (`LICENSE_PUBLIC_KEY`) и подписать себе лицензию | игнорируется: проверяется только вшитый ключ |
15
+ | Направить программу на поддельный центр (`CENTRAL_URL`) | игнорируется: адрес центра вшит |
16
+ | Перенести ключ активации на другой компьютер | центр отклоняет: ключ привязан к идентификатору установки |
17
+ | Прочитать исходный код в файле | код обфусцирован: имена функций заменены, большинство строк закодировано |
18
+ | Пропатчить исполняемый файл и вырезать проверки | **возможно** для опытного специалиста. Защита — договор и юридическая ответственность |
19
+
20
+ ## 2. Подготовка
21
+
22
+ - Node.js **той же версии**, что указана в `engines` (сборка берёт исполняемый файл Node текущей версии; для Linux скачивает официальный с nodejs.org).
23
+ - `npm ci` — нужны dev-зависимости `esbuild`, `javascript-obfuscator`, `postject`.
24
+ - Доступ в интернет при первой сборке для Linux: около 30 МБ, кэшируется в `dist/.node-cache`.
25
+
26
+ ## 3. Сборка
27
+
28
+ ```
29
+ npm run build:desktop -- --pubkey-url https://neuroalpha.odant.org/api/public/license-key
30
+ ```
31
+
32
+ Параметры:
33
+
34
+ | Параметр | По умолчанию | Назначение |
35
+ |---|---|---|
36
+ | `--pubkey-url <url>` или `--pubkey <файл.pem>` | — (обязательно) | открытый ключ центра |
37
+ | `--central <url>` | `https://neuroalpha.odant.org` | адрес центра, вшиваемый в программу |
38
+ | `--platform win,linux` | `win,linux` | какие файлы собрать |
39
+ | `--no-obfuscate` | выкл. | только для отладки, **не выдавать клиентам** |
40
+
41
+ Результат (около 85 МБ для Windows и 120 МБ для Linux) с контрольными суммами `.sha256`:
42
+
43
+ ```
44
+ dist/desktop/neuroalpha-0.4.0-win-x64.exe
45
+ dist/desktop/neuroalpha-0.4.0-linux-x64
46
+ ```
47
+
48
+ > Сборка с открытым ключом тестового центра работает только с ним. Для клиентов собирайте строго с ключом боевого центра.
49
+
50
+ ## 4. Подпись для Windows
51
+
52
+ Без подписи Windows SmartScreen предупреждает при запуске. Подпишите файл сертификатом разработчика (лучше EV):
53
+
54
+ ```
55
+ signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f codesign.pfx /p <пароль> dist\desktop\neuroalpha-0.4.0-win-x64.exe
56
+ ```
57
+
58
+ После подписи пересчитайте `.sha256`. Предупреждение сборщика `signature seems corrupted` — ожидаемо: исходная подпись `node.exe` снимается при внедрении кода.
59
+
60
+ ## 5. Образ Docker для компьютера клиента
61
+
62
+ Сначала соберите Linux-версию, затем образ:
63
+
64
+ ```
65
+ docker build -f Dockerfile.desktop --build-arg VERSION=0.4.0 -t registry.odant.org/neuroalpha-desktop:0.4.0 .
66
+ docker push registry.odant.org/neuroalpha-desktop:0.4.0
67
+ ```
68
+
69
+ Внутри образа — тот же защищённый файл. Обычный `Dockerfile` (исходный код, режим из переменных) предназначен **только** для вашей инфраструктуры: центр и облачные машины. Клиентам его не выдавать.
70
+
71
+ ## 6. Публикация
72
+
73
+ 1. Загрузите файлы на сервер раздачи (доступ только по ссылке или после входа).
74
+ 2. Задайте на центре переменные, чтобы ссылки появились в кабинете VIP-клиентов:
75
+
76
+ | Переменная | Пример |
77
+ |---|---|
78
+ | `DESKTOP_DOWNLOAD_WINDOWS` | `https://download.odant.org/neuroalpha/0.4.0/neuroalpha-0.4.0-win-x64.exe` |
79
+ | `DESKTOP_DOWNLOAD_LINUX` | `https://download.odant.org/neuroalpha/0.4.0/neuroalpha-0.4.0-linux-x64` |
80
+ | `DESKTOP_DOCKER_IMAGE` | `registry.odant.org/neuroalpha-desktop:0.4.0` |
81
+ | `DESKTOP_LATEST_VERSION` | `0.4.0` — установки со старой версией получат отметку о новой версии |
82
+ | `DESKTOP_DOWNLOAD_URL` | ссылка для обновления, передаётся вместе с отметкой |
83
+
84
+ Отметка о новой версии сейчас доступна программе через `/api/local/status` (поле `update`). Баннер в интерфейсе терминала и автообновление запланированы.
85
+
86
+ ## 7. Проверка перед выдачей клиентам
87
+
88
+ 1. Запустить `.exe` на чистой машине: открывается экран «Активация».
89
+ 2. В кабинете тестового VIP-клиента получить код и активировать: открывается терминал, тариф VIP.
90
+ 3. «Отвязать» в кабинете: в течение минуты баннер «Новые заявки приостановлены».
91
+ 4. `neuroalpha --version` показывает версию и дату сборки.
92
+ 5. Linux: проверить в контейнере `debian:bookworm-slim` (`--version` и запуск с `HOST=0.0.0.0`).
@@ -0,0 +1,101 @@
1
+ <!-- audience: client -->
2
+ # Руководство клиента: где работает НейроАльфа
3
+
4
+ НейроАльфа работает на **вашей персональной машине**. Выберите вариант, который вам подходит:
5
+
6
+ | | ☁ В облаке | 💻 На своём компьютере |
7
+ |---|---|---|
8
+ | Для кого | все тарифы | тариф VIP по договору |
9
+ | Что нужно от вас | только браузер | компьютер с Windows или Linux, включённый во время торгов |
10
+ | Как войти | из личного кабинета | открывается на этом компьютере |
11
+ | Токен брокера хранится | на вашей облачной машине, в зашифрованном виде | только на вашем компьютере, в зашифрованном виде |
12
+
13
+ Перейти к нужному варианту можно в личном кабинете, в блоке **«Где работать»**.
14
+
15
+ ## 1. В облаке
16
+
17
+ 1. Оплатите тариф. Персональная машина создаётся автоматически, обычно за 2–5 минут.
18
+ 2. В личном кабинете, в блоке «Где работать», нажмите **«Перейти в терминал»**.
19
+ 3. Адрес машины вида `naXXXXXX.odant.org` можно сохранить в закладки: если вы вошли в кабинет, вход выполняется автоматически.
20
+
21
+ Машина работает круглосуточно, компьютер держать включённым не нужно.
22
+
23
+ ## 2. На своём компьютере (VIP)
24
+
25
+ ### Требования
26
+
27
+ - Windows 10/11 (64-бит) или Linux x64; 4 ГБ памяти, 3 ГБ свободного места;
28
+ - стабильный интернет во время торговой сессии Мосбиржи;
29
+ - компьютер включён и не уходит в сон с 9:50 до 23:50 МСК, если включена автоматическая торговля.
30
+
31
+ ### Установка на Windows
32
+
33
+ 1. Скачайте программу по ссылке из личного кабинета («⬇ Windows»).
34
+ 2. Запустите файл. Если Windows покажет предупреждение SmartScreen, нажмите «Подробнее» → «Выполнить в любом случае».
35
+ 3. Откроется браузер с экраном **«Активация»**.
36
+ 4. В личном кабинете нажмите **«Получить код активации»** и введите код (вида `NA-XXXX-XXXX`) в программе. Код действует 10 минут и срабатывает один раз.
37
+ 5. Чтобы терминал запускался сам при входе в Windows, выполните один раз в командной строке:
38
+
39
+ ```
40
+ neuroalpha-0.4.0-win-x64.exe --install
41
+ ```
42
+
43
+ Повторный запуск программы, когда она уже работает, просто открывает терминал в браузере. Адрес терминала: `http://localhost:3000`.
44
+
45
+ ### Установка на Linux
46
+
47
+ ```
48
+ chmod +x neuroalpha-0.4.0-linux-x64
49
+ ./neuroalpha-0.4.0-linux-x64 --install
50
+ sudo loginctl enable-linger $USER # чтобы работала без входа в систему
51
+ ```
52
+
53
+ Откройте `http://localhost:3000` и активируйте программу кодом из кабинета.
54
+
55
+ ### Через Docker
56
+
57
+ ```
58
+ docker run -d --name neuroalpha --restart unless-stopped -p 127.0.0.1:3000:3000 -v neuroalpha:/data registry.odant.org/neuroalpha-desktop:0.4.0
59
+ ```
60
+
61
+ Откройте `http://localhost:3000` и активируйте программу. Чтобы войти в терминал после активации, откройте его из личного кабинета кнопкой **«Открыть»**.
62
+
63
+ ### Где хранятся данные
64
+
65
+ | Система | Папка |
66
+ |---|---|
67
+ | Windows | `%LOCALAPPDATA%\NeuroAlpha\data` |
68
+ | Linux | `~/.local/share/neuroalpha/data` |
69
+ | Docker | том `neuroalpha` |
70
+
71
+ Удалить программу из автозапуска: `--uninstall`. Данные и активация при этом сохраняются.
72
+
73
+ ### Перенос на другой компьютер
74
+
75
+ 1. В личном кабинете у текущего компьютера нажмите **«Отвязать»**. Программа на нём в течение минуты перестанет отправлять новые заявки.
76
+ 2. Если хотите сохранить историю, скопируйте папку данных на новый компьютер.
77
+ 3. Установите программу на новом компьютере и активируйте её новым кодом из кабинета.
78
+
79
+ ## 3. Подтверждение заявок из Telegram и MAX
80
+
81
+ Привяжите Telegram или MAX в личном кабинете. В режиме «Подтверждение заявок» каждый пакет заявок приходит сообщением с кнопками **«✓ Исполнить»** и **«✕ Отклонить»**. Кнопки работают и для облачной машины, и для программы на компьютере. Нажатие выполняется в течение нескольких секунд. Если пакет за это время уже заменён новым, программа ничего не исполнит и сообщит об этом. Кнопки действуют 2 часа и срабатывают один раз.
82
+
83
+ ## 4. Если пропал интернет или центр недоступен
84
+
85
+ Лицензия действует 72 часа без связи с центром. Всё это время программа работает как обычно. Если связи нет дольше:
86
+
87
+ - новые заявки приостанавливаются;
88
+ - **защитные стоп-заявки по открытым позициям продолжают работать**;
89
+ - открытые позиции программа не закрывает.
90
+
91
+ Причина остановки показывается баннером над списком счетов. Связь восстановится — работа продолжится автоматически.
92
+
93
+ ## 5. Безопасность
94
+
95
+ - Терминал на компьютере доступен только с этого компьютера. Не открывайте порт 3000 в интернет и не пробрасывайте его на роутере.
96
+ - Токен брокера никогда не передаётся в центр, в чат ИИ и в браузер: в настройках видна только маска `t.…abcd`.
97
+ - Если вы подозреваете, что токен брокера стал известен посторонним, перевыпустите его в личном кабинете брокера и вставьте новый в настройках счёта.
98
+
99
+ ## 6. Поддержка
100
+
101
+ Пишите на `support@neuroalpha.ru` или в бот НейроАльфа в Telegram / MAX. Укажите адрес машины (`naXXXXXX`) или имя компьютера из личного кабинета.
@@ -0,0 +1,109 @@
1
+ # Эксплуатация и безопасность
2
+
3
+ ## 1. Как машина клиента получает право торговать
4
+
5
+ Раз в минуту машина отправляет в центр техническое состояние и получает лицензию, подписанную ключом Ed25519 центра, сроком на 72 часа (`LICENSE_TTL_HOURS`). В лицензии указаны тариф клиента, срок подписки и состояние торговли из админки.
6
+
7
+ | Ситуация | Новые заявки | Защитные стопы |
8
+ |---|---|---|
9
+ | Всё в порядке | ✓ | ✓ |
10
+ | Нет связи с центром, лицензия ещё действует | ✓ | ✓ |
11
+ | Лицензия истекла; подписка не активна; «Пауза»; аренда завершена; программа на компьютере не активирована | ✕ | ✓ |
12
+ | Центр отверг ключ машины (ключ заменён, компьютер отвязан, машина удалена) — лицензия отзывается сразу | ✕ | ✓ |
13
+ | «⛔ Стоп» в админке | ✕ | ✕ |
14
+
15
+ Открытые позиции машина никогда не закрывает принудительно. Клиент видит причину остановки баннером над счетами.
16
+
17
+ ## 2. Что машины отправляют в центр
18
+
19
+ **Только техническое состояние:**
20
+
21
+ - версия программы и Node.js, время работы, память, размер базы, имя хоста;
22
+ - число счетов (всего, в автоматическом режиме, на паузе, с дневным стопом) и брокеры;
23
+ - сколько секунд назад была синхронизация и прогноз;
24
+ - разрешена ли торговля и почему; остаток срока лицензии;
25
+ - до пяти последних ошибок связи.
26
+
27
+ **Никогда не отправляются:** суммы, позиции, сделки, токены брокеров, email и персональные данные клиента. Центр дополнительно отбрасывает все поля, не входящие в этот список.
28
+
29
+ ## 3. Консоль администратора → «Машины клиентов»
30
+
31
+ | Колонка | Значение |
32
+ |---|---|
33
+ | Машина | ID, адрес (облако) или имя компьютера, состояние развёртывания |
34
+ | Связь | онлайн, если машина выходила на связь за последние 3 минуты |
35
+ | Состояние | счета, наличие дневных стопов, разрешена ли торговля, память и размер базы, ошибки (подробно — во всплывающей подсказке) |
36
+ | Торговля | торгует / пауза / аварийная остановка |
37
+
38
+ Действия: «Возобновить», «Пауза», «Стоп», «Новый ключ» (облако), «Отвязать компьютер», «Завершить аренду» / «Восстановить». Все команды доходят до машины при следующей связи — до 1 минуты.
39
+
40
+ ## 4. Типовые ситуации
41
+
42
+ | Ситуация | Что делать |
43
+ |---|---|
44
+ | Машина «нет связи» | проверить под в кластере (`kubectl -n naXXXXXX get pods`, `logs`). Торговля продолжается до 72 ч, потом приостанавливаются только новые заявки |
45
+ | «Ошибка развёртывания» | текст ошибки — в подсказке. Центр повторяет каждые 2 минуты с новым ключом. Частые причины: права сервисного аккаунта, нет Secret с wildcard-сертификатом в namespace, недоступен webhook PaaS |
46
+ | Ключ машины мог утечь | «🔑 Новый ключ»: старый перестаёт работать сразу |
47
+ | Клиент сменил компьютер | клиент сам нажимает «Отвязать» в кабинете, либо администратор — «Отвязать компьютер» |
48
+ | Клиент сообщил об утечке токена брокера | клиент перевыпускает токен у брокера и вставляет новый в настройках счёта. Старый токен отозвать может только брокер |
49
+ | Нужно немедленно остановить торговлю клиента | «⛔ Стоп» (не дольше 1 мин). Закрыть позиции клиент может сам в приложении брокера |
50
+ | Подозрение на компрометацию ключа подписи центра | см. документ «Развёртывание центрального сервера», раздел 3 |
51
+ | Клиент не может войти: «слишком много попыток» | блокировка по email и IP снимается сама через 15 минут |
52
+
53
+ ## 5. Защита, включённая в версии 0.4.0
54
+
55
+ - Вход обязателен для всего `/api` и WebSocket. События рассылаются только владельцу данных.
56
+ - Токен брокера хранится в зашифрованном виде (AES-256-GCM) и никогда не уходит в браузер, события или чат ИИ.
57
+ - Проверка `Origin` для изменяющих запросов (CSRF); CSP, SRI и DOMPurify в терминале.
58
+ - Проверка TLS ко всем брокерам с корневым сертификатом НУЦ Минцифры (`config/certs`).
59
+ - Ограничение попыток входа; случайные коды привязки ботов; кнопки «Исполнить» в ботах работают только из привязанного чата.
60
+ - Вебхук MAX только с секретом; настройки моделей ИИ меняет только администратор.
61
+ - На компьютере клиента программа без пароля доступна только с этого же компьютера; адрес, ключ центра и режим вшиты в сборку.
62
+
63
+ ## 6. Исполнение заявок и журнал
64
+
65
+ - У каждой заявки постоянный идентификатор (`clientOrderId`, для Т-Банка — `orderId`). Он закрепляется **до** отправки, поэтому повтор пакета не создаёт дублей. Уже принятые брокером заявки при повторе пакета «partial» не отправляются; при таймауте повтор идёт с тем же идентификатором.
66
+ - Если счёт занят пересчётом заявок, «Исполнить» ждёт до 15 секунд и затем сообщает об этом, а не пропускает нажатие молча.
67
+ - **Журнал исполнения** (кнопка 📋 в карточке счёта): отправленные заявки, ошибки брокера, защитные стопы, срабатывание и снятие дневного стопа, аварийная остановка. Хранятся последние 5000 событий на счёт.
68
+ - **Дневной стоп** не снимается при сохранении настроек. Снять его можно только кнопкой «Снять стоп до конца дня» с подтверждением; до конца дня (МСК) он на этом счёте больше не срабатывает. Блокировку за автоследование снять нельзя.
69
+ - **«⛔ Остановить всё»** в разделе «Брокерские счета»: авто-торговля на всех счетах встаёт на паузу, все активные заявки у брокера снимаются. Позиции не закрываются, защитные стопы работают.
70
+
71
+ ## 7. Известные ограничения
72
+
73
+ - Стоп-заявки на стороне брокера — бета, только Т-Банк, по умолчанию выключены (см. раздел 11). Без них защитные стопы работают, только пока работает машина.
74
+ - Полная поддержка — только Т-Банк. Финам, БКС, Алор, Цифра и ITI — бета: только «Подтверждение заявок» (нет средней цены для защитных стопов, отмены заявок и истории). СберИнвестиции, ВТБ и Альфа не подключаются: у них нет открытого API для заявок.
75
+
76
+ ## 8. Хранение счетов
77
+
78
+ С версии 0.4.1 каждый брокерский счёт — отдельная запись в таблице `broker_accounts`. При сохранении записываются только поля, изменённые этим участком кода, поверх актуальной записи в транзакции. Поэтому синхронизация, пересчёт заявок и сохранение настроек не затирают друг друга, в том числе сработавший дневной стоп и режим «Только чтение». При первом запуске счета переносятся из прежнего хранения автоматически. Прежняя запись остаётся на случай отката версии.
79
+
80
+ Стратег (ИИ) не принимает токены брокера через чат: токен вставляется только в форму счёта.
81
+
82
+ ## 9. Кнопки «Исполнить / Отклонить» в Telegram и MAX для машин клиентов
83
+
84
+ 1. Машина формирует пакет заявок и отправляет уведомление с кнопками в центр.
85
+ 2. Центр пишет клиенту в его привязанный чат. В данных кнопки — только одноразовый короткий ID, действует 2 часа.
86
+ 3. При нажатии центр проверяет, что кнопку нажали в чате, привязанном к владельцу машины, и ставит команду в очередь машины. Обе кнопки этого уведомления после нажатия гаснут.
87
+ 4. Команда доходит до машины **внутри подписанной лицензии**, поэтому подделать её на пути нельзя. 15 минут после уведомления машина связывается с центром каждые 5 секунд, поэтому нажатие срабатывает почти сразу.
88
+ 5. Машина выполняет команду, только если тот же пакет всё ещё ждёт подтверждения. Если пакет уже исполнен, отклонён или заменён новым, команда отклоняется, и клиент получает об этом сообщение.
89
+ 6. Машина подтверждает выполнение (`ack`). Центр больше не отдаёт эту команду, а машина помнит выполненные ID и не исполнит пакет дважды.
90
+
91
+ ## 10. Контроль связи машин
92
+
93
+ Раз в 2 минуты центр проверяет облачные машины. Если машина не выходила на связь дольше `INSTANCE_OFFLINE_ALERT_MIN` (по умолчанию 10 минут), всем администраторам в Telegram/MAX приходит одно сообщение. В нём указаны машина, клиент, число счетов в «Автомате» и сколько ещё будет действовать лицензия без связи. Когда машина возвращается, приходит сообщение «снова на связи». Компьютеры клиентов по умолчанию не отслеживаются, так как их выключают на ночь; включить отслеживание можно переменной `INSTANCE_ALERT_DESKTOP=1`.
94
+
95
+ ## 11. Защитные стоп-заявки у брокера (бета, Т-Банк)
96
+
97
+ Включаются на счёте: «⚙» → «Защитные стоп-заявки у брокера». Как работает:
98
+
99
+ - на каждую бумагу с позицией стратегии у Т-Банка выставляется стоп-лосс с исполнением по рынку и сроком «до отмены»: −2,6% от средней цены для покупок, +2,2% для шортов, ровно на число лотов позиции. Стоп сработает, даже если машина выключена;
100
+ - раз в 25 секунд стоп-заявки сверяются с позициями: при изменении объёма заявка переставляется, без позиции — снимается;
101
+ - **перед любой закрывающей заявкой стратегии стоп-заявка по этой бумаге снимается.** Если снять не удалось, закрывающая заявка не отправляется, чтобы не продать дважды и не открыть шорт на маржинальном счёте;
102
+ - внутренний защитный стоп по бумаге со стоп-заявкой у брокера не дублируется;
103
+ - после срабатывания стоп-заявки новая выставляется не раньше чем через 2 минуты и после обновления портфеля;
104
+ - при выключении переключателя, режиме «Только чтение» или «⛔ Стоп» из центра программа снимает свои стоп-заявки. Чужие стоп-заявки (выставленные вручную в приложении брокера) не трогаются никогда;
105
+ - все действия видны в журнале исполнения (📋).
106
+
107
+ **Первое включение:** проверьте на одной небольшой позиции. В приложении Т-Банка → «Заявки» должна появиться стоп-заявка, а после закрытия позиции стратегией она должна исчезнуть. При ручных сделках по той же бумаге в приложении брокера сверка поправит объём в течение 25 секунд, но в эти секунды стоп-заявка может не соответствовать позиции.
108
+ - Обновление образа всех облачных машин из админки и автообновление программы на компьютере запланированы.
109
+ - Тома облачных машин нужно резервировать средствами кластера: в них база клиента и ключ шифрования его токенов.
@@ -0,0 +1,91 @@
1
+ # Справочник переменных окружения
2
+
3
+ Обозначения ролей: **C** — центр (`central`), **I** — облачная машина клиента (`instance`, `cloud`), **D** — `dev`, локальный запуск разработчика. В защищённой сборке для компьютера клиента режим, адрес центра и ключ центра вшиты, поэтому соответствующие переменные игнорируются (отмечено ✕).
4
+
5
+ ## 1. Режим и адреса
6
+
7
+ | Переменная | Роли | По умолчанию | Назначение |
8
+ |---|---|---|---|
9
+ | `NEUROALPHA_ROLE` | все | `dev` (или `central` при `CLOUD_MODE=1`) | `central`, `instance` или `dev`. В сборке для компьютера ✕ |
10
+ | `INSTANCE_KIND` | I | `cloud` | `cloud` или `desktop`. В сборке ✕ (всегда `desktop`) |
11
+ | `CLOUD_MODE` | C | — | устаревший синоним `NEUROALPHA_ROLE=central` |
12
+ | `CENTRAL_URL` | I | `https://neuroalpha.odant.org` | адрес центра для лицензий, входа и уведомлений. В сборке ✕ |
13
+ | `CLOUD_URL` | I, D | `https://neuroalpha.odant.org` | адрес ИИ-прокси центра (провайдер «НейроАльфа Облако»). В сборке ✕ |
14
+ | `CENTRAL_PUBLIC_URL` | C | адрес из запроса | публичный адрес центра, передаётся машинам при развёртывании |
15
+ | `PORT` | все | `3000` | порт HTTP |
16
+ | `HOST` | все | все интерфейсы; компьютер — `127.0.0.1` | адрес прослушивания |
17
+ | `NEUROALPHA_DATA_DIR` | все | `./data`; в образах `/data`; компьютер — профиль пользователя | каталог базы и ключей |
18
+ | `TRUST_PROXY` | D | — | `1`: брать IP клиента из `X-Forwarded-For` (в C и I включено всегда) |
19
+ | `LOCAL_TRUST_LAN` | D | — | `1`: пускать без пароля и из локальной сети (небезопасно) |
20
+ | `OPEN_BROWSER` | D | — | `1`: открыть браузер после запуска |
21
+
22
+ ## 2. Ключи и лицензии
23
+
24
+ | Переменная | Роли | По умолчанию | Назначение |
25
+ |---|---|---|---|
26
+ | `MASTER_KEY` | все | файл `<data>/.master.key` | шифрование секретов в базе (токены брокеров, ботов, ключи ИИ) |
27
+ | `LICENSE_SIGNING_KEY` | C | файл `<data>/.license-ed25519.pem` | закрытый ключ подписи лицензий (PEM, допускается `\n` в одной строке) |
28
+ | `LICENSE_SIGNING_KEY_FILE` | C | — | путь к файлу закрытого ключа вместо переменной |
29
+ | `LICENSE_TTL_HOURS` | C | `72` | срок действия лицензии без связи с центром (не меньше 1 ч) |
30
+ | `LICENSE_PUBLIC_KEY` | I | — | открытый ключ центра (PEM). В сборке ✕ |
31
+ | `LICENSE_PUBLIC_KEY_FILE` | I | — | путь к файлу открытого ключа |
32
+ | `INSTANCE_ID` | I | — | ID машины (`naXXXXXX`). На компьютере берётся из файла активации |
33
+ | `INSTANCE_KEY` | I | — | ключ машины `na_inst_…`. На компьютере — из файла активации |
34
+ | `INSTANCE_URL` | I | `https://<INSTANCE_ID>.<INSTANCE_DOMAIN>` | адрес терминала в уведомлениях |
35
+ | `BROKER_CA_FILE` | все | `config/certs/russian_trusted_root_ca.pem` | корневой сертификат НУЦ Минцифры для TLS к брокерам |
36
+
37
+ ## 3. Машины клиентов и развёртывание (центр)
38
+
39
+ | Переменная | По умолчанию | Назначение |
40
+ |---|---|---|
41
+ | `PAAS_DRIVER` | `none` | `none` (вручную), `k8s`, `webhook` |
42
+ | `PAAS_WEBHOOK_URL` | — | адрес PaaS для `webhook` |
43
+ | `PAAS_WEBHOOK_SECRET` | — | ключ подписи HMAC-SHA256 (`X-NA-Signature`) |
44
+ | `K8S_API_URL`, `K8S_TOKEN`, `K8S_CA_FILE` | сервисный аккаунт пода | доступ к API Kubernetes для `k8s` |
45
+ | `INSTANCE_IMAGE` | `registry.odant.org/neuroalpha:latest` | образ облачной машины |
46
+ | `INSTANCE_DOMAIN` | `odant.org` | домен поддоменов машин |
47
+ | `INSTANCE_SCHEME` | `https` | схема адресов машин |
48
+ | `INSTANCE_INGRESS_CLASS` | `nginx` | класс ingress |
49
+ | `INSTANCE_INGRESS_NAMESPACE` | `ingress-nginx` | namespace ingress-контроллера для NetworkPolicy |
50
+ | `INSTANCE_TLS_SECRET` | `odant-wildcard-tls` | Secret с wildcard-сертификатом в namespace машины |
51
+ | `INSTANCE_STORAGE` | `5Gi` | размер тома машины |
52
+ | `INSTANCE_STORAGE_CLASS` | класс по умолчанию | StorageClass тома |
53
+ | `INSTANCE_OFFLINE_ALERT_MIN` | `10` | через сколько минут без связи оповещать администраторов (не меньше 3) |
54
+ | `INSTANCE_ALERT_DESKTOP` | — | `1`: оповещать и о компьютерах клиентов |
55
+ | `MAX_DESKTOP_INSTALLS` | `1` | сколько компьютеров можно активировать на одного клиента |
56
+ | `DESKTOP_DOWNLOAD_WINDOWS`, `DESKTOP_DOWNLOAD_LINUX` | — | ссылки на скачивание в кабинете VIP |
57
+ | `DESKTOP_DOCKER_IMAGE` | — | образ для команды `docker run` в кабинете |
58
+ | `DESKTOP_LATEST_VERSION`, `DESKTOP_DOWNLOAD_URL` | — | сведения о новой версии для установок на компьютер |
59
+
60
+ ## 4. Модель ИИ
61
+
62
+ | Переменная | По умолчанию | Назначение |
63
+ |---|---|---|
64
+ | `LLM_PROVIDER` | `openai` | `openai`, `anthropic`, `none` |
65
+ | `LLM_BASE_URL`, `LLM_MODEL` | — | адрес и модель апстрима |
66
+ | `LLM_API_KEY` / `LLM_API_KEY_FILE` | — | ключ доступа или путь к файлу с ключом |
67
+ | `LLM_REASONING_EFFORT` | `low` | уровень рассуждений |
68
+ | `LLM_MAX_TOKENS` | `4000` | ограничение ответа |
69
+ | `LLM_TIMEOUT_MS` | `90000` | тайм-аут запроса |
70
+ | `GIGACHAT_KEY`, `GIGACHAT_CA_FILE` | — | ключ GigaChat и сертификат НУЦ |
71
+
72
+ ## 5. Боты, оплата, прочее (центр)
73
+
74
+ | Переменная | Назначение |
75
+ |---|---|
76
+ | `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_NAME` | бот Telegram (можно задать в админке) |
77
+ | `MAX_BOT_TOKEN`, `MAX_BOT_NAME` | бот MAX (можно задать в админке) |
78
+ | `MAX_WEBHOOK_SECRET` | включает вебхук MAX с проверкой секрета (без него вебхук выключен) |
79
+ | `YOOKASSA_SHOP_ID`, `YOOKASSA_SECRET_KEY` | ЮKassa (можно задать в админке) |
80
+ | `YOOKASSA_RECEIPT` | `1`: отправлять чек 54-ФЗ |
81
+ | `YOOKASSA_VAT_CODE` | код НДС в чеке, по умолчанию `1` |
82
+ | `NEUROALPHA_API_KEY` | D: активировать локальную лицензию по ключу подписки при запуске |
83
+ | `LOCAL_STRICT_LICENSE` | D: без лицензии — тариф «Без подписки» вместо «Хозяина» |
84
+ | `DIVIDENDS_MODE` | служебная: `raw` — без дивидендной коррекции котировок (для сравнений) |
85
+
86
+ ## 6. Сборка
87
+
88
+ | Переменная | Назначение |
89
+ |---|---|
90
+ | `PDF_BROWSER` | путь к Edge/Chrome для `node scripts/build-docs.mjs` |
91
+ | `ADMIN_PASS` | пароль для `scripts/set-admin-password.mjs` |
@@ -0,0 +1,253 @@
1
+ # Стресс-тест стратегии: результаты от 22.10.2026
2
+
3
+ Стресс-тест проверяет ту же стратегию с теми же правилами решений, но с реалистичным исполнением. Прогон выполнен на копии рабочей базы с зафиксированными настройками:
4
+
5
+ - окно обучения 90 дней, история 1000 дней;
6
+ - торговля в обе стороны («Лонг и шорт»), автоадаптация включена;
7
+ - издержки в отчёте: комиссия 0,04% + проскальзывание 0,01% на сторону;
8
+ - плата за заём при шорте 20% годовых.
9
+
10
+ Период проверки: 25.01.2024 — 22.10.2026, около 2,75 года, 5355 сделок. Рынок (индекс МосБиржи) за период: **−23,1%**; за 12 месяцев −7,8%; за 6 месяцев −12,3%.
11
+
12
+ ## 1. Результаты
13
+
14
+ | Сценарий | Итог | Годовых | PF | Просадка | Ср. сделка | 12 мес. | 6 мес. |
15
+ |---|---|---|---|---|---|---|---|
16
+ | **Как в основном отчёте** | +9064% | **+419,8%** | 1,32 | −20,9% | +0,162% | +400,6% | +217,3% |
17
+ | Издержки 0,15% на сторону | −72,8% | −37,9% | 0,93 | −85,2% | −0,034% | −44,9% | −19,0% |
18
+ | Вход внутри следующей свечи | +2279% | +217,8% | 1,20 | −28,1% | +0,118% | +147,8% | +64,9% |
19
+ | Вход на свечу позже (`open[t+2]`) | +585% | +101,8% | 1,10 | −43,2% | +0,075% | +39,4% | −5,6% |
20
+ | Только свечи со сделками | +9232% | +423,2% | 1,32 | −20,9% | +0,163% | +395,2% | +214,0% |
21
+ | ≤ 5% оборота свечи и целые лоты | +352% | +73,3% | 1,16 | −29,4% | +0,152% | +39,0% | +13,0% |
22
+ | Шорт только разрешённых бумаг | +3834% | +281,8% | 1,27 | −19,0% | +0,136% | +265,2% | +144,3% |
23
+ | **Как живой контур**, издержки 0,05% | +183% | **+46,2%** | 1,12 | −30,1% | +0,091% | +21,0% | −3,2% |
24
+ | **Как живой контур**, издержки 0,10% | −44,6% | **−19,4%** | 0,95 | −61,0% | −0,003% | −28,0% | −27,8% |
25
+ | Всё строго (`t+2`), издержки 0,05% | +14,1% | +4,9% | 1,01 | −40,2% | +0,048% | −18,8% | −25,8% |
26
+ | Всё строго, издержки 0,15% | −97,4% | −73,7% | 0,74 | −97,6% | −0,138% | −81,3% | −72,3% |
27
+ | Всё строго, издержки 0,25% | −99,9% | −92,3% | 0,52 | −99,9% | −0,328% | −90,3% | −72,0% |
28
+
29
+ Сценарий «как живой контур»: вход и выход по средней цене следующей свечи (живые цены брокера), только свечи со сделками, позиция не больше 5% оборота свечи, целые лоты, шорт только разрешённых Т-Банком бумаг.
30
+
31
+ **Порог безубыточности** — издержки на сторону, при которых итог за период равен нулю:
32
+
33
+ | Исполнение | Порог |
34
+ |---|---|
35
+ | как в основном отчёте | 0,128% |
36
+ | как живой контур | **0,085%** |
37
+ | строгое (`t+2`) | 0,054% |
38
+
39
+ ## 2. Выводы
40
+
41
+ 1. **Цифра «≈420% годовых» воспроизводится, но только при допущениях основного отчёта:** сделка по цене открытия следующей свечи, издержки 0,05% на сторону, неограниченная ликвидность и сложный процент без ограничения объёма.
42
+ 2. **Основная часть доходности приходится на первые минуты после сигнала.** Если сместить вход внутрь следующей свечи, остаётся +218% годовых; если на свечу позже, +102%. Сигнал быстро «протухает», поэтому критична скорость исполнения.
43
+ 3. **Ёмкость стратегии ограничена.** Ограничение «не больше 5% оборота свечи» снижает результат до +73% годовых: сложный процент упирается в ликвидность бумаг второго и третьего эшелона.
44
+ 4. **Признак «свеча без сделок» на результат не влияет.** Подозрение ревью о входе по несуществующей цене не подтвердилось.
45
+ 5. **Решающий фактор — реальные издержки.** При исполнении, близком к живому контуру, стратегия в плюсе только до 0,085% на сторону:
46
+ - при 0,05% — +46% годовых при падении рынка на 23%;
47
+ - при 0,10% — убыток.
48
+
49
+ Средняя сделка зарабатывает около 0,09% до издержек, поэтому каждая сотая процента комиссии и проскальзывания критична.
50
+ 6. **Последние 6 месяцев даже в сценарии «как живой контур» около нуля (−3%)**, а за 12 месяцев +21%. Это не отложенная выборка: константы стратегии подбирались на этой истории.
51
+
52
+ ## 3. Что это значит для продукта
53
+
54
+ - **Обещания «>400% годовых» и потолки доходности тарифов (60/120/240%) не подтверждаются** реалистичной проверкой. Их нужно убрать с лендинга, из тарифов и из ответов Стратега. Это требование и закона «О рекламе» (ст. 28).
55
+ - Реалистичный ориентир при издержках не выше 0,05% на сторону и исполнении внутри следующей свечи — **десятки процентов годовых с просадками до 30%**, без гарантий.
56
+ - Нужен тариф брокера с минимальной комиссией («Трейдер» / «Премиум» у Т-Банка: 0,04–0,05%) и лимитные заявки на 1 шаг цены (уже реализовано), иначе стратегия убыточна.
57
+
58
+ ## 4. Рекомендации
59
+
60
+ 1. **Измерить реальные издержки на живом счёте:** комиссию и проскальзывание по фактическим сделкам относительно цены сигнала. Это главный неизвестный параметр. При реальных издержках выше 0,085% на сторону торговлю нужно остановить.
61
+ 2. **Сократить частоту сделок:** поднять порог входа так, чтобы ожидаемое движение покрывало реальные издержки с запасом. Стресс-тест встроен в каждое исследование, эффект будет виден сразу.
62
+ 3. **Ограничить сделки бумагами первого эшелона** или жёстко лимитировать объём от оборота: ёмкость стратегии во втором и третьем эшелоне мала.
63
+ 4. **Зафиксировать версию стратегии и вести честный учёт** живых результатов с даты фиксации. Только это даёт настоящую отложенную выборку.
64
+ 5. **Исправить ошибку выжившего:** загрузить исторический состав индекса широкого рынка на каждую дату (ISS МосБиржи публикует его) и отбирать бумаги только по данным до даты прогноза.
65
+
66
+ ## 5. Реальные издержки на счёте Т-Банка (фактические сделки)
67
+
68
+ До 28.01.2026 счёт работал как **счёт автоследования Т-Банка**: в истории есть удержания «за управление» и «за результат», то есть сделки того периода совершал не алгоритм НейроАльфы. После этой даты по счёту прошло 111 сделок, закрыто 50 позиций, почти все в октябре 2026.
69
+
70
+ | Показатель | Факт |
71
+ |---|---|
72
+ | Комиссия брокера | **0,041%** на сторону |
73
+ | Цена исполнения против цены в момент сигнала (закрытие предыдущей свечи) | медиана **+0,030%**, средневзвешенно **+0,076%** не в пользу клиента |
74
+ | Цена исполнения против средней цены свечи сделки | +0,040% |
75
+ | Итог закрытых позиций до комиссий | −45 ₽ (PF 1,00) |
76
+ | После комиссий | −2 957 ₽ |
77
+
78
+ **Реальные издержки — около 0,07–0,12% на сторону.** При прежнем пороге порог безубыточности (0,085%) находится внутри этого диапазона, поэтому живой счёт и работал около нуля до комиссий. Тест и практика согласуются. Выборка мала (50 позиций), выводы предварительные.
79
+
80
+ ## 6. Порог входа под реальные издержки (принято)
81
+
82
+ Перебор множителя порога входа: исполнение как в живом контуре, издержки 0,09% на сторону.
83
+
84
+ | Множитель | Вход от | Годовых | PF | Просадка | Сделок | 12 мес. | 6 мес. |
85
+ |---|---|---|---|---|---|---|---|
86
+ | ×1 (было) | 0,25% | −5,8% | 0,98 | −47% | 5194 | −17,8% | −23,9% |
87
+ | ×1,5 | 0,375% | +5,5% | 1,03 | −42% | 1697 | −5,8% | −17,3% |
88
+ | **×2 (принято)** | **0,50%** | **+19,9%** | **1,24** | −29% | 662 | +18,6% | −6,3% |
89
+ | ×2,5 | 0,625% | +16,4% | 1,38 | −28% | 312 | +8,8% | −6,0% |
90
+ | ×3 | 0,75% | +9,7% | 1,46 | −23% | 156 | +11,0% | −5,7% |
91
+ | ×4 | 1,0% | +4,7% | 2,47 | −5,6% | 47 | +11,2% | +8,6% |
92
+
93
+ Множитель **×2** закреплён в коде (`server/model/edge.js`) и действует одинаково в бэктесте и в живом контуре. Порог выхода масштабируется вместе с ним. Соседние значения (×1,5–×3) тоже прибыльны, то есть выбор не держится на одной случайной точке. Но множитель подобран на той же истории.
94
+
95
+ Стратегия с порогом ×2 (полный стресс-тест):
96
+
97
+ | Сценарий | Годовых | PF | Просадка | 12 мес. | 6 мес. |
98
+ |---|---|---|---|---|---|
99
+ | Как в основном отчёте | +67,2% | 1,39 | −35,6% | +29,7% | +6,4% |
100
+ | **Как живой контур, издержки 0,05%** | **+30,0%** | 1,37 | −27,6% | +28,5% | −0,1% |
101
+ | **Как живой контур, издержки 0,10%** | **+17,5%** | 1,21 | −29,8% | +16,3% | −7,7% |
102
+ | Строгое исполнение (`t+2`), 0,05% | +12,7% | 1,15 | −28,4% | +11,2% | −15,3% |
103
+ | Строгое исполнение, 0,15% | −11,7% | 0,88 | −41,3% | −17,8% | −33,3% |
104
+
105
+ Порог безубыточности по издержкам на сторону вырос: при исполнении как в живом контуре **с 0,085% до 0,175%**, при строгом — с 0,054% до 0,101%. Запас над реальными издержками примерно двукратный.
106
+
107
+ ## 7. Изменения в продукте
108
+
109
+ - Удалены обещания доходности («>400% годовых», «до 60/120/240% годовых») с лендинга, из оферты, правил, тарифов, справки и инструкции Стратега. Стратегу запрещено обещать доходность.
110
+ - Удалён «дроссель» размера позиций по потолку доходности тарифа. Тарифы различаются только возможностями: счета, лимиты, шорты, установка на компьютер.
111
+ - Витрина на лендинге показывает реалистичную оценку стресс-теста (живое исполнение, издержки 0,10%) с предупреждением о рисках вместо модельной доходности. Убраны ярлык «Live» у статичного примера и подстановка «−22,3%» вместо отсутствующих данных.
112
+
113
+ ## 8. Меры для реального повышения доходности (внедрено 23.10.2026)
114
+
115
+ Проверка по фактическим сделкам счёта Т-Банка и симуляции показала три главных резерва:
116
+
117
+ 1. **Разница издержек между эшелонами.** По бумаге 1-го эшелона (`SBER`, `T`, `GAZP`, `LKOH`, `TRMK`) фактическое отклонение цены исполнения от цены сигнала составило **0,004–0,025%**, а по 2–3-му эшелону (`NKHP`, `CBOM`, `SMLT`, `PHOR`) — **0,12–0,34%**. Введена двухступенчатая шкала порогов (`server/model/edge.js`): `ENTRY_EDGE_MULT = 1.65` (вход от `0,413%` для 1-го эшелона) и дополнительный множитель `TIER2_EDGE_MULT = 1.20` для 2–3-го эшелона (вход от `0,495%`).
118
+ 2. **Равный ансамбль «Внимание через карту + Своя инерция» (50% / 50%).** При высоком пороге чистая инерция давала более высокий PF на хвосте распределения, а внимание точнее учитывало переток между соседями. Объединение обоих прогнозов с равным весом `50/50` внутри модели «Внимание через карту» (`server/model/models.js`) повысило ранговую точность `IC` с `0,0996` до **`0,1010`**, а реалистичный итог «Внимания» вывел её на 1-е место среди всех моделей (`+110,1%`, PF `1,34` против `+69,8%`, PF `1,24` у чистой инерции).
119
+ 3. **Пассивные лимитные заявки по живому стакану и размещение свободного кэша в LQDT.**
120
+ - При высоком пороге капитал ~95% времени свободен от сделок. В настройки счёта Т-Банка добавлен переключатель **«Свободные рубли в фонд денежного рынка LQDT»** (`cashSweep`): когда сигналов нет, свободные рубли сверх 3%-го резерва размещаются в паи `LQDT`, а при появлении сигнала нужное число паёв автоматически продаётся перед покупкой акций.
121
+ - Перед отправкой заявки в Т-Банк запрашивается живой стакан `GetOrderBook (depth=1)`: если спред разъехался шире допустимого порога — вход отклоняется; иначе лимитная цена ставится внутри спреда (не хуже середины спреда), без переплаты за пересечение стакана.
122
+
123
+ Итоговые показатели ядра с равным ансамблем `50/50` (период `25.01.2024 — 23.10.2026`, индекс МосБиржи `−22,9%`):
124
+
125
+ | Сценарий | Итог за период | Годовых (CAGR) | PF | Просадка | 12 мес. | 6 мес. |
126
+ |---|---|---|---|---|---|---|
127
+ | Как в основном отчёте (0,05%) | +425,3% | +83,1% | 1,39 | −49,5% | +64,6% | −5,2% |
128
+ | **Как живой контур, издержки 0,05%** | **+176,6%** | **+44,9%** | **1,49** | −29,6% | +57,2% | −1,1% |
129
+ | **Как живой контур, издержки 0,10%** | **+111,1%** | **+31,3%** | **1,34** | −30,1% | +43,3% | −8,0% |
130
+ | **Живой контур + эшелоны + кэш в LQDT (16%/год)** | **+209,4%** | **+50,9%** | **1,32** | −29,7% | **+66,0%** | **+0,8%** |
131
+ | Строгое исполнение (`t+2`), 0,05% | +79,6% | +23,8% | 1,25 | −26,1% | +25,2% | −11,0% |
132
+ | Строгое исполнение (`t+2`), 0,15% | −9,3% | −3,5% | 0,97 | −40,2% | −9,3% | −30,7% |
133
+
134
+ Порог безубыточности по издержкам на сторону:
135
+ - в сценарии «как живой контур» — **0,229% на сторону** (было `0,085%` в `v0.4.2` и `0,175%` в `v0.5.0`);
136
+ - при строгом исполнении с задержкой `open[t+2]` — **0,138% на сторону** (было `0,054%` в `v0.4.2` и `0,101%` в `v0.5.0`).
137
+
138
+ ## 9. Раздельная проверка In-Sample (70%) и Out-of-Sample (30%)
139
+
140
+ Проверка разделена на две непересекающиеся части при живом исполнении и реальных издержках `0,09%` на сторону:
141
+ - **In-Sample (первые 70% истории, до 22.01.2026)** — участок обучения и отбора;
142
+ - **Out-of-Sample (последние 30% истории, с 22.01.2026 по 22.10.2026)** — контрольный участок 2026 года.
143
+
144
+ | Модель и порог | In-Sample (до 22.01.2026) | Контрольный участок OOS (с 22.01.2026) | Весь период |
145
+ |---|---|---|---|
146
+ | Чистое «Внимание» (`×1,65 / ×1,20`) | +52,4% (PF 1,28) | −32,3% (PF 0,65) | +3,1% (PF 1,01) |
147
+ | Чистая «Своя инерция» (`×1,65 / ×1,20`) | +66,6% (PF 1,43) | +9,9% (PF 1,11) | +83,0% (PF 1,27) |
148
+ | Адаптивный вес по окну обучения (`×1,65 / ×1,20`) | +76,0% (PF 1,47) | −3,2% (PF 0,96, DD 26,1%) | +70,4% (PF 1,23) |
149
+ | Адаптивный вес по окну обучения (`×2,00 / ×1,20`) | +92,2% (PF 2,02) | +7,7% (PF 1,17, DD 26,0%) | +107,0% (PF 1,61) |
150
+ | **Равный ансамбль 50/50 (`×1,65 / ×1,20`)** | **+90,9% (PF 1,59, DD 14,4%)** | **+16,7% (PF 1,18, DD 29,9%, 190 сд.)** | **+122,8% (PF 1,37)** |
151
+ | Равный ансамбль 50/50 (`×1,80 / ×1,20`) | +69,5% (PF 1,63, DD 17,5%) | +19,5% (PF 1,26, DD 32,3%, 134 сд.) | +102,5% (PF 1,43) |
152
+ | Равный ансамбль 50/50 (`×2,00 / ×1,20`) | +99,5% (PF 2,43, DD 10,2%) | +8,4% (PF 1,16, DD 30,7%, 92 сд.) | +116,3% (PF 1,67) |
153
+
154
+ **Важная оговорка по методологии:** переход от чистого «Внимания» к ансамблю с инерцией и сама двухступенчатая шкала порогов делались уже после просмотра результатов 2026 года. Вариант с полностью слепым подбором веса внутри каждого 90-дневного окна обучения дал на участке 2026 года от `−3,2%` (при `×1,65`) до `+7,7%` (при `×2,0`). Поэтому цифры `+16,7%` на 2026 годе и `+31% … +51%` годовых в таблице выше являются верхней (оптимистичной) оценкой, а единственной строгой проверкой без заглядывания в будущее служит живой счёт начиная с фиксации `v0.5.3`.
155
+
156
+ ## 10. Дивиденды, займ для шортов, защита фьючерсом и налоги (v0.5.4)
157
+
158
+ **Ошибка в данных о дивидендах (исправлена).** У привилегированных акций (`SBERP`, `SNGSP`, `TATNP`, `BANEP`, `LSNGP`, `NKNCP`, `RTKMP`) список дивидендов был пустым: источник публикует их на странице обыкновенной акции. Из‑за этого дивидендный гэп префов (у `SBERP` — 13,4%, у `SNGSP` в 2025 году — 16,3%) в бэктесте выглядел как обычное движение цены. После исправления учтено 237 дивидендов вместо 208. Итог «как живой контур, 0,10%» почти не изменился (+31,3% → +31,1% годовых), но обучение и проверка теперь идут на корректных ценах.
159
+
160
+ **Дивидендный предохранитель (`server/model/dividends.js`, одинаково в бэктесте и в живой торговле):**
161
+ - за 1–2 торговых дня до экс-даты — не открываем шорт;
162
+ - в последний день с правом на дивиденд — закрываем шорты стратегии (дивиденд ≥ 0,3%) и покупки (дивиденд ≥ 1%), новые покупки не открываем.
163
+
164
+ На истории эффект небольшой (+0,2 п. п. годовых): сделки короткие и почти никогда не попадали на отсечку. Предохранитель нужен прежде всего на живом счёте: шорт через отсечку `SBERP` стоил бы списания компенсации 13,4% от позиции.
165
+
166
+ **Займ бумаг для шортов.** Плата за займ (20% годовых от позиции на время удержания) обошлась в **0,65–0,8% стартового капитала в год**. Перевод шортов на фьючерсы на акции этого не окупит — не делаем.
167
+
168
+ **Защита от движения рынка фьючерсом на индекс.** Проверка по реальным ценам вечного фьючерса `IMOEXF`:
169
+
170
+ | Сценарий (живой контур, 0,10%) | Годовых | PF | Просадка | 12 мес. | 6 мес. |
171
+ |---|---|---|---|---|---|
172
+ | Без защиты | +31,1% | 1,33 | −29,9% | +41,1% | −9,4% |
173
+ | Защита по значению индекса | +48,1% | 1,30 | −27,5% | +57,0% | +2,1% |
174
+ | **Защита по реальным ценам IMOEXF** | +35,9% | 1,28 | **−34,4%** | +33,0% | −13,8% |
175
+
176
+ Почти половина «выгоды» по значению индекса была искусственной: 10-минутный расчёт индекса запаздывает относительно фьючерса. На реальных ценах фьючерса защита не снижает просадку, а увеличивает её, и ухудшает результат за 12 и 6 месяцев. Средняя чистая бета портфеля и так около нуля (−0,9% капитала), поэтому защищать почти нечего. **Защита фьючерсом в живую торговлю не внедряется**, сценарий оставлен в стресс-тесте для прозрачности.
177
+
178
+ **Налоги.** В таблицу стресс-теста добавлена колонка «после НДФЛ» — грубая оценка с налогом 13% с годовой прибыли. Для «живого контура, 0,10%» это **+27,1% годовых** вместо +31,1%.
179
+
180
+ ## 11. Больше сделок того же качества и фильтр согласия моделей (v0.5.5)
181
+
182
+ По протоколу «сначала диагностика, затем проверка трёх заранее зафиксированных гипотез на In-Sample 70% и Out-of-Sample 30%» проверены три изменения без подбора числовых параметров:
183
+
184
+ 1. **Убрать относительный отсев кандидатов (`filterDynamicTopK`).** Раньше 2–4-й кандидаты отбрасывались, если их сигнал был меньше `62% / 78%` от 1-го лидера — даже когда они сами уверенно превышали абсолютный порог выгоды, а 95% капитала лежало без дела.
185
+ 2. **Согласие моделей (`Внимание` и `Инерция` одного знака).** В редких случаях (`~1,2%` сделок), когда чистое внимание к соседям и собственная инерция смотрят в противоположные стороны, сигнал ослабляется вдвое (`×0,5`), чтобы не открывать сделку на конфликте моделей. Диагностика показала, что такие спорные сделки были убыточны (`PF 0,05`, средняя сделка `−0,289%`).
186
+ 3. **Удержание до полного горизонта `H = 2,5 ч` по мягкому порогу** (проверено и **отклонено**: улучшает `In-Sample`, но ухудшает контрольный участок `OOS` с `+14,2%` до `+12,0%`).
187
+
188
+ Результаты на живом исполнении с реальными издержками по эшелонам (`0,065% / 0,125%`):
189
+
190
+ | Вариант | In-Sample (до 22.01.2026) | Контрольный участок OOS (с 22.01.2026) | Полный период |
191
+ |---|---|---|---|
192
+ | База (`v0.5.4`) | `+82,6%` (PF `1,54`, 339 сд., ср. `+0,399%`) | `+14,2%` (PF `1,15`, 188 сд., ср. `−0,012%`) | `+108,6%` (PF `1,33`, 527 сд., ср. `+0,253%`) |
193
+ | 1. Без относительного отсева (`noRelDrop`) | `+87,6%` (PF `1,57`, 371 сд.) | `+15,2%` (PF `1,17`, 190 сд.) | `+116,1%` (PF `1,36`, 561 сд.) |
194
+ | 2. Согласие обеих моделей | `+83,7%` (PF `1,54`, 324 сд., ср. `+0,428%`) | `+14,3%` (PF `1,15`, 186 сд.) | `+110,0%` (PF `1,34`, 510 сд., ср. `+0,270%`) |
195
+ | 3. Мягкое удержание до `H = 2,5 ч` *(отклонено)* | `+114,5%` (PF `1,66`) | `+12,0%` *(хуже базы)* | `+140,2%` |
196
+ | **Принято в `v0.5.5`: 1 + 2 (без отсева + согласие моделей)** | **`+88,1%` (PF `1,58`, 356 сд.)** | **`+15,3%` (PF `1,17`, 188 сд., ср. `+0,005%`)** | **`+118,1%` (PF `1,36`, 559 сд., ср. `+0,261%`)** |
197
+
198
+ Итог полного стресс-теста `v0.5.5`:
199
+ - **Сравнение моделей при реалистичном исполнении:** «Внимание через карту» — `IC = 0,1009`, **`+118,1%`** (`PF 1,36`) против `+101,0%` (`PF 1,34`) у чистой инерции;
200
+ - **Как живой контур, издержки `0,05%`:** **`+183,8%`** (`+46,3%` годовых, `PF 1,51`, просадка `29,4%`, 559 сделок);
201
+ - **Как живой контур, издержки `0,10%`:** **`+119,0%`** (`+33,1%` годовых / `+28,8%` после НДФЛ, `PF 1,36`, просадка `30,2%`);
202
+ - **Живой контур + эшелоны + кэш в LQDT (`16%`/год):** **`+218,5%`** (`+52,5%` годовых / `+45,7%` после НДФЛ, `PF 1,33`, 12м **`+64,2%`**);
203
+ - **Порог безубыточности по издержкам на сторону:** вырос с `0,229%` до **`0,237%`** (живой контур) и с `0,138%` до **`0,142%`** (`open[t+2]`).
204
+
205
+ ## 12. Вечерняя сессия и макро-признаки (v0.5.6 → v0.5.7)
206
+
207
+ Две заранее заданные проверки по тому же протоколу: первые 70% истории и контрольные последние 30%, живое исполнение, издержки по эшелонам.
208
+
209
+ | Вариант | Первые 70% | Последние 30% (контроль) | Весь период |
210
+ |---|---|---|---|
211
+ | **Без вечернего правила, без макро (принято)** | **+89,4%** (PF 1,59) | **+15,2%** (PF 1,17) | **+118,1%**, просадка 30,3% |
212
+ | Вечером порог ×1,25 | +86,8% (PF 1,67) | +13,9% (PF 1,17) | +112,7% |
213
+ | Вечером новые входы запрещены | +77,4% (PF 1,72) | +13,2% (PF 1,17) | +100,8% |
214
+ | Макро-признаки (нефть, юань, доллар, золото, облигации) | +101,6% (PF 1,68) | +5,6% (PF 1,06) | +113,0%, просадка 37,9% |
215
+
216
+ - **Макро-признаки не включаются:** лучше на первой части истории, втрое хуже на контрольной, просадка растёт до 38% — признак подгонки.
217
+ - **Вечернее ужесточение порога убрано из живой торговли.** До этой версии на счёте после 19:00 МСК порог входа повышался в 1,25 раза, а в бэктесте такого правила не было. Проверка показала, что оба вечерних фильтра уменьшают итог на обеих частях истории, хотя отдельные вечерние сделки в среднем убыточны. Теперь правила счёта и бэктеста совпадают.
218
+ - **Опережение фьючерса на индекс как признак не проверялось:** модель прогнозирует движение акции относительно рынка, а фьючерс опережает в основном сам рынок — эту часть прогноз и так вычитает.
219
+
220
+ ## 13. Исправлена ошибка выжившего (v0.5.8)
221
+
222
+ **Что было не так.** Набор бумаг брался из сегодняшнего состава индекса МосБиржи широкого рынка, а бумаги с торгами меньше 60% периода отбрасывались. На истории модель торговала только теми компаниями, которые дожили до сегодняшнего дня и остались в индексе — то есть заранее «знала», кто выживет.
223
+
224
+ **Что сделано.**
225
+ - Состав индекса берётся с МосБиржи на середину каждого месяца за весь период и кэшируется (`server/model/vocab.js`).
226
+ - Набор бумаг — все бумаги, входившие в индекс хотя бы раз за период: 131 вместо ~99.
227
+ - Для каждой свечи отмечается, входила ли бумага в индекс на эту дату (`tk.inIndex`). Новые позиции открываются только по таким бумагам — и в бэктесте, и в живом прогнозе. Выбывшие бумаги остаются в матрице рынка как контекст, но в таблице прогноза не показываются.
228
+ - Отсев по покрытию торгов снижен с 60% до 15% периода: раньше он сам выбрасывал бумаги, вышедшие на биржу позже или ушедшие с неё.
229
+
230
+ **Результат (период 25.01.2024 — 23.10.2026):**
231
+
232
+ | Сценарий | До исправления | После исправления |
233
+ |---|---|---|
234
+ | Как живой контур, 0,05% | +183,8% (+46,3% годовых, PF 1,51, просадка 29,4%) | +154,8% (+40,6% годовых, PF 1,36, просадка 33,3%) |
235
+ | **Как живой контур, 0,10%** | +119,0% (+33,1% годовых, PF 1,36, просадка 30,2%) | **+93,6% (+27,2% годовых, после НДФЛ +23,7%, PF 1,24, просадка 36,6%)** |
236
+ | Живой контур + эшелоны + LQDT | +218,5% (+52,5% годовых) | +191,6% (+47,7% годовых, просадка 29,5%) |
237
+ | Последние 12 мес. (живой контур, 0,10%) | +42,2% | **+12,3%** |
238
+ | Последние 6 мес. (живой контур, 0,10%) | −8,6% | −12,5% |
239
+ | Строгое исполнение (`t+2`), 0,05% | +87,8% | +32,8% |
240
+ | Порог безубыточности, живой контур | 0,237% на сторону | **0,214%** |
241
+ | Порог безубыточности, `t+2` | 0,142% | **0,091%** |
242
+
243
+ Ошибка выжившего завышала реалистичную оценку примерно на 6 п. п. годовых, а результат последних 12 месяцев — более чем втрое. Особенно чувствительна к ней задержка исполнения: при входе на свечу позже запас над издержками сократился с 0,142% до 0,091% на сторону. Это ещё одна причина держать исполнение быстрым. Цифры этого раздела — текущая честная оценка стратегии.
244
+
245
+ ## 14. Ограничения проверки и правило контроля на живом счёте
246
+
247
+ - Состав рынка — исторический состав индекса широкого рынка на каждый месяц (с v0.5.8). Бумаги, полностью исчезнувшие с биржи без истории котировок на МосБирже, в проверку попасть не могут.
248
+ - Список бумаг для шорта — текущий список Т-Банка, а не на каждую дату.
249
+ - Средняя цена свечи — приближение. Реальная цена исполнения лимитной заявки зависит от стакана и очереди, а неисполненные заявки здесь не моделируются.
250
+ - В карточку брокерского счёта и журнал (`📋`) встроена **автоматическая сверка живого счёта со стресс-тестом** (`executionAudit`): фактическая комиссия, отклонение цены исполнения от цены сигнала по 1-му и 2–3-му эшелонам, поштучный учёт исполненных и снятых заявок (`fill rate`) и сравнение суммарных издержек на сторону с порогом безубыточности (`≤ 0,229%`).
251
+ - **Регламент контроля живой торговли:** если после 100 закрытых сделок на версии `v0.5.3+` фактический `PF` после комиссий остаётся ниже `1,10` либо фактические издержки на сторону превышают `0,15%`, авто-торговлю следует остановить или повысить порог входа до `×2,0`.
252
+
253
+ Стресс-тест теперь выполняется автоматически в каждом исследовании и показывается в карточке эксперимента («Стресс-тест исполнения»).
Binary file
@@ -0,0 +1,147 @@
1
+ # Copyright (c) 2026 Roman A. Perepelkin. All rights reserved. Proprietary software.
2
+ # Шаблон машины клиента НейроАльфа. Замените na235412 на ID машины из админки центра (вкладка «Машины клиентов»),
3
+ # а значения Secret — на переменные, показанные при создании машины (ключ показывается один раз).
4
+ #
5
+ # sed 's/na235412/naXXXXXX/g' instance.yaml | kubectl apply -f -
6
+ #
7
+ # Одна реплика и стратегия Recreate обязательны: база SQLite на томе не допускает двух процессов сразу.
8
+ apiVersion: v1
9
+ kind: Namespace
10
+ metadata:
11
+ name: na235412
12
+ labels:
13
+ app.kubernetes.io/part-of: neuroalpha
14
+ ---
15
+ apiVersion: v1
16
+ kind: Secret
17
+ metadata:
18
+ name: neuroalpha-instance
19
+ namespace: na235412
20
+ type: Opaque
21
+ stringData:
22
+ NEUROALPHA_ROLE: instance
23
+ INSTANCE_ID: na235412
24
+ INSTANCE_KEY: na_inst_REPLACE_ME
25
+ CENTRAL_URL: https://neuroalpha.odant.org
26
+ CLOUD_URL: https://neuroalpha.odant.org
27
+ # Открытый ключ центра одной строкой с \n — вставьте как есть из админки, в одинарных кавычках
28
+ LICENSE_PUBLIC_KEY: '-----BEGIN PUBLIC KEY-----\nREPLACE_ME\n-----END PUBLIC KEY-----'
29
+ # Необязательно: ключ шифрования токенов брокера. Если не задан — создаётся на томе /data/.master.key
30
+ # MASTER_KEY: "случайная строка 32+ символа"
31
+ ---
32
+ apiVersion: v1
33
+ kind: PersistentVolumeClaim
34
+ metadata:
35
+ name: neuroalpha-data
36
+ namespace: na235412
37
+ spec:
38
+ accessModes: [ReadWriteOnce]
39
+ resources:
40
+ requests:
41
+ storage: 5Gi
42
+ ---
43
+ apiVersion: apps/v1
44
+ kind: Deployment
45
+ metadata:
46
+ name: neuroalpha
47
+ namespace: na235412
48
+ spec:
49
+ replicas: 1
50
+ strategy:
51
+ type: Recreate
52
+ selector:
53
+ matchLabels: { app: neuroalpha }
54
+ template:
55
+ metadata:
56
+ labels: { app: neuroalpha }
57
+ spec:
58
+ securityContext:
59
+ runAsNonRoot: true
60
+ runAsUser: 1000
61
+ fsGroup: 1000
62
+ containers:
63
+ - name: neuroalpha
64
+ image: registry.odant.org/neuroalpha:0.4.0
65
+ imagePullPolicy: IfNotPresent
66
+ ports:
67
+ - containerPort: 3000
68
+ envFrom:
69
+ - secretRef:
70
+ name: neuroalpha-instance
71
+ volumeMounts:
72
+ - name: data
73
+ mountPath: /data
74
+ resources:
75
+ requests: { cpu: "500m", memory: "1Gi" }
76
+ limits: { cpu: "2", memory: "3Gi" }
77
+ readinessProbe:
78
+ httpGet: { path: /healthz, port: 3000 }
79
+ initialDelaySeconds: 10
80
+ periodSeconds: 15
81
+ livenessProbe:
82
+ httpGet: { path: /healthz, port: 3000 }
83
+ initialDelaySeconds: 60
84
+ periodSeconds: 30
85
+ failureThreshold: 4
86
+ securityContext:
87
+ allowPrivilegeEscalation: false
88
+ capabilities: { drop: [ALL] }
89
+ volumes:
90
+ - name: data
91
+ persistentVolumeClaim:
92
+ claimName: neuroalpha-data
93
+ ---
94
+ apiVersion: v1
95
+ kind: Service
96
+ metadata:
97
+ name: neuroalpha
98
+ namespace: na235412
99
+ spec:
100
+ selector: { app: neuroalpha }
101
+ ports:
102
+ - port: 80
103
+ targetPort: 3000
104
+ ---
105
+ apiVersion: networking.k8s.io/v1
106
+ kind: Ingress
107
+ metadata:
108
+ name: neuroalpha
109
+ namespace: na235412
110
+ annotations:
111
+ # WebSocket /ws держит соединение долго
112
+ nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
113
+ nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
114
+ spec:
115
+ ingressClassName: nginx
116
+ tls:
117
+ - hosts: [na235412.odant.org]
118
+ # wildcard-сертификат *.odant.org, скопированный в каждый namespace (или выпускаемый cert-manager)
119
+ secretName: odant-wildcard-tls
120
+ rules:
121
+ - host: na235412.odant.org
122
+ http:
123
+ paths:
124
+ - path: /
125
+ pathType: Prefix
126
+ backend:
127
+ service:
128
+ name: neuroalpha
129
+ port:
130
+ number: 80
131
+ ---
132
+ # Машине нужен только исходящий доступ (центр, Мосбиржа, брокеры, новости) и входящий — от ingress.
133
+ apiVersion: networking.k8s.io/v1
134
+ kind: NetworkPolicy
135
+ metadata:
136
+ name: neuroalpha-ingress-only
137
+ namespace: na235412
138
+ spec:
139
+ podSelector: { matchLabels: { app: neuroalpha } }
140
+ policyTypes: [Ingress]
141
+ ingress:
142
+ - from:
143
+ - namespaceSelector:
144
+ matchLabels:
145
+ kubernetes.io/metadata.name: ingress-nginx
146
+ ports:
147
+ - port: 3000
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuroalpha",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
5
5
  "author": "Roman A. Perepelkin",
6
6
  "license": "UNLICENSED",
@@ -21,7 +21,7 @@
21
21
  "files": [
22
22
  "LICENSE",
23
23
  "bin/",
24
- "deploy/install.sh",
24
+ "deploy/",
25
25
  "server/",
26
26
  "public/",
27
27
  "config/",
@@ -7,7 +7,7 @@
7
7
  import crypto from 'node:crypto';
8
8
  import { db } from '../db.js';
9
9
  import { getUserById, effectivePlan, ensureApiKey } from '../auth/index.js';
10
- import { notifyUser, registerInstanceActionHandler } from '../notify/index.js';
10
+ import { notifyUser, registerInstanceActionHandler, registerInstanceChatRelay } from '../notify/index.js';
11
11
  import { signToken, centralPublicKeyPem } from './signing.js';
12
12
  import { PAAS_DRIVER, provisioningEnabled, provisionApply, provisionDelete } from './provision.js';
13
13
 
@@ -419,6 +419,7 @@ export function queueInstanceChatCommand(userId, text, channel = 'tg') {
419
419
  const online = Boolean(inst.last_seen && Date.now() - inst.last_seen < 3 * 60000);
420
420
  return { instanceId: inst.id, online };
421
421
  }
422
+ registerInstanceChatRelay(queueInstanceChatCommand);
422
423
 
423
424
  // Команды для машины: отдаются в каждой лицензии, пока машина не подтвердит выполнение (ack)
424
425
  function pendingCommands(instId, acked = []) {
@@ -5,9 +5,8 @@ import { TRUSTED_CA } from '../tls-ca.js';
5
5
  import { IS_INSTANCE } from '../role.js';
6
6
  import { getIdentity } from '../instance/identity.js';
7
7
  import { relayNotifyToCentral } from '../instance/agent.js';
8
- import { db, kvGet, kvSet } from '../db.js';
9
- import { encryptSecret, decryptSecret, getUserById, runWithUser, generateApiKey, hashPassword, ensureApiKey, effectivePlan, countUserChatToday } from '../auth/index.js';
10
- import { queueInstanceChatCommand } from '../instance/central.js';
8
+ import { db, kvGet, kvSet, countUserChatToday } from '../db.js';
9
+ import { encryptSecret, decryptSecret, getUserById, runWithUser, generateApiKey, hashPassword, ensureApiKey, effectivePlan } from '../auth/index.js';
11
10
  import { round } from '../util.js';
12
11
 
13
12
  let actionHandlers = { execute: null, reject: null };
@@ -16,6 +15,9 @@ export function registerBrokerHandlers(h) { actionHandlers = h; }
16
15
  let chatHandlers = { ask: null, resolve: null };
17
16
  export function registerChatHandlers(h) { chatHandlers = h; }
18
17
 
18
+ let instanceChatRelay = null;
19
+ export function registerInstanceChatRelay(fn) { instanceChatRelay = fn; }
20
+
19
21
  // Надёжный HTTP-клиент для platform-api2.max.ru (поддерживает сертификат НУЦ Минцифры РФ)
20
22
  const maxCa = TRUSTED_CA;
21
23
 
@@ -270,8 +272,8 @@ async function handleMessengerChat(text, channel, chatId, sendFn) {
270
272
  const hasLocalAccounts = Boolean(
271
273
  db.prepare('SELECT 1 FROM broker_accounts WHERE user_id = ? LIMIT 1').get(u.id),
272
274
  );
273
- if (!hasLocalAccounts && u.role !== 'admin') {
274
- const relayed = queueInstanceChatCommand(u.id, text, channel);
275
+ if (!hasLocalAccounts && u.role !== 'admin' && instanceChatRelay) {
276
+ const relayed = instanceChatRelay(u.id, text, channel);
275
277
  if (relayed) {
276
278
  await sendFn(relayed.online
277
279
  ? '⏳ Запрос передан Стратегу на вашу машину — ответ придёт сюда через несколько секунд…'