neuroalpha 0.6.9 → 0.6.10

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.9",
3
+ "version": "0.6.10",
4
4
  "description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
5
5
  "author": "Roman A. Perepelkin",
6
6
  "license": "UNLICENSED",
@@ -12,6 +12,7 @@ import { strategyDefaults } from '../config.js';
12
12
  import { ENTRY_EDGE_MULT, TIER1_LIQUID_SYMBOLS, symbolEdgeMult, computeTokenEdgeMults, QUARANTINE_DECAY, isStockQuarantined, canEnterIntraday, shouldExitBeforeNight, isRegimeShocked, isMarketDown } from './edge.js';
13
13
  import { buildDividendCalendar, nextDividendAt, dividendRules, DIV_TAX } from './dividends.js';
14
14
  import { simulateStrategy, alignSeries } from './simulator.js';
15
+ import { STRATEGY_POLICY, exitHoldRatio } from './policy.js';
15
16
  import { ensureLqdtHistory } from '../data/lqdt.js';
16
17
 
17
18
  const DAY = 86400000;
@@ -38,7 +39,8 @@ export { isMarketDown };
38
39
  // 15: защита от биржевых планок (high === low) и блокировка утренних шортов 2-го эшелона в первые 20 минут сессии (07:00–07:20 МСК)
39
40
  // 16: главная оценка — симулятор с правилами живого контура (simulator.js), аварийный стоп 7%
40
41
  // 17: капитал в LQDT как залог, половинный размер 2-го эшелона, строки с плечом 3 и 5
41
- const DATA_VERSION = 17;
42
+ // 18: глубина сопровождения позиции и вечернего окна входов holdBars=13 (130 мин) во всех контурах симуляции
43
+ const DATA_VERSION = 18;
42
44
 
43
45
  const AUTO_LOCKED = { modelHorizon: 2.5, modelMinEdge: 2.5, modelExitRatio: 0.95 };
44
46
  function reuseParamsKey(params) {
@@ -395,7 +397,9 @@ async function run(runId, p, state) {
395
397
  const stockPnl = new Float64Array(N), stockCnt = new Float64Array(N);
396
398
  const stockAllowed = (i, edgeRatio = 0) => !autoAdapt || !isStockQuarantined(stockCnt[i], stockPnl[i], edgeRatio);
397
399
  let curExitRatio = autoAdapt ? 0.95 : exitRatio;
398
- const stepBars = autoAdapt ? Math.max(3, Math.floor(H / 5)) : H;
400
+ const holdBars = autoAdapt ? (STRATEGY_POLICY.holdBars ?? H) : H;
401
+ const horizonMin = Math.max(90, holdBars * BAR_INTERVAL);
402
+ const stepBars = autoAdapt ? Math.max(3, Math.floor(holdBars / 5)) : holdBars;
399
403
  const maxSlotWeight = dir === 'long' ? Math.min(0.58, Math.max(1 / K, 1.8 / K)) : Math.min(0.75, 3.0 / (2 * K));
400
404
  let closedThisBar = new Set();
401
405
 
@@ -458,21 +462,21 @@ async function run(runId, p, state) {
458
462
  }
459
463
  }
460
464
  }
461
- const timeOk = !autoAdapt || canEnterIntraday(barTs, H, BAR_INTERVAL);
465
+ const timeOk = !autoAdapt || canEnterIntraday(barTs, holdBars, BAR_INTERVAL);
462
466
  const canOpenAt = (i, edgeRatio = 0) => timeOk && !held.has(i) && !closedThisBar.has(i) && stockAllowed(i, edgeRatio) && canEnter(i, t);
463
467
  const okLong = (i) => !divRule(i, t)?.blockLong && (!needTrade || (tokens[i].has[tIn] && tokens[i].open[tIn] > 0)) && (!autoAdapt || !isRegimeShocked(ds, i, t, 'long'));
464
- const okShort = (i) => !divRule(i, t)?.blockShort && (!shortable || shortable.has(tokens[i].symbol)) && (!needTrade || (tokens[i].has[tIn] && tokens[i].open[tIn] > 0)) && (!autoAdapt || (!isRegimeShocked(ds, i, t, 'short') && canEnterIntraday(barTs, H, BAR_INTERVAL, 'short', tokens[i].symbol)));
468
+ const okShort = (i) => !divRule(i, t)?.blockShort && (!shortable || shortable.has(tokens[i].symbol)) && (!needTrade || (tokens[i].has[tIn] && tokens[i].open[tIn] > 0)) && (!autoAdapt || (!isRegimeShocked(ds, i, t, 'short') && canEnterIntraday(barTs, holdBars, BAR_INTERVAL, 'short', tokens[i].symbol)));
465
469
  let order, entries;
466
470
  if (dir !== 'long') {
467
471
  const edge = (i) => pr[i] * sg[i] * Math.sqrt(H);
468
472
  const need = effMinEdge * 2 * realCost * needMult;
469
473
  const exitNeed = need * curExitRatio;
470
- const earlyExitNeed = need * 0.35;
471
- // держим позицию, пока ожидаемое движение выше порога выхода (первые два полушага — мягкий порог, чтобы не резать сделку от шума)
474
+ // держим позицию по единой рампе удержания exitHoldRatio (до 45 мин — 0.35, затем плавный подъём к 0.95 к концу глубины сопровождения horizonMin)
472
475
  for (const [i, h] of [...held]) {
473
476
  const e = edge(i);
474
477
  const barsHeld = tIn - h.entryT;
475
- const req = (autoAdapt && barsHeld <= 2 * stepBars ? earlyExitNeed : exitNeed) * symMult[i];
478
+ const holdRatio = exitHoldRatio(barsHeld * BAR_INTERVAL, horizonMin, { baseEndRatio: curExitRatio, autoAdapt }, STRATEGY_POLICY);
479
+ const req = need * holdRatio * symMult[i];
476
480
  const shocked = autoAdapt && isRegimeShocked(ds, i, t, h.side);
477
481
  if (shocked || !(h.side === 'long' ? e > req : e < -req) || !liquid(i, t)) {
478
482
  closePos(i, h, tIn, false, shocked ? 'Защита от экстремального сквиза/обвала' : 'Прогноз ослаб');
@@ -513,11 +517,11 @@ async function run(runId, p, state) {
513
517
  const edge = (i) => pr[i] * sg[i] * Math.sqrt(H);
514
518
  const need = effMinEdge * 2 * realCost * needMult;
515
519
  const exitNeed = need * curExitRatio;
516
- const earlyExitNeed = need * 0.35;
517
520
  for (const [i, h] of [...held]) {
518
521
  const e = edge(i);
519
522
  const barsHeld = tIn - h.entryT;
520
- const req = (autoAdapt && barsHeld <= 2 * stepBars ? earlyExitNeed : exitNeed) * symMult[i];
523
+ const holdRatio = exitHoldRatio(barsHeld * BAR_INTERVAL, horizonMin, { baseEndRatio: curExitRatio, autoAdapt }, STRATEGY_POLICY);
524
+ const req = need * holdRatio * symMult[i];
521
525
  const shocked = autoAdapt && isRegimeShocked(ds, i, t, 'long');
522
526
  if (shocked || !(e > req) || !liquid(i, t)) closePos(i, h, tIn, false, shocked ? 'Защита от экстремального обвала' : '');
523
527
  }
@@ -1120,7 +1124,9 @@ async function runStabilityLoop(targetId, runRow, st) {
1120
1124
  const stockPnl = new Float64Array(N), stockCnt = new Float64Array(N);
1121
1125
  const stockAllowed = (i, edgeRatio = 0) => !autoAdapt || !isStockQuarantined(stockCnt[i], stockPnl[i], edgeRatio);
1122
1126
  const curExitRatio = autoAdapt ? 0.95 : exitRatio;
1123
- const stepBars = autoAdapt ? Math.max(3, Math.floor(H / 5)) : H;
1127
+ const holdBars = autoAdapt ? (STRATEGY_POLICY.holdBars ?? H) : H;
1128
+ const horizonMin = Math.max(90, holdBars * BAR_INTERVAL);
1129
+ const stepBars = autoAdapt ? Math.max(3, Math.floor(holdBars / 5)) : holdBars;
1124
1130
  const maxSlotWeight = dir === 'long' ? Math.min(0.58, Math.max(1 / K, 1.8 / K)) : Math.min(0.75, 3.0 / (2 * K));
1125
1131
  let closedThisBar = new Set();
1126
1132
  const closePos = (i, h, t) => {
@@ -1158,17 +1164,18 @@ async function runStabilityLoop(targetId, runRow, st) {
1158
1164
  if (shouldExitBeforeNight(barTs, tsOf(h.entryT), stepBars, BAR_INTERVAL)) closePos(i, h, tIn);
1159
1165
  }
1160
1166
  }
1161
- const timeOk = !autoAdapt || canEnterIntraday(barTs, H, BAR_INTERVAL);
1167
+ const timeOk = !autoAdapt || canEnterIntraday(barTs, holdBars, BAR_INTERVAL);
1162
1168
  const canOpenAt = (i, edgeRatio = 0) => timeOk && !held.has(i) && !closedThisBar.has(i) && stockAllowed(i, edgeRatio) && canEnter(i, t);
1163
1169
  const okLong = (i) => !divRule(i, t)?.blockLong && (!autoAdapt || !isRegimeShocked(ds, i, t, 'long'));
1164
- const okShort = (i) => !divRule(i, t)?.blockShort && (!autoAdapt || (!isRegimeShocked(ds, i, t, 'short') && canEnterIntraday(barTs, H, BAR_INTERVAL, 'short', tokens[i].symbol)));
1170
+ const okShort = (i) => !divRule(i, t)?.blockShort && (!autoAdapt || (!isRegimeShocked(ds, i, t, 'short') && canEnterIntraday(barTs, holdBars, BAR_INTERVAL, 'short', tokens[i].symbol)));
1165
1171
  const edge = (i) => pr[i] * sg[i] * Math.sqrt(H);
1166
- const need = effMinEdge * 2 * realCost * ENTRY_EDGE_MULT, exitNeed = need * curExitRatio, earlyExitNeed = need * 0.35;
1172
+ const need = effMinEdge * 2 * realCost * ENTRY_EDGE_MULT, exitNeed = need * curExitRatio;
1167
1173
  let order, entries;
1168
1174
  if (dir !== 'long') {
1169
1175
  for (const [i, h] of [...held]) {
1170
1176
  const e = edge(i), barsHeld = tIn - h.entryT;
1171
- const req = (autoAdapt && barsHeld <= 2 * stepBars ? earlyExitNeed : exitNeed) * symMult[i];
1177
+ const holdRatio = exitHoldRatio(barsHeld * BAR_INTERVAL, horizonMin, { baseEndRatio: curExitRatio, autoAdapt }, STRATEGY_POLICY);
1178
+ const req = need * holdRatio * symMult[i];
1172
1179
  const shocked = autoAdapt && isRegimeShocked(ds, i, t, h.side);
1173
1180
  if (shocked || !(h.side === 'long' ? e > req : e < -req) || !liquid(i, t)) closePos(i, h, tIn);
1174
1181
  }
@@ -1196,7 +1203,8 @@ async function runStabilityLoop(targetId, runRow, st) {
1196
1203
  } else if (effMinEdge > 0) {
1197
1204
  for (const [i, h] of [...held]) {
1198
1205
  const e = edge(i), barsHeld = tIn - h.entryT;
1199
- const req = (autoAdapt && barsHeld <= 2 * stepBars ? earlyExitNeed : exitNeed) * symMult[i];
1206
+ const holdRatio = exitHoldRatio(barsHeld * BAR_INTERVAL, horizonMin, { baseEndRatio: curExitRatio, autoAdapt }, STRATEGY_POLICY);
1207
+ const req = need * holdRatio * symMult[i];
1200
1208
  const shocked = autoAdapt && isRegimeShocked(ds, i, t, 'long');
1201
1209
  if (shocked || !(e > req) || !liquid(i, t)) closePos(i, h, tIn);
1202
1210
  }