neuroalpha 0.6.10 → 0.6.11
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/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +7 -5
- package/docs//320/234/320/225/320/245/320/220/320/235/320/230/320/232/320/220_/320/230_/320/237/320/220/320/240/320/220/320/234/320/225/320/242/320/240/320/253.md +24 -19
- package/package.json +1 -1
- package/public/app.js +14 -0
- package/public/index.html +4 -0
- package/server/brokers/index.js +21 -24
- package/server/model/edge.js +4 -3
- package/server/model/policy.js +46 -13
- package/server/model/research.js +2 -1
- package/server/model/simulator.js +9 -8
- package/server/strategist/index.js +23 -9
package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
Экран разделён на две части (границу между ними можно перетаскивать мышью, а кнопку чата в шапке — сворачивать/разворачивать):
|
|
25
25
|
|
|
26
|
-
- **Слева — ИИ-агент «Стратег»**: ваш персональный аналитик и помощник. Понимает текст и голосовой ввод (`🎙`)
|
|
26
|
+
- **Слева — ИИ-агент «Стратег»**: ваш персональный аналитик и помощник. Понимает текст и голосовой ввод (`🎙`), поддерживает быстрый старт чистой беседы кнопкой **`➕ Новая`** и просмотр сохранённых диалогов (`.md`) по кнопке **`🗂 Архив`**. Умеет выводить пошаговую инструкцию по подключению любого из 9 брокеров, находить ваши субсчета по токену и подключать их прямо из диалога, разбирать текущий живой прогноз, новости Мосбиржи и состояние ваших брокерских счетов.
|
|
27
27
|
- **Справа — единое рабочее окно с левым меню навигации (`#main-nav`)**, в котором сразу при входе открывается раздел **«Брокерские счета»**:
|
|
28
28
|
1. **Торговля:**
|
|
29
29
|
- **Брокерские счета** — управление всеми подключёнными счетами (боевыми и тестовыми).
|
|
@@ -65,13 +65,15 @@
|
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
-
## 4. Прогноз
|
|
68
|
+
## 4. Прогноз сейчас, эшелоны ликвидности и сопровождение сделок
|
|
69
69
|
|
|
70
70
|
В разделе **«Прогноз сейчас»** каждые **45 секунд** по живым котировкам Мосбиржи обновляются две таблицы: **«Сильнее рынка»** (кандидаты в покупку) и **«Слабее рынка»** (кандидаты в шорт).
|
|
71
71
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
72
|
+
- **Разделение на 1-й и 2-й эшелоны ликвидности:**
|
|
73
|
+
- **1-й эшелон (33 наиболее ликвидные акции Мосбиржи):** низкие потери в стакане (`0,065%` на сторону = `0,13%` за полный круг со всеми комиссиями), базовый порог входа `~+0,40%`, размер позиции масштабируется на полное плечо счёта (до `50%` бюджета в лонг и до `38%` в шорт).
|
|
74
|
+
- **2–3-й эшелон (остальные ~92 акции):** более широкий спред в стакане (`0,125%` на сторону = `0,25%` за полный круг), порог входа повышен в `1,20×` (`~+0,48%`), размер позиции масштабируется от плеча асимметрично (`max(0,50, 0,38 × плечо)` для лонга и `max(0,35, 0,22 × плечо)` для шорта) и ограничен `15%` от реального 10-минутного оборота свечи.
|
|
75
|
+
- **Авто-балансировка по тренду индекса:** когда индекс Мосбиржи растёт, покупки идут по базовому порогу (`+0,40%` / `+0,48%`), а вход в контртрендовый шорт ужесточается в `1,25×` (`−0,50%` / `−0,60%`). На падающем рынке система автоматически облегчает шорты (`×0,90`) и ужесточает покупки (`×1,25`).
|
|
76
|
+
- **Глубина сопровождения `130 минут` (`holdBars = 13`) и фиксация прибыли:** первые 45 минут после открытия действует мягкий порог удержания (`0,35×`), затем к 130-й минуте он плавно поднимается к `0,95×` с подстройкой под сегодняшнюю волатильность бумаги. При достижении порога Take-Profit включается скользящая защита прибыли (в 1-м эшелоне удерживает `≥70%` пика, во 2-м эшелоне — `≥75%` пика с допустимым откатом не более `0,35%`).
|
|
75
77
|
|
|
76
78
|
---
|
|
77
79
|
|
|
@@ -89,30 +89,35 @@
|
|
|
89
89
|
|
|
90
90
|
Даже идеальный прогноз (`IC > 0.10`) можно превратить в убыток, если неправильно входить, пересиживать сделки или торговать бумагами, в которых сломалась структура. Поэтому поверх прогноза работает **адаптивный движок портфеля**:
|
|
91
91
|
|
|
92
|
-
### 5.1. Ожидаемое движение (`Edge`) и
|
|
93
|
-
Модель выдаёт прогноз в стандартных отклонениях $\hat{y}_i$. Чтобы перевести его в реальные ожидаемые проценты движения за
|
|
92
|
+
### 5.1. Ожидаемое движение (`Edge`), эшелоны ликвидности и издержки «на сторону»
|
|
93
|
+
Модель выдаёт прогноз в стандартных отклонениях $\hat{y}_i$. Чтобы перевести его в реальные ожидаемые проценты движения за мульти-горизонт обучения $H = 15$ свечей ($2,5$ часа), вычисляется:
|
|
94
94
|
$$\text{Edge}_i = \hat{y}_i \cdot \sigma_i \cdot \sqrt{H}$$
|
|
95
|
-
|
|
95
|
+
Каждая сделка состоит из **двух операций (двух «сторон»)**: входа и выхода. При одинаковой комиссии брокера (`0,04%` на сторону = `0,08%` за круг) реальные потери в биржевом стакане зависят от эшелона ликвидности:
|
|
96
|
+
* **1-й эшелон (`TIER1_LIQUID_SYMBOLS`, 33 ликвидные акции Мосбиржи, включая `SBER`, `LKOH`, `GAZP`, `T`, `YDEX`, `PLZL`, `VTBR`, `OZON`, `MTLR`, `BSPB`, `RTKM`, `SVCB` и др.):** плотный стакан с потерями на спреде `~0,025%` → полные издержки **`0,065%` на сторону (`0,13%` за круг)**, базовый порог входа — **`1,0×`** (`~+0,40%`).
|
|
97
|
+
* **2–3-й эшелон (остальные ~92 акции, включая высоковолатильные бумаги с широким спредом `SMLT`, `PIKK`, `POSI`, `AFKS`, `VKCO`, `SPBE`, `UGLD`, `HEAD` и др.):** потери в стакане `~0,085%` → полные издержки **`0,125%` на сторону (`0,25%` за круг)**, порог входа повышен в **`1,20×` (`TIER2_EDGE_MULT`, `~+0,48%`)**.
|
|
96
98
|
|
|
97
|
-
### 5.2.
|
|
98
|
-
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
### 5.2. Глубина сопровождения `130 минут` (`holdBars = 13`), динамическая рампа удержания и `Profit Lock`
|
|
100
|
+
В режиме самообучения портфель проверяется на каждом шаге `holdBars / 5` (каждые 20–30 минут), а новые внутридневные входы прекращаются за `130 минут` до окончания вечерней сессии (`21:30 МСК`):
|
|
101
|
+
* **Первые 45 минут жизни сделки:** действует мягкий порог удержания (`0,35 × порог входа`), чтобы случайный внутридневной шум не выбил позицию сразу после открытия.
|
|
102
|
+
* **С 45-й по 130-ю минуту (`exitHoldRatio`):** порог удержания плавно поднимается к `0,95 × порог входа`, динамически масштабируясь по отношению сегодняшней фактической волатильности бумаги к её обычной сигме (`realizedBarPct / sigmaBarPct` в коридоре `[0,75..1,25]`).
|
|
103
|
+
* **Скользящая фиксация прибыли (`profitLockLevels`):** включает защиту безубытка от `+0,85% × tierMult` (после 15 мин) и скользящий Take-Profit от `+1,20% × tierMult` (`+1,55% × tierMult` в первые 15 мин). В 1-м эшелоне трал удерживает не менее `70%` пиковой прибыли (допуская откат до `0,45%`), а во 2-м эшелоне действует уплотнённый трал — удерживает **не менее `75%` пика и допускает откат не более `0,35%`**, забирая импульс до разворота широкого спреда.
|
|
101
104
|
|
|
102
|
-
### 5.3. Временный карантин «акций-убийц» с экспоненциальным затуханием
|
|
105
|
+
### 5.3. Временный карантин «акций-убийц» с экспоненциальным затуханием и пробоем сильным сигналом
|
|
103
106
|
На рынке периодически возникают бумаги, в которых из-за корпоративного конфликта, допэмиссии или специфических новостей перестают работать статистические связи.
|
|
104
|
-
* Система ведёт по каждой акции скользящий
|
|
105
|
-
* Если бумага за серию сделок (`stockCnt >=
|
|
106
|
-
*
|
|
107
|
+
* Система ведёт по каждой акции скользящий FIFO-счётчик чистой доходности закрытых кругов сделок `stockPnl[i]`.
|
|
108
|
+
* Если бумага за серию сделок (`stockCnt >= 2` при `stockPnl < -1.2%` или `stockPnl < -2.5%`) принесла накопленный убыток, она ставится **на карантин** — обычные сигналы по ней пропускаются.
|
|
109
|
+
* **Амнистия и пробой сильным сигналом (`110%`):** на каждом 30-минутном шаге счётчики умножаются на коэффициент затухания `0.9985` (период полураспада ~15 торговых дней), а особо сильный сигнал (**`≥ 110%` от индивидуального порога входа**) пробивает карантин досрочно.
|
|
107
110
|
|
|
108
111
|
### 5.4. Адаптивная балансировка `Long / Short` по тренду индекса Мосбиржи
|
|
109
112
|
Модель предсказывает движение акции *относительно индекса*.
|
|
110
113
|
* Когда индекс Мосбиржи находится в среднесрочном падающем тренде (ниже уровня 10 торговых дней назад или в 3-дневном проливе >1%), покупать акции против падающего рынка опасно, а продавать слабые акции (шорт) — наоборот, вдвойне выгодно.
|
|
111
114
|
* В режиме `Покупки и продажи (longshort)` с включённым самообучением система автоматически **повышает входной фильтр для покупок (`×1.25`)** и **облегчает вход для коротких продаж (`×0.90`)** на падающем рынке (и наоборот — на растущем рынке).
|
|
112
|
-
* Именно эта механика позволяет стратегии стабильно зарабатывать и в растущие месяцы, и на затяжном падении индекса
|
|
115
|
+
* Именно эта механика позволяет стратегии стабильно зарабатывать и в растущие месяцы, и на затяжном падении индекса Мосбиржи.
|
|
113
116
|
|
|
114
|
-
### 5.5.
|
|
115
|
-
В режиме автопилота (`modelTopK = 0`) система
|
|
117
|
+
### 5.5. Распределение капитала по эшелонам, плечу и чистой отдаче (`Dynamic Top-K` + `Conviction Sizing` + `netPickSort`)
|
|
118
|
+
В режиме автопилота (`modelTopK = 0`) система держит до 4 лонгов и до 4 шортов, распределяя капитал между отобранными лидерами пропорционально **чистой ожидаемой отдаче после вычета круговых издержек эшелона** (`|Edge| − 0,13%` для 1-го эшелона и `|Edge| − 0,25%` для 2-го):
|
|
119
|
+
* В **1-м эшелоне** размер слота масштабируется на полное плечо счёта (`maxLeverage`), а предел на одну бумагу составляет до **`50%` бюджета в лонг и до `38%` в шорт** (умноженные на плечо).
|
|
120
|
+
* Во **2-м эшелоне** действует асимметричное масштабирование от плеча счёта (`tierSizeMult`): для лонга — `max(0,50, 0,38 × плечо)`, для шорта — `max(0,35, 0,22 × плечо)`, при пределе `22%` в лонг / `18%` в шорт и жёстком потолке ликвидности **не более `15%` от медианного 10-минутного оборота свечи (`barTurnoverRub`)**.
|
|
116
121
|
|
|
117
122
|
---
|
|
118
123
|
|
|
@@ -150,7 +155,7 @@ $$\text{Edge}_i = \hat{y}_i \cdot \sigma_i \cdot \sqrt{H}$$
|
|
|
150
155
|
4. **Только лимитные заявки с точным шагом цены (`limitOffsetPct`):** Заявки выставляются от живой цены с защитным лимитным отступом (по умолчанию `0.1%`), округлённым строго по шагу цены (`MINSTEP` / `minPriceIncrement`) конкретной бумаги.
|
|
151
156
|
5. **Защита краёв сессии (`skipEdgeMinutes`):** В окна `06:50–07:25`, `09:50–10:10` и `18:35–18:55` МСК новые входы не открываются и фиксация прибыли не срабатывает на шуме стакана; аварийные стопы работают всегда.
|
|
152
157
|
6. **Аварийный стоп позиции — 7% от средней цены входа** (у брокера стоп-заявкой и во внутреннем контуре). Модель торгует возврат к среднему, поэтому стоп стоит широко: проверка на истории показала, что прежние 4,5–5% закрывали позиции на пике отклонения и снижали итог на обеих половинах периода.
|
|
153
|
-
6.1. **Плечо (`maxLeverage`, настройка счёта):** множитель размера позиций
|
|
158
|
+
6.1. **Плечо (`maxLeverage`, настройка счёта):** множитель размера позиций и предел суммарной позиции (`×` капитал). В ликвидных бумагах 1-го эшелона размер масштабируется на полное плечо счёта, а во 2–3-м эшелоне действует асимметричное масштабирование (`max(0,50, 0,38 × плечо)` для лонга и `max(0,35, 0,22 × плечо)` для шорта) под защитой потолка `15%` от 10-минутного оборота свечи (`barTurnoverRub`). Работает только при включённой маржинальной торговле; тариф «Инвестор» — без плеча, «Профи» — до 3, «VIP» и «Хозяин» — до 7. Предел заявки дополнительно проверяет брокер (`GetMaxLots`, маржинальный лимит).
|
|
154
159
|
6.2. **Стоп по общей просадке (`maxDrawdownPct`, настройка счёта, 0 — выкл.):** если оценка счёта опустилась от своего максимума на заданный процент, новые входы запрещаются до ручного снятия («Снять стоп по просадке»); открытые позиции сопровождаются. После вывода денег стоп снимается вручную.
|
|
155
160
|
6.3. **Капитал в LQDT как залог** (при включённых «Свободные рубли в LQDT» и маржинальной торговле): паи LQDT не продаются под входы — внутридневные позиции открываются под обеспечение и закрываются до ночи (платы за перенос нет), а свободные рубли размещаются в LQDT, когда у стратегии нет открытых позиций.
|
|
156
161
|
6.4. **Вход против планки:** если брокер отклонил вход из-за ценового коридора Мосбиржи (код 30099 — бумага у планки), вход в эту бумагу в ту же сторону блокируется до конца торгового дня.
|
|
@@ -166,13 +171,13 @@ $$\text{Edge}_i = \hat{y}_i \cdot \sigma_i \cdot \sqrt{H}$$
|
|
|
166
171
|
|
|
167
172
|
Изменение правил проверяется в лаборатории `scripts/strategy-lab.mjs` (варианты — в `scripts/strategy-lab-variants.mjs`) и принимается, только если оно лучше текущих правил **и на первой, и на второй половине периода**. Каждая отправленная заявка записывается в журнал решений `trade_decisions` (причина, ожидание и порог, контекст дня, плечо, версия программы; `GET /api/brokers/:id/decisions`).
|
|
168
173
|
|
|
169
|
-
Проверка правил
|
|
174
|
+
Проверка правил 13.11.2026 (пошаговые прогнозы 06.2024–11.2026, 59 051 свеча, 900 сделок, капитал 800 тыс. ₽, LQDT за период — 18,5% годовых):
|
|
170
175
|
|
|
171
176
|
| Правила | CAGR (весь период) | 1-я / 2-я половина | PF | Макс. просадка | Худшая сделка, % капитала |
|
|
172
177
|
|---|---|---|---|---|---|
|
|
173
178
|
| До 06.11.2026 (без LQDT, 2-й эшелон полным размером) | 12,9% | 19,3% / 6,8% | 1,40 | 4,4% | −2,0% |
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
179
|
+
| Текущие (`holdBars=13`, ревизия эшелонов), плечо 1× | **39,5%** | **49,2% / 30,7%** | **1,85** | **2,0%** | −2,2% |
|
|
180
|
+
| Текущие (`holdBars=13`, ревизия эшелонов), плечо 2× | **59,8%** | **73,8% / 47,4%** | **2,03** | **3,6%** | −4,3% |
|
|
181
|
+
| Текущие (`holdBars=13`, ревизия эшелонов), плечо 3× | **83,7%** | **104,6% / 65,6%** | **2,07** | **5,9%** | −6,5% |
|
|
177
182
|
|
|
178
183
|
Отклонены (хуже хотя бы на одной половине или с катастрофическим хвостом): защита вечным фьючерсом IMOEXF (издержки больше выигрыша), пассивные лимитные входы, снижение порога входа, 6–8 позиций на сторону, выход по времени, отключение фиксации прибыли, стоп по волатильности и «стоп отмены идеи», размер позиции по риску. Результат на истории не гарантирует будущий: плечо пропорционально увеличивает и доходность, и просадки.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuroalpha",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.11",
|
|
4
4
|
"description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
|
|
5
5
|
"author": "Roman A. Perepelkin",
|
|
6
6
|
"license": "UNLICENSED",
|
package/public/app.js
CHANGED
|
@@ -1651,6 +1651,20 @@ async function viewArchivedMarkdown(id) {
|
|
|
1651
1651
|
}
|
|
1652
1652
|
}
|
|
1653
1653
|
|
|
1654
|
+
async function startNewChatSession() {
|
|
1655
|
+
try {
|
|
1656
|
+
if (speechRec && speechActive) stopVoiceInput(false);
|
|
1657
|
+
await post('/api/chat/new');
|
|
1658
|
+
$('#archive-modal').hidden = true;
|
|
1659
|
+
if (document.body.classList.contains('chat-closed')) setChatClosed(false);
|
|
1660
|
+
$('#chat-input')?.focus();
|
|
1661
|
+
} catch (e) {
|
|
1662
|
+
toast(e.message, 'error');
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
$('#btn-chat-new')?.addEventListener('click', startNewChatSession);
|
|
1667
|
+
$('#archive-new-chat')?.addEventListener('click', startNewChatSession);
|
|
1654
1668
|
$('#btn-chat-archive')?.addEventListener('click', openChatArchiveList);
|
|
1655
1669
|
$('#archive-back')?.addEventListener('click', openChatArchiveList);
|
|
1656
1670
|
$('#archive-close')?.addEventListener('click', () => { $('#archive-modal').hidden = true; });
|
package/public/index.html
CHANGED
|
@@ -37,6 +37,9 @@
|
|
|
37
37
|
<textarea id="chat-input" rows="2" placeholder="Спросите Стратега о прогнозе, портфеле или настройках счёта…"></textarea>
|
|
38
38
|
<div class="prompt-bar">
|
|
39
39
|
<div class="prompt-left">
|
|
40
|
+
<button type="button" id="btn-chat-new" class="ghost-select model-btn" title="Начать новую сессию со Стратегом (текущая беседа сохранится в архив)">
|
|
41
|
+
<span>➕ Новая</span>
|
|
42
|
+
</button>
|
|
40
43
|
<button type="button" id="btn-chat-archive" class="ghost-select model-btn" title="Архив предыдущих сессий со Стратегом (.md)">
|
|
41
44
|
<span>🗂 Архив</span>
|
|
42
45
|
</button>
|
|
@@ -346,6 +349,7 @@
|
|
|
346
349
|
<div class="modal-head" style="gap:10px;flex-wrap:wrap">
|
|
347
350
|
<b id="archive-title">Архив сессий со Стратегом</b>
|
|
348
351
|
<span class="grow"></span>
|
|
352
|
+
<button id="archive-new-chat" type="button" class="small" title="Начать новую сессию со Стратегом">➕ Новая сессия</button>
|
|
349
353
|
<button id="archive-back" type="button" class="small" hidden>← Все сессии</button>
|
|
350
354
|
<button id="archive-open-chat" type="button" class="primary small" hidden title="Загрузить эту сессию в панель чата и продолжить диалог">Открыть в чате</button>
|
|
351
355
|
<button id="archive-download-md" type="button" class="small" hidden title="Скачать файл сессии в формате Markdown (.md)">⬇ Скачать .md</button>
|
package/server/brokers/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { barsFromHours, BAR_INTERVAL, BAR_MS } from '../model/dataset.js';
|
|
|
13
13
|
import { forecastNow } from '../model/forecast.js';
|
|
14
14
|
import { TIER1_LIQUID_SYMBOLS, QUARANTINE_OVERRIDE_RATIO, symbolEdgeMult, isStockQuarantined, computeAccountQuarantine, canEnterIntraday, shouldExitBeforeNight } from '../model/edge.js';
|
|
15
15
|
import { liveDividendGuard } from '../model/dividends.js';
|
|
16
|
-
import { STRATEGY_POLICY, sessionEdgeWarning, evaluateProfitLock, protectiveStopPct, exitHoldRatio, tierSizeMult, MAX_LEVERAGE } from '../model/policy.js';
|
|
16
|
+
import { STRATEGY_POLICY, sessionEdgeWarning, evaluateProfitLock, protectiveStopPct, exitHoldRatio, tierSizeMult, symbolCapRub, MAX_LEVERAGE } from '../model/policy.js';
|
|
17
17
|
import { MODELS } from '../model/models.js';
|
|
18
18
|
import { blockingNews } from '../news/index.js';
|
|
19
19
|
import { round, MSK, DAY, fmtBarMsk } from '../util.js';
|
|
@@ -2398,7 +2398,7 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
2398
2398
|
const totalNew = newBuys.length + newShorts.length;
|
|
2399
2399
|
const activeTotal = Math.max(2, activeLongs + activeShorts + totalNew);
|
|
2400
2400
|
const dynSlotRub = (isOwnerPlan && autoAdapt)
|
|
2401
|
-
? budget * Math.min(0.
|
|
2401
|
+
? budget * Math.min(0.50, Math.max(slotRub / Math.max(1, budget), 1.8 / Math.min(slots, activeTotal + 1)))
|
|
2402
2402
|
: (vipBoost && autoAdapt)
|
|
2403
2403
|
? budget * Math.min(0.40, Math.max(slotRub / Math.max(1, budget), 1.5 / Math.min(slots, activeTotal + 1)))
|
|
2404
2404
|
: slotRub;
|
|
@@ -2460,9 +2460,9 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
2460
2460
|
const lqdtLots = Math.floor(lqdtAlgoQty / lqdtSp.lotSize);
|
|
2461
2461
|
const neededForNewRub = combinedNew.reduce((s, { r, side }) => {
|
|
2462
2462
|
const isT1 = TIER1_LIQUID_SYMBOLS.has(r.symbol);
|
|
2463
|
-
const
|
|
2463
|
+
const posSide = side === 'short' ? 'short' : 'long';
|
|
2464
2464
|
const barTurnRub = !isT1 ? recentBarTurnoverRub(r.symbol) : 0;
|
|
2465
|
-
const liqCap = (
|
|
2465
|
+
const liqCap = symbolCapRub(r.symbol, posSide, budget, barTurnRub);
|
|
2466
2466
|
return s + Math.min(liqCap, (params.modelAllocMode === 'invvol' && r.weight ? dynSlotRub * r.weight : dynSlotRub) * convOf(r));
|
|
2467
2467
|
}, 0);
|
|
2468
2468
|
const needFromLqdtRub = Math.min(Math.max(0, budget * 0.99 - usedRub), neededForNewRub) - availRub;
|
|
@@ -2481,37 +2481,34 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
2481
2481
|
}
|
|
2482
2482
|
}
|
|
2483
2483
|
}
|
|
2484
|
+
const netPickWeight = (r) => {
|
|
2485
|
+
const rawPct = Math.abs(r.expectedPct || 0);
|
|
2486
|
+
if (!autoAdapt) return Math.max(0.01, rawPct);
|
|
2487
|
+
const roundTripCostPct = TIER1_LIQUID_SYMBOLS.has(r.symbol) ? 0.13 : 0.25;
|
|
2488
|
+
return Math.max(0.01, rawPct - roundTripCostPct);
|
|
2489
|
+
};
|
|
2484
2490
|
if (autoAdapt && combinedNew.length > 1) {
|
|
2485
|
-
combinedNew.sort((a, b) =>
|
|
2491
|
+
combinedNew.sort((a, b) => netPickWeight(b.r) - netPickWeight(a.r));
|
|
2486
2492
|
}
|
|
2487
|
-
let remWeightSum = combinedNew.reduce((s, x) => s +
|
|
2493
|
+
let remWeightSum = combinedNew.reduce((s, x) => s + netPickWeight(x.r), 0);
|
|
2488
2494
|
|
|
2489
|
-
// Жёсткий предохранитель концентрации на одну бумагу в реальном стакане
|
|
2490
|
-
// • для ликвидных бумаг 1-го эшелона: до
|
|
2491
|
-
// • для бумаг 2–3
|
|
2495
|
+
// Жёсткий предохранитель концентрации на одну бумагу в реальном стакане брокера (symbolCapRub в policy.js):
|
|
2496
|
+
// • для ликвидных бумаг 1-го эшелона: до 50% счёта на один лонг и до 38% счёта на один шорт;
|
|
2497
|
+
// • для бумаг 2–3 эшелона: до 22% счёта на один лонг и до 18% на один шорт (но не более 15% от 10-минутного оборота свечи),
|
|
2492
2498
|
// чтобы крупная заявка не выкупала тонкий стакан против самой себя и всегда оставляла капитал на 3–4 бумаги!
|
|
2493
|
-
const maxLongTier1Rub = budget * 0.45;
|
|
2494
|
-
const maxShortTier1Rub = budget * 0.35;
|
|
2495
|
-
const maxLongTier2Rub = budget * 0.22;
|
|
2496
|
-
const maxShortTier2Rub = budget * 0.18;
|
|
2497
|
-
|
|
2498
2499
|
for (const { r, side } of combinedNew) {
|
|
2499
|
-
const wCur =
|
|
2500
|
+
const wCur = netPickWeight(r);
|
|
2500
2501
|
const propShare = autoAdapt && remWeightSum > 0 ? availRub * (wCur / remWeightSum) : availRub / Math.max(1, remNew);
|
|
2501
2502
|
remWeightSum = Math.max(0, remWeightSum - wCur);
|
|
2502
2503
|
remNew--;
|
|
2503
2504
|
const isTier1 = TIER1_LIQUID_SYMBOLS.has(r.symbol);
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
: (isTier1 ? maxLongTier1Rub : maxLongTier2Rub));
|
|
2509
|
-
// Лимит от реального 10-минутного оборота стакана (Liquidity Cap): во 2-м эшелоне не входим объёмом больше 12% от 10-минутного оборота биржи,
|
|
2505
|
+
const posSide = side === 'short' ? 'short' : 'long';
|
|
2506
|
+
// множитель размера по эшелону и направлению: 1-й эшелон — плечо счёта, 2–3-й — асимметричное масштабирование от плеча (policy.js)
|
|
2507
|
+
const tierMult = autoAdapt ? tierSizeMult(r.symbol, leverage, posSide) : 1;
|
|
2508
|
+
// Лимит от реального 10-минутного оборота стакана (Liquidity Cap): во 2-м эшелоне не входим объёмом больше 15% от 10-минутного оборота биржи,
|
|
2510
2509
|
// чтобы заявка не становилась «стенкой» для стаканных роботов и исполнялась целиком без проскальзывания
|
|
2511
2510
|
const barTurnoverRub = !isTier1 ? recentBarTurnoverRub(r.symbol) : 0;
|
|
2512
|
-
const sideCapRub = (
|
|
2513
|
-
? Math.max(45000, Math.min(rawSideCapRub, barTurnoverRub * 0.12))
|
|
2514
|
-
: rawSideCapRub;
|
|
2511
|
+
const sideCapRub = symbolCapRub(r.symbol, posSide, budget * tierMult, barTurnoverRub);
|
|
2515
2512
|
const rawSum = Math.min(sideCapRub, (params.modelAllocMode === 'invvol' && r.weight ? dynSlotRub * r.weight : dynSlotRub) * tierMult * convOf(r));
|
|
2516
2513
|
const sum = Math.min(rawSum, propShare, availRub);
|
|
2517
2514
|
const refPx = r.price || specs[r.symbol]?.lastPrice || 0;
|
package/server/model/edge.js
CHANGED
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
export const ENTRY_EDGE_MULT = 1.60;
|
|
9
9
|
export const TIER2_EDGE_MULT = 1.20;
|
|
10
10
|
|
|
11
|
-
// Голубые фишки и наиболее ликвидные акции 1-го эшелона Мосбиржи (глубокий стакан, минимальный
|
|
12
|
-
//
|
|
11
|
+
// Голубые фишки и наиболее ликвидные акции 1-го эшелона Мосбиржи (глубокий стакан, минимальный спред, нулевая частота срабатывания 7%-го стопа):
|
|
12
|
+
// бумаги с повышенным хвостовым риском и широким спредом (SMLT, PIKK, POSI, AFKS, VKCO, SPBE, UGLD, HEAD, CBOM) идут по правилам 2-го эшелона (TIER2_EDGE_MULT).
|
|
13
13
|
export const TIER1_LIQUID_SYMBOLS = new Set([
|
|
14
14
|
'SBER', 'SBERP', 'LKOH', 'GAZP', 'T', 'YDEX', 'ROSN', 'NVTK', 'GMKN', 'PLZL',
|
|
15
15
|
'VTBR', 'TATN', 'TATNP', 'SNGS', 'SNGSP', 'ALRS', 'CHMF', 'NLMK', 'MAGN', 'MOEX',
|
|
16
|
-
'OZON', '
|
|
16
|
+
'OZON', 'PHOR', 'AFLT', 'MTSS', 'IRAO', 'TRNFP', 'RUAL', 'FLOT', 'X5',
|
|
17
|
+
'MTLR', 'BSPB', 'RTKM', 'SVCB',
|
|
17
18
|
]);
|
|
18
19
|
|
|
19
20
|
export function symbolEdgeMult(symbol, { sigma = 0, avgSigma = 0, avgBarValRub = 0 } = {}) {
|
package/server/model/policy.js
CHANGED
|
@@ -19,11 +19,28 @@ export const STRATEGY_POLICY = Object.freeze({
|
|
|
19
19
|
profitLock: true,
|
|
20
20
|
// размер позиции: 'slot' — доля бюджета × сила сигнала
|
|
21
21
|
sizing: Object.freeze({ mode: 'slot' }),
|
|
22
|
-
// Множитель размера позиции по
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
22
|
+
// Множитель размера позиции по эшелонам:
|
|
23
|
+
// • 1-й эшелон (33 ликвидные акции, PF 2,86 и 0 стопов на 59 051 свече): масштабируется на полное плечо счёта (lev),
|
|
24
|
+
// предельный вес бумаги — до 50% бюджета в лонг и до 38% в шорт (базовый слот 0,50);
|
|
25
|
+
// • 2-й эшелон: асимметричное масштабирование от плеча счёта (лонг max(0,50, 0,38 × lev), шорт max(0,35, 0,22 × lev))
|
|
26
|
+
// под защитой потолка реальной ликвидности стакана (15% от медианного 10-минутного оборота свечи barTurnoverRub)
|
|
27
|
+
// и плотного сопровождения Take-Profit (удержание ≥75% пика и откат ≤0,35%).
|
|
28
|
+
// Проверка 13.11.2026 (06.2024–11.2026, 59 051 свеча): улучшает итог на обеих половинах истории при любом плече
|
|
29
|
+
// • плечо 1×: CAGR 49,2%/30,7% по половинам (весь 39,5%, PF 1,85, DD 2,0%) против 43,8%/27,3% (весь 35,1%, PF 1,63);
|
|
30
|
+
// • плечо 2×: CAGR 73,8%/47,4% по половинам (весь 59,8%, 6 мес. 105,0%, PF 2,03, DD 3,6%) против 54,5%/40,6% (весь 47,2%, PF 1,87, DD 3,4%);
|
|
31
|
+
// • плечо 3×: CAGR 104,6%/65,6% по половинам (весь 83,7%, PF 2,07, DD 5,9%) против 65,9%/55,2% (весь 60,3%, PF 2,03, DD 6,0%).
|
|
32
|
+
sizeMultT2LongBase: 0.50,
|
|
33
|
+
sizeMultT2LongLev: 0.38,
|
|
34
|
+
sizeMultT2ShortBase: 0.35,
|
|
35
|
+
sizeMultT2ShortLev: 0.22,
|
|
36
|
+
capT1Long: 0.50,
|
|
37
|
+
capT1Short: 0.38,
|
|
38
|
+
capT2Long: 0.22,
|
|
39
|
+
capT2Short: 0.18,
|
|
40
|
+
turnoverCapMult: 0.15,
|
|
41
|
+
tpRetainT2: 0.75,
|
|
42
|
+
tpTrailT2: 0.35,
|
|
43
|
+
netPickSort: true,
|
|
27
44
|
// капитал хранится в фонде денежного рынка LQDT как залог: внутридневные позиции открываются под обеспечение
|
|
28
45
|
// (без платы за перенос — позиции закрываются до ночи), LQDT не продаётся под входы
|
|
29
46
|
cashYield: true,
|
|
@@ -63,12 +80,15 @@ export function sessionEdgeWarning(ts = Date.now()) {
|
|
|
63
80
|
// ---------- фиксация прибыли ----------
|
|
64
81
|
// Уровни в процентах выигрыша от средней цены. peakGainPct — лучший выигрыш с момента входа.
|
|
65
82
|
// Возвращает { floorPct, minExitPct, kind } — выход, когда текущий выигрыш опустился до floorPct, но не ниже minExitPct; или null.
|
|
66
|
-
export function profitLockLevels(sym, peakGainPct, heldMin, roundTripFeePct = 0.10) {
|
|
83
|
+
export function profitLockLevels(sym, peakGainPct, heldMin, roundTripFeePct = 0.10, policy = STRATEGY_POLICY) {
|
|
84
|
+
const isT1 = TIER1_LIQUID_SYMBOLS.has(sym);
|
|
67
85
|
const tierMult = symbolEdgeMult(sym);
|
|
68
86
|
const minNetFloorPct = Math.max(roundTripFeePct + 0.30, Math.round(0.38 * tierMult * 100) / 100);
|
|
69
87
|
const tpTriggerPct = (heldMin < 15 ? 1.55 : 1.20) * tierMult;
|
|
70
88
|
if (peakGainPct >= tpTriggerPct) {
|
|
71
|
-
|
|
89
|
+
const retain = !isT1 && policy?.tpRetainT2 ? policy.tpRetainT2 : 0.70;
|
|
90
|
+
const trail = !isT1 && policy?.tpTrailT2 ? policy.tpTrailT2 : 0.45;
|
|
91
|
+
return { floorPct: Math.max(peakGainPct * retain, peakGainPct - trail, minNetFloorPct), minExitPct: minNetFloorPct, kind: 'tp' };
|
|
72
92
|
}
|
|
73
93
|
if (heldMin >= 15 && peakGainPct >= 0.85 * tierMult) {
|
|
74
94
|
return { floorPct: Math.max(minNetFloorPct, peakGainPct * 0.45), minExitPct: roundTripFeePct + 0.22, kind: 'be' };
|
|
@@ -123,23 +143,36 @@ export function exitHoldRatio(heldMin, horizonMin, { sigmaBarPct = 0, realizedBa
|
|
|
123
143
|
|
|
124
144
|
// ---------- плечо и множители размера по эшелонам ----------
|
|
125
145
|
export const MAX_LEVERAGE = 7;
|
|
126
|
-
export function tierSizeMult(symbol, leverage = 1,
|
|
146
|
+
export function tierSizeMult(symbol, leverage = 1, sideOrPolicy = 'long', policyArg = STRATEGY_POLICY) {
|
|
147
|
+
const isObj = sideOrPolicy && typeof sideOrPolicy === 'object';
|
|
148
|
+
const side = isObj ? 'long' : (sideOrPolicy === 'short' ? 'short' : 'long');
|
|
149
|
+
const policy = isObj ? sideOrPolicy : (policyArg || STRATEGY_POLICY);
|
|
127
150
|
const lev = Math.max(1, Math.min(MAX_LEVERAGE, Number(leverage) || 1));
|
|
128
|
-
|
|
151
|
+
if (TIER1_LIQUID_SYMBOLS.has(symbol)) return policy.sizeMultT1 ?? lev;
|
|
152
|
+
if (policy.sizeMultT2 !== undefined) return policy.sizeMultT2;
|
|
153
|
+
const raw = side === 'short'
|
|
154
|
+
? Math.max(policy.sizeMultT2ShortBase ?? 0.35, (policy.sizeMultT2ShortLev ?? 0.22) * lev)
|
|
155
|
+
: Math.max(policy.sizeMultT2LongBase ?? 0.50, (policy.sizeMultT2LongLev ?? 0.38) * lev);
|
|
156
|
+
return Math.round(raw * 1000) / 1000;
|
|
129
157
|
}
|
|
130
158
|
|
|
131
159
|
// ---------- предельный размер позиции на одну бумагу ----------
|
|
132
|
-
export function symbolCapRub(symbol, side, budget, barTurnoverRub = 0) {
|
|
160
|
+
export function symbolCapRub(symbol, side, budget, barTurnoverRub = 0, policy = STRATEGY_POLICY) {
|
|
133
161
|
const isT1 = TIER1_LIQUID_SYMBOLS.has(symbol);
|
|
134
|
-
const
|
|
135
|
-
|
|
162
|
+
const isShort = side === 'short';
|
|
163
|
+
const capPct = isShort
|
|
164
|
+
? (isT1 ? (policy?.capT1Short ?? 0.38) : (policy?.capT2Short ?? 0.18))
|
|
165
|
+
: (isT1 ? (policy?.capT1Long ?? 0.50) : (policy?.capT2Long ?? 0.22));
|
|
166
|
+
const rawCap = budget * capPct;
|
|
167
|
+
const turnMult = policy?.turnoverCapMult ?? 0.15;
|
|
168
|
+
return (!isT1 && barTurnoverRub > 0) ? Math.max(45000, Math.min(rawCap, barTurnoverRub * turnMult)) : rawCap;
|
|
136
169
|
}
|
|
137
170
|
|
|
138
171
|
// Размер новой позиции в рублях до учёта свободного кэша и пропорциональной доли:
|
|
139
172
|
// 'slot' — рабочий слот × сила сигнала (conv), не больше предела бумаги;
|
|
140
173
|
// 'risk' — при срабатывании стопа счёт теряет не больше riskPct% бюджета.
|
|
141
174
|
export function targetPositionRub({ symbol, side, budget, slotRub, conv = 1, stopPct, barTurnoverRub = 0 }, policy = STRATEGY_POLICY) {
|
|
142
|
-
const cap = symbolCapRub(symbol, side, budget, barTurnoverRub);
|
|
175
|
+
const cap = symbolCapRub(symbol, side, budget, barTurnoverRub, policy);
|
|
143
176
|
const sz = policy.sizing || {};
|
|
144
177
|
if (sz.mode === 'risk' && stopPct > 0) {
|
|
145
178
|
const byRisk = (budget * (sz.riskPct / 100)) / (stopPct / 100);
|
package/server/model/research.js
CHANGED
|
@@ -40,7 +40,8 @@ export { isMarketDown };
|
|
|
40
40
|
// 16: главная оценка — симулятор с правилами живого контура (simulator.js), аварийный стоп 7%
|
|
41
41
|
// 17: капитал в LQDT как залог, половинный размер 2-го эшелона, строки с плечом 3 и 5
|
|
42
42
|
// 18: глубина сопровождения позиции и вечернего окна входов holdBars=13 (130 мин) во всех контурах симуляции
|
|
43
|
-
|
|
43
|
+
// 19: ревизия правил 1-го и 2-го эшелонов (MTLR/BSPB/RTKM/SVCB в T1, асимметричный масштаб T2 от плеча, лимит 50%/38% T1 и плотный трал T2)
|
|
44
|
+
const DATA_VERSION = 19;
|
|
44
45
|
|
|
45
46
|
const AUTO_LOCKED = { modelHorizon: 2.5, modelMinEdge: 2.5, modelExitRatio: 0.95 };
|
|
46
47
|
function reuseParamsKey(params) {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
isStockQuarantined, canEnterIntraday, shouldExitBeforeNight, isMarketDown,
|
|
18
18
|
} from './edge.js';
|
|
19
19
|
import { nextDividendAt, dividendRules } from './dividends.js';
|
|
20
|
-
import { STRATEGY_POLICY, MAX_LEVERAGE, sessionEdgeWarning, profitLockLevels, protectiveStopPct, exitHoldRatio, targetPositionRub, counterTrendBlocked, thesisStopPx, dayContext } from './policy.js';
|
|
20
|
+
import { STRATEGY_POLICY, MAX_LEVERAGE, sessionEdgeWarning, profitLockLevels, protectiveStopPct, exitHoldRatio, tierSizeMult, targetPositionRub, counterTrendBlocked, thesisStopPx, dayContext } from './policy.js';
|
|
21
21
|
|
|
22
22
|
// Выравнивание внешнего ряда (свечи IMOEXF, дневные цены LQDT) по шкале датасета с переносом последней цены
|
|
23
23
|
export function alignSeries(timeline, rows) {
|
|
@@ -50,7 +50,7 @@ export function simulateStrategy({ ds, wf, p, policy = STRATEGY_POLICY, capital
|
|
|
50
50
|
const cooldownBars = Math.round((policy.cooldownMin ?? 30) / BAR_INTERVAL);
|
|
51
51
|
const stopCooldownBars = Math.round((policy.stopCooldownMin ?? 60) / BAR_INTERVAL);
|
|
52
52
|
const horizonMin = Math.max(90, holdBars * BAR_INTERVAL);
|
|
53
|
-
const slotW = policy.slotPct ?? (plan === 'owner' ? 0.
|
|
53
|
+
const slotW = policy.slotPct ?? (plan === 'owner' ? 0.50 : plan === 'vip' ? 0.40 : plan === 'pro' ? Math.min(0.28, 1.15 / 8) : Math.min(0.24, 1.05 / 5));
|
|
54
54
|
const convMax = plan === 'owner' ? 1.65 : plan === 'vip' ? 1.30 : 1;
|
|
55
55
|
const convK = plan === 'owner' ? 0.30 : 0.25;
|
|
56
56
|
const lev = Math.max(1, Math.min(MAX_LEVERAGE, Number(leverage) || 1));
|
|
@@ -189,7 +189,7 @@ export function simulateStrategy({ ds, wf, p, policy = STRATEGY_POLICY, capital
|
|
|
189
189
|
}
|
|
190
190
|
if (done) continue;
|
|
191
191
|
if (policy.profitLock) {
|
|
192
|
-
const lv = profitLockLevels(tk.symbol, pos.peakGain * 100, heldMinStart, roundTripFeePct);
|
|
192
|
+
const lv = profitLockLevels(tk.symbol, pos.peakGain * 100, heldMinStart, roundTripFeePct, policy);
|
|
193
193
|
if (lv) {
|
|
194
194
|
const gO = gainOf(pos, o) * 100;
|
|
195
195
|
if (gO <= lv.floorPct && gO >= lv.minExitPct) { closePos(i, pos, t, o, 'Фиксация прибыли', 'lock'); stats.locks++; done = true; }
|
|
@@ -199,7 +199,7 @@ export function simulateStrategy({ ds, wf, p, policy = STRATEGY_POLICY, capital
|
|
|
199
199
|
if (done) continue;
|
|
200
200
|
pos.peakGain = Math.max(pos.peakGain, gainOf(pos, favorable));
|
|
201
201
|
if (policy.profitLock) {
|
|
202
|
-
const lv = profitLockLevels(tk.symbol, pos.peakGain * 100, heldMinStart + BAR_INTERVAL, roundTripFeePct);
|
|
202
|
+
const lv = profitLockLevels(tk.symbol, pos.peakGain * 100, heldMinStart + BAR_INTERVAL, roundTripFeePct, policy);
|
|
203
203
|
const gC = gainOf(pos, c) * 100;
|
|
204
204
|
if (lv && gC <= lv.floorPct && gC >= lv.minExitPct) { closePos(i, pos, t, c, 'Фиксация прибыли', 'lock'); stats.locks++; }
|
|
205
205
|
}
|
|
@@ -325,12 +325,13 @@ export function simulateStrategy({ ds, wf, p, policy = STRATEGY_POLICY, capital
|
|
|
325
325
|
...candL.slice(0, Math.max(0, K - nL)).map((i) => [i, 'long']),
|
|
326
326
|
...candS.slice(0, Math.max(0, K - nS)).map((i) => [i, 'short']),
|
|
327
327
|
];
|
|
328
|
-
|
|
328
|
+
const pickWeight = (i) => (policy.netPickSort !== false ? Math.max(0.0001, Math.abs(edge(i)) - 2 * costOf(i)) : Math.max(0.0001, Math.abs(edge(i))));
|
|
329
|
+
picks.sort((a, b) => pickWeight(b[0]) - pickWeight(a[0]));
|
|
329
330
|
let avail = Math.max(0, budget * maxGross - grossUsed(t));
|
|
330
|
-
let remW = picks.reduce((s, [i]) => s +
|
|
331
|
+
let remW = picks.reduce((s, [i]) => s + pickWeight(i), 0);
|
|
331
332
|
for (const [i, side] of picks) {
|
|
332
333
|
const e = edge(i);
|
|
333
|
-
const w =
|
|
334
|
+
const w = pickWeight(i);
|
|
334
335
|
const propShare = remW > 0 ? avail * (w / remW) : avail;
|
|
335
336
|
remW -= w;
|
|
336
337
|
const sigmaBarPct = sg[i] * 100;
|
|
@@ -338,7 +339,7 @@ export function simulateStrategy({ ds, wf, p, policy = STRATEGY_POLICY, capital
|
|
|
338
339
|
if (counterTrendBlocked(side, ctx, policy)) { stats.blockedCT++; continue; }
|
|
339
340
|
const stopPct = protectiveStopPct(side, { sigmaBarPct, realizedBarPct: ctx.realizedBarPct, horizonBars: holdBars }, policy);
|
|
340
341
|
const conv = Math.min(convMax, Math.max(0.85, 0.80 + convK * (Math.abs(e) / need)));
|
|
341
|
-
const tm =
|
|
342
|
+
const tm = tierSizeMult(tokens[i].symbol, lev, side, policy);
|
|
342
343
|
if (!(tm > 0)) continue;
|
|
343
344
|
const target = targetPositionRub({ symbol: tokens[i].symbol, side, budget: budget * capScale * tm, slotRub: budget * slotW * tm, conv: convMax > 1 ? conv : 1, stopPct, barTurnoverRub: isT1(i) ? 0 : barTurnover(i, t) }, policy);
|
|
344
345
|
const alloc = Math.min(target, propShare, avail);
|
|
@@ -249,16 +249,30 @@ ${liveTradingSummary}
|
|
|
249
249
|
- Если данные инструментов расходятся с календарём или временем — **верь данным инструментов**: это фактические позиции и исполненные сделки.
|
|
250
250
|
|
|
251
251
|
## Как устроено автоматическое ядро платформы «НейроАльфа» (Рынок как язык)
|
|
252
|
-
- **Словарь:** ~
|
|
252
|
+
- **Словарь:** ~125 акций широкого рынка Мосбиржи (MOEXBMI) + индекс IMOEX. Свечи 10-минутные, дивидендные отсечки скорректированы со сверкой по реальному гэпу цены, объёмы нормированы по 24-часовому суточному профилю (valRatio по часам МСК).
|
|
253
253
|
- **Карта рынка и авто-эмбеддинг (modelEmbedDim = 0 Авто):** окно обучения фиксировано на 90 торговых днях. На каждом шаге размерность карты рынка автоматически определяется по спектральному порогу Марченко–Пастура (отсечение рыночного шума от истинных факторов).
|
|
254
|
-
- **12 голов внимания и мульти-горизонт (1,5–3,5 часа):** модель смотрит на 3 масштаба времени (1ч, 3ч, день), цену и денежный поток, узких и широких соседей по карте, предсказывая чистое остаточное движение каждой акции относительно индекса
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
254
|
+
- **12 голов внимания и мульти-горизонт обучения (2,5 часа = 15 свечей, диапазон 1,5–3,5 часа):** модель смотрит на 3 масштаба времени (1ч, 3ч, день), цену и денежный поток, узких и широких соседей по карте, предсказывая чистое остаточное движение каждой акции относительно индекса Мосбиржи (Edge_i = y_i × sigma_i × √15).
|
|
255
|
+
- **Эшелоны ликвидности и реальные издержки («на сторону» и «за полный круг»):**
|
|
256
|
+
- «Издержки на сторону» — это потери на одной операции (отдельно на входе и отдельно на выходе); за полный круг сделки («купил → продал») они удваиваются. Комиссия брокера (Т-Банк) одинаковая для всех бумаг (0,04% на сторону = 0,08% за круг), а потери в биржевом стакане (спред + проскальзывание) зависят от эшелона:
|
|
257
|
+
- **1-й эшелон (33 ликвидные акции с глубоким стаканом:** SBER, SBERP, LKOH, GAZP, T, YDEX, ROSN, NVTK, GMKN, PLZL, VTBR, TATN, TATNP, SNGS, SNGSP, ALRS, CHMF, NLMK, MAGN, MOEX, OZON, PHOR, AFLT, MTSS, IRAO, TRNFP, RUAL, FLOT, X5, MTLR, BSPB, RTKM, SVCB): потери в стакане ~0,025% + комиссия 0,04% = **0,065% на сторону (0,13% за круг)**.
|
|
258
|
+
- **2–3-й эшелон (остальные ~92 акции,** включая высоковолатильные бумаги с более широким спредом и хвостовым риском: SMLT, PIKK, POSI, AFKS, VKCO, SPBE, UGLD, HEAD, CBOM, LSRG, SFIN, FESH и др.): потери в стакане ~0,085% + комиссия 0,04% = **0,125% на сторону (0,25% за круг)**.
|
|
259
|
+
- **Пороги входа по эшелонам и тренду индекса:**
|
|
260
|
+
- Базовый порог выгоды на вход = 2,5 × 2 × (комиссия + проскальзывание) × 1,60. Для 1-го эшелона при настройке издержек 0,05% базовый порог составляет **+0,40%** (с тонкой поправкой symbolEdgeMult на оборот и волатильность бумаги в коридоре 0,88..1,45).
|
|
261
|
+
- Для 2–3-го эшелона порог входа повышен в **1,20× (TIER2_EDGE_MULT)** — до **~+0,48%**, чтобы ожидаемый импульс с запасом перекрывал более широкий спред стакана. В первые 20 минут утренней сессии (07:00–07:20 МСК) шорты 2-го эшелона блокируются из-за пустого утреннего стакана.
|
|
262
|
+
- Авто-балансировка по тренду индекса (isMarketDown): на растущем рынке покупки открываются от базового порога (+0,40% 1-й эшелон и +0,48% 2-й эшелон), а контртрендовые шорты ужесточаются в 1,25× (до −0,50% и −0,60%). На падающем рынке — наоборот: покупки ужесточаются в 1,25×, а шорты облегчаются в 0,90×. Всегда называй пользователю точный индивидуальный порог конкретной бумаги (\`порог_покупки_проц\` / \`порог_шорта_проц\`).
|
|
263
|
+
- **Размер позиций, плечо счёта и приоритет аллокации (policy.js):**
|
|
264
|
+
- **Dynamic Top-K и Conviction Sizing:** робот держит до 4 лонгов и до 4 шортов. Базовый слот в режиме владельца — 50% бюджета, масштабируется на силу сигнала conv (от 0,85× до 1,65×).
|
|
265
|
+
- **Масштабирование по эшелонам и плечу (tierSizeMult и symbolCapRub):**
|
|
266
|
+
1. В **1-м эшелоне** размер позиции умножается на полное плечо счёта (maxLeverage = 1×..7×), а потолок концентрации на одну бумагу составляет **50% в лонг и 38% в шорт** (умноженные на плечо).
|
|
267
|
+
2. Во **2-м эшелоне** действует асимметричное масштабирование от плеча счёта: для **лонга** множитель равен max(0,50, 0,38 × плечо) (на плече 2× → 0,76×, на 3× → 1,14×), а для более рискованного **шорта** — max(0,35, 0,22 × плечо) (на 1× → 0,35×, на 2× → 0,44×, на 3× → 0,66×). Предел на одну бумагу 2-го эшелона — 22% в лонг и 18% в шорт, но **не более 15% от реального медианного 10-минутного оборота стакана (barTurnoverRub)**, чтобы заявка никогда не двигала цену против себя.
|
|
268
|
+
- **Приоритет распределения капитала (netPickSort):** кандидаты в слоты отбираются по силе z-скора прогноза, а очерёдность и доля капитала между отобранными сделками определяются по **чистой ожидаемой отдаче за вычетом круговых издержек эшелона** (|expectedPct| − 0,13% для 1-го эшелона и |expectedPct| − 0,25% для 2-го эшелона).
|
|
269
|
+
- **Капитал в фонде денежного рынка LQDT как залог:** свободный капитал работает в LQDT, а внутридневные позиции открываются под обеспечение и закрываются до ночи (без платы брокеру за перенос через ночь).
|
|
270
|
+
- **Глубина сопровождения позиций (130 минут), фиксация прибыли и защитные контуры:**
|
|
271
|
+
- **Глубина сопровождения (holdBars = 13 свечей = 130 минут = 2 ч 10 м):** плановая ревизия портфеля идёт каждые 20–30 минут (holdBars / 5), а вечернее окно новых внутридневных входов закрывается за 130 минут до конца торгов (после **21:30 МСК** новые входы не открываются, перед 23:40–23:50 МСК позиции закрываются перед ночью).
|
|
272
|
+
- **Плавная рампа удержания (exitHoldRatio):** первые **45 минут** после входа действует мягкий порог удержания (0,35× от порога входа), чтобы сделку не выбило шумом; затем с 45-й по **130-ю минуту** порог плавно поднимается к 0,95×, динамически подстраиваясь под сегодняшнюю волатильность бумаги (realizedBarPct / sigmaBarPct в коридоре [0,75..1,25]: в спокойный день держит дольше, в турбулентный фиксирует быстрее).
|
|
273
|
+
- **Скользящая фиксация прибыли (profitLockLevels):** включает безубыток от +0,85% × tierMult (после 15 мин) и скользящий Take-Profit от +1,20% × tierMult (+1,55% × tierMult в первые 15 мин). В 1-м эшелоне трал удерживает ≥70% пиковой прибыли (допуская откат до 0,45%), а во 2-м эшелоне работает **уплотнённый трал — удерживает ≥75% пика и допускает откат не более 0,35%**, забирая импульс до разворота спреда.
|
|
274
|
+
- **Аварийный стоп — 7% от средней цены входа** (выставляется реальной стоп-заявкой у брокера и контролируется внутри свечи).
|
|
275
|
+
- **Карантин убыточных бумаг (isStockQuarantined):** FIFO-учёт кругов сделок с периодом полураспада ~15 торговых дней (0,9985 на 30-минутном шаге); если бумага накопила серию убытков (≥2 сделок с суммарным итогом < −1,2% или < −2,5%), слабые сигналы по ней пропускаются, но **сильный сигнал (≥ 110% от индивидуального порога входа) пробивает карантин**.
|
|
262
276
|
|
|
263
277
|
## Брокерские счета и режимы работы
|
|
264
278
|
Полная поддержка — Т-Банк (T-Invest API, включая секцию TQBR и SPBRU для SPBE). Финам, БКС, Алор, Цифра брокер и ITI Capital — бета, только «Подтверждение заявок». СберИнвестиции, ВТБ и Альфа-Инвестиции сейчас не подключаются (нет открытого API для заявок).
|