neuroalpha 0.3.15 → 0.3.17
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/README.md +79 -170
- package/config/strategy.json +2 -2
- package/docs//320/221/320/240/320/236/320/232/320/225/320/240/320/253_API.md +4 -3
- package/docs//320/224/320/225/320/237/320/233/320/236/320/231.md +10 -17
- package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +96 -156
- 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 +29 -45
- package/package.json +1 -1
- package/public/admin.html +9 -8
- package/public/app.js +464 -930
- package/public/index.html +148 -223
- package/public/landing.html +19 -33
- package/public/legal.html +3 -4
- package/public/styles.css +6 -2
- package/server/api/routes.js +10 -3
- package/server/auth/index.js +53 -34
- package/server/brokers/index.js +434 -104
- package/server/data/moex.js +74 -23
- package/server/db.js +3 -2
- package/server/model/dataset.js +10 -2
- package/server/model/forecast.js +56 -19
- package/server/model/live.js +30 -6
- package/server/model/models.js +113 -31
- package/server/model/research.js +100 -26
- package/server/params-meta.js +19 -19
- package/server/strategist/index.js +423 -484
- package/server/strategist/tools.js +486 -303
package/server/model/research.js
CHANGED
|
@@ -34,8 +34,8 @@ export function isMarketDown(ds, t) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Версия обработки данных: при её смене прежние прогоны не считаются «такими же» (у старых её нет).
|
|
37
|
-
//
|
|
38
|
-
const DATA_VERSION =
|
|
37
|
+
// 8 — скорректированный спектр Марченко–Пастура (sigmaBulk) для 0 — Авто + динамическая концентрация слота (Conviction Sizing)
|
|
38
|
+
const DATA_VERSION = 8;
|
|
39
39
|
|
|
40
40
|
const AUTO_LOCKED = { modelHorizon: 2.5, modelMinEdge: 2.5, modelExitRatio: 0.95 };
|
|
41
41
|
function reuseParamsKey(params) {
|
|
@@ -147,8 +147,13 @@ async function run(runId, p, state) {
|
|
|
147
147
|
const H = barsFromHours(effHorizon); // горизонт в свечах по 10 минут
|
|
148
148
|
const maxH = autoAdapt ? Math.round(H * 1.4) : H;
|
|
149
149
|
const tillTs = Date.now();
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
// Выравниваем начало периода исследования по началу календарных суток МСК,
|
|
151
|
+
// а для чтения датасета из SQLite всегда берём единый широкий горизонт (fromTs = 0),
|
|
152
|
+
// чтобы смена «Окна обучения» (modelTrainDays) или «Периода исследования» (modelHistoryDays)
|
|
153
|
+
// НИКОГДА не сдвигала fromTs и не вызывала повторную загрузку котировок!
|
|
154
|
+
const todayStartMsk = Math.floor((tillTs + MSK) / DAY) * DAY - MSK;
|
|
155
|
+
const researchFrom = todayStartMsk - p.modelHistoryDays * DAY;
|
|
156
|
+
const fromTs = todayStartMsk - 1650 * DAY;
|
|
152
157
|
|
|
153
158
|
emit({ stage: 'data', pct: 0, text: 'Загружаем словарь рынка (состав индекса широкого рынка)…' });
|
|
154
159
|
const ds = await loadDataset({
|
|
@@ -250,18 +255,39 @@ async function run(runId, p, state) {
|
|
|
250
255
|
self: [round(attM.coef[HEADS.length], 4), round(attM.coef[HEADS.length + 1], 4), round(attM.coef[HEADS.length + 2], 4)],
|
|
251
256
|
} : {}),
|
|
252
257
|
};
|
|
253
|
-
emit({ stage: 'sim', pct: Math.round(((fi + 1) / folds) *
|
|
258
|
+
emit({ stage: 'sim', pct: Math.round(((fi + 1) / folds) * 80), text: mainText, viz: liveViz });
|
|
254
259
|
testStart = testEnd;
|
|
255
260
|
fi++;
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
emit({ stage: '
|
|
263
|
+
emit({ stage: 'calc', pct: 81, text: 'Финальный пересчёт · подготовка временного ряда прогнозов…' });
|
|
259
264
|
await tick();
|
|
260
265
|
const testTs = [...yraw.keys()].sort((a, b) => a - b);
|
|
261
266
|
const tsOf = (t) => timeline[t];
|
|
262
267
|
|
|
263
268
|
// ---------- портфель из лучших прогнозов: покупки, продажи без покупки, пары «нейтрально к рынку» ----------
|
|
264
|
-
const
|
|
269
|
+
const rawTopK = Number(p.modelTopK ?? 2);
|
|
270
|
+
const autoK = rawTopK === 0 || autoAdapt;
|
|
271
|
+
const K = rawTopK > 0 ? rawTopK : 4;
|
|
272
|
+
// Адаптивный отбор числа акций (Dynamic Top-K):
|
|
273
|
+
// лидер №1 берётся всегда при пробое порога; следующие бумаги (2…K) берутся только если их сигнал
|
|
274
|
+
// достаточно силён относительно лидера (>= 68% от силы №1) и уверенно перекрывает порог (>= 1.25 * need),
|
|
275
|
+
// чтобы не размывать капитал на слабые окраинные сигналы
|
|
276
|
+
const filterDynamicTopK = (cands, edgeFn, needVal, maxCount) => {
|
|
277
|
+
if (!cands.length || maxCount <= 0) return [];
|
|
278
|
+
const lim = Math.min(cands.length, maxCount);
|
|
279
|
+
if (!autoK || lim <= 1) return cands.slice(0, lim);
|
|
280
|
+
const bestE = Math.abs(edgeFn(cands[0]));
|
|
281
|
+
const out = [cands[0]];
|
|
282
|
+
for (let m = 1; m < lim; m++) {
|
|
283
|
+
const curE = Math.abs(edgeFn(cands[m]));
|
|
284
|
+
const relMin = m === 1 ? 0.62 : 0.78;
|
|
285
|
+
const absMin = m === 1 ? needVal * 1.15 : needVal * 1.45;
|
|
286
|
+
if (curE >= bestE * relMin && curE >= absMin) out.push(cands[m]);
|
|
287
|
+
else break;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
};
|
|
265
291
|
const dir = p.modelDirection;
|
|
266
292
|
const exitRatio = Number(p.modelExitRatio ?? 0.5);
|
|
267
293
|
const mktFilter = p.modelMarketFilter === 'ma';
|
|
@@ -313,7 +339,7 @@ async function run(runId, p, state) {
|
|
|
313
339
|
const stockAllowed = (i) => !autoAdapt || !(stockCnt[i] >= 3 && stockPnl[i] < -0.012);
|
|
314
340
|
let curExitRatio = autoAdapt ? 0.95 : exitRatio;
|
|
315
341
|
const stepBars = autoAdapt ? Math.max(3, Math.floor(H / 5)) : H;
|
|
316
|
-
const maxSlotWeight = dir === 'long' ? Math.min(0.
|
|
342
|
+
const maxSlotWeight = dir === 'long' ? Math.min(0.58, Math.max(1 / K, 1.8 / K)) : Math.min(0.75, 3.0 / (2 * K));
|
|
317
343
|
|
|
318
344
|
const closePos = (i, h, t, end, why) => {
|
|
319
345
|
const exitPrice = px(i, t);
|
|
@@ -365,9 +391,11 @@ async function run(runId, p, state) {
|
|
|
365
391
|
const candS = [...pr.keys()].filter((i) => !held.has(i) && stockAllowed(i) && liquid(i, t) && edge(i) < -needS).sort((a, b) => pr[a] - pr[b]);
|
|
366
392
|
const nL = K - countSide('long');
|
|
367
393
|
const nS = K - countSide('short');
|
|
394
|
+
const pickedL = filterDynamicTopK(candL, edge, needL, Math.max(0, nL));
|
|
395
|
+
const pickedS = filterDynamicTopK(candS, edge, needS, Math.max(0, nS));
|
|
368
396
|
entries = [
|
|
369
|
-
...
|
|
370
|
-
...
|
|
397
|
+
...pickedL.map((i) => [i, 'long', edge(i)]),
|
|
398
|
+
...pickedS.map((i) => [i, 'short', edge(i)]),
|
|
371
399
|
];
|
|
372
400
|
} else if (effMinEdge > 0) {
|
|
373
401
|
// ожидаемое движение (в долях) = прогноз × обычный размах × √H; входим, только если оно окупает сделку
|
|
@@ -383,7 +411,8 @@ async function run(runId, p, state) {
|
|
|
383
411
|
}
|
|
384
412
|
const free = K - held.size;
|
|
385
413
|
const needL = autoAdapt && bear ? need * 1.25 : need;
|
|
386
|
-
const
|
|
414
|
+
const rawCand = mktDown ? [] : [...pr.keys()].filter((i) => !held.has(i) && stockAllowed(i) && liquid(i, t) && edge(i) > needL).sort((a, b) => pr[b] - pr[a]);
|
|
415
|
+
const cand = filterDynamicTopK(rawCand, edge, needL, Math.max(0, free));
|
|
387
416
|
order = [...held.keys(), ...cand];
|
|
388
417
|
} else {
|
|
389
418
|
const cand = mktDown ? [] : [...pr.keys()].filter((i) => stockAllowed(i) && pr[i] > 0 && liquid(i, t)).sort((a, b) => pr[b] - pr[a]).slice(0, K);
|
|
@@ -402,11 +431,29 @@ async function run(runId, p, state) {
|
|
|
402
431
|
const usedStart = [...held.values()].reduce((s, h) => s + h.alloc, 0);
|
|
403
432
|
let avail = Math.max(0, equity * 0.99 - usedStart);
|
|
404
433
|
const needEdge = effMinEdge * 2 * realCost;
|
|
434
|
+
// Динамическая концентрация капитала под реально работающие позиции:
|
|
435
|
+
// если при K=5 на рынке есть всего 1–2 сильных сигнала, не замораживаем 60% денег в кэше под пустые слоты,
|
|
436
|
+
// а взвешиваем слот от фактического числа занятых + новых позиций и силы каждого сигнала (Conviction Sizing)
|
|
437
|
+
const activeTotal = Math.max(2, held.size + newEntries.length);
|
|
438
|
+
const dynSlotWeight = autoAdapt
|
|
439
|
+
? Math.min(dir === 'long' ? 0.58 : 0.75, Math.max(maxSlotWeight, (dir === 'long' ? 1.8 : 3.0) / Math.min(slots, activeTotal + 1)))
|
|
440
|
+
: maxSlotWeight;
|
|
441
|
+
// Сортируем новые входы по убыванию силы сигнала, чтобы лучшие идеи получали приоритетную долю капитала
|
|
442
|
+
if (autoAdapt && newEntries.length > 1) {
|
|
443
|
+
newEntries.sort((a, b) => Math.abs(b[2] ?? 0) - Math.abs(a[2] ?? 0));
|
|
444
|
+
}
|
|
445
|
+
const edgeWeights = newEntries.map(([i, , edgeAt]) => {
|
|
446
|
+
const effE = edgeAt !== null ? Math.abs(edgeAt) : (sg ? Math.abs(pr[i] * sg[i] * Math.sqrt(H)) : 0);
|
|
447
|
+
return Math.max(0.0001, effE);
|
|
448
|
+
});
|
|
449
|
+
let remWeightSum = edgeWeights.reduce((a, b) => a + b, 0);
|
|
405
450
|
for (let idx = 0; idx < newEntries.length; idx++) {
|
|
406
451
|
const [i, side, edgeAt] = newEntries[idx];
|
|
407
|
-
const effEdge =
|
|
408
|
-
const conv = autoAdapt && needEdge > 0 && effEdge > 0 ? Math.min(1.
|
|
409
|
-
const
|
|
452
|
+
const effEdge = edgeWeights[idx];
|
|
453
|
+
const conv = autoAdapt && needEdge > 0 && effEdge > 0 ? Math.min(1.65, Math.max(0.85, 0.80 + 0.30 * (effEdge / needEdge))) : 1;
|
|
454
|
+
const propShare = autoAdapt && remWeightSum > 0 ? avail * (effEdge / remWeightSum) : avail / Math.max(1, newEntries.length - idx);
|
|
455
|
+
remWeightSum = Math.max(0, remWeightSum - effEdge);
|
|
456
|
+
const baseAlloc = autoAdapt ? Math.min(equity * dynSlotWeight * conv, propShare, avail) : Math.min(equity / slots, avail / Math.max(1, newEntries.length - idx));
|
|
410
457
|
let alloc = baseAlloc;
|
|
411
458
|
if (invVol && avgInv > 0 && sg[i] > 0) {
|
|
412
459
|
const w = Math.max(0.4, Math.min(1.8, (1 / sg[i]) / avgInv));
|
|
@@ -465,9 +512,15 @@ async function run(runId, p, state) {
|
|
|
465
512
|
const cost = realCost;
|
|
466
513
|
const results = {};
|
|
467
514
|
let splitTs = null;
|
|
468
|
-
for (
|
|
515
|
+
for (let ni = 0; ni < names.length; ni++) {
|
|
516
|
+
const n = names[ni];
|
|
517
|
+
const lbl = MODELS[n]?.label || n;
|
|
518
|
+
emit({ stage: 'calc', pct: 82 + ni * 2, text: `Финальный пересчёт · симуляция портфеля (${ni + 1} из ${names.length}): «${lbl}»…` });
|
|
519
|
+
await tick();
|
|
520
|
+
if (state.cancelled) throw new Error('Отменено');
|
|
469
521
|
const sim = simulate(n, cost);
|
|
470
522
|
const report = computeReport(sim.trades, sim.curve, p.capital, p.oosPct);
|
|
523
|
+
await tick();
|
|
471
524
|
const gross = simulate(n, 0, 0);
|
|
472
525
|
const grossReport = computeReport(gross.trades, gross.curve, p.capital, p.oosPct);
|
|
473
526
|
splitTs = report.splitTs;
|
|
@@ -478,11 +531,16 @@ async function run(runId, p, state) {
|
|
|
478
531
|
return seg.length > 1 ? (seg[seg.length - 1].equity / seg[0].equity - 1) * 100 : 0;
|
|
479
532
|
};
|
|
480
533
|
const SEG = { full: [-Infinity, Infinity], inSample: [-Infinity, splitTs - 1], outOfSample: [splitTs, Infinity] };
|
|
481
|
-
const icStats = (n) => {
|
|
534
|
+
const icStats = async (n) => {
|
|
482
535
|
const out = {};
|
|
483
536
|
const weekly = new Map();
|
|
484
537
|
const all = [];
|
|
485
|
-
for (
|
|
538
|
+
for (let idx = 0; idx < testTs.length; idx++) {
|
|
539
|
+
if ((idx & 2047) === 0) {
|
|
540
|
+
await tick();
|
|
541
|
+
if (state.cancelled) throw new Error('Отменено');
|
|
542
|
+
}
|
|
543
|
+
const t = testTs[idx];
|
|
486
544
|
// совпадение прогноза с фактом — только по ликвидным акциям
|
|
487
545
|
const pr = preds[n].get(t), y = yraw.get(t);
|
|
488
546
|
const ok = [...pr.keys()].filter((i) => tokens[i].liq[t] >= LIQ_MIN);
|
|
@@ -508,8 +566,12 @@ async function run(runId, p, state) {
|
|
|
508
566
|
|
|
509
567
|
const comparison = {};
|
|
510
568
|
const weeklyIc = {};
|
|
511
|
-
for (
|
|
512
|
-
const
|
|
569
|
+
for (let ni = 0; ni < names.length; ni++) {
|
|
570
|
+
const n = names[ni];
|
|
571
|
+
const lbl = MODELS[n]?.label || n;
|
|
572
|
+
emit({ stage: 'calc', pct: 88 + ni * 2, text: `Финальный пересчёт · ранговая точность IC по неделям (${ni + 1} из ${names.length}): «${lbl}»…` });
|
|
573
|
+
await tick();
|
|
574
|
+
const ic = await icStats(n);
|
|
513
575
|
weeklyIc[n] = ic.weekly;
|
|
514
576
|
const rep = results[n].report;
|
|
515
577
|
comparison[n] = {};
|
|
@@ -529,7 +591,8 @@ async function run(runId, p, state) {
|
|
|
529
591
|
}
|
|
530
592
|
|
|
531
593
|
const chosen = names.includes(p.modelType) ? p.modelType : 'attention';
|
|
532
|
-
|
|
594
|
+
emit({ stage: 'calc', pct: 95, text: `Финальный пересчёт · проверка фазовой устойчивости (5 сдвигов точки входа)…` });
|
|
595
|
+
await tick();
|
|
533
596
|
const report = results[chosen].report;
|
|
534
597
|
report.period = { from: tsOf(testTs[0]), till: tsOf(testTs[testTs.length - 1]) };
|
|
535
598
|
report.bars = testTs.length;
|
|
@@ -546,18 +609,29 @@ async function run(runId, p, state) {
|
|
|
546
609
|
// Быстрая проверка устойчивости по сдвигам точки пересмотра и старта (на готовых прогнозах, без переобучения)
|
|
547
610
|
const bpdInt = Math.max(1, Math.round(bpd));
|
|
548
611
|
const offsets = [...new Set([0, Math.max(1, Math.floor(H / 3)), Math.max(2, Math.floor((2 * H) / 3)), bpdInt * 2, bpdInt * 4].filter((o) => o < Math.min(testTs.length / 4, bpdInt * 10)))];
|
|
549
|
-
const stabRuns =
|
|
612
|
+
const stabRuns = [];
|
|
613
|
+
for (let idx = 0; idx < offsets.length; idx++) {
|
|
614
|
+
const off = offsets[idx];
|
|
615
|
+
if (idx > 0) await tick();
|
|
550
616
|
const sm = idx === 0 ? results[chosen].sim : simulate(chosen, cost, p.modelShortFeePct, off);
|
|
551
617
|
const rep = idx === 0 ? results[chosen].report.full : computeReport(sm.trades, sm.curve, p.capital, p.oosPct).full;
|
|
552
|
-
|
|
618
|
+
stabRuns.push({
|
|
553
619
|
label: off === 0 ? 'база' : off < H ? `фаза +${off * BAR_INTERVAL}м` : `старт +${Math.round(off / bpd)}д`,
|
|
554
620
|
returnPct: rep?.returnPct ?? 0,
|
|
555
621
|
maxDrawdownPct: rep?.maxDrawdownPct ?? 0,
|
|
556
622
|
trades: rep?.trades ?? 0,
|
|
557
|
-
};
|
|
558
|
-
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
559
625
|
const stability = summarizeStability(stabRuns, 'phase');
|
|
560
626
|
|
|
627
|
+
emit({ stage: 'calc', pct: 98, text: `Финальный пересчёт · запись ${results[chosen].sim.trades.length} сделок и кривых капитала в базу…` });
|
|
628
|
+
await tick();
|
|
629
|
+
const chosenTrades = results[chosen].sim.trades;
|
|
630
|
+
for (let ti = 0; ti < chosenTrades.length; ti++) {
|
|
631
|
+
saveTrade(runId, chosenTrades[ti]);
|
|
632
|
+
if ((ti & 511) === 511) await tick();
|
|
633
|
+
}
|
|
634
|
+
|
|
561
635
|
saveRunExtra(runId, {
|
|
562
636
|
kind: 'model',
|
|
563
637
|
symbols: tokens.map((t) => t.symbol),
|
|
@@ -625,8 +699,8 @@ async function runStabilityLoop(targetId, runRow, st) {
|
|
|
625
699
|
const shifts = [-8, -4, 0, 4, 8];
|
|
626
700
|
const H = barsFromHours(p.modelHorizon);
|
|
627
701
|
const tillTs = Date.now();
|
|
628
|
-
|
|
629
|
-
const fromTs =
|
|
702
|
+
const todayStartMsk = Math.floor((tillTs + MSK) / DAY) * DAY - MSK;
|
|
703
|
+
const fromTs = todayStartMsk - 1650 * DAY;
|
|
630
704
|
const periodTill = runRow.metrics?.period?.till || tillTs;
|
|
631
705
|
const emitStab = (m) => { if (!st.cancelled) hub.emit({ type: 'stability:progress', runId: targetId, ...m }); };
|
|
632
706
|
emitStab({ pct: 2, text: 'Проверка устойчивости: чтение локальных цен…' });
|
package/server/params-meta.js
CHANGED
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
// Описание всех настроек: для формы в интерфейсе, для ИИ-стратега и для проверки значений.
|
|
3
3
|
// hidden — не показывать в интерфейсе; ai:false — ИИ менять запрещено (честность проверки).
|
|
4
4
|
export const PARAMS = [
|
|
5
|
-
{ key: 'modelAutoAdapt', label: 'Самообучение правил торговли (Авто)', hint: 'Полный автомат: стратегия сама учится на мульти-горизонте (1,5–3,5 ч),
|
|
6
|
-
{ key: 'modelTopK', label: 'Сколько акций держать', hint: '
|
|
7
|
-
{ key: 'modelDirection', label: 'Направление торговли', hint: '
|
|
8
|
-
{ key: 'modelHorizon', label: 'Горизонт прогноза, часов (ручной режим)', hint: '
|
|
9
|
-
{ key: 'modelMinEdge', label: 'Порог выгоды на вход (ручной режим)', hint: '
|
|
10
|
-
{ key: 'modelExitRatio', label: 'Порог на выход (ручной режим)', hint: '
|
|
11
|
-
{ key: 'modelAllocMode', label: 'Распределение суммы между акциями', hint: '
|
|
12
|
-
{ key: 'modelMarketFilter', label: 'Фильтр падающего рынка', hint: '
|
|
13
|
-
{ key: 'modelTrainDays', label: 'Окно обучения, торговых дней', hint: '
|
|
14
|
-
{ key: 'modelHistoryDays', label: 'Период исследования, дней', hint: 'За сколько последних календарных дней проверять стратегию на истории.', min: 120, max: 1100 },
|
|
15
|
-
{ key: 'modelEmbedDim', label: 'Размерность карты рынка (
|
|
16
|
-
{ key: 'modelTextWeight', label: 'Вес бизнес-профилей компаний на карте', hint: '0 — карта рынка строится только по совместному поведению
|
|
17
|
-
{ key: 'modelUseMacro', label: 'Учитывать нефть, валюту, облигации', hint: 'Дополнительно учитывает чувствительность акций к Brent, юаню, доллару, золоту и RGBI.', type: 'checkbox', hidden: true },
|
|
5
|
+
{ key: 'modelAutoAdapt', label: 'Самообучение правил торговли (Авто)', hint: 'Полный автомат: стратегия сама учится на мульти-горизонте (1,5–3,5 ч), сама выбирает число акций (1–4) и размер слота по силе сигнала, управляет порогами входа и выхода и временно ставит на карантин убыточные акции.', type: 'checkbox', hidden: true, fixed: true },
|
|
6
|
+
{ key: 'modelTopK', label: 'Сколько акций держать (0 — Авто)', hint: '0 — система сама выбирает от 1 до 4 акций на каждом шаге по силе и концентрации сигналов; 1…20 — верхний предел числа позиций на сторону.', min: 0, max: 20, hidden: true, fixed: true },
|
|
7
|
+
{ key: 'modelDirection', label: 'Направление торговли', hint: 'Автоматически подстраивается под тариф и статус маржинальной торговли на брокерском счёте.', type: 'select', options: [['long', 'Только в рост (без шорта)'], ['longshort', 'В рост и в падение (с шортом)']], hidden: true, fixed: true },
|
|
8
|
+
{ key: 'modelHorizon', label: 'Горизонт прогноза, часов (ручной режим)', hint: 'Управляется автоматически (мульти-горизонт 1,5–3,5 ч).', min: 0.5, max: 8, hidden: true, fixed: true },
|
|
9
|
+
{ key: 'modelMinEdge', label: 'Порог выгоды на вход (ручной режим)', hint: 'Управляется автоматически (2,5× издержек).', min: 0, max: 10, hidden: true, fixed: true },
|
|
10
|
+
{ key: 'modelExitRatio', label: 'Порог на выход (ручной режим)', hint: 'Управляется автоматически (двухступенчатый выход 0,35 / 0,95).', min: 0, max: 1, step: 0.05, hidden: true, fixed: true },
|
|
11
|
+
{ key: 'modelAllocMode', label: 'Распределение суммы между акциями', hint: 'Управляется автоматически по силе сигнала (Conviction Sizing).', type: 'select', options: [['equal', 'Поровну'], ['invvol', 'Обратно размаху акции']], hidden: true, fixed: true },
|
|
12
|
+
{ key: 'modelMarketFilter', label: 'Фильтр падающего рынка', hint: 'Адаптивная балансировка порогов к тренду индекса включена автоматически.', type: 'select', options: [['off', 'Выключен'], ['ma', 'Не покупать при падении индекса']], hidden: true, fixed: true },
|
|
13
|
+
{ key: 'modelTrainDays', label: 'Окно обучения, торговых дней', hint: 'Фиксировано на эталонном окне 90 торговых дней.', min: 30, max: 400, hidden: true, fixed: true },
|
|
14
|
+
{ key: 'modelHistoryDays', label: 'Период исследования, дней', hint: 'За сколько последних календарных дней проверять стратегию на истории.', min: 120, max: 1100, hidden: true, fixed: true },
|
|
15
|
+
{ key: 'modelEmbedDim', label: 'Размерность карты рынка / эмбеддинг (0 — Авто)', hint: 'Авто-калибровка по спектру собственных чисел Марченко–Пастура.', min: 0, max: 1000, hidden: true, fixed: true },
|
|
16
|
+
{ key: 'modelTextWeight', label: 'Вес бизнес-профилей компаний на карте', hint: '0 — карта рынка строится только по совместному поведению цен.', min: 0, max: 1, step: 0.1, hidden: true, fixed: true },
|
|
17
|
+
{ key: 'modelUseMacro', label: 'Учитывать нефть, валюту, облигации', hint: 'Дополнительно учитывает чувствительность акций к Brent, юаню, доллару, золоту и RGBI.', type: 'checkbox', hidden: true, fixed: true },
|
|
18
18
|
{ key: 'modelType', label: 'Какая стратегия торгует', hint: 'Внимание через карту.', type: 'select', options: [['attention', 'Внимание через карту'], ['leadlag', 'Кто за кем ходит'], ['inertia', 'Своя инерция']], hidden: true, fixed: true },
|
|
19
19
|
{ key: 'modelUseDividends', label: 'Учитывать дивиденды', hint: 'Цены до даты отсечки уменьшаются на размер дивиденда со сверкой по реальному падению цены.', type: 'checkbox', hidden: true, fixed: true },
|
|
20
|
-
{ key: 'capital', label: 'Стартовая сумма, ₽', hint: 'С какой суммой начинает
|
|
21
|
-
{ key: 'commissionPct', label: 'Комиссия брокера и биржи, % за сделку', hint: 'Сколько берут брокер и биржа за покупку и отдельно за продажу.', min: 0, max: 1, ai: false },
|
|
22
|
-
{ key: 'slippagePct', label: 'Проскальзывание при исполнении, %', hint: 'На сколько реальная цена сделки в стакане обычно хуже ожидаемой.', min: 0, max: 1, ai: false },
|
|
23
|
-
{ key: 'modelShortFeePct', label: 'Плата брокеру за заём акций (шорт), % годовых', hint: 'Ставка брокера за удержание коротких позиций (начисляется пропорционально времени удержания сделки).', min: 0, max: 100 },
|
|
24
|
-
{ key: 'autopilotBudget', label: 'Лимит исследований автопилота за задание', hint: 'Сколько экспериментов Стратег может провести подряд за одно
|
|
25
|
-
{ key: 'paperDailyReview', label: 'ИИ разбирает каждый торговый день', hint: 'После окончания торгового дня Стратег сам разбирает сделки
|
|
20
|
+
{ key: 'capital', label: 'Стартовая сумма, ₽', hint: 'С какой суммой начинает счёт.', min: 10000, max: 1e9, hidden: true },
|
|
21
|
+
{ key: 'commissionPct', label: 'Комиссия брокера и биржи, % за сделку', hint: 'Сколько берут брокер и биржа за покупку и отдельно за продажу.', min: 0, max: 1, ai: false, hidden: true, fixed: true },
|
|
22
|
+
{ key: 'slippagePct', label: 'Проскальзывание при исполнении, %', hint: 'На сколько реальная цена сделки в стакане обычно хуже ожидаемой.', min: 0, max: 1, ai: false, hidden: true, fixed: true },
|
|
23
|
+
{ key: 'modelShortFeePct', label: 'Плата брокеру за заём акций (шорт), % годовых', hint: 'Ставка брокера за удержание коротких позиций (начисляется пропорционально времени удержания сделки).', min: 0, max: 100, hidden: true, fixed: true },
|
|
24
|
+
{ key: 'autopilotBudget', label: 'Лимит исследований автопилота за задание', hint: 'Сколько экспериментов Стратег может провести подряд за одно задание.', min: 1, max: 30, step: 1, int: true, ai: false, hidden: true },
|
|
25
|
+
{ key: 'paperDailyReview', label: 'ИИ разбирает каждый торговый день', hint: 'После окончания торгового дня Стратег сам разбирает сделки и пишет сводку в чат.', type: 'checkbox', hidden: true },
|
|
26
26
|
{ key: 'newsEnabled', label: 'Следить за новостями', hint: 'Раз в 10 минут собирает новости и раскрытия компаний.', type: 'checkbox', hidden: true, fixed: true },
|
|
27
27
|
{ key: 'newsFilter', label: 'Блокировать сделки против важных свежих новостей', hint: 'В живой торговле и брокерских заявках не покупать бумагу на сильном негативе и не шортить на сильном позитиве за последние 6 часов.', type: 'checkbox' },
|
|
28
28
|
{ key: 'oosPct', label: 'Контрольная часть, %', hint: 'Доля периода в конце, не используемая для подбора настроек.', min: 0, max: 60, ai: false, hidden: true },
|