neuroalpha 0.6.5 → 0.6.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuroalpha",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
5
5
  "author": "Roman A. Perepelkin",
6
6
  "license": "UNLICENSED",
@@ -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, symbolEdgeMult, isStockQuarantined, canEnterIntraday, shouldExitBeforeNight } from '../model/edge.js';
15
15
  import { liveDividendGuard } from '../model/dividends.js';
16
- import { STRATEGY_POLICY, sessionEdgeWarning, evaluateProfitLock, protectiveStopPct, tierSizeMult, MAX_LEVERAGE } from '../model/policy.js';
16
+ import { STRATEGY_POLICY, sessionEdgeWarning, evaluateProfitLock, protectiveStopPct, exitHoldRatio, tierSizeMult, 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';
@@ -2147,10 +2147,15 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
2147
2147
  const heldMs = effEntryTs > 0 ? Math.max(0, nowTs - effEntryTs) : 0;
2148
2148
  const heldMin = heldMs / 60000;
2149
2149
  const horizonMin = Math.max(90, effHorizon * 60);
2150
- // Плавный переход порога удержания от мягкого (0.35 до 45 мин) к плотному (0.95 к концу горизонта 2.5 ч) без резкой «ступеньки» на 65-й минуте
2150
+ // Плавный переход порога удержания от мягкого (0.35 до 45 мин) к динамическому плотному (0.95 × clamp(realizedBarPct / sigmaBarPct, 0.75, 1.25) к концу горизонта 2.5 ч)
2151
2151
  const symMult = r.edgeMult || symbolEdgeMult(sym);
2152
- const rampProgress = autoAdapt ? (heldMin <= 45 ? 0 : Math.min(1, (heldMin - 45) / (horizonMin - 45))) : 1;
2153
- const rampedExitThr = earlyExitThresholdPct + (exitThresholdPct - earlyExitThresholdPct) * rampProgress;
2152
+ const holdRatio = exitHoldRatio(heldMin, horizonMin, {
2153
+ sigmaBarPct: Number(r.day?.sigmaBarPct || r.sigmaPct) || 0,
2154
+ realizedBarPct: Number(r.day?.realizedBarPct) || 0,
2155
+ baseEndRatio: exitRatio,
2156
+ autoAdapt,
2157
+ });
2158
+ const rampedExitThr = round((fc.thresholdPct || 0) * holdRatio, 3);
2154
2159
  const curExitThr = round(rampedExitThr * symMult, 3);
2155
2160
  const softExitThr = round(earlyExitThresholdPct * symMult, 3);
2156
2161
  const nightExit = !onlyEntries && autoAdapt && shouldExitBeforeNight(nowTs, effEntryTs, stepBars, BAR_INTERVAL);
@@ -34,6 +34,10 @@ export const STRATEGY_POLICY = Object.freeze({
34
34
  // паузы повторного входа после выхода, минуты
35
35
  cooldownMin: 30,
36
36
  stopCooldownMin: 60,
37
+ // динамическая подстройка верхнего порога удержания (0.95) по фактической внутридневной волатильности акции (realizedBarPct / sigmaBarPct):
38
+ // в спокойный день (множитель до 0.75×) не вытряхивает позицию раньше времени, в турбулентный день (до 1.25×) быстрее фиксирует выход при затухании прогноза.
39
+ // Проверка 09.11.2026 (06.2024–11.2026, плечо 3×): CAGR 58,6%/52,1% по половинам (весь 55,1%, PF 1,97, DD 6,4%) против 57,5%/48,1% (весь 52,6%, PF 1,89, DD 6,8%).
40
+ exitVolClamp: Object.freeze([0.75, 1.25]),
37
41
  });
38
42
 
39
43
  // ---------- окна начала и конца сессий (широкий спред, гэпы, аукционы) ----------
@@ -95,6 +99,22 @@ export function protectiveStopPct(side, { sigmaBarPct = 0, realizedBarPct = 0, h
95
99
  return side === 'long' ? (st.longPct ?? 7.0) : (st.shortPct ?? 7.0);
96
100
  }
97
101
 
102
+ // ---------- порог удержания (выхода по ослаблению прогноза) ----------
103
+ // Первые 45 минут действует мягкий порог 0.35, затем к концу горизонта (horizonMin) он плавно поднимается к baseEndRatio (0.95),
104
+ // масштабированному по отношению фактического сегодняшнего размаха свечей бумаги (realizedBarPct) к её обычной волатильности (sigmaBarPct) в коридоре [0.75..1.25].
105
+ export function exitHoldRatio(heldMin, horizonMin, { sigmaBarPct = 0, realizedBarPct = 0, baseEndRatio = 0.95, autoAdapt = true } = {}, policy = STRATEGY_POLICY) {
106
+ if (!autoAdapt) return baseEndRatio;
107
+ const startRatio = 0.35;
108
+ const ramp = heldMin <= 45 ? 0 : Math.min(1, (heldMin - 45) / Math.max(1, horizonMin - 45));
109
+ let endRatio = baseEndRatio;
110
+ const clamp = policy?.exitVolClamp;
111
+ if (Array.isArray(clamp) && clamp.length === 2 && sigmaBarPct > 0 && realizedBarPct > 0) {
112
+ const vr = Math.max(clamp[0], Math.min(clamp[1], realizedBarPct / sigmaBarPct));
113
+ endRatio = baseEndRatio * vr;
114
+ }
115
+ return startRatio + (endRatio - startRatio) * ramp;
116
+ }
117
+
98
118
  // ---------- плечо и множители размера по эшелонам ----------
99
119
  export const MAX_LEVERAGE = 7;
100
120
  export function tierSizeMult(symbol, leverage = 1, policy = STRATEGY_POLICY) {
@@ -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, targetPositionRub, counterTrendBlocked, thesisStopPx, dayContext } from './policy.js';
20
+ import { STRATEGY_POLICY, MAX_LEVERAGE, sessionEdgeWarning, profitLockLevels, protectiveStopPct, exitHoldRatio, targetPositionRub, counterTrendBlocked, thesisStopPx, dayContext } from './policy.js';
21
21
 
22
22
  // Выравнивание внешнего ряда (свечи IMOEXF, дневные цены LQDT) по шкале датасета с переносом последней цены
23
23
  export function alignSeries(timeline, rows) {
@@ -245,8 +245,10 @@ export function simulateStrategy({ ds, wf, p, policy = STRATEGY_POLICY, capital
245
245
  if (pos.side === 'long' ? fl.crash : fl.squeeze) { exitsAt.push([i, 'Защита от сквиза/обвала', 'regime']); continue; }
246
246
  if (policy.signalExit === false) continue;
247
247
  const heldMin = barsHeldNext * BAR_INTERVAL;
248
- const ramp = heldMin <= 45 ? 0 : Math.min(1, (heldMin - 45) / (horizonMin - 45));
249
- const thr = (0.35 + (0.95 - 0.35) * ramp) * need * mult[i] * tierScale(i);
248
+ const sigmaBarPct = sg[i] * 100;
249
+ const ctx = dayContext(ds, i, t, sigmaBarPct);
250
+ const holdRatio = exitHoldRatio(heldMin, horizonMin, { sigmaBarPct, realizedBarPct: ctx.realizedBarPct, baseEndRatio: 0.95, autoAdapt: true }, policy);
251
+ const thr = holdRatio * need * mult[i] * tierScale(i);
250
252
  const soft = 0.35 * need * mult[i] * tierScale(i);
251
253
  const e = edge(i);
252
254
  const signed = pos.side === 'long' ? e : -e;