Explain Torch forecast inputs in dashboard

This commit is contained in:
Codex
2026-06-23 07:44:15 +03:00
parent a548c0e890
commit 12f470e0a3
3 changed files with 317 additions and 0 deletions
+98
View File
@@ -511,6 +511,62 @@ HTML = r"""
background: #ffffff;
}
.forecast-chip b { display: block; color: var(--muted); font-size: 10px; margin-bottom: 2px; }
.feature-panel {
margin: 0 0 9px;
border: 1px solid var(--border);
border-radius: 7px;
background: #fff;
}
.feature-panel summary {
cursor: pointer;
padding: 8px 9px;
font-size: 12px;
font-weight: 850;
color: var(--accent);
list-style-position: inside;
}
.feature-note {
padding: 0 9px 8px;
color: var(--muted);
font-size: 11px;
line-height: 1.35;
}
.feature-table-wrap {
max-height: 360px;
overflow: auto;
border-top: 1px solid var(--border);
}
.feature-table {
font-size: 12px;
min-width: 720px;
}
.feature-table th,
.feature-table td {
white-space: normal;
vertical-align: top;
padding: 7px 8px;
}
.feature-table .param-name {
font-weight: 850;
color: var(--text);
}
.feature-table .technical-name {
margin-top: 2px;
color: var(--muted);
font-size: 10px;
word-break: break-word;
}
.feature-group {
display: inline-flex;
align-items: center;
min-height: 20px;
padding: 2px 6px;
border-radius: 5px;
background: var(--panel-2);
color: var(--muted);
font-size: 10px;
font-weight: 800;
}
.layout { grid-template-columns: 1.2fr 0.8fr; align-items: start; }
.panel { padding: 14px; min-width: 0; }
.table-wrap { overflow: auto; max-height: 330px; border: 1px solid var(--border); border-radius: 7px; }
@@ -767,6 +823,7 @@ HTML = r"""
<span>${num(pattern.score, 2)}</span>
</div>
${forecastHtml(market.forecast || {})}
${featureSnapshotHtml(market.forecast || {})}
<canvas width="520" height="220"></canvas>
<div class="indicators">
<div class="indicator"><b>RSI14</b>${num(last.rsi_14, 2)}</div>
@@ -847,6 +904,43 @@ HTML = r"""
</div>`;
}
function featureSnapshotHtml(forecast) {
const rows = Array.isArray(forecast?.feature_snapshot) ? forecast.feature_snapshot : [];
if (!rows.length) return '';
const body = rows.map(feature => `
<tr>
<td>
<div class="param-name">${escapeHtml(feature.label || feature.name || '-')}</div>
<div class="technical-name">${escapeHtml(feature.name || '')}</div>
</td>
<td><span class="feature-group">${escapeHtml(feature.group || '-')}</span></td>
<td>${escapeHtml(feature.raw_display ?? valueText(feature.raw_value))}</td>
<td>${escapeHtml(feature.model_display ?? valueText(feature.model_value))}</td>
<td>${escapeHtml(feature.meaning || '')}</td>
<td>${escapeHtml(feature.interpretation || '')}</td>
</tr>
`).join('');
return `<details class="feature-panel">
<summary>Входы нейросети (${rows.length})</summary>
<div class="feature-note">Текущее значение берется с последней закрытой свечи. Значение в модели - это нормализованный вход после обучающих mean/scale; именно его получает LSTM/GRU.</div>
<div class="feature-table-wrap">
<table class="feature-table">
<thead>
<tr>
<th>Параметр</th>
<th>Группа</th>
<th>Сейчас</th>
<th>В модели</th>
<th>Что это</th>
<th>Что значит</th>
</tr>
</thead>
<tbody>${body}</tbody>
</table>
</div>
</details>`;
}
function drawCandles(canvas, candles) {
const ctx = canvas.getContext('2d');
const w = canvas.width, h = canvas.height;
@@ -1117,6 +1211,10 @@ HTML = r"""
const sign = parsed > 0 ? '+' : '';
return `${sign}${parsed.toFixed(digits)}`;
}
function valueText(value, digits = 4) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed.toFixed(digits) : '-';
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[ch]));
}