neuroalpha 0.5.12 → 0.5.14
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.
- package/deploy/docs/01-central.md +96 -0
- package/deploy/docs/02-cloud-instances.md +130 -0
- package/deploy/docs/03-desktop-build.md +92 -0
- package/deploy/docs/04-client-guide.md +101 -0
- package/deploy/docs/05-operations.md +109 -0
- package/deploy/docs/06-env-reference.md +91 -0
- package/deploy/docs/07-stress-test.md +253 -0
- package/deploy/docs/pdf/01-central.pdf +0 -0
- package/deploy/docs/pdf/02-cloud-instances.pdf +0 -0
- package/deploy/docs/pdf/03-desktop-build.pdf +0 -0
- package/deploy/docs/pdf/04-client-guide.pdf +0 -0
- package/deploy/docs/pdf/05-operations.pdf +0 -0
- package/deploy/docs/pdf/06-env-reference.pdf +0 -0
- package/deploy/docs/pdf/07-stress-test.pdf +0 -0
- package/deploy/k8s/instance.yaml +147 -0
- package/package.json +2 -2
- package/public/app.js +2 -1
- package/server/index.js +6 -2
- package/server/instance/central.js +13 -0
- package/server/notify/index.js +124 -7
- package/server/strategist/index.js +114 -0
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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.
|
|
3
|
+
"version": "0.5.14",
|
|
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/
|
|
24
|
+
"deploy/",
|
|
25
25
|
"server/",
|
|
26
26
|
"public/",
|
|
27
27
|
"config/",
|
package/public/app.js
CHANGED
|
@@ -3308,9 +3308,10 @@ async function renderCloudPane() {
|
|
|
3308
3308
|
</div>
|
|
3309
3309
|
<div class="srow sblock">
|
|
3310
3310
|
<div class="stxt">
|
|
3311
|
-
<div class="slbl">Уведомления
|
|
3311
|
+
<div class="slbl">Уведомления и общение со Стратегом в Telegram и MAX</div>
|
|
3312
3312
|
<div class="hint">
|
|
3313
3313
|
Ваш код привязки: <b><code>${esc(code)}</code></b> — отправьте этот код боту в Telegram или в мессенджер MAX.<br>
|
|
3314
|
+
После привязки туда приходят сигналы и кнопки подтверждения заявок, а также вы можете писать боту любые вопросы ИИ-Стратегу (или команды <code>/forecast</code>, <code>/portfolio</code>, <code>/today</code>, <code>/news</code>).<br>
|
|
3314
3315
|
Статус: Telegram — ${u.tgConnected ? '<b class="pos">подключён ✓</b>' : '<span class="muted">не привязан</span>'} · MAX — ${u.maxConnected ? '<b class="pos">подключён ✓</b>' : '<span class="muted">не привязан</span>'}
|
|
3315
3316
|
</div>
|
|
3316
3317
|
</div>
|
package/server/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import { runningResearch, runningStability } from './model/research.js';
|
|
|
21
21
|
import { startNews } from './news/index.js';
|
|
22
22
|
import { startNotifyPolling } from './notify/index.js';
|
|
23
23
|
import { startBrokerScheduler, brokersState, executeBrokerOrders, rejectBrokerOrders } from './brokers/index.js';
|
|
24
|
-
import { chatState } from './strategist/index.js';
|
|
24
|
+
import { chatState, askFromMessenger } from './strategist/index.js';
|
|
25
25
|
import { hub } from './hub.js';
|
|
26
26
|
|
|
27
27
|
markStaleRuns();
|
|
@@ -61,8 +61,12 @@ wss.on('connection', (ws, req) => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// Команды из Telegram / MAX, пришедшие с центра: подтверждение пакета заявок или вопрос к Стратегу
|
|
65
65
|
setInstanceCommandHandler((cmd) => runWithUser(getUserById(1), async () => {
|
|
66
|
+
if (cmd.type === 'chat' && cmd.text) {
|
|
67
|
+
const res = await askFromMessenger(cmd.text, { channel: cmd.channel || 'tg' });
|
|
68
|
+
return `🤖 Стратег:\n\n${String(res?.text || 'Готово.').slice(0, 3400)}`;
|
|
69
|
+
}
|
|
66
70
|
if (cmd.type !== 'batch') return null;
|
|
67
71
|
const acc = brokersState().accounts.find((a) => a.id === cmd.accountId);
|
|
68
72
|
if (!acc) return 'Команда из мессенджера: счёт не найден.';
|
|
@@ -407,6 +407,19 @@ registerInstanceActionHandler(async (actionId, { channel, chatId }) => {
|
|
|
407
407
|
return `${a.action === 'exec' ? '✓ Команда «Исполнить»' : '✕ Команда «Отклонить»'} отправлена на машину${online ? ' — выполнится в течение нескольких секунд' : ' (сейчас она не на связи — команда выполнится при подключении, в течение 2 часов)'}.`;
|
|
408
408
|
});
|
|
409
409
|
|
|
410
|
+
// Доставка текстового вопроса из Telegram / MAX на выделенную машину клиента (если она есть и на связи)
|
|
411
|
+
export function queueInstanceChatCommand(userId, text, channel = 'tg') {
|
|
412
|
+
const list = instancesOfUser(userId).filter((i) => !i.revoked);
|
|
413
|
+
if (!list.length) return null;
|
|
414
|
+
// предпочитаем машину, которая недавно выходила на связь
|
|
415
|
+
const inst = list.find((i) => i.last_seen && Date.now() - i.last_seen < 3 * 60000) || list[0];
|
|
416
|
+
const cmdId = crypto.randomBytes(9).toString('base64url');
|
|
417
|
+
db.prepare('INSERT INTO instance_commands (id, instance_id, payload, created_at) VALUES (?, ?, ?, ?)')
|
|
418
|
+
.run(cmdId, inst.id, JSON.stringify({ id: cmdId, type: 'chat', text: String(text || '').slice(0, 1500), channel }), Date.now());
|
|
419
|
+
const online = Boolean(inst.last_seen && Date.now() - inst.last_seen < 3 * 60000);
|
|
420
|
+
return { instanceId: inst.id, online };
|
|
421
|
+
}
|
|
422
|
+
|
|
410
423
|
// Команды для машины: отдаются в каждой лицензии, пока машина не подтвердит выполнение (ack)
|
|
411
424
|
function pendingCommands(instId, acked = []) {
|
|
412
425
|
if (acked.length) {
|