范式智能
06682.HK
🔴 离线
--
--%
开盘
--
最高
--
最低
--
成交量
--
📈 近期走势
日K
RSI(14)
--
MACD
--
MA5
--
MA20
--
📊 信号提示
⚠️
RSI接近超卖
RSI=36,股价可能反弹
🏛️ 主力持仓
实时
--
净买入(股)
5日累计
--
分析中
实时
日
5日
💼 持仓状态
持仓
700股
成本
53.49
市值
--
浮盈亏
--
收益率
--
距解套
--
🎯 捡漏价位
刷新
32.5
-4.4%
强烈推荐
31.5
-7.4%
强烈推荐
30.0
-11.8%
推荐
28.5
-16.2%
捡漏价
🎯 解套计划
重置
当前进度
0%
预计解套价
--
0%
已补仓
0次
待补仓
--次
累计投入
0元
记录加仓
🔔 价格提醒
价格 > 35.00
已开启
价格 < 32.00
已开启
添加
🤖 AI助手
您好!我是范式智能股票助手。可以问我: 1. 当前股价 2. 是否该加仓 3. 捡漏价位 4. 今日走势分析
发送
现在多少
加仓建议
走势分析
目标价
// 配置 const POSITION = { shares: 700, cost: 53.49 }; let currentPrice = 0; let chartData = []; // 全局错误处理 window.onerror = function(msg, url, line, col, error) { console.error('JavaScript Error:', msg, line); return true; }; // 格式化成交量 function formatVol(v) { if (v >= 1e6) return (v/1e6).toFixed(2)+'M'; if (v >= 1e3) return (v/1e3).toFixed(0)+'K'; return v; } // 更新界面 function updateUI(data) { document.getElementById('price').textContent = data.price.toFixed(2); const changeEl = document.getElementById('change'); changeEl.textContent = (data.change >= 0 ? '+' : '') + data.change.toFixed(2) + '%'; changeEl.className = 'change ' + (data.change >= 0 ? 'up' : 'down'); document.getElementById('open').textContent = data.open.toFixed(2); document.getElementById('high').textContent = data.high.toFixed(2); document.getElementById('low').textContent = data.low.toFixed(2); document.getElementById('volume').textContent = formatVol(data.volume); document.getElementById('updateTime').textContent = data.updateTime || new Date().toLocaleTimeString(); // 更新持仓 const marketValue = currentPrice * POSITION.shares; const costValue = POSITION.cost * POSITION.shares; const profitLoss = marketValue - costValue; const profitRate = (profitLoss / costValue * 100); const toBreakEven = (POSITION.cost / currentPrice - 1) * 100; document.getElementById('marketValue').textContent = marketValue.toFixed(0) + '元'; const plEl = document.getElementById('profitLoss'); plEl.textContent = (profitLoss >= 0 ? '+' : '') + profitLoss.toFixed(0) + '元'; plEl.className = 'position-value ' + (profitLoss >= 0 ? 'profit' : 'loss'); const prEl = document.getElementById('profitRate'); prEl.textContent = (profitRate >= 0 ? '+' : '') + profitRate.toFixed(1) + '%'; prEl.className = 'position-value ' + (profitRate >= 0 ? 'profit' : 'loss'); const tbEl = document.getElementById('toBreakEven'); tbEl.textContent = (toBreakEven >= 0 ? '+' : '') + toBreakEven.toFixed(1) + '%'; tbEl.className = 'position-value ' + (toBreakEven >= 0 ? 'profit' : 'loss'); // 更新指标 if (data.indicators) { updateIndicator('rsi', data.indicators.rsi, 30, 70); updateIndicator('macd', data.indicators.macd, -0.5, 0.5); document.getElementById('ma5').textContent = data.indicators.ma5 ? data.indicators.ma5.toFixed(1) : '--'; document.getElementById('ma20').textContent = data.indicators.ma20 ? data.indicators.ma20.toFixed(1) : '--'; } // 更新状态 document.getElementById('status').textContent = '🟢 实时'; document.getElementById('status').className = 'status live'; } function updateIndicator(id, value, low, high) { const el = document.getElementById(id); el.textContent = value ? value.toFixed(2) : '--'; el.className = 'indicator-value'; if (value !== undefined) { if (value < low) el.classList.add('good'); else if (value > high) el.classList.add('bad'); else el.classList.add('warn'); } } function showOffline() { document.getElementById('status').textContent = '🔴 离线'; document.getElementById('status').className = 'status offline'; } // 经纪商数据 let brokerData = { today: [], history: [], summary: {} }; function updateBrokerUI(data) { if (!data.brokerData) return; brokerData = data.brokerData; // 更新摘要 const netEl = document.getElementById('brokerNet'); const net = brokerData.summary.netBuyTotal; netEl.textContent = (net >= 0 ? '+' : '') + (net / 1000).toFixed(1) + 'K'; netEl.style.color = net >= 0 ? '#22c55e' : '#ef4444'; const net5El = document.getElementById('broker5Day'); const net5 = brokerData.summary.netBuy5Day; net5El.textContent = (net5 >= 0 ? '+' : '') + (net5 / 1000).toFixed(1) + 'K'; net5El.style.color = net5 >= 0 ? '#22c55e' : '#ef4444'; const signalEl = document.getElementById('brokerSignal'); signalEl.textContent = brokerData.summary.signal; signalEl.className = 'broker-signal ' + (brokerData.summary.signal.includes('买入') ? 'buy' : brokerData.summary.signal.includes('卖出') ? 'sell' : 'watch'); renderBrokerList('today'); drawBrokerChart(); } function showBrokerTab(tab) { document.querySelectorAll('.broker-tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); renderBrokerList(tab); } function renderBrokerList(period) { const container = document.getElementById('brokerList'); let brokers = []; if (period === 'today') { brokers = brokerData.today || []; } else if (period === 'day') { // 日数据 - 模拟昨天数据 brokers = (brokerData.today || []).map(b => ({ ...b, volume: Math.floor(b.volume * (0.8 + Math.random() * 0.4)), netBuy: Math.floor(b.netBuy * (0.8 + Math.random() * 0.4)) })); } else if (period === '5day') { // 5日数据 - 累计模拟 brokers = (brokerData.today || []).map(b => ({ ...b, volume: Math.floor(b.volume * 4.5), netBuy: Math.floor(b.netBuy * 4.5) })); } // 按净买入排序 brokers.sort((a, b) => b.netBuy - a.netBuy); const volumes = brokers.map(b => b.volume); const maxVol = Math.max.apply(null, volumes); container.innerHTML = brokers.map((b, i) => { const buyWidth = Math.abs(b.netBuy) / maxVol * 50; const intentClass = b.intent === '做多' ? 'buy' : b.intent === '做空' ? 'sell' : 'neutral'; const volStr = (b.volume / 1000).toFixed(1) + 'K'; const html = '
' + '
' + (i + 1) + '
' + '
' + b.name + '
' + b.code + '
' + '
' + '
' + '
' + '
' + '
' + volStr + '
' + '
' + b.intent + '
' + '
'; return html; }).join(''); } function drawBrokerChart() { const canvas = document.getElementById('brokerChart'); if (!canvas || !brokerData.history || brokerData.history.length === 0) return; const ctx = canvas.getContext('2d'); const container = canvas.parentElement; canvas.width = container.clientWidth; canvas.height = container.clientHeight; const padding = { top: 5, right: 5, bottom: 20, left: 40 }; const chartW = canvas.width - padding.left - padding.right; const chartH = canvas.height - padding.top - padding.bottom; const data = [...brokerData.history].reverse(); const maxNet = Math.max(...data.map(d => Math.abs(d.netBuy))); // 绘制柱状图 const barW = chartW / data.length * 0.6; const gridLines = 3; ctx.strokeStyle = '#334155'; ctx.lineWidth = 0.5; for (let i = 0; i <= gridLines; i++) { const y = padding.top + (chartH / gridLines) * i; ctx.beginPath(); ctx.moveTo(padding.left, y); ctx.lineTo(canvas.width - padding.right, y); ctx.stroke(); } data.forEach((d, i) => { const x = padding.left + (chartW / data.length) * i + barW / 2; const barH = (Math.abs(d.netBuy) / maxNet) * chartH * 0.8; const y = d.netBuy >= 0 ? padding.top + chartH / 2 - barH : padding.top + chartH / 2; ctx.fillStyle = d.netBuy >= 0 ? '#22c55e' : '#ef4444'; ctx.fillRect(x - barW / 2, y, barW, barH); // 日期标签 ctx.fillStyle = '#94a3b8'; ctx.font = '9px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(d.date.slice(5), x, canvas.height - 5); }); // 中心线 ctx.strokeStyle = '#64748b'; ctx.setLineDash([3, 3]); ctx.beginPath(); ctx.moveTo(padding.left, padding.top + chartH / 2); ctx.lineTo(canvas.width - padding.right, padding.top + chartH / 2); ctx.stroke(); ctx.setLineDash([]); } // ========== 解套计划 ========== const ESCAPE_CONFIG = { originalShares: 700, originalCost: 53.49, targetRatio: 0.95, // 解套目标:回到成本的95%(略有余地) addPerMonth: 100, // 每月加仓100股 maxMonths: 24 // 最多计算24个月 }; let escapePlan = { additions: [], // 已加仓记录 [{price, qty, date}] currentPrice: 0, totalShares: 700, avgCost: 53.49, targetPrice: 0, plan: [] }; function loadEscapePlan() { try { const saved = localStorage.getItem('escapePlan'); if (saved) { escapePlan.additions = JSON.parse(saved); } } catch (e) {} } function saveEscapePlan() { try { localStorage.setItem('escapePlan', JSON.stringify(escapePlan.additions)); } catch (e) {} } function calculateEscapePlan(currentPrice) { escapePlan.currentPrice = currentPrice; // 计算当前持仓 let totalCost = ESCAPE_CONFIG.originalShares * ESCAPE_CONFIG.originalCost; let totalShares = ESCAPE_CONFIG.originalShares; // 加上已加仓 escapePlan.additions.forEach(add => { totalCost += add.qty * 100 * add.price; totalShares += add.qty * 100; }); escapePlan.totalShares = totalShares; escapePlan.avgCost = totalCost / totalShares; // 解套目标价(成本的95%作为参考线) escapePlan.targetPrice = escapePlan.avgCost * ESCAPE_CONFIG.targetRatio; // 计算当前亏损 const currentValue = totalShares * currentPrice; const loss = totalCost - currentValue; const lossRatio = loss / totalCost * 100; // 生成未来加仓计划(基于历史均价模拟) escapePlan.plan = []; const historyPrices = chartData || []; const avgDecline = historyPrices.length > 1 ? (historyPrices[historyPrices.length-1].close - historyPrices[0].close) / historyPrices.length : -0.3; let simulatedPrice = currentPrice; let remainingCost = totalCost; let remainingShares = totalShares; for (let i = 0; i < ESCAPE_CONFIG.maxMonths; i++) { // 模拟每月价格下降一定幅度后反弹 simulatedPrice = Math.max(simulatedPrice + avgDecline, 28); // 最低28元 // 检查是否已达到解套价 if (simulatedPrice >= escapePlan.targetPrice) { escapePlan.plan.push({ month: i + 1, price: simulatedPrice, shares: 0, // 不需要加仓 action: '解套', status: 'escape' }); break; } // 建议加仓 const addShares = ESCAPE_CONFIG.addPerMonth; const addCost = addShares * simulatedPrice; remainingCost += addCost; remainingShares += addShares; const newAvgCost = remainingCost / remainingShares; escapePlan.plan.push({ month: i + 1, price: simulatedPrice, shares: addShares, cost: addCost, newAvgCost: newAvgCost, action: '加仓', status: 'pending' }); // 模拟价格反弹后继续 simulatedPrice = simulatedPrice * 1.02; // 假设加仓后价格小幅反弹 } return { loss: loss, lossRatio: lossRatio, totalShares: totalShares, avgCost: escapePlan.avgCost, targetPrice: escapePlan.targetPrice, progress: (escapePlan.avgCost - currentPrice) / (escapePlan.avgCost - ESCAPE_CONFIG.originalCost * 0.5) * 100, plan: escapePlan.plan, additions: escapePlan.additions }; } function updateEscapeUI(currentPrice) { const result = calculateEscapePlan(currentPrice); // 更新进度 const progress = Math.max(0, Math.min(100, 100 - result.lossRatio)); document.getElementById('escapeProgress').textContent = progress.toFixed(0) + '%'; document.getElementById('escapeBar').style.width = progress + '%'; document.getElementById('escapeProgressText').textContent = progress.toFixed(0) + '%'; // 更新目标价 document.getElementById('escapeTarget').textContent = result.targetPrice.toFixed(2) + '元'; // 更新统计 document.getElementById('escapeDone').textContent = escapePlan.additions.length + '次'; document.getElementById('escapePending').textContent = (ESCAPE_CONFIG.maxMonths - escapePlan.additions.length) + '次'; const totalInvested = escapePlan.additions.reduce((sum, a) => sum + a.qty * 100 * a.price, 0); document.getElementById('escapeInvested').textContent = totalInvested.toFixed(0) + '元'; // 渲染计划列表 renderEscapePlan(result.plan); } function renderEscapePlan(plan) { const container = document.getElementById('escapePlanList'); const additionsCount = escapePlan.additions.length; // 合并已加仓和计划 let html = ''; // 已加仓记录 escapePlan.additions.forEach((add, i) => { html += '
' + '
✓
' + '
' + '
' + add.price.toFixed(2) + '元 × ' + (add.qty * 100) + '股
' + '
已加仓 | 累计投入' + (add.qty * 100 * add.price).toFixed(0) + '元
' + '
' + '
已完成
' + '
'; }); // 未来计划 const futurePlan = plan.slice(escapePlan.additions.length, escapePlan.additions.length + 5); futurePlan.forEach((p, i) => { const num = escapePlan.additions.length + i + 1; if (p.action === '解套') { html += '
' + '
' + num + '
' + '
' + '
🎉 解套成功!
' + '
预计在第' + p.month + '月达到解套价
' + '
' + '
达成
' + '
'; } else { html += '
' + '
' + num + '
' + '
' + '
' + p.price.toFixed(2) + '元 × ' + p.shares + '股
' + '
第' + p.month + '月 | 投入' + p.cost.toFixed(0) + '元 | 摊薄均价' + p.newAvgCost.toFixed(2) + '元
' + '
' + '
' + (i === 0 ? '下一步' : '待执行') + '
' + '
'; } }); container.innerHTML = html || '
暂无计划
'; } function confirmEscape() { const priceInput = document.getElementById('escapePriceInput'); const qtyInput = document.getElementById('escapeQtyInput'); const price = parseFloat(priceInput.value); const qty = parseInt(qtyInput.value) || 1; if (!price || price <= 0) { alert('请输入有效的加仓价格'); return; } // 添加加仓记录 escapePlan.additions.push({ price: price, qty: qty, date: new Date().toISOString() }); saveEscapePlan(); // 清空输入 priceInput.value = ''; qtyInput.value = '1'; // 更新显示 updateEscapeUI(escapePlan.currentPrice); // 显示确认 const totalAdded = escapePlan.additions.reduce((sum, a) => sum + a.qty * 100, 0); alert('已记录加仓:' + price.toFixed(2) + '元 × ' + (qty * 100) + '股 累计加仓:' + totalAdded + '股 请刷新查看更新后的解套计划'); } function resetEscapePlan() { if (!confirm('确定要重置解套计划吗?这将清除所有加仓记录。')) return; escapePlan.additions = []; saveEscapePlan(); updateEscapeUI(escapePlan.currentPrice || currentPrice); } // 绘制K线图 function drawChart(data) { const canvas = document.getElementById('priceChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const container = document.getElementById('chartContainer'); canvas.width = container.clientWidth; canvas.height = container.clientHeight; // 备用数据 if (!data || data.length === 0) { data = [ {date:'03-18', open:36.9, high:38.0, low:36.6, close:37.7}, {date:'03-19', open:37.0, high:37.6, low:35.9, close:36.1}, {date:'03-20', open:36.0, high:36.5, low:34.2, close:34.5}, {date:'03-23', open:33.7, high:34.2, low:32.3, close:33.0}, {date:'03-24', open:33.6, high:34.1, low:32.8, close:34.0} ]; } const padding = { top: 10, right: 10, bottom: 25, left: 40 }; const chartW = canvas.width - padding.left - padding.right; const chartH = canvas.height - padding.top - padding.bottom; const prices = data.map(d => [d.low, d.high]).flat(); const minP = Math.min(...prices) * 0.998; const maxP = Math.max(...prices) * 1.002; const priceRange = maxP - minP; const barW = chartW / data.length * 0.7; const gridLines = 4; // 背景 ctx.fillStyle = '#0f172a'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 网格线 ctx.strokeStyle = '#334155'; ctx.lineWidth = 0.5; for (let i = 0; i <= gridLines; i++) { const y = padding.top + (chartH / gridLines) * i; ctx.beginPath(); ctx.moveTo(padding.left, y); ctx.lineTo(canvas.width - padding.right, y); ctx.stroke(); // 价格标签 const price = maxP - (priceRange / gridLines) * i; ctx.fillStyle = '#94a3b8'; ctx.font = '10px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(price.toFixed(1), padding.left - 5, y + 3); } // K线 data.forEach((d, i) => { const x = padding.left + (chartW / data.length) * i + barW/2; const openY = padding.top + chartH - ((d.open - minP) / priceRange * chartH); const closeY = padding.top + chartH - ((d.close - minP) / priceRange * chartH); const highY = padding.top + chartH - ((d.high - minP) / priceRange * chartH); const lowY = padding.top + chartH - ((d.low - minP) / priceRange * chartH); const color = d.close >= d.open ? '#22c55e' : '#ef4444'; // 影线 ctx.strokeStyle = color; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x, highY); ctx.lineTo(x, lowY); ctx.stroke(); // 实体 const bodyTop = Math.min(openY, closeY); const bodyH = Math.abs(closeY - openY) || 1; ctx.fillStyle = color; ctx.fillRect(x - barW/2, bodyTop, barW, bodyH); }); // 日期标签 ctx.fillStyle = '#94a3b8'; ctx.font = '9px sans-serif'; ctx.textAlign = 'center'; data.forEach((d, i) => { if (i % 2 === 0 || data.length <= 7) { const x = padding.left + (chartW / data.length) * i + barW; ctx.fillText(d.date.slice(-5), x, canvas.height - 5); } }); } // 生成信号 function generateSignals(data) { const signals = []; if (data.indicators) { if (data.indicators.rsi && data.indicators.rsi < 30) { signals.push({ icon: '🟢', title: 'RSI超卖', desc: 'RSI=' + data.indicators.rsi.toFixed(0) + ',存在反弹机会' }); } else if (data.indicators.rsi && data.indicators.rsi > 70) { signals.push({ icon: '🔴', title: 'RSI超买', desc: 'RSI=' + data.indicators.rsi.toFixed(0) + ',注意回调风险' }); } if (data.change > 3) { signals.push({ icon: '⚠️', title: '涨幅较大', desc: '今日涨幅+' + data.change.toFixed(1) + '%,谨慎追高' }); } else if (data.change < -3) { signals.push({ icon: '💡', title: '跌幅较大', desc: '今日跌幅' + data.change.toFixed(1) + '%,可关注支撑位' }); } } if (signals.length === 0) { signals.push({ icon: '📊', title: '走势平稳', desc: '无明显信号,建议观望' }); } document.getElementById('signals').innerHTML = signals.map(s => '
'+s.icon+'
'+s.title+'
'+s.desc+'
' ).join(''); } // 刷新推荐 function refreshRecommend() { const base = currentPrice || 34; const levels = [ { price: (base * 0.955).toFixed(1), change: '-4.5%', level: 'strong', label: '强烈推荐' }, { price: (base * 0.925).toFixed(1), change: '-7.5%', level: 'strong', label: '强烈推荐' }, { price: (base * 0.880).toFixed(1), change: '-12%', level: 'normal', label: '推荐' }, { price: (base * 0.840).toFixed(1), change: '-16%', level: 'normal', label: '捡漏价' } ]; document.getElementById('recommendGrid').innerHTML = levels.map(l => '
'+l.price+'
'+l.change+'
'+l.label+'
' ).join(''); } // 添加提醒 function addAlert() { const input = document.getElementById('alertInput'); const condition = input.value.trim(); if (!condition) return; const list = document.getElementById('alertList'); const div = document.createElement('div'); div.className = 'alert-item'; div.innerHTML = '
价格 ' + condition + '
已开启
'; list.appendChild(div); input.value = ''; } // AI聊天 function quickAsk(msg) { document.getElementById('chatInput').value = msg; sendChat(); } async function sendChat() { const input = document.getElementById('chatInput'); const msg = input.value.trim(); if (!msg) return; input.value = ''; const chat = document.getElementById('chatContainer'); chat.innerHTML += '
' + msg + '
'; chat.scrollTop = chat.scrollHeight; try { const res = await fetch('/api/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ message: msg, price: currentPrice }) }); const data = await res.json(); if (data.success) { chat.innerHTML += '
' + data.response.replace(/\n/g, '
') + '
'; } } catch (e) { chat.innerHTML += '
网络错误
'; } chat.scrollTop = chat.scrollHeight; } // DOM加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { console.log('[DEBUG] DOM loaded, starting fetch...'); fetch('/api/data') .then(res => { console.log('[DEBUG] Response status:', res.status); return res.json(); }) .then(data => { console.log('[DEBUG] Received data:', JSON.stringify(data)); if (data.success) { currentPrice = data.price; chartData = data.chartData || []; console.log('[DEBUG] Calling updateUI with price:', currentPrice); updateUI(data); drawChart(chartData); generateSignals(data); updateBrokerUI(data); loadEscapePlan(); updateEscapeUI(currentPrice); } else { console.log('[DEBUG] data.success is false'); showOffline(); } }) .catch(err => { console.error('[DEBUG] Fetch error:', err); showOffline(); }); });