// ==UserScript==
// @name BTC\ETH等币种实时价格 + 折线图 (支持选择币种)
// @namespace http://tampermonkey.net/
// @version 0.7
// @description 显示实时价格,支持左右选择币种,折线图展示,收起展开,全局控制,缓存50点数据。
// @author Tom-dog
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
"use strict";
// 动态加载 Chart.js
function loadChartJs(callback) {
const script = document.createElement("script");
script.src =
"https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js";
script.onload = callback;
document.head.appendChild(script);
}
loadChartJs(() => {
initUI();
setInterval(updatePrices, 5000);
});
// ✅ 从 OKX 获取 USDT 交易对,然后写死在这里
// 示例:你可以先请求一次 https://www.okx.com/api/v5/market/tickers?instType=SPOT
// 筛选 instId 里包含 "-USDT" 的,把前半部分存进 coinList
const coinList = [
"BTC", "ETH", "OKB", "SOL", "XRP", "DOGE", "ADA", "TRX", "DOT", "MATIC",
"LTC", "LINK", "FIL", "ATOM", "AVAX", "PEPE", "SHIB", "MEME"
];
// 当前选择
let selectedCoins = { left: "ETH", right: "BTC" };
const charts = {};
const priceSpans = { left: null, right: null };
function initUI() {
const wrapper = document.createElement("div");
wrapper.style.position = "fixed";
wrapper.style.top = "0";
wrapper.style.left = "50%";
wrapper.style.transform = "translateX(-50%)";
wrapper.style.backgroundColor = "#fff";
wrapper.style.color = "#000";
wrapper.style.zIndex = "999999";
wrapper.style.width = "650px";
wrapper.style.padding = "5px";
wrapper.style.fontSize = "14px";
wrapper.style.border = "1px solid #ccc";
// 顶部控制栏
const controlBar = document.createElement("div");
controlBar.style.display = "flex";
controlBar.style.justifyContent = "space-between";
controlBar.style.alignItems = "center";
controlBar.style.marginBottom = "8px";
// 左选择框
const leftWrap = document.createElement("div");
const leftSelect = document.createElement("select");
coinList.forEach((c) => {
const opt = document.createElement("option");
opt.value = c;
opt.text = c;
if (c === selectedCoins.left) opt.selected = true;
leftSelect.appendChild(opt);
});
leftSelect.onchange = () => {
selectedCoins.left = leftSelect.value;
resetChart("left", selectedCoins.left);
};
const leftPrice = document.createElement("span");
leftPrice.style.marginLeft = "6px";
leftPrice.innerText = "--";
priceSpans.left = leftPrice;
leftWrap.appendChild(leftSelect);
leftWrap.appendChild(leftPrice);
// 右选择框
const rightWrap = document.createElement("div");
const rightSelect = document.createElement("select");
coinList.forEach((c) => {
const opt = document.createElement("option");
opt.value = c;
opt.text = c;
if (c === selectedCoins.right) opt.selected = true;
rightSelect.appendChild(opt);
});
rightSelect.onchange = () => {
selectedCoins.right = rightSelect.value;
resetChart("right", selectedCoins.right);
};
const rightPrice = document.createElement("span");
rightPrice.style.marginLeft = "6px";
rightPrice.innerText = "--";
priceSpans.right = rightPrice;
rightWrap.appendChild(rightSelect);
rightWrap.appendChild(rightPrice);
// 收起展开按钮
const toggleAllBtn = document.createElement("button");
toggleAllBtn.innerText = "收起";
toggleAllBtn.onclick = () => {
const boxes = wrapper.querySelectorAll(".chart-box");
const isHidden = Array.from(boxes).every((b) => b.style.display === "none");
boxes.forEach((box) => (box.style.display = isHidden ? "block" : "none"));
toggleAllBtn.innerText = isHidden ? "收起" : "展开";
};
controlBar.appendChild(leftWrap);
controlBar.appendChild(rightWrap);
controlBar.appendChild(toggleAllBtn);
wrapper.appendChild(controlBar);
// 图表容器
const chartsContainer = document.createElement("div");
chartsContainer.style.display = "flex";
chartsContainer.style.gap = "8px";
["left", "right"].forEach((pos) => {
const section = document.createElement("div");
section.style.flex = "1";
// 折线图容器
const chartBox = document.createElement("div");
chartBox.className = "chart-box";
chartBox.style.height = "150px";
const canvas = document.createElement("canvas");
canvas.id = `chart-${pos}`;
chartBox.appendChild(canvas);
section.appendChild(chartBox);
chartsContainer.appendChild(section);
// 初始化图表
charts[pos] = initChart(canvas, selectedCoins[pos]);
});
wrapper.appendChild(chartsContainer);
document.body.appendChild(wrapper);
}
// 初始化 Chart
function initChart(canvas, coin) {
return new Chart(canvas, {
type: "line",
data: {
labels: [],
datasets: [
{
label: `${coin} Price`,
data: [],
borderColor: "blue",
fill: false,
tension: 0.1,
},
],
},
options: {
responsive: true,
animation: false,
plugins: {
legend: { display: false }, // ✅ 不显示图例
},
scales: {
x: { display: false },
y: { beginAtZero: false },
},
},
});
}
// 重置图表
function resetChart(pos, coin) {
const chart = charts[pos];
// 清空旧数据
chart.data.labels = [];
chart.data.datasets[0].data = [];
chart.data.datasets[0].label = `${coin} Price`;
chart.update();
// 价格置为 --
priceSpans[pos].innerText = "--";
}
// 获取并更新价格
async function updatePrices() {
const time = new Date().getTime();
for (const pos of ["left", "right"]) {
const coin = selectedCoins[pos];
try {
const res = await fetch(
`https://www.okx.com/api/v5/market/ticker?instId=${coin}-USDT&_=${time}`
).then((r) => r.json());
const price = parseFloat(res.data[0].last);
updateChart(pos, coin, price);
} catch (e) {
console.error(`获取 ${coin} 价格失败:`, e);
}
}
}
// 更新图表
function updateChart(pos, coin, price) {
const chart = charts[pos];
const label = new Date().toLocaleTimeString();
const dataset = chart.data.datasets[0];
dataset.label = `${coin} Price`;
dataset.data.push(price);
chart.data.labels.push(label);
// 限制最大 50 点
if (dataset.data.length > 50) {
dataset.data.shift();
chart.data.labels.shift();
}
// ✅ 更新选择框后面的价格
priceSpans[pos].innerText = `$${price}`;
chart.update();
}
})();