Explain Torch forecast inputs in dashboard
This commit is contained in:
@@ -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 => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user