使用方法同上[淘股吧]

<! DOCT YPE html><html lang= zh-CN ><head> <meta charset= UTF-8 > <meta name= viewport content= width=device-width, initial-scale=1.0, user-scalable=yes > <title>情绪指标公式 · 十年保本 · 交易心理量化器</title> <style> * { box-sizing: border-box; } body { background: #0b1120; font-family: ‘Segoe UI‘, ‘Roboto‘, ‘Inter‘, system-ui, -apple-system, ‘Courier New‘, monospace; margin: 0; padding: 24px 16px; color: #e2e8f0; } .container { max-width: 1400px; margin: 0 auto; background: #0f172a; border-radius: 2rem; box-shadow: 0 25px 40px -12px rgba(0, 0, 0, 0.5); overflow: hidden; padding: 1.5rem 1.8rem 2rem 1.8rem; backdrop-filter: blur(0px); border: 1px solid #1e293b; } h1 { font-size: 1.8rem; font-weight: 600; margin: 0 0 0.25rem 0; letter-spacing: -0.3px; background: linear-gradient(135deg, #c084fc, #60a5fa); -webkit-background-clip: text; background-clip: text; color: transparent; } .sub { color: #94a3b8; border-left: 3px solid #3b82f6; padding-left: 12px; margin: 0.5rem 0 1.5rem 0; font-size: 0.85rem; } .grid-2 { display: flex; flex-wrap: wrap; gap: 2rem; } .panel { flex: 1.2; min-width: 260px; background: #111827; border-radius: 1.5rem; padding: 1.2rem 1.4rem; border: 1px solid #1f2a44; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .chart-panel { flex: 2; min-width: 320px; background: #0f172f; border-radius: 1.5rem; padding: 1rem; border: 1px solid #1e2a4a; } .param-group { margin-bottom: 1.4rem; } label { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.85rem; font-weight: 500; letter-spacing: 0.3px; color: #cbd5e6; margin-bottom: 6px; } .param-name { font-family: monospace; background: #02061760; padding: 2px 6px; border-radius: 20px; } input, select { width: 100%; background: #010409; border: 1px solid #2d3a5e; padding: 10px 12px; border-radius: 1rem; color: #f1f5f9; font-family: monospace; font-size: 0.9rem; transition: 0.2s; } input:focus { outline: none; border-color: #818cf8; box-shadow: 0 0 0 2px #6366f120; } .value-hint { font-size: 0.7rem; color: #6c86a3; margin-top: 4px; } button { background: #2563eb; border: none; width: 100%; padding: 12px; border-radius: 2rem; font-weight: 600; font-size: 0.9rem; color: white; cursor: pointer; transition: 0.2s; margin-top: 12px; font-family: inherit; } button:hover { background: #3b82f6; transform: scale(0.98); } .result-card { margin-top: 20px; background: #0b1120cc; border-radius: 1.2rem; padding: 0.8rem 1.2rem; text-align: center; border: 1px solid #334155; } .score { font-size: 3rem; font-weight: 800; font-family: monospace; letter-spacing: 2px; background: linear-gradient(135deg, #fbbf24, #f59e0b); -webkit-background-clip: text; background-clip: text; color: transparent; } .score-label { font-size: 0.7rem; text-transform: uppercase; color: #9ca3af; } .emotion-level { margin-top: 8px; font-weight: 500; background: #1e293b; display: inline-block; padding: 4px 14px; border-radius: 40px; font-size: 0.75rem; } canvas { width: 100%; background: #020617; border-radius: 1.2rem; margin-top: 8px; border: 1px solid #1e2a5e; } .legend { display: flex; gap: 1rem; justify-content: center; margin: 12px 0 6px; font-size: 0.7rem; } hr { border-color: #1f2a44; margin: 16px 0; } footer { text-align: center; font-size: 0.7rem; margin-top: 1.8rem; color: #4b556b; } @media (max-width: 780px) { .container { padding: 1rem; } .panel, .chart-panel { min-width: 100%; } } </style> <script src= https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js ></script></head><body><div class= container > <h1>⚡ 情绪指标公式 · 麻木修正模型</h1> <div class= sub >基于亏损比例翻倍放大 → 基础情绪 → 亏损麻木修正 (E_final) | 交易心理量化工具</div>
<div class= grid-2 > <!-- 左侧参数面板 --> <div class= panel > <div class= param-group > <label><span class= param-name >📉 当前亏损比例 x</span> <span id= xValDisplay style= font-family:monospace >0.05</span></label> <input type= range id= xSlider min= 0 max= 0.6 step= 0.002 value= 0.05 > <div class= value-hint >实际亏损比例 (例如 0.05 = 5%)</div> </div>
<div class= param-group > <label><span class= param-name >λ (lambda) · 第一次翻倍痛苦倍数</span> <input type= number id= lambda step= 0.1 value= 1.8 ></label> <div class= value-hint >当 x = 0.04 时,主观痛苦当量 y = λ * x</div> </div>
<div class= param-group > <label><span class= param-name >⚠️ 开始麻木阈值 x_m</span> <input type= number id= xm step= 0.01 value= 0.12 ></label> <div class= value-hint >亏损超过此值,情绪开始钝化 (麻木区起点)</div> </div>
<div class= param-group > <label><span class= param-name >⛔ 完全麻木阈值 x_M</span> <input type= number id= xM step= 0.02 value= 0.28 ></label> <div class= value-hint >亏损 ≥ x_M 后情绪固定为 E_min</div> </div>
<div class= param-group > <label><span class= param-name >🌀 完全麻木情绪分 E_min</span> <input type= number id= emin step= 1 value= 12 ></label> <div class= value-hint >极度亏损后的残留情绪值 (0~100)</div> </div>
<button id= calcBtn >⟳ 重新计算 更新曲线</button>
<div class= result-card > <div class= score-label >当前情绪分数 E_final</div> <div class= score id= finalScore >--</div> <div id= emotionDesc class= emotion-level >——</div> <div style= font-size:0.7rem; margin-top:8px; >🔍 基于公式: 对数翻倍放大 + 二次麻木衰减</div> </div> <div class= legend style= flex-wrap:wrap; > <span>⬤ 基础E_base</span> <span style= color:#f97316; >⬤ 最终E_final</span> <span style= color:#a855f7; >⬤ 麻木阈值区</span> </div> </div>
<!-- 右侧图表区 --> <div class= chart-panel > <canvas id= emotionChart width= 600 height= 350 style= width:100%; height:auto; max-height:380px ></canvas> <div style= font-size:0.7rem; text-align:center; margin-top:8px; >横轴: 亏损比例 x | 纵轴: 情绪分数 (0~100)</div> </div> </div> <hr> <div style= font-size:0.75rem; line-height:1.4; background:#0a0f1c; border-radius:1rem; padding:12px; > 📐 公式回忆 · 参数推演:<br> n = max(0, log₂(x / 0.02)) | y = [1 + (λ-1)/ln2 · ln(1+n)] · x | E_base = min(100, (y / 0.30) × 100)<br> 麻木修正: E_final = E_base (x ≤ x_m) ; E_final = E_max - k·(x - x_m)² (x_m < x ≤ x_M) ; E_final = E_min (x ≥ x_M)<br> ※ 其中 E_max = E_base(x_m) , k = (E_max - E_min)/(x_M - x_m)² </div> <footer>情绪指标 · 基于“翻倍痛苦放大”与“亏损麻木”模型 | 参数可调 | 用于交易心理监测</footer></div>
<script> (function() { // DOM 元素绑定 const xSlider = getElementById(‘xSlider‘); const xValDisplay = getElementById(‘xValDisplay‘); const lambdaInput = getElementById(‘lambda‘); const xmInput = getElementById(‘xm‘); const xMInput = getElementById(‘xM‘); const eminInput = getElementById(‘emin‘); const calcBtn = getElementById(‘calcBtn‘); const finalScoreSpan = getElementById(‘finalScore‘); const emotionDescSpan = getElementById(‘emotionDesc‘);
let chart = null;
// 自然对数 ln2 常量 const LN2 = Math.LN2;
// 核心计算函数: 给定 x, λ, x_m, x_M, E_min 返回 E_final (并附带 E_base) function computeEmotion(x, lambda, x_m, x_M, E_min) { // 边界保护: x 不能小于0 if (x <= 0) return { E_base: 0, E_final: 0 }; // 基准阈值 0.02 const threshold = 0.02; let n = 0; if (x > threshold) { n = Math.log2(x / threshold); if (n < 0) n = 0; } // n 翻倍次数 (公式 max(0, log2(x/0.02))) // 计算 y = [1 + (λ-1)/ln2 * ln(1+n)] * x let y; if (n === 0) { y = x; // 无放大 } else { const factor = 1 + ((lambda - 1) / LN2) * Math.log(1 + n); y = factor * x; } // E_base = min(100, (y / 0.30) * 100) let E_base = (y / 0.30) * 100; if (E_base > 100) E_base = 100; if (E_base < 0) E_base = 0;
// 计算最终情绪 E_final (麻木修正) let E_final; if (x <= x_m) { E_final = E_base; } else if (x >= x_M) { E_final = E_min; } else { // 先求 E_max = E_base(x_m) const E_max = (() => { let n_m = 0; if (x_m > threshold) { n_m = Math.log2(x_m / threshold); if (n_m < 0) n_m = 0; } let y_m; if (n_m === 0) y_m = x_m; else { const factor_m = 1 + ((lambda - 1) / LN2) * Math.log(1 + n_m); y_m = factor_m * x_m; } let base_m = (y_m / 0.30) * 100; if (base_m > 100) base_m = 100; if (base_m < 0) base_m = 0; return base_m; })(); const k = (E_max - E_min) / ((x_M - x_m) ** 2); let delta = x - x_m; let val = E_max - k * (delta * delta); E_final = Math.min(Math.max(val, E_min), E_max); if (isNaN(E_final)) E_final = E_min; } // 边界裁剪 0-100 E_final = Math.min(100, Math.max(0, E_final)); return { E_base: Math.min(100, Math.max(0, E_base)), E_final }; }
// 批量生成曲线数据 (0 ~ maxX) function generateCurve(lambda, x_m, x_M, E_min, maxX = 0.5, steps = 300) { const xs = []; const baseVals = []; const finalVals = []; for (let i = 0; i <= steps; i++) { let x = (i / steps) * maxX; if (x > 0.6) x = 0.6; xs.push(x); const { E_base, E_final } = computeEmotion(x, lambda, x_m, x_M, E_min); baseVals.push(E_base); finalVals.push(E_final); } return { xs, baseVals, finalVals }; }
// 更新UI显示 刷新图表 function update() { // 获取各参数 let x = parseFloat(xSlider.value); if (isNaN(x)) x = 0.05; x = Math.min(0.6, Math.max(0, x)); xSlider.value = x; xValDisplay.innerText = x.toFixed(4);
let lambda = parseFloat(lambdaInput.value); if (isNaN(lambda)) lambda = 1.8; if (lambda < 1.0) lambda = 1.0; let x_m = parseFloat(xmInput.value); if (isNaN(x_m)) x_m = 0.12; let x_M = parseFloat(xMInput.value); if (isNaN(x_M)) x_M = 0.28; let E_min = parseFloat(eminInput.value); if (isNaN(E_min)) E_min = 12; E_min = Math.min(100, Math.max(0, E_min));
// 保证逻辑合理: x_m < x_M if (x_m >= x_M) { x_m = x_M - 0.02; if (x_m < 0) x_m = 0; xmInput.value = x_m.toFixed(4); } if (x_M < x_m + 0.01) x_M = x_m + 0.05; xMInput.value = x_M.toFixed(4); // 当前点计算 const { E_base: currentBase, E_final: currentFinal } = computeEmotion(x, lambda, x_m, x_M, E_min); finalScoreSpan.innerText = Math.round(currentFinal); // 情绪描述 if (currentFinal >= 80) emotionDescSpan.innerText = ‘🔥 极度痛苦 / 情绪过载‘; else if (currentFinal >= 60) emotionDescSpan.innerText = ‘⚠️ 明显焦虑 / 压力区‘; else if (currentFinal >= 40) emotionDescSpan.innerText = ‘📉 中度不适 / 警惕区‘; else if (currentFinal >= 20) emotionDescSpan.innerText = ‘🍃 轻度麻木 / 适应期‘; else emotionDescSpan.innerText = ‘❄️ 深度麻木 / 情绪钝化 (防御机制)‘; // 绘制全局曲线 (最大展示到 0.55 足够覆盖阈值) const maxX_display = 0.55; const curveData = generateCurve(lambda, x_m, x_M, E_min, maxX_display, 400); if (chart) { chart.data.datasets[0].data = curveData.finalVals; chart.data.datasets[1].data = curveData.baseVals; chart.data.labels = curveData.xs.map(v => v.toFixed(3)); chart.update(); } else { // 初次创建图表 const ctx = getElementById(‘emotionChart‘).getContext(‘2d‘); chart = new Chart(ctx, { type: ‘line‘, data: { labels: curveData.xs.map(v => v.toFixed(3)), datasets: [ { label: ‘最终情绪 E_final (麻木修正)‘, data: curveData.finalVals, borderColor: ‘#f97316‘, backgroundColor: ‘transparent‘, borderWidth: 2.5, pointRadius: 0, tension: 0.2, borderDash: [], fill: false }, { label: ‘基础情绪 E_base (无麻木)‘, data: curveData.baseVals, borderColor: ‘#60a5fa‘, backgroundColor: ‘transparent‘, borderWidth: 2, pointRadius: 0, tension: 0.2, borderDash: [6, 5], fill: false } ] }, options: { responsive: true, maintainAspectRatio: true, plugins: { tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${ctx.raw.toFixed(1)} 分` } }, legend: { labels: { color: ‘#cbd5e6‘, font: { size: 11 } } } }, scales: { x: { title: { display: true, text: ‘亏损比例 x‘, color: ‘#94a3b8‘ }, ticks: { color: ‘#b9c7db‘, callback: (val) => parseFloat(val).toFixed(2) } }, y: { title: { display: true, text: ‘情绪分数 (0-100)‘, color: ‘#94a3b8‘ }, min: 0, max: 100, ticks: { stepSize: 20, color: ‘#b9c7db‘ } } }, elements: { point: { radius: 0 } } } }); } // 额外在图表上标注麻木区间指示 (可以用注释插件但简单起见不作额外库) // 向canvas上方绘制标注太麻烦,通过dataset备注即可。 if (chart) { // 更新数据集不重建 chart.data.datasets[0].data = curveData.finalVals; chart.data.datasets[1].data = curveData.baseVals; chart.data.labels = curveData.xs.map(v => v.toFixed(3)); chart.update(); } }
// 滑块实时联动 xSlider.addEventListener(‘input‘, () => { const val = parseFloat(xSlider.value); xValDisplay.innerText = val.toFixed(4); update(); }); [lambdaInput, xmInput, xMInput, eminInput].forEach(inp => { inp.addEventListener(‘input‘, () => update()); }); calcBtn.addEventListener(‘click‘, () => update());
// 初始化时额外确保参数有效性 function init() { if (parseFloat(xmInput.value) >= parseFloat(xMInput.value)) { xMInput.value = (parseFloat(xmInput.value) + 0.08).toFixed(4); } update(); } init(); })();</script></body></html>