牛人开发的通达信插件,可盘中依据涨停时间显示涨停或炸板的个股信息。
个股所属概念,调用KPL数据。
通过通达信的“通用浏览器”调用使用。
界面如图:
使用方法步骤:
1、把下面源代码复制到“记事本”,并保存为.html格式,命名为“涨停炸板时间轴.html”
2、“涨停炸板时间轴.html”可存放于本机任意位置。
3、在通达信自定义版面,新建“通用浏览器”,在地址上选取该““涨停炸板时间轴.html””文件即可。
“涨停炸板时间轴.html”源代码如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>涨停时间轴</title> <style> body { font-family: "Microsoft YaHei", sans-serif; margin: 0px; background: #000; color: #fff; padding-top: 90px; } .controls { display: flex; flex-direction: column; position: fixed; top: 0; left: 0; right: 0; background: #000; padding: 2px; z-index: 1000; box-shadow: 0 2px 5px rgba(0,0,0,0.5); } .market-overview { width: 100%; padding: 2px 0; border-bottom: 1px solid #333; margin-bottom: 1px; } .market-row { display: flex; justify-content: center; flex-wrap: wrap; font-size: 14px; line-height: 1.5; } .stats-bar { padding: 1px 2px; background: #222; display: flex; flex-direction: column; font-size: 14px; display: flex; gap: 5px; border-bottom: 1px solid #333; } .market-row span { margin: 0 8px; color: #fff; } .market-row .red { color: #ff0000; } .market-row .green { color: #00ff00; } .market-row .white { color: #ffffff; } .market-row .highlight { color: #ffd700; } .market-row .blue { color: #00bfff; } .original-controls { display: flex; justify-content: center; align-items: center; gap: 2px; padding-top: 2px; } .type-label { color: #ffd700; font-size: 18px; margin-right: 2px; white-space: nowrap; } #cztCount { color: #00ff00; font-size: 0.8em; margin-left: 1px; } #ztCount { color: #ff0000; font-size: 0.8em; margin-left: 1px; } .date-control { display: flex; align-items: center; gap: 8px; } .date-btn { background: none; border: 1px solid #FFD700; color: #FFD700; padding: 2px 6px; border-radius: 4px; cursor: pointer; font-size: 12px; } .refresh-btn { background: #FFD700; color: #1a1a1a; border: none; padding: 2px 6px; border-radius: 4px; cursor: pointer; font-size: 12px; } .timeline { position: relative; max-width: 1200px; margin: 0 auto; padding: 0 0px; margin-top: 65px; } .timeline::before { content: ''; position: absolute; left: 50%; transform: translateX(-50%); top: 0; bottom: 0; width: 4px; background: #ffd700; box-shadow: 0 0 10px #ffd700; } .timeline-item { position: relative; margin-bottom: 2px; display: flex; justify-content: space-between; width: 100%; } .time-marker.red { color: #ff0000; text-shadow: 0 0 8px #ff0000; } .time-marker.white { color: #ffffff; text-shadow: 0 0 8px #ffffff; } .time-marker.purple-red { color: #ff00ff; text-shadow: 0 0 8px #ff00ff; } .time-marker { position: absolute; left: 50%; transform: translateX(-50%); top: 0; width: 50px; text-align: center; font-size: 16px; font-weight: bold; } .left-panel, .right-panel { width: 45%; min-height: 20px; padding: 2px; background: #1a1a1a; border-radius: 6px; border: 1px solid #333; } .stock-item { display: grid; grid-template-columns: minmax(max-content, auto) minmax(max-content, auto) minmax(max-content, 1fr); gap: 2px; padding: 2px; margin: 2px 0; align-items: center; background: #2a2a2a; border-radius: 4px; border-left: 2px solid #ffd700; } .stock-name { font-weight: bold; white-space: nowrap; overflow: visible; text-overflow: clip; color: #ffd700; text-decoration: none; font-size: 14px; min-width: max-content; } .stock-name:hover { color: white; text-shadow: 0 0 8px #ffd700; } .board-status { text-align: center; color: #ffd700; font-size: 14px; padding: 1px 2px; border-radius: 3px; min-width: max-content; } .stock-item.has-yesterday { grid-template-columns: minmax(max-content, auto) minmax(max-content, auto) minmax(max-content, auto) minmax(max-content, 1fr); } .stock-item.has-fengdan { grid-template-columns: minmax(max-content, auto) minmax(max-content, auto) minmax(max-content, auto) minmax(max-content, 1fr); } .yesterday-dayb { font-size: 12px; color: #888; padding: 0 3px; min-width: 20px; text-align: center; } .reason-box { cursor: pointer; padding: 2px; border-radius: 3px; transition: all 0.3s; font-size: 12px; white-space: nowrap; overflow: visible; text-overflow: clip; background: rgba(255,255,255,0.1); min-width: max-content; } .fengdan-box { font-size: 12px; color: #ffd700; padding: 0 3px; text-align: right; min-width: max-content; } @keyframes glow { 0% { box-shadow: 0 0 5px currentColor; } 50% { box-shadow: 0 0 20px currentColor; } 100% { box-shadow: 0 0 5px currentColor; } } </style> </head> <body> <div> <div> <div id="statsBar"> <div>板块:<span id="reasonCount">0</span></div> <div>连板:<span id="daybCount">0</span></div> </div> <div id="marketRow1"></div> <div id="marketRow2"></div> <div id="marketRow3"></div> </div> <div> <span> 炸板<span id="cztCount">0</span>|涨停<span id="ztCount">0</span> </span> <div> <button id="prev-day"><</button> <span id="current-date"></span> <button id="next-day">></button> <button id="refreshBtn">刷新</button> </div> </div> </div> <div id="timeline"></div> <script> function getBeijingDate() { const now = new Date(); return new Date(now.getTime() + (now.getTimezoneOffset() * 60000) + (3600000 * 8)); } function isWorkday(date) { const day = date.getDay(); return day !== 0 && day !== 6; } function getLastWorkday() { const now = getBeijingDate(); const isTodayWorkday = isWorkday(now); const currentHour = now.getHours(); const currentMinute = now.getMinutes(); if (isTodayWorkday) { if (currentHour > 9 || (currentHour === 9 && currentMinute >= 15)) { return now; } } let lastWorkday = new Date(now); do { lastWorkday.setDate(lastWorkday.getDate() - 1); } while (!isWorkday(lastWorkday)); return lastWorkday; } function adjustDate(increment) { const step = increment ? 1 : -1; let newDate = new Date(currentDate); do { newDate.setDate(newDate.getDate() + step); } while (!isWorkday(newDate)); const lastWorkday = getLastWorkday(); if(newDate > lastWorkday) newDate = new Date(lastWorkday); currentDate = newDate; updateDateDisplay(); renderTimeline(); // 确保日期变化后刷新数据 } function formatAPIDate(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}${m}${d}`; } function formatDisplayDate(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const weekdays = ['日','一','二','三','四','五','六']; return `${y}${m}${d}周${weekdays[date.getDay()]}`; } function updateDateDisplay() { document.getElementById('current-date').textContent = formatDisplayDate(currentDate); } function isTradingTime() { const now = getBeijingDate(); const hours = now.getHours(); const minutes = now.getMinutes(); const time = hours * 100 + minutes; return (time >= 915 && time <= 1131) || (time >= 1259 && time <= 1501); } function setupAutoRefresh() { const now = getBeijingDate(); if(isWorkday(now) && isTradingTime()) { if(!refreshInterval) { refreshInterval = setInterval(renderTimeline, 5000); } } else { clearInterval(refreshInterval); refreshInterval = null; } } // 初始化当前日期 let currentDate = getLastWorkday(); let refreshInterval = null; async function fetchMarketData(date) { const aday = formatAPIDate(date); try { const [marketRes, blockRes] = await Promise.all([ fetch(`https://data.10jqka.com.cn/mobileapi/hotspot_focus/market_state/v1/overview?date=${aday}`), fetch(`https://goserver.huanshoulv.com/aimapp/surgeLimit/surgeTopBlock?date=${aday}&sort_field_name=surgedays&sort_type=-1&ts=1740497791359&sign=77e7a16f153f39995bdb487c3e0e79d4&appversion=2.0.0.6&has_dep=false&channel=hsl-app&div=ANDH020006`) ]); const [marketData, blockData] = await Promise.all([ marketRes.json(), blockRes.json() ]); const rise = marketData.data.rise_fall.rise; const fall = marketData.data.rise_fall.fall; const deuce = marketData.data.rise_fall.deuce; const zt = marketData.data.rise_fall.limit_up; const dt = marketData.data.rise_fall.limit_down; const jcje = marketData.data.turnover.now; const zcje = marketData.data.turnover.pre; const jhb = marketData.data.limit_up.now; const zhb = marketData.data.limit_up.pre; const blocks = blockData.data.block.slice(0, 3) .map(b => `${b.block_name} ${b.weight}`) .join(' '); return { rise, fall, deuce, zt, dt, jcje, zcje, jhb, zhb, blocks }; } catch (error) { console.error('市场数据加载失败:', error); return null; } } async function getHSL(aday) { try { const headers = new Headers({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'}); const mainUrl = `https://goserver.huanshoulv.com/aimapp/surgeLimit/surgeTopBlock?date=${aday}&sort_field_name=surgedays&sort_type=-1&ts=1740497791359&sign=77e7a16f153f39995bdb487c3e0e79d4&appversion=2.0.0.6&has_dep=false&channel=hsl-app&div=ANDH020006`; const mainRes = await fetch(mainUrl, {headers}); const mainData = await mainRes.json(); const stocks = {}; const blocks = mainData.data.block; for (const block of blocks) { const bkname = encodeURIComponent(block.block_name); const bkzt = block.weight; const blockUrl = `https://goserver.huanshoulv.com/aimapp/surgeLimit/surgeTopBlock?date=${aday}&sort_field_name=surgedays&block_name=${bkname}&sort_type=-1&ts=1740497791359&sign=77e7a16f153f39995bdb487c3e0e79d4&appversion=2.0.0.6&has_dep=false&channel=hsl-app&div=ANDH020006`; const blockRes = await fetch(blockUrl, {headers}); const blockData = await blockRes.json(); blockData.data.surgeList.forEach(asure => { const code = asure[0]; const fdl = asure[7]; const nowc = asure[3]; const fdValue = fdl * nowc; stocks[code] = { concept: block.block_name, lb: asure[26], fd: fdValue > 100000000 ? `${(fdValue/100000000).toFixed(2)}亿` : `${Math.round(fdValue/10000)}万` }; }); } return stocks; } catch (error) { console.error('HSL数据获取失败:', error); return {}; } } async function getHSLztyy(aday) { try { const headers = new Headers({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36' }); const isLatestTradingDay = await checkIsLatestTradingDay(aday); const url1 = isLatestTradingDay ? `http://goserver.huanshoulv.com/aimapp/surgeLimit/surgeLimitList?page=1&type=1&sort_type=-1&page_count=5000&channel=hsl-app&ts=1560867041083&sign=8e4ef91d04fca0817e2b21e9c816f14e` : `http://goserver.huanshoulv.com/aimapp/surgeLimit/surgeLimitHistory?date=${aday}&history_type=1&page=1&type=1&sort_type=-1&page_count=5000&channel=hsl-app&ts=1560867041083&sign=8e4ef91d04fca0817e2b21e9c816f14e`; const url2 = isLatestTradingDay ? `http://goserver.huanshoulv.com/aimapp/surgeLimit/surgeLimitList?page=1&type=2&sort_type=-1&page_count=5000&channel=hsl-app&ts=1560867041083&sign=8e4ef91d04fca0817e2b21e9c816f14e` : `http://goserver.huanshoulv.com/aimapp/surgeLimit/surgeLimitHistory?date=${aday}&history_type=1&page=1&type=2&sort_type=-1&page_count=5000&channel=hsl-app&ts=1560867041083&sign=8e4ef91d04fca0817e2b21e9c816f14e`; const [res1, res2] = await Promise.all([ fetch(url1, { headers }), fetch(url2, { headers }) ]); const data1 = await res1.json(); const data2 = await res2.json(); const list = [...data1.data.surgeList, ...data2.data.surgeList]; const hslztyy = {}; function parseReason(str) { if (!str) return null; const matchPlus = str.match(/看多\+(.*?)\(/); if (matchPlus) return matchPlus[1]; const matchStart = str.match(/^(.*?)\(/); return matchStart ? matchStart[1] : str.split('+')[0]; // 如果都不匹配,取第一个概念 } list.forEach(item => { const code = item[32] || item[0]; // 兼容不同接口可能的不同字段位置 const reason = parseReason(item[3]) || '未知概念'; hslztyy[code] = reason; }); return hslztyy; } catch (error) { console.error('HSL涨停原因获取失败:', error); return {}; } } function getLastday() { const now = getBeijingDate(); const isTodayWorkday = isWorkday(now); const currentHour = now.getHours(); const currentMinute = now.getMinutes(); if (isTodayWorkday) { return formatAPIDate(now); } let lastWorkday = new Date(now); do { lastWorkday.setDate(lastWorkday.getDate() - 1); } while (!isWorkday(lastWorkday)); return formatAPIDate(lastWorkday); } async function checkIsLatestTradingDay(dateStr) { return dateStr === getLastday(); } // 主数据接口 async function fetchData(date) { const currentDateObj = new Date(date); const formattedDate = formatAPIDate(currentDateObj); // 获取昨日数据 const prevDateObj = new Date(currentDateObj); prevDateObj.setDate(prevDateObj.getDate() -1); const prevDate = getLastWorkday(prevDateObj); const prevFormattedDate = formatAPIDate(prevDate); try { const [ztRes, cztRes, hslData, hslztyyData, prevRes] = await Promise.all([ fetch(`https://api.duishu.com//lhbapp/zhangting/index?day=${formattedDate}&pagecount=20&position=2&page=1&type=1&orderKey=zt_time&orderValue=desc&apiversion=8.9`), fetch(`https://api.duishu.com//lhbapp/zhangting/index?day=${formattedDate}&pagecount=20&page=1&type=2&apiversion=8.9`), getHSL(formattedDate), getHSLztyy(formattedDate), fetch(`https://api.duishu.com//lhbapp/zhangting/index?day=${prevFormattedDate}&pagecount=200&position=2&page=1&type=1&orderKey=zt_time&orderValue=desc&apiversion=8.9`) ]); const [ztData, cztData, prevData] = await Promise.all([ ztRes.json(), cztRes.json(), prevRes.json().catch(() => ({ data: { stock_list: { list: [] }}})) // 容错处理 ]); const prevDaybMap = prevData.data.stock_list.list.reduce((acc, item) => { const code = item[0].text.split('#')[1]; acc[code] = item[3].text.replace('连', '').replace('首', '1'); return acc; }, {}); const processItems = (items, isZt) => items.map(item => { const code = item[0].text.split('#')[1]; let reason = hslData[code]?.concept || '未知概念'; if (reason === '未知概念' && hslztyyData[code]) { reason = hslztyyData[code]; } return { time: item[2].text, name: item[0].text.split('#')[0], code: code, zf: item[1].text, reason: reason, dayb: item[3].text.replace('连', '').replace('首', '1'), prev_dayb: prevDaybMap[code] || '-', fengdan: item[5].text, type: isZt ? 'zt' : 'czt' }; }); return [ ...processItems(ztData.data.stock_list.list, true), ...processItems(cztData.data.stock_list.list, false) ]; } catch (error) { console.error('数据获取失败:', error); return []; } } function createStockItem(item, isCzt) { const div = document.createElement('div'); div.className = `stock-item ${isCzt ? 'has-yesterday' : 'has-fengdan'}`; div.innerHTML = ` <a href="http://www.treeid/code_${item.code}"> ${item.name} </a> <div class="board-status selectable" data-dayb="${item.dayb}" style="--hue:${Math.random() * 360}"> ${isCzt ? item.zf : item.dayb} </div> ${isCzt ? ` <div title="昨日连板天数"> ${item.prev_dayb} </div> ` : ''} <div class="reason-box selectable" data-reason="${item.reason}" style="--hue:${Math.random() * 360}"> ${item.reason} </div> ${!isCzt ? ` <div title="封单金额"> ${item.fengdan} </div> ` : ''} `; return div; } async function renderTimeline() { try { const marketData = await fetchMarketData(currentDate); if (marketData) { const jcjeNum = parseFloat(marketData.jcje.replace(/,/g, '')) || 0; const zcjeNum = parseFloat(marketData.zcje.replace(/,/g, '')) || 0; const jhbNum = parseInt(marketData.jhb) || 0; const zhbNum = parseInt(marketData.zhb) || 0; document.getElementById('marketRow1').innerHTML = ` <span>上涨<span>${marketData.rise}</span> 下跌<span>${marketData.fall}</span> 平盘<span>${marketData.deuce}</span> 涨停<span>${marketData.zt}</span> 跌停<span>${marketData.dt}</span> `; document.getElementById('marketRow2').innerHTML = ` <span>今额:<span class="${jcjeNum > zcjeNum ? 'red' : 'green'}">${marketData.jcje}</span> 昨额:<span>${marketData.zcje}</span> 今板高<span class="${jhbNum > zhbNum ? 'red' : 'green'}">${marketData.jhb}</span> 昨板高<span>${marketData.zhb}</span> `; document.getElementById('marketRow3').innerHTML = marketData.blocks.split(' ').map(b => `<span>${b.split(' ')[0]} <span>${b.split(' ')[1]}</span></span>` ).join(''); } const data = await fetchData(currentDate); let ztCount = 0, cztCount = 0; const sortedData = data.sort((a, b) => { const timeA = parseInt(a.time.replace(':', '')); const timeB = parseInt(b.time.replace(':', '')); return timeB - timeA; }); const groupedData = sortedData.reduce((acc, item) => { item.type === 'zt' ? ztCount++ : cztCount++; acc[item.time] = acc[item.time] || { zt: [], czt: [] }; item.type === 'zt' ? acc[item.time].zt.push(item) : acc[item.time].czt.push(item); return acc; }, {}); document.getElementById('ztCount').textContent = ztCount; document.getElementById('cztCount').textContent = cztCount; const timeline = document.getElementById('timeline'); timeline.innerHTML = ''; Object.entries(groupedData).forEach(([time, items]) => { const [hours, minutes] = time.split(':').map(Number); const totalTime = hours * 100 + minutes; let timeColorClass = ''; if (totalTime < 1000) { timeColorClass = 'red'; } else if (totalTime >= 1000 && totalTime < 1430) { timeColorClass = 'white'; } else { timeColorClass = 'purple-red'; } const container = document.createElement('div'); container.className = 'timeline-item'; container.innerHTML = ` <div class="time-marker ${timeColorClass}">${time}</div> <div> ${items.czt.map(item => createStockItem(item, true).outerHTML).join('')} </div> <div> ${items.zt.map(item => createStockItem(item, false).outerHTML).join('')} </div> `; timeline.appendChild(container); }); const restoreSelection = (storageKey, dataAttr) => { const selections = JSON.parse(sessionStorage.getItem(storageKey) || '[]'); if (selections.length) { requestAnimationFrame(() => { let needUpdate = false; selections.forEach(({ value, color }) => { document.querySelectorAll(`[data-${dataAttr}="${value}"]`).forEach(box => { box.style.backgroundColor = color; box.style.color = '#000'; box.style.fontWeight = 'bold'; box.style.animation = 'glow 1.5s ease-in-out infinite'; needUpdate = true; }); }); if(needUpdate) updateCount(); }); } updateCount(); }; restoreSelection('selections', 'reason'); restoreSelection('daybSelections', 'dayb'); } catch (error) { console.error('数据加载失败:', error); } } function updateCount() { const reasonCounts = {}; document.querySelectorAll('.reason-box[data-reason]').forEach(item => { if (item.style.backgroundColor) { const reason = item.dataset.reason.split(' ')[0]; reasonCounts[reason] = (reasonCounts[reason] || 0) + 1; } }); const sortedReasons = Object.entries(reasonCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([k, v]) => `${k}${v}`); document.getElementById('reasonCount').textContent = sortedReasons.join(' ') || '0'; const daybCounts = {}; document.querySelectorAll('.board-status[data-dayb]').forEach(item => { if (item.style.backgroundColor) { const dayb = item.dataset.dayb; daybCounts[dayb] = (daybCounts[dayb] || 0) + 1; } }); const sortedDayb = Object.entries(daybCounts) .sort((a, b) => b[0].localeCompare(a[0])) .map(([k, v]) => `${k}${v}`); document.getElementById('daybCount').textContent = sortedDayb.join(' ') || '0'; } // 事件监听器 document.getElementById('prev-day').onclick = () => adjustDate(false); document.getElementById('next-day').onclick = () => adjustDate(true); document.getElementById('refreshBtn').onclick = renderTimeline; document.getElementById('timeline').addEventListener('click', (e) => { const target = e.target.closest('.selectable'); if (!target) return; const isDayb = target.classList.contains('board-status'); const storageKey = isDayb ? 'daybSelections' : 'selections'; const dataAttr = isDayb ? 'dayb' : 'reason'; const selections = JSON.parse(sessionStorage.getItem(storageKey) || '[]'); const currentColor = `hsl(${target.style.getPropertyValue('--hue')},100%,50%)`; const value = target.dataset[dataAttr]; const existingIndex = selections.findIndex(s => s.value === value); if (existingIndex >= 0) { selections.splice(existingIndex, 1); document.querySelectorAll(`[data-${dataAttr}="${value}"]`).forEach(el => { el.style.backgroundColor = ''; el.style.color = ''; el.style.animation = ''; }); } else { if (selections.length >= 200) selections.shift(); selections.push({ value, color: currentColor }); document.querySelectorAll(`[data-${dataAttr}="${value}"]`).forEach(el => { el.style.backgroundColor = currentColor; el.style.color = '#000'; el.style.fontWeight = 'bold'; el.style.animation = 'glow 1.5s ease-in-out infinite'; }); } sessionStorage.setItem(storageKey, JSON.stringify(selections)); updateCount(); }); // 初始化 updateDateDisplay(); renderTimeline(); setInterval(setupAutoRefresh, 60000); setupAutoRefresh(); </script> </body> </html>
标签: 涨停时间轴
版权声明:文章来自网络!方法技巧仅供参考!拾荒网10Huang.CN,财富在手十指紧握!与努力的人共勉!