Files
jackyu66gitandCursor 2fb1dfee14 chore: seal Design Vision v1 and monorepo scaffold
Archive the differentiated YuXinGu product docs, AI engineering system,
design contract, and Go/Vue scaffold. Next execution prioritizes Cece-parity
over early innovation (see .ai/product/STRATEGY.md).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 16:00:44 +08:00

333 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>LINK HMM Regime — K线</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0d1117;color:#c9d1d9;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;overflow:hidden;touch-action:none}
#topbar{background:#161b22;padding:10px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #30363d;flex-wrap:wrap;gap:6px}
#topbar h1{font-size:14px;font-weight:600;color:#f0f6fc}
.legend{display:flex;gap:10px;font-size:11px;flex-wrap:wrap}
.legend span{display:flex;align-items:center;gap:4px}
.legend i{display:inline-block;width:10px;height:10px;border-radius:2px}
#chart{width:100%;height:calc(100vh - 44px);display:block;cursor:crosshair}
#loading{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#8b949e;font-size:14px;z-index:10}
#tooltip{position:fixed;background:#21262d;color:#c9d1d9;padding:6px 10px;border-radius:6px;font-size:12px;border:1px solid #30363d;pointer-events:none;display:none;white-space:nowrap;z-index:20}
</style>
</head>
<body>
<div id="topbar">
<h1>LINK/USD — HMM Regime (1m K线)</h1>
<div class="legend">
<span><i style="background:rgba(46,204,113,0.5);border:1px solid #2ecc71"></i> Low Spread</span>
<span><i style="background:rgba(241,196,15,0.4);border:1px solid #f1c40f"></i> Mid Spread</span>
<span><i style="background:rgba(231,76,60,0.4);border:1px solid #e74c3c"></i> High Spread</span>
</div>
</div>
<canvas id="chart"></canvas>
<div id="tooltip"></div>
<div id="loading">Loading data...</div>
<script>
(function(){
var canvas = document.getElementById('chart');
var ctx = canvas.getContext('2d');
var tt = document.getElementById('tooltip');
var RC = ['rgba(231,76,60,0.28)','rgba(241,196,15,0.18)','rgba(46,204,113,0.22)'];
var RL = ['#e74c3c','#f1c40f','#2ecc71'];
// Remap: HMM labels → spread-ordered. R0=lowest spread, R1=highest, R2=mid
// low→green(idx2), mid→yellow(idx1), high→red(idx0)
var RM = {0:2, 1:0, 2:1};
function rc(r) { return RC[RM[r]]; }
function rl(r) { return RL[RM[r]]; }
var GC = '#21262d', AC = '#8b949e', BG = '#0d1117';
var GREEN = '#26a69a', RED = '#ef5350';
var MA = {t:16,r:70,b:28,l:10};
var DATA = null;
var SIGNALS = null;
var viewStart = 0, viewEnd = 1;
var isPanning = false, panStart = 0;
var pinchDist0 = 0, viewMid0 = 0.5, viewRange0 = 1;
function init() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight - 44;
draw();
}
function draw() {
if (!DATA) return;
var W = canvas.width, H = canvas.height;
var pw = W - MA.l - MA.r, ph = H - MA.t - MA.b;
var N = DATA.length;
var i0 = Math.floor(viewStart * N);
var i1 = Math.ceil(viewEnd * N) - 1;
i0 = Math.max(0, i0); i1 = Math.min(N - 1, i1);
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = BG;
ctx.fillRect(0, 0, W, H);
// Price range
var pMin = Infinity, pMax = -Infinity;
for (var i = i0; i <= i1; i++) {
var d = DATA[i];
if (d.l < pMin) pMin = d.l;
if (d.h > pMax) pMax = d.h;
}
var pR = pMax - pMin || 0.001;
pMin -= pR * 0.05; pMax += pR * 0.05; pR = pMax - pMin;
function tX(i) { return MA.l + ((i - i0) / (i1 - i0 || 1)) * pw; }
function tY(v) { return MA.t + (1 - (v - pMin) / pR) * ph; }
// Regime background bands
var prevR = -1, bStart = i0;
for (var i = i0; i <= i1; i++) {
var r = DATA[i].r;
if (r !== prevR) {
if (prevR >= 0) {
ctx.fillStyle = rc(prevR);
ctx.fillRect(tX(bStart) - 1, MA.t, tX(i) - tX(bStart) + 2, ph);
}
bStart = i; prevR = r;
}
}
if (prevR >= 0) {
ctx.fillStyle = rc(prevR);
ctx.fillRect(tX(bStart) - 1, MA.t, tX(i1) - tX(bStart) + 2, ph);
}
// Grid + Y labels
var nY = 8;
ctx.strokeStyle = GC; ctx.lineWidth = 0.5;
for (var gy = 0; gy <= nY; gy++) {
var y = MA.t + (gy / nY) * ph;
var price = pMax - (gy / nY) * pR;
ctx.beginPath(); ctx.moveTo(MA.l, y); ctx.lineTo(MA.l + pw, y); ctx.stroke();
ctx.fillStyle = AC; ctx.font = '11px -apple-system,BlinkMacSystemFont,sans-serif';
ctx.textAlign = 'right'; ctx.fillText(price.toFixed(4), W - 5, y + 4);
}
// X labels
var nX = 6;
ctx.textAlign = 'center'; ctx.fillStyle = AC;
ctx.font = '10px -apple-system,BlinkMacSystemFont,sans-serif';
for (var gx = 0; gx <= nX; gx++) {
var idx = i0 + Math.floor((gx / nX) * (i1 - i0 + 1));
if (idx > i1) idx = i1;
var x = tX(idx);
var d = new Date(DATA[idx].t * 1000);
ctx.fillText(d.toISOString().substring(11, 19), x, H - MA.b + 16);
}
// ─── K-line candlesticks ───
var candleW = Math.max(1, (pw / (i1 - i0 + 1)) * 0.7);
var candleGap = (pw / (i1 - i0 + 1));
for (var i = i0; i <= i1; i++) {
var k = DATA[i];
var cx = tX(i);
var openY = tY(k.o), closeY = tY(k.c);
var highY = tY(k.h), lowY = tY(k.l);
var isGreen = k.c >= k.o;
// Wick
ctx.strokeStyle = isGreen ? GREEN : RED;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(cx, highY);
ctx.lineTo(cx, lowY);
ctx.stroke();
// Body
var bodyTop = isGreen ? closeY : openY;
var bodyH = Math.max(1, Math.abs(closeY - openY));
ctx.fillStyle = isGreen ? GREEN : RED;
ctx.fillRect(cx - candleW / 2, bodyTop, candleW, bodyH);
}
// Transition markers (dashed vertical lines)
prevR = DATA[Math.max(0, i0 - 1)].r;
for (var i = i0; i <= i1; i++) {
if (DATA[i].r !== prevR) {
var mx = tX(i);
ctx.strokeStyle = rl(DATA[i].r); ctx.lineWidth = 1;
ctx.setLineDash([2, 3]);
ctx.beginPath(); ctx.moveTo(mx, MA.t); ctx.lineTo(mx, MA.t + ph); ctx.stroke();
ctx.setLineDash([]);
prevR = DATA[i].r;
}
}
// ─── Signal markers (spread surge > 95% + >2x + 3min cooldown) ───
if (SIGNALS) {
for (var si = 0; si < SIGNALS.length; si++) {
var sig = SIGNALS[si];
if (sig.idx < i0 || sig.idx > i1) continue;
var candleIdx = -1;
for (var ci = i0; ci <= i1; ci++) {
if (DATA[ci].t <= sig.ts && (ci === i1 || DATA[ci+1].t > sig.ts)) {
candleIdx = ci; break;
}
}
if (candleIdx < 0) continue;
var sx = tX(candleIdx);
var k = DATA[candleIdx];
var sy = tY(k.l) + 8; // below the candle low
// Red triangle
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(sx - 5, sy + 10);
ctx.lineTo(sx + 5, sy + 10);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#ff4444'; ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(sx - 5, sy + 10);
ctx.lineTo(sx + 5, sy + 10);
ctx.closePath();
ctx.stroke();
}
}
// Price labels at edges
ctx.fillStyle = AC; ctx.font = 'bold 11px -apple-system,BlinkMacSystemFont,sans-serif';
ctx.textAlign = 'right';
ctx.fillText(DATA[i0].c.toFixed(4), MA.l - 4, tY(DATA[i0].c) + 4);
ctx.textAlign = 'left';
ctx.fillText(DATA[i1].c.toFixed(4), MA.l + pw + 4, tY(DATA[i1].c) + 4);
// Zoom %
ctx.fillStyle = AC; ctx.font = '10px sans-serif'; ctx.textAlign = 'right';
ctx.fillText(Math.round((viewEnd - viewStart) * 100) + '%', W - 10, MA.t - 4);
}
// Zoom (scroll)
canvas.addEventListener('wheel', function(e) {
e.preventDefault();
if (!DATA) return;
var rect = canvas.getBoundingClientRect();
var mx = (e.clientX - rect.left - MA.l) / (canvas.width - MA.l - MA.r);
mx = Math.max(0, Math.min(1, mx));
var mid = viewStart + mx * (viewEnd - viewStart);
var range = viewEnd - viewStart;
range = Math.max(0.003, Math.min(1, range * (e.deltaY > 0 ? 1.3 : 0.7)));
viewStart = Math.max(0, mid - range * mx);
viewEnd = Math.min(1, viewStart + range);
viewStart = Math.max(0, viewEnd - range);
draw();
}, {passive: false});
// Pan (drag)
canvas.addEventListener('mousedown', function(e) { isPanning = true; panStart = e.clientX; });
window.addEventListener('mousemove', function(e) {
if (!DATA) return;
if (isPanning) {
var dx = e.clientX - panStart; panStart = e.clientX;
var pw = canvas.width - MA.l - MA.r;
var shift = -(dx / pw) * (viewEnd - viewStart);
viewStart = Math.max(0, Math.min(1 - (viewEnd - viewStart), viewStart + shift));
viewEnd = viewStart + (viewEnd - viewStart);
draw(); return;
}
// Tooltip
var rect = canvas.getBoundingClientRect();
var mx = e.clientX - rect.left;
if (mx < MA.l || mx > canvas.width - MA.r) { tt.style.display = 'none'; return; }
var pw = canvas.width - MA.l - MA.r;
var idx = Math.floor(viewStart * DATA.length + ((mx - MA.l) / pw) * (viewEnd - viewStart) * DATA.length);
idx = Math.max(0, Math.min(DATA.length - 1, idx));
var k = DATA[idx];
var d = new Date(k.t * 1000);
var chg = ((k.c - k.o) / k.o * 100).toFixed(2);
var sign = chg >= 0 ? '+' : '';
var sigHTML = '';
if (SIGNALS) {
for (var si = 0; si < SIGNALS.length; si++) {
var sg = SIGNALS[si];
if (Math.abs(sg.ts - k.t) <= 30) {
sigHTML = '<br><span style="color:#ff6b6b">⚠ SPREAD SURGE</span> ' +
sg.spread.toFixed(4) + ' (' + (sg.spread/sg.baseline).toFixed(1) + 'x baseline)';
break;
}
}
}
tt.innerHTML = '<b>O</b> ' + k.o.toFixed(4) + ' <b>H</b> ' + k.h.toFixed(4) +
' <b>L</b> ' + k.l.toFixed(4) + ' <b>C</b> ' + k.c.toFixed(4) +
' <b style="color:' + (k.c >= k.o ? '#26a69a' : '#ef5350') + '">' + sign + chg + '%</b>' +
'<br><span style="color:#8b949e">' + d.toISOString().substring(11, 19) + ' &nbsp; R' + k.r + '</span>' + sigHTML;
tt.style.display = 'block';
tt.style.left = (e.clientX + 15) + 'px';
tt.style.top = (e.clientY - 50) + 'px';
});
window.addEventListener('mouseup', function() { isPanning = false; });
canvas.addEventListener('mouseleave', function() { isPanning = false; tt.style.display = 'none'; });
// Double-click reset
canvas.addEventListener('dblclick', function() { viewStart = 0; viewEnd = 1; draw(); });
// Touch
canvas.addEventListener('touchstart', function(e) {
if (e.touches.length === 1) { isPanning = true; panStart = e.touches[0].clientX; }
if (e.touches.length === 2) {
isPanning = false;
pinchDist0 = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
viewMid0 = (viewStart + viewEnd) / 2; viewRange0 = viewEnd - viewStart;
}
}, {passive: false});
canvas.addEventListener('touchmove', function(e) {
if (!DATA) return;
if (e.touches.length === 2) {
e.preventDefault();
var d = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
var s = pinchDist0 > 0 ? d / pinchDist0 : 1;
var nr = Math.max(0.003, Math.min(1, viewRange0 / s));
viewStart = Math.max(0, viewMid0 - nr / 2);
viewEnd = Math.min(1, viewStart + nr); viewStart = Math.max(0, viewEnd - nr);
draw(); return;
}
if (e.touches.length === 1 && isPanning) {
var dx = e.touches[0].clientX - panStart; panStart = e.touches[0].clientX;
var pw = canvas.width - MA.l - MA.r;
var shift = -(dx / pw) * (viewEnd - viewStart);
viewStart = Math.max(0, Math.min(1 - (viewEnd - viewStart), viewStart + shift));
viewEnd = viewStart + (viewEnd - viewStart);
draw(); return;
}
}, {passive: false});
canvas.addEventListener('touchend', function() { isPanning = false; tt.style.display = 'none'; });
window.addEventListener('resize', init);
Promise.all([
fetch('l2_LINK_kline_data.json').then(function(r){return r.json()}),
fetch('l2_LINK_signals.json').then(function(r){return r.json()})
]).then(function(results) {
DATA = results[0];
SIGNALS = results[1];
document.getElementById('loading').remove();
init();
})
.catch(function(e) {
document.getElementById('loading').textContent = 'Error: ' + e.message;
});
})();
</script>
</body>
</html>