neuroalpha 0.3.12 → 0.3.13
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/LICENSE +13 -17
- package/README.md +4 -2
- package/bin/cli.js +1 -1
- package/deploy/install.sh +1 -1
- package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +1 -1
- 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 +1 -1
- package/package.json +2 -2
- package/public/admin.html +43 -20
- package/public/app.js +524 -211
- package/public/index.html +43 -3
- package/public/landing.html +328 -115
- package/public/legal.html +37 -16
- package/public/styles.css +24 -3
- package/server/api/routes.js +61 -29
- package/server/auth/index.js +109 -20
- package/server/billing/yookassa.js +1 -1
- package/server/brokers/index.js +179 -34
- package/server/config.js +1 -1
- package/server/data/moex.js +1 -1
- package/server/db.js +17 -3
- package/server/hub.js +1 -1
- package/server/index.js +4 -4
- package/server/llm/client.js +1 -1
- package/server/llm/providers.js +1 -1
- package/server/model/dataset.js +1 -1
- package/server/model/forecast.js +188 -17
- package/server/model/linalg.js +1 -1
- package/server/model/live.js +6 -19
- package/server/model/metrics.js +1 -1
- package/server/model/models.js +1 -1
- package/server/model/profiles.js +1 -1
- package/server/model/research.js +12 -36
- package/server/model/vocab.js +1 -1
- package/server/news/index.js +1 -1
- package/server/notify/index.js +1 -1
- package/server/params-meta.js +4 -3
- package/server/settings.js +1 -1
- package/server/strategist/analytics.js +2 -2
- package/server/strategist/index.js +2 -2
- package/server/strategist/tools.js +1 -1
- package/server/util.js +1 -1
package/public/landing.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- Copyright (c) 2026 Roman A. Perepelkin.
|
|
1
|
+
<!-- Copyright (c) 2026 Roman A. Perepelkin. All rights reserved. Proprietary software. -->
|
|
2
2
|
<!doctype html>
|
|
3
3
|
<html lang="ru">
|
|
4
4
|
<head>
|
|
@@ -210,7 +210,8 @@
|
|
|
210
210
|
.bcard p { margin: 0; color: var(--muted); font-size: 14.5px; line-height: 1.55; }
|
|
211
211
|
|
|
212
212
|
/* ---------- Живая витрина стратегий ---------- */
|
|
213
|
-
.showcase-grid { display: grid; grid-template-columns: repeat(
|
|
213
|
+
.showcase-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; align-items: stretch; }
|
|
214
|
+
@media (max-width: 960px) { .showcase-grid { grid-template-columns: 1fr; } }
|
|
214
215
|
.sc-card {
|
|
215
216
|
background: var(--panel); border: 1px solid var(--line);
|
|
216
217
|
border-radius: 18px; padding: 24px;
|
|
@@ -231,7 +232,8 @@
|
|
|
231
232
|
padding: 8px 18px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px; transition: .15s;
|
|
232
233
|
}
|
|
233
234
|
.period-seg button.active { background: linear-gradient(135deg, var(--accent), var(--accent2)); color: #fff; }
|
|
234
|
-
.plans { display: grid; grid-template-columns: repeat(
|
|
235
|
+
.plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; align-items: stretch; }
|
|
236
|
+
@media (max-width: 960px) { .plans { grid-template-columns: 1fr; } }
|
|
235
237
|
.plan {
|
|
236
238
|
background: var(--panel); border: 1px solid var(--line);
|
|
237
239
|
border-radius: 18px; padding: 30px 26px;
|
|
@@ -244,19 +246,32 @@
|
|
|
244
246
|
border-color: rgba(96, 165, 250, 0.55);
|
|
245
247
|
box-shadow: 0 20px 50px -12px rgba(37, 99, 235, 0.35);
|
|
246
248
|
}
|
|
249
|
+
.plan-vip-wide {
|
|
250
|
+
margin-top: 22px;
|
|
251
|
+
background: linear-gradient(135deg, rgba(120, 53, 15, 0.22), rgba(17, 24, 39, 0.94));
|
|
252
|
+
border: 1px solid rgba(245, 158, 11, 0.5);
|
|
253
|
+
border-radius: 18px; padding: 28px 32px;
|
|
254
|
+
display: grid; grid-template-columns: 1fr 1.3fr auto; gap: 28px; align-items: center;
|
|
255
|
+
position: relative;
|
|
256
|
+
box-shadow: 0 20px 50px -15px rgba(217, 119, 6, 0.25);
|
|
257
|
+
}
|
|
258
|
+
@media (max-width: 960px) { .plan-vip-wide { grid-template-columns: 1fr; } }
|
|
247
259
|
.pop-ribbon {
|
|
248
260
|
position: absolute; top: 14px; right: 16px;
|
|
249
261
|
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
250
262
|
color: #fff; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px;
|
|
251
263
|
text-transform: uppercase; letter-spacing: 0.5px;
|
|
252
264
|
}
|
|
253
|
-
.plan h3 { margin: 0 0 4px; font-size: 21px; }
|
|
265
|
+
.plan h3, .plan-vip-wide h3 { margin: 0 0 4px; font-size: 21px; }
|
|
254
266
|
.plan-desc { font-size: 13.5px; color: var(--muted); margin-bottom: 14px; }
|
|
255
267
|
.price { font-size: 38px; font-weight: 800; margin: 6px 0 18px; letter-spacing: -1px; }
|
|
256
268
|
.price span { font-size: 14px; font-weight: 400; color: var(--muted); letter-spacing: 0; }
|
|
257
|
-
.plan ul { list-style: none; padding: 0; margin: 0 0 26px; font-size: 14.5px; color: #cbd5e1; flex: 1; }
|
|
258
|
-
.plan
|
|
259
|
-
|
|
269
|
+
.plan ul, .plan-vip-wide ul { list-style: none; padding: 0; margin: 0 0 26px; font-size: 14.5px; color: #cbd5e1; flex: 1; }
|
|
270
|
+
.plan-vip-wide ul { margin: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px 20px; }
|
|
271
|
+
@media (max-width: 720px) { .plan-vip-wide ul { grid-template-columns: 1fr; } }
|
|
272
|
+
.plan li, .plan-vip-wide li { margin: 10px 0; position: relative; padding-left: 22px; line-height: 1.45; }
|
|
273
|
+
.plan-vip-wide li { margin: 0; }
|
|
274
|
+
.plan li::before, .plan-vip-wide li::before { content: "✓"; color: var(--green); font-weight: 800; position: absolute; left: 0; top: 0; }
|
|
260
275
|
|
|
261
276
|
/* ---------- Анимация появления при прокрутке ---------- */
|
|
262
277
|
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .6s ease, transform .6s ease; }
|
|
@@ -314,13 +329,13 @@
|
|
|
314
329
|
<nav class="nav-links">
|
|
315
330
|
<a href="#showcase">Результаты</a>
|
|
316
331
|
<a href="#how">Технология</a>
|
|
317
|
-
<a href="#
|
|
332
|
+
<a href="#paas-sec">Как работает PaaS</a>
|
|
318
333
|
<a href="#brokers">Брокеры и мессенджеры</a>
|
|
319
334
|
<a href="#pricing">Тарифы</a>
|
|
320
335
|
</nav>
|
|
321
336
|
<div style="display:flex;gap:10px" id="nav-auth">
|
|
322
337
|
<button class="btn" id="btn-login-open">Войти</button>
|
|
323
|
-
<a class="btn primary" href="#
|
|
338
|
+
<a class="btn primary" href="#pricing" id="btn-nav-cta">Открыть терминал →</a>
|
|
324
339
|
</div>
|
|
325
340
|
</div>
|
|
326
341
|
</header>
|
|
@@ -351,8 +366,8 @@
|
|
|
351
366
|
<h1><span class="grad-text">ИИ-автопилот,</span> который читает рынок акций как единый язык</h1>
|
|
352
367
|
<p class="hero-sub">Платформа находит опережающие волны капитала между связанными бумагами, сама адаптирует пороги под комиссию вашего брокера и присылает готовые заявки в <b>Telegram и MAX</b> с подтверждением в 1 клик.</p>
|
|
353
368
|
<div class="hero-actions">
|
|
354
|
-
<button class="btn primary lg" id="btn-hero-start"
|
|
355
|
-
<a class="btn lg" href="#
|
|
369
|
+
<button class="btn primary lg" id="btn-hero-start">Попробовать бесплатно</button>
|
|
370
|
+
<a class="btn lg" href="#pricing">Выбрать тариф PaaS →</a>
|
|
356
371
|
</div>
|
|
357
372
|
<div class="mini-stats">
|
|
358
373
|
<div class="mini-stat"><div class="num pos">+95% … +422%</div><div class="lbl">на истории 2024–2026 против −23,6% у индекса Мосбиржи</div></div>
|
|
@@ -455,26 +470,27 @@
|
|
|
455
470
|
</div>
|
|
456
471
|
</section>
|
|
457
472
|
|
|
458
|
-
<section id="
|
|
473
|
+
<section id="paas-sec" class="reveal">
|
|
459
474
|
<div class="sec-head">
|
|
460
|
-
<div class="sec-tag"
|
|
461
|
-
<h2
|
|
462
|
-
<p class="sec-sub"
|
|
475
|
+
<div class="sec-tag">Облачная PaaS-инфраструктура · Без установки на компьютер</div>
|
|
476
|
+
<h2>От исследования стратегии до реальных заявок за 1 минуту</h2>
|
|
477
|
+
<p class="sec-sub">Вся вычислительная мощность и ИИ-управляющий «Стратег» работают в защищённом облаке 24/7. Вы управляете счетами прямо из браузера или со смартфона в Telegram и MAX.</p>
|
|
463
478
|
</div>
|
|
464
479
|
<div class="bento">
|
|
465
|
-
<div class="bcard
|
|
480
|
+
<div class="bcard">
|
|
466
481
|
<div class="bicon">1</div>
|
|
467
|
-
<h3
|
|
468
|
-
<p
|
|
482
|
+
<h3>Тестируйте без ограничений</h3>
|
|
483
|
+
<p>Даже на бесплатном тарифе вы можете проводить эксперименты на полной мощности ядра, проверять стратегии на сдвигах истории и видеть весь потенциал доходности.</p>
|
|
469
484
|
</div>
|
|
470
|
-
<div class="bcard
|
|
485
|
+
<div class="bcard">
|
|
471
486
|
<div class="bicon">2</div>
|
|
472
|
-
<h3
|
|
473
|
-
<p
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
487
|
+
<h3>Выберите мощность тарифа</h3>
|
|
488
|
+
<p>От консервативного тарифа «Инвестор» (работа только в рост до 50% годовых) и «Профи» (покупки + шорты до 100% годовых) до полного форсажа «VIP» без потолка доходности.</p>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="bcard">
|
|
491
|
+
<div class="bicon">3</div>
|
|
492
|
+
<h3>Торгуйте с жёсткими предохранителями</h3>
|
|
493
|
+
<p>Подключите счёт Т-Банка, БКС, Финам или Алор в режиме «Подтверждение в 1 клик в Telegram / MAX» или «Полный автомат» с защитой стартового портфеля.</p>
|
|
478
494
|
</div>
|
|
479
495
|
</div>
|
|
480
496
|
</section>
|
|
@@ -519,58 +535,78 @@
|
|
|
519
535
|
</div>
|
|
520
536
|
<div class="plans">
|
|
521
537
|
<div class="plan">
|
|
522
|
-
<h3>Старт (
|
|
523
|
-
<div class="plan-desc">Для
|
|
524
|
-
<div class="price">0 ₽ <span>/
|
|
538
|
+
<h3>Старт (Тест-драйв)</h3>
|
|
539
|
+
<div class="plan-desc">Для тестирования любых стратегий и проверки сигналов на виртуальном счёте без реальной торговли</div>
|
|
540
|
+
<div class="price">0 ₽ <span>/ бесплатно</span></div>
|
|
525
541
|
<ul>
|
|
526
|
-
<li
|
|
527
|
-
<li
|
|
528
|
-
<li
|
|
529
|
-
<li
|
|
542
|
+
<li><b>Полный доступ к Экспериментам</b>: тестируйте стратегии любой доходности</li>
|
|
543
|
+
<li>1 виртуальный демо-счёт в реальном времени со сверкой коридора</li>
|
|
544
|
+
<li>Карта рынка, новости Мосбиржи и ИИ-управляющий «Стратег»</li>
|
|
545
|
+
<li>Без подключения реальных брокерских счетов</li>
|
|
530
546
|
</ul>
|
|
531
|
-
<button class="btn" type="button" id="btn-free-start"
|
|
547
|
+
<button class="btn" type="button" id="btn-free-start">Начать тестирование</button>
|
|
532
548
|
</div>
|
|
533
549
|
|
|
534
|
-
<div class="plan
|
|
535
|
-
<
|
|
536
|
-
<
|
|
537
|
-
<div class="plan-desc">Для активного управления своим счётом с подтверждением в Telegram и MAX</div>
|
|
550
|
+
<div class="plan">
|
|
551
|
+
<h3>Инвестор</h3>
|
|
552
|
+
<div class="plan-desc">Консервативная реальная торговля только от покупки (без шортов)</div>
|
|
538
553
|
<div class="price" id="price-investor">3 900 ₽ <span>/ мес</span></div>
|
|
539
554
|
<ul>
|
|
540
|
-
<li
|
|
541
|
-
<li
|
|
542
|
-
<li
|
|
543
|
-
<li
|
|
544
|
-
<li
|
|
555
|
+
<li>До <b>2 брокерских счетов</b> (Т-Банк, БКС, Финам, Алор)</li>
|
|
556
|
+
<li>Лимит капитала в торговле: <b>до 1 000 000 ₽</b> на каждом счёте</li>
|
|
557
|
+
<li>Режим <b>«Только в рост (без шорта)»</b>, консервативный слот</li>
|
|
558
|
+
<li>Предельный уровень доходности стратегии: <b>до 50% годовых</b></li>
|
|
559
|
+
<li>Подтверждение заявок в 1 клик в <b>Telegram и MAX</b></li>
|
|
545
560
|
</ul>
|
|
546
|
-
<button class="btn primary" data-buy="investor">Оформить
|
|
561
|
+
<button class="btn primary" data-buy="investor">Оформить «Инвестор»</button>
|
|
547
562
|
</div>
|
|
548
563
|
|
|
549
|
-
<div class="plan">
|
|
550
|
-
<
|
|
551
|
-
<
|
|
564
|
+
<div class="plan featured">
|
|
565
|
+
<div class="pop-ribbon">Оптимальный выбор</div>
|
|
566
|
+
<h3>Профи</h3>
|
|
567
|
+
<div class="plan-desc">Заработок и на росте, и на падении акций (с шортами)</div>
|
|
552
568
|
<div class="price" id="price-pro">9 900 ₽ <span>/ мес</span></div>
|
|
553
569
|
<ul>
|
|
554
|
-
<li>До 5 брокерских
|
|
555
|
-
<li
|
|
556
|
-
<li
|
|
557
|
-
<li
|
|
570
|
+
<li>До <b>5 брокерских счетов</b> параллельно</li>
|
|
571
|
+
<li>Лимит капитала в торговле: <b>до 10 000 000 ₽</b> на каждом счёте</li>
|
|
572
|
+
<li>Режим <b>«В рост и в падение (с шортом)»</b> с адаптацией к индексу</li>
|
|
573
|
+
<li>Предельный уровень доходности стратегии: <b>до 100% годовых</b></li>
|
|
574
|
+
<li>До 5 виртуальных счетов и 500 экспериментов в сутки</li>
|
|
558
575
|
</ul>
|
|
559
|
-
<button class="btn primary" data-buy="pro">Оформить
|
|
576
|
+
<button class="btn primary" data-buy="pro">Оформить «Профи»</button>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
|
|
580
|
+
<div class="plan-vip-wide">
|
|
581
|
+
<div>
|
|
582
|
+
<span class="pop-ribbon" style="position:static;display:inline-block;margin-bottom:8px;background:linear-gradient(135deg,#d97706,#f59e0b)">Полная институциональная мощность</span>
|
|
583
|
+
<h3 style="font-size:24px;margin-bottom:6px">👑 Тариф VIP (Без ограничений)</h3>
|
|
584
|
+
<div class="plan-desc" style="margin-bottom:10px">Индивидуальный контур без ограничений по доходности, числу счетов и объёму капитала</div>
|
|
585
|
+
<div style="font-size:26px;font-weight:800;color:#fbbf24">По согласованию</div>
|
|
586
|
+
</div>
|
|
587
|
+
<ul>
|
|
588
|
+
<li><b>Без ограничений</b> по числу брокерских счетов и сумме капитала на каждом счёте</li>
|
|
589
|
+
<li><b>Без потолка годовой доходности</b> (100% мощности лучших экспериментов)</li>
|
|
590
|
+
<li>Включён <b>VIP-форсаж</b>: внутричасовой перехват <code>H/2</code> + динамическая концентрация <code>Conviction Sizing</code></li>
|
|
591
|
+
<li>Персональный VIP-токен активации от администратора платформы</li>
|
|
592
|
+
</ul>
|
|
593
|
+
<div style="display:flex;flex-direction:column;gap:10px;min-width:220px">
|
|
594
|
+
<button class="btn primary" type="button" id="btn-vip-req" style="background:linear-gradient(135deg,#d97706,#f59e0b);justify-content:center">Согласовать условия VIP</button>
|
|
595
|
+
<button class="btn" type="button" id="btn-vip-token-open" style="font-size:13px;padding:9px 14px;justify-content:center">У меня есть VIP-токен</button>
|
|
560
596
|
</div>
|
|
561
597
|
</div>
|
|
562
598
|
</section>
|
|
563
599
|
|
|
564
600
|
<footer>
|
|
565
601
|
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px">
|
|
566
|
-
<div>© 2026
|
|
602
|
+
<div>© 2026 Автор и правообладатель: Перепелкин Р.А. · Оператор платформы: ООО «БИС» («Бизнес Интер Софт», г. Рязань) · Все права защищены</div>
|
|
567
603
|
<div style="display:flex;gap:18px">
|
|
568
604
|
<a href="/legal#offer" style="color:#93c5fd">Публичная оферта</a>
|
|
569
605
|
<a href="/legal#privacy" style="color:#93c5fd">Политика конфиденциальности</a>
|
|
570
606
|
<a href="/legal#risks" style="color:#93c5fd">Декларация о рисках</a>
|
|
571
607
|
</div>
|
|
572
608
|
</div>
|
|
573
|
-
<div style="margin-top:8px"
|
|
609
|
+
<div style="margin-top:8px">Проприетарное программное обеспечение (PaaS). Сервис является аналитическим программно-вычислительным комплексом и не является индивидуальной инвестиционной рекомендацией или доверительным управлением. Результаты на исторических данных не гарантируют доходности в будущем.</div>
|
|
574
610
|
</footer>
|
|
575
611
|
</div>
|
|
576
612
|
|
|
@@ -625,6 +661,64 @@
|
|
|
625
661
|
</div>
|
|
626
662
|
</div>
|
|
627
663
|
|
|
664
|
+
<!-- Модальное окно подписания Договора-оферты и Декларации о рисках перед подпиской -->
|
|
665
|
+
<div class="modal" id="offer-modal" hidden>
|
|
666
|
+
<div class="modal-box" style="max-width:660px">
|
|
667
|
+
<h3>Договор публичной оферты и Декларация о рисках</h3>
|
|
668
|
+
<div style="background:#070b14;border:1px solid var(--line);border-radius:10px;padding:14px 16px;max-height:260px;overflow-y:auto;font-size:13px;line-height:1.55;color:#cbd5e1;margin-bottom:14px">
|
|
669
|
+
<p style="margin-top:0"><b>1. Предмет договора:</b> Настоящая Публичная оферта регулирует предоставление доступа к облачной аналитической платформе «НейроАльфа» (PaaS/SaaS) согласно выбранному тарифному плану («Старт», «Инвестор», «Профи», «VIP»).</p>
|
|
670
|
+
<p><b>2. Правовой статус (отсутствие ИИР и ДУ):</b> Сервис является исключительно программно-техническим средством (калькулятором статистических закономерностей). Исполнитель <b>не осуществляет инвестиционное консультирование</b>, не даёт индивидуальных инвестиционных рекомендаций (ФЗ № 39-ФЗ) и не осуществляет доверительное управление. Все сделки совершаются Пользователем самостоятельно на его личном брокерском счёте.</p>
|
|
671
|
+
<p><b>3. Параметры тарифов и предельные уровни доходности:</b>
|
|
672
|
+
<br>• <b>«Старт» (0 ₽)</b> — тестирование в Экспериментах и виртуальный счёт без реальной торговли;
|
|
673
|
+
<br>• <b>«Инвестор»</b> — до 2 брокерских счетов, лимит до 1 000 000 ₽ на каждом, торговля только от покупки («Только в рост (без шорта)»), предельный целевой темп доходности стратегии до 50% годовых;
|
|
674
|
+
<br>• <b>«Профи»</b> — до 5 брокерских счетов, лимит до 10 000 000 ₽ на каждом, торговля в рост и в падение (с шортами), предельный целевой темп доходности до 100% годовых;
|
|
675
|
+
<br>• <b>«VIP»</b> — по индивидуальному согласованию (VIP-токен), без ограничений по числу счетов, сумме капитала и потолку годовой доходности (включая полушаговое сопровождение H/2 и динамическую концентрацию слота).
|
|
676
|
+
</p>
|
|
677
|
+
<p><b>4. Запрет автоследования и ретрансляции сигналов:</b> Пользователю строго запрещается подключать к платформе счета, участвующие в сервисах автоследования или копирования сделок (как в роли ведущего/автора стратегии, так и в роли подписчика), а также передавать или перепродавать сигналы третьим лицам. При обнаружении признаков автоследования счёт автоматически блокируется.</p>
|
|
678
|
+
<p style="margin-bottom:0"><b>5. Декларация о рисках:</b> Торговля ценными бумагами сопряжена с риском потери части или всего инвестированного капитала. Результаты на исторических данных не гарантируют доходности в будущем. Полный текст: <a href="/legal" target="_blank" style="color:#93c5fd">открыть в новой вкладке →</a></p>
|
|
679
|
+
</div>
|
|
680
|
+
<div style="display:grid;gap:10px;font-size:13px;color:#e2e8f0;margin-bottom:16px">
|
|
681
|
+
<label style="display:flex;gap:10px;align-items:flex-start;cursor:pointer">
|
|
682
|
+
<input type="checkbox" id="offer-chk-1" style="width:17px;height:17px;margin-top:2px;flex:none">
|
|
683
|
+
<span>Я прочитал(а) и безоговорочно принимаю условия <a href="/legal#offer" target="_blank" style="color:#93c5fd">Договора публичной оферты</a> и <a href="/legal#privacy" target="_blank" style="color:#93c5fd">Политики обработки персональных данных</a>.</span>
|
|
684
|
+
</label>
|
|
685
|
+
<label style="display:flex;gap:10px;align-items:flex-start;cursor:pointer">
|
|
686
|
+
<input type="checkbox" id="offer-chk-2" style="width:17px;height:17px;margin-top:2px;flex:none">
|
|
687
|
+
<span>Я ознакомлен(а) с <a href="/legal#risks" target="_blank" style="color:#93c5fd">Декларацией о рисках</a>, понимаю, что сервис не является индивидуальной инвестиционной рекомендацией, и обязуюсь <b>не использовать автоследование</b> на подключаемых счетах.</span>
|
|
688
|
+
</label>
|
|
689
|
+
</div>
|
|
690
|
+
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap">
|
|
691
|
+
<button class="btn" type="button" id="btn-offer-decline">Отказ (Отмена)</button>
|
|
692
|
+
<button class="btn primary" type="button" id="btn-offer-accept" disabled>Подписать оферту и продолжить →</button>
|
|
693
|
+
</div>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<!-- Модальное окно согласования VIP и ввода VIP-токена -->
|
|
698
|
+
<div class="modal" id="vip-modal" hidden>
|
|
699
|
+
<div class="modal-box" style="max-width:480px">
|
|
700
|
+
<h3>Тариф VIP · Индивидуальные условия</h3>
|
|
701
|
+
<p style="font-size:13.5px;color:var(--muted);margin:0 0 14px">Тариф VIP снимает все ограничения по числу счетов, объёму торгового капитала и потолку годовой доходности, активируя полный форсаж ядра (<code>H/2</code> + <code>Conviction Sizing</code>).</p>
|
|
702
|
+
<form id="vip-req-form" style="margin-bottom:18px;padding-bottom:16px;border-bottom:1px solid var(--line)">
|
|
703
|
+
<div style="font-size:13px;font-weight:600;color:#f59e0b">1. Оставить заявку на согласование условий VIP:</div>
|
|
704
|
+
<input name="name" placeholder="Ваше имя" style="width:100%;padding:10px 12px;border-radius:9px;border:1px solid var(--line);background:var(--bg);color:var(--text)">
|
|
705
|
+
<input name="contact" required placeholder="Ваш Telegram (@username) или телефон для связи" style="width:100%;padding:10px 12px;border-radius:9px;border:1px solid var(--line);background:var(--bg);color:var(--text)">
|
|
706
|
+
<input name="capitalRub" placeholder="Планируемый объём капитала (например, 25 млн ₽)" style="width:100%;padding:10px 12px;border-radius:9px;border:1px solid var(--line);background:var(--bg);color:var(--text)">
|
|
707
|
+
<button class="btn primary" type="submit" style="background:linear-gradient(135deg,#d97706,#f59e0b)">Отправить заявку администратору</button>
|
|
708
|
+
</form>
|
|
709
|
+
<form id="vip-token-form">
|
|
710
|
+
<div style="font-size:13px;font-weight:600;color:#93c5fd">2. Уже получили персональный VIP-токен от администратора?</div>
|
|
711
|
+
<div style="display:flex;gap:8px">
|
|
712
|
+
<input name="code" required placeholder="VIP-…" style="flex:1;padding:10px 12px;border-radius:9px;border:1px solid var(--line);background:var(--bg);color:var(--text)">
|
|
713
|
+
<button class="btn primary" type="submit">Активировать VIP</button>
|
|
714
|
+
</div>
|
|
715
|
+
</form>
|
|
716
|
+
<div style="margin-top:12px;text-align:right">
|
|
717
|
+
<button class="btn" type="button" id="btn-vip-close" style="border:0;color:var(--muted)">Закрыть</button>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
628
722
|
<script>
|
|
629
723
|
// ---------- 1. Анимированный граф перетоков капитала на фоне Hero ----------
|
|
630
724
|
(() => {
|
|
@@ -736,72 +830,66 @@
|
|
|
736
830
|
return;
|
|
737
831
|
}
|
|
738
832
|
cab.hidden = false;
|
|
739
|
-
const key = currentUser.apiKey || '';
|
|
740
|
-
const cmd = key ? `npx neuroalpha --key ${key}` : 'npx neuroalpha';
|
|
741
|
-
const cliEl = document.getElementById('landing-cli-cmd');
|
|
742
|
-
if (cliEl && key) cliEl.textContent = cmd;
|
|
743
833
|
const navCta = document.getElementById('btn-nav-cta');
|
|
744
|
-
if (navCta
|
|
745
|
-
|
|
746
|
-
|
|
834
|
+
if (navCta) {
|
|
835
|
+
if (currentUser.role === 'admin') {
|
|
836
|
+
navCta.href = '/admin';
|
|
837
|
+
navCta.textContent = 'Панель админа →';
|
|
838
|
+
} else {
|
|
839
|
+
navCta.href = '/';
|
|
840
|
+
navCta.textContent = 'Открыть терминал →';
|
|
841
|
+
}
|
|
747
842
|
}
|
|
748
843
|
const untilStr = currentUser.planUntil > Date.now()
|
|
749
844
|
? `оплачен до ${new Date(currentUser.planUntil).toLocaleDateString('ru-RU')}`
|
|
750
845
|
: 'бессрочно';
|
|
846
|
+
const capLim = currentUser.limits?.maxCapitalPerAccountRub
|
|
847
|
+
? (currentUser.limits.maxCapitalPerAccountRub >= 100000000 ? 'без ограничений' : `до ${(currentUser.limits.maxCapitalPerAccountRub / 1000000).toLocaleString('ru-RU')} млн ₽/счёт`)
|
|
848
|
+
: 'демо-режим';
|
|
849
|
+
const retLim = currentUser.limits?.maxAnnualReturnPct > 0
|
|
850
|
+
? `до ${currentUser.limits.maxAnnualReturnPct}% годовых`
|
|
851
|
+
: 'без ограничений (VIP-форсаж)';
|
|
751
852
|
cab.innerHTML = `<div class="cab-card">
|
|
752
853
|
<div class="cab-grid">
|
|
753
854
|
<div>
|
|
754
|
-
<div class="sec-tag" style="margin-bottom:4px">Личный кабинет · Аккаунт активен</div>
|
|
855
|
+
<div class="sec-tag" style="margin-bottom:4px">Личный кабинет PaaS · Аккаунт активен</div>
|
|
755
856
|
<h3 style="margin:0 0 8px;font-size:22px">${currentUser.name} <span style="color:var(--muted);font-weight:400;font-size:15px">(${currentUser.email})</span></h3>
|
|
756
857
|
<div style="margin-bottom:14px;font-size:14.5px;color:#cbd5e1">
|
|
757
|
-
Тариф: <b class="pos">${currentUser.planName}</b> (${untilStr}) ·
|
|
858
|
+
Тариф: <b class="pos">${currentUser.planName}</b> (${untilStr}) · Брокерских счетов: до <b>${currentUser.limits?.maxBrokerAccounts ?? 0}</b> (${capLim}) · Предел доходности стратегии: <b>${retLim}</b> · Режим: <b>${currentUser.limits?.allowShorts ? 'в рост и в падение (с шортом)' : 'только в рост (без шорта)'}</b>.
|
|
758
859
|
</div>
|
|
759
|
-
<div style="
|
|
760
|
-
|
|
761
|
-
<
|
|
762
|
-
<button type="button" class="btn" id="cab-copy-key" style="padding:7px 14px;font-size:13px">Скопировать APIKey</button>
|
|
763
|
-
<button type="button" class="btn" id="cab-regen-key" style="padding:7px 14px;font-size:13px">Перевыпустить</button>
|
|
860
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
|
861
|
+
<input id="cab-promo-in" placeholder="Промокод или VIP-токен" style="padding:8px 12px;border-radius:9px;border:1px solid var(--line);background:var(--bg);color:var(--text);font-size:13.5px;width:220px">
|
|
862
|
+
<button type="button" class="btn" id="cab-promo-btn" style="padding:8px 14px;font-size:13px">Активировать код</button>
|
|
764
863
|
</div>
|
|
765
864
|
</div>
|
|
766
865
|
<div style="display:flex;flex-direction:column;gap:10px">
|
|
767
|
-
<
|
|
768
|
-
<div
|
|
769
|
-
<
|
|
770
|
-
<
|
|
771
|
-
</div>
|
|
772
|
-
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:4px">
|
|
773
|
-
<a class="btn primary" href="#pricing" style="flex:1">Сменить / продлить тариф</a>
|
|
774
|
-
${currentUser.role === 'admin' ? '<a class="btn" href="/admin">Консоль админа →</a><a class="btn" href="/?admin=1">Терминал →</a>' : ''}
|
|
866
|
+
<a class="btn primary lg" href="/" style="justify-content:center">Перейти в торговый терминал →</a>
|
|
867
|
+
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
|
868
|
+
<a class="btn" href="#pricing" style="flex:1">Сменить / продлить тариф</a>
|
|
869
|
+
${currentUser.role === 'admin' ? '<a class="btn" href="/admin">Консоль админа →</a>' : ''}
|
|
775
870
|
</div>
|
|
776
871
|
</div>
|
|
777
872
|
</div>
|
|
778
873
|
</div>`;
|
|
779
874
|
|
|
780
|
-
document.getElementById('cab-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
875
|
+
document.getElementById('cab-promo-btn')?.addEventListener('click', () => {
|
|
876
|
+
const code = document.getElementById('cab-promo-in')?.value.trim();
|
|
877
|
+
if (!code) return;
|
|
878
|
+
requireOfferAgreement(async () => {
|
|
879
|
+
const r = await fetch('/api/auth/redeem-promo', {
|
|
880
|
+
method: 'POST',
|
|
881
|
+
headers: { 'Content-Type': 'application/json' },
|
|
882
|
+
body: JSON.stringify({ code }),
|
|
883
|
+
});
|
|
884
|
+
const j = await r.json();
|
|
885
|
+
if (!r.ok) return alert(j.error || 'Ошибка активации кода');
|
|
886
|
+
currentUser = j.user;
|
|
887
|
+
renderCabinet();
|
|
888
|
+
alert(`Код успешно активирован! Ваш тариф: ${currentUser.planName}`);
|
|
889
|
+
});
|
|
795
890
|
});
|
|
796
891
|
}
|
|
797
892
|
|
|
798
|
-
document.getElementById('btn-copy-landing-cli')?.addEventListener('click', (e) => {
|
|
799
|
-
const txt = document.getElementById('landing-cli-cmd')?.textContent || 'npx neuroalpha';
|
|
800
|
-
navigator.clipboard?.writeText(txt);
|
|
801
|
-
e.target.textContent = '✓ Скопировано';
|
|
802
|
-
setTimeout(() => { e.target.textContent = 'Скопировать'; }, 2000);
|
|
803
|
-
});
|
|
804
|
-
|
|
805
893
|
document.getElementById('oauth-btns')?.addEventListener('click', (e) => {
|
|
806
894
|
const b = e.target.closest('button[data-oauth]');
|
|
807
895
|
if (!b) return;
|
|
@@ -893,22 +981,61 @@
|
|
|
893
981
|
const { runs } = await (await fetch('/api/public/showcase')).json();
|
|
894
982
|
const box = document.getElementById('showcase-list');
|
|
895
983
|
if (!runs?.length) return;
|
|
896
|
-
|
|
984
|
+
const uniqueRuns = [];
|
|
985
|
+
const seenRet = new Set();
|
|
986
|
+
for (const x of runs) {
|
|
987
|
+
const rk = Math.round(Number(x.metrics?.returnPct || 0));
|
|
988
|
+
if (seenRet.has(rk)) continue;
|
|
989
|
+
seenRet.add(rk);
|
|
990
|
+
uniqueRuns.push(x);
|
|
991
|
+
if (uniqueRuns.length >= 3) break;
|
|
992
|
+
}
|
|
993
|
+
uniqueRuns.sort((a, b) => Number(a.metrics?.returnPct || 0) - Number(b.metrics?.returnPct || 0));
|
|
994
|
+
box.innerHTML = uniqueRuns.map((r, idx) => {
|
|
995
|
+
const from = r.period?.from || 0, till = r.period?.till || 0;
|
|
996
|
+
const years = Math.max(0.5, (till - from) / (365.25 * 86400000));
|
|
997
|
+
const ret = Number(r.metrics.returnPct || 0);
|
|
998
|
+
const cagr = years >= 0.25 && ret > -99 ? ((Math.pow(1 + ret / 100, 1 / years) - 1) * 100) : (ret / years);
|
|
999
|
+
const mktPct = Number(r.metrics.marketPct || -22.3);
|
|
1000
|
+
const isLong = r.params.modelDirection === 'long' || cagr <= 52;
|
|
1001
|
+
const isPro = !isLong && cagr <= 105;
|
|
1002
|
+
const tColor = r.tierColor || (isLong ? 'blue' : isPro ? 'indigo' : 'gold');
|
|
1003
|
+
const tBadge = r.tierBadge || (isLong ? 'Для тарифа «Инвестор»' : isPro ? 'Для тарифа «Профи»' : '👑 Для тарифа «VIP»');
|
|
1004
|
+
const tNote = r.tierNote || (isLong
|
|
1005
|
+
? 'Торговля только от покупки (без шортов) · потолок тарифа до 50% годовых · до 1 млн ₽/счёт'
|
|
1006
|
+
: isPro
|
|
1007
|
+
? 'Заработок на росте и падении акций (с шортами) · потолок тарифа до 100% годовых · до 10 млн ₽/счёт'
|
|
1008
|
+
: 'Полная мощность без потолка доходности · форсаж H/2 + концентрация слота Conviction Sizing');
|
|
1009
|
+
const badgeStyle = tColor === 'gold'
|
|
1010
|
+
? 'background:rgba(245,158,11,0.18);border:1px solid rgba(245,158,11,0.5);color:#fbbf24'
|
|
1011
|
+
: tColor === 'indigo'
|
|
1012
|
+
? 'background:rgba(99,102,241,0.2);border:1px solid rgba(129,140,248,0.5);color:#a5b4fc'
|
|
1013
|
+
: 'background:rgba(59,130,246,0.16);border:1px solid rgba(96,165,250,0.45);color:#93c5fd';
|
|
1014
|
+
const cardBorder = tColor === 'gold' ? 'border-color:rgba(245,158,11,0.4)' : '';
|
|
1015
|
+
return `<div class="sc-card" style="${cardBorder}">
|
|
1016
|
+
<div style="margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;gap:8px">
|
|
1017
|
+
<span style="display:inline-block;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:700;letter-spacing:0.3px;${badgeStyle}">${tBadge}</span>
|
|
1018
|
+
<span style="font-size:12px;color:var(--muted)">Бумаг в портфеле: ${r.params.modelTopK || 2}</span>
|
|
1019
|
+
</div>
|
|
897
1020
|
<div class="sc-head">
|
|
898
1021
|
<div>
|
|
899
|
-
<div style="font-weight:700;font-size:
|
|
900
|
-
<div style="font-size:
|
|
1022
|
+
<div style="font-weight:700;font-size:17px">${r.title}</div>
|
|
1023
|
+
<div style="font-size:12.5px;color:var(--muted);margin-top:3px">${tNote}</div>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div style="text-align:right;flex:none">
|
|
1026
|
+
<div class="sc-ret pos">${fmt(cagr, 1)}% <span style="font-size:13px;font-weight:600">/ год</span></div>
|
|
1027
|
+
<div style="font-size:12px;color:var(--muted)">всего ${fmt(ret, 1)}% за ${years.toFixed(1).replace('.', ',')} г.</div>
|
|
901
1028
|
</div>
|
|
902
|
-
<div class="sc-ret pos">${fmt(r.metrics.returnPct, 1)}%</div>
|
|
903
1029
|
</div>
|
|
904
1030
|
${sparkSvg(r.curve, r.marketCurve, idx)}
|
|
905
1031
|
<div class="sc-kpis">
|
|
906
|
-
<div><span class="muted">Индекс Мосбиржи</span><b class="neg">${fmt(
|
|
907
|
-
<div><span class="muted"
|
|
1032
|
+
<div><span class="muted">Индекс Мосбиржи</span><b class="${mktPct >= 0 ? 'pos' : 'neg'}">${fmt(mktPct, 1)}%</b></div>
|
|
1033
|
+
<div><span class="muted">Макс. просадка</span><b>${fmt(r.metrics.maxDrawdownPct, 1).replace('+','')}%</b></div>
|
|
908
1034
|
<div><span class="muted">На 1 ₽ убытка</span><b>${fmt(r.metrics.profitFactor, 2).replace('+','')} ₽</b></div>
|
|
909
1035
|
</div>
|
|
910
|
-
<a class="btn" href="#
|
|
911
|
-
</div
|
|
1036
|
+
<a class="btn" href="#pricing">Выбрать этот профиль в облаке →</a>
|
|
1037
|
+
</div>`;
|
|
1038
|
+
}).join('');
|
|
912
1039
|
} catch {}
|
|
913
1040
|
}
|
|
914
1041
|
|
|
@@ -945,20 +1072,106 @@
|
|
|
945
1072
|
document.getElementById('auth-close').addEventListener('click', () => { document.getElementById('auth-modal').hidden = true; });
|
|
946
1073
|
document.getElementById('auth-toggle').addEventListener('click', () => openAuth(!isReg));
|
|
947
1074
|
|
|
948
|
-
|
|
1075
|
+
let onOfferAccepted = null;
|
|
1076
|
+
const offerModal = document.getElementById('offer-modal');
|
|
1077
|
+
const offerChk1 = document.getElementById('offer-chk-1');
|
|
1078
|
+
const offerChk2 = document.getElementById('offer-chk-2');
|
|
1079
|
+
const offerAcceptBtn = document.getElementById('btn-offer-accept');
|
|
1080
|
+
|
|
1081
|
+
function syncOfferBtn() {
|
|
1082
|
+
if (offerAcceptBtn) offerAcceptBtn.disabled = !(offerChk1?.checked && offerChk2?.checked);
|
|
1083
|
+
}
|
|
1084
|
+
offerChk1?.addEventListener('change', syncOfferBtn);
|
|
1085
|
+
offerChk2?.addEventListener('change', syncOfferBtn);
|
|
1086
|
+
|
|
1087
|
+
function requireOfferAgreement(actionFn) {
|
|
1088
|
+
onOfferAccepted = actionFn;
|
|
1089
|
+
if (offerChk1) offerChk1.checked = false;
|
|
1090
|
+
if (offerChk2) offerChk2.checked = false;
|
|
1091
|
+
syncOfferBtn();
|
|
1092
|
+
offerModal.hidden = false;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
document.getElementById('btn-offer-decline')?.addEventListener('click', () => {
|
|
1096
|
+
offerModal.hidden = true;
|
|
1097
|
+
onOfferAccepted = null;
|
|
1098
|
+
});
|
|
1099
|
+
offerAcceptBtn?.addEventListener('click', async () => {
|
|
1100
|
+
offerAcceptBtn.disabled = true;
|
|
949
1101
|
try {
|
|
950
|
-
|
|
1102
|
+
await fetch('/api/auth/accept-offer', { method: 'POST' }).catch(() => {});
|
|
1103
|
+
offerModal.hidden = true;
|
|
1104
|
+
if (onOfferAccepted) await onOfferAccepted();
|
|
1105
|
+
} finally {
|
|
1106
|
+
onOfferAccepted = null;
|
|
1107
|
+
syncOfferBtn();
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
async function startCheckout(plan) {
|
|
1112
|
+
requireOfferAgreement(async () => {
|
|
1113
|
+
try {
|
|
1114
|
+
const res = await fetch('/api/billing/checkout', {
|
|
1115
|
+
method: 'POST',
|
|
1116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1117
|
+
body: JSON.stringify({ plan, months: selectedMonths, returnOrigin: location.origin }),
|
|
1118
|
+
});
|
|
1119
|
+
const j = await res.json();
|
|
1120
|
+
if (!res.ok) throw new Error(j.error || 'Ошибка перехода к оплате');
|
|
1121
|
+
if (j.confirmationUrl) location.href = j.confirmationUrl;
|
|
1122
|
+
} catch (e) {
|
|
1123
|
+
alert(e.message);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
document.getElementById('btn-vip-req')?.addEventListener('click', () => {
|
|
1129
|
+
document.getElementById('vip-modal').hidden = false;
|
|
1130
|
+
});
|
|
1131
|
+
document.getElementById('btn-vip-token-open')?.addEventListener('click', () => {
|
|
1132
|
+
if (!currentUser) { openAuth(true); return; }
|
|
1133
|
+
document.getElementById('vip-modal').hidden = false;
|
|
1134
|
+
document.querySelector('#vip-token-form input[name=code]')?.focus();
|
|
1135
|
+
});
|
|
1136
|
+
document.getElementById('btn-vip-close')?.addEventListener('click', () => {
|
|
1137
|
+
document.getElementById('vip-modal').hidden = true;
|
|
1138
|
+
});
|
|
1139
|
+
document.getElementById('vip-req-form')?.addEventListener('submit', async (e) => {
|
|
1140
|
+
e.preventDefault();
|
|
1141
|
+
const f = e.target.elements;
|
|
1142
|
+
const res = await fetch('/api/auth/vip-request', {
|
|
1143
|
+
method: 'POST',
|
|
1144
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1145
|
+
body: JSON.stringify({ name: f.name.value, contact: f.contact.value, capitalRub: f.capitalRub.value }),
|
|
1146
|
+
});
|
|
1147
|
+
const j = await res.json();
|
|
1148
|
+
if (!res.ok) return alert(j.error || 'Ошибка отправки заявки');
|
|
1149
|
+
document.getElementById('vip-modal').hidden = true;
|
|
1150
|
+
alert('Заявка на тариф VIP отправлена администратору! Мы свяжемся с вами для согласования условий и выдачи персонального VIP-токена.');
|
|
1151
|
+
});
|
|
1152
|
+
document.getElementById('vip-token-form')?.addEventListener('submit', (e) => {
|
|
1153
|
+
e.preventDefault();
|
|
1154
|
+
const code = e.target.elements.code.value.trim();
|
|
1155
|
+
if (!code) return;
|
|
1156
|
+
if (!currentUser) {
|
|
1157
|
+
document.getElementById('vip-modal').hidden = true;
|
|
1158
|
+
openAuth(true);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
document.getElementById('vip-modal').hidden = true;
|
|
1162
|
+
requireOfferAgreement(async () => {
|
|
1163
|
+
const r = await fetch('/api/auth/redeem-promo', {
|
|
951
1164
|
method: 'POST',
|
|
952
1165
|
headers: { 'Content-Type': 'application/json' },
|
|
953
|
-
body: JSON.stringify({
|
|
1166
|
+
body: JSON.stringify({ code }),
|
|
954
1167
|
});
|
|
955
|
-
const j = await
|
|
956
|
-
if (!
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
alert(
|
|
960
|
-
}
|
|
961
|
-
}
|
|
1168
|
+
const j = await r.json();
|
|
1169
|
+
if (!r.ok) return alert(j.error || 'Не удалось активировать VIP-токен');
|
|
1170
|
+
currentUser = j.user;
|
|
1171
|
+
renderCabinet();
|
|
1172
|
+
alert(`Персональный VIP-токен активирован! Ваш тариф: ${currentUser.planName}`);
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
962
1175
|
|
|
963
1176
|
document.querySelectorAll('[data-buy]').forEach((b) => b.addEventListener('click', () => {
|
|
964
1177
|
const plan = b.dataset.buy;
|