牛人开发的通达信插件,可盘中依据涨停时间显示涨停或炸板的个股信息。
个股所属概念,调用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,财富在手十指紧握!与努力的人共勉!

