docs(sec): SEC-9 закрыт — возврат залога при ничьей

Сводка: открыто 8 (было 9), закрыто 8. SEC-9 перенесён в закрытые с полной
историей: причина (close_bet.rs:66), замер доли ничьих (33.6%), решение
владельца (правка контракта), состав правок, собственная мутационная приёмка,
деплой и живой эффект (залипшие #0/#1 закрылись сами, ваулт −1000).
Хвост SEC-8 о «вечном залипании» помечен как устранённый.

Инструменты приёмки:
- tie-refund-accept.cjs — ставки циклом, проверка статуса на цепи
  (живой прогон: 6 возвратов из 25 ставок)
- tie-distribution.cjs, policy-economics.cjs — выбор политики ничьей
- vault-check.cjs — разбор Bet-аккаунтов сырыми байтами
This commit is contained in:
2026-09-14 02:19:28 +03:00
parent 6ee30c39fb
commit b8a1f8dc39
5 changed files with 241 additions and 5 deletions
+48
View File
@@ -0,0 +1,48 @@
// Полное распределение: тики между входом и выходом 15-сек окна + шаг самого фида.
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync(process.env.DB || '/app/data/price-history.db');
const rows = db.prepare('SELECT ts, price FROM price_history ORDER BY ts ASC').all();
const vals = rows.map((r) => ({ ts: Number(r.ts), p: Number(r.price) }));
const SCALE = 1e8;
console.log('записей:', vals.length, '| диапазон:', (vals[0].p / SCALE).toFixed(2), '→', (vals[vals.length - 1].p / SCALE).toFixed(2));
// Шаг самого фида: как меняется цена от записи к записи
const stepHist = {};
let zeroSteps = 0;
for (let i = 1; i < vals.length; i++) {
const d = Math.round(Math.abs(vals[i].p - vals[i - 1].p) / SCALE * 100); // в сотых
stepHist[d] = (stepHist[d] || 0) + 1;
if (d === 0) zeroSteps++;
}
const stepKeys = Object.keys(stepHist).map(Number).sort((a, b) => a - b);
console.log('--- шаг фида (от записи к записи, в копейках) ---');
for (const k of stepKeys.slice(0, 12)) console.log(` ровно ${k} коп: ${stepHist[k]}`);
console.log(' уникальных шагов:', stepKeys.length, '| «без изменений»:', zeroSteps, `(${((100 * zeroSteps) / (vals.length - 1)).toFixed(1)}%)`);
const at = (ts) => {
let lo = 0, hi = vals.length - 1, res = null;
while (lo <= hi) {
const m = (lo + hi) >> 1;
if (vals[m].ts <= ts) { res = vals[m].p; lo = m + 1; } else hi = m - 1;
}
return res;
};
const W = 15000, STEP = 1000;
const hist = {};
let n = 0;
for (let t = vals[0].ts; t + W <= vals[vals.length - 1].ts; t += STEP) {
const a = at(t), b = at(t + W);
if (a === null || b === null) continue;
const kop = Math.round(Math.abs(b - a) / SCALE * 100);
hist[kop] = (hist[kop] || 0) + 1;
n++;
}
const keys = Object.keys(hist).map(Number).sort((a, b) => a - b);
console.log('--- окон 15 с проверено:', n, '| уникальных значений |delta|:', keys.length);
console.log(' распределение по модулю разницы (копейки):');
for (const k of keys.slice(0, 15)) console.log(` |Δ|=${k} коп: ${hist[k]} (${((100 * hist[k]) / n).toFixed(2)}%)`);
const tie = hist[0] || 0;
console.log(`НИЧЬЯ (|Δ|=0): ${tie} = ${((100 * tie) / n).toFixed(2)}%`);
const near = (keys.filter((k) => k > 0 && k <= 5).reduce((s, k) => s + hist[k], 0));
console.log(`почти-ничья (|Δ| = 1..5 коп): ${near} = ${((100 * near) / n).toFixed(2)}%`);