// ==UserScript==
// @name Steam 市场价格均线
// @namespace http://tampermonkey.net/
// @version 2024-03-29
// @description 在Steam市场的历史成交价格上显示任意日期内的均线。
// @author Cliencer Goge
// @match https://steamcommunity.com/market/listings/*/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=steamcommunity.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @license GPLv3
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
Hook_pricehistory_zoomLifetime()
Hook_pricehistory_zoomDays()
var settingdialog=initsettingdialog()
var saves = readstorage()
const url = window.location.href;
const parts = new URL(url).pathname.split('/');
const appId = parts[3];
const itemName = parts[4];
var lineoption={lines:[],series:[],seriesColors:[]}
var strFormatPrefix = "¥"
var strFormatSuffix = ""
var mainbutton = createButton()
var prices = g_plotPriceHistory.data[0]
myCreatePriceHistoryGraph( 5 )
function generatelineoption(){
lineoption ={lines:[prices],series:[{lineWidth:3, markerOptions:{show: false, style:'circle'}}],seriesColors:["#688F3E"]}
for(var line of saves.linelist){
var lineav = calculateAverage(prices, line.days)
lineoption.lines.push(lineav)
lineoption.series.push({lineWidth:1, markerOptions:{show: false, style:'circle'},highlighter:{formatString: '
'+line.days +'日线
%s
'+strFormatPrefix +'%0.2f'+strFormatSuffix+'
日均售出 %d 件'}})
lineoption.seriesColors.push(line.color)
}
}
function Hook_pricehistory_zoomDays(){
if (typeof pricehistory_zoomDays === 'function') {
console.log("Hook pricehistory_zoomDays成功");
const originalPricehistoryZoomDays = pricehistory_zoomDays;
pricehistory_zoomDays = function(arg1, arg2, arg3, arg4) {
console.log('pricehistory_zoomDays:', arg1, arg2, arg3, arg4);
return originalPricehistoryZoomDays.apply(this, [arg1, arg2, arg3, arg4]);
};
} else {
console.log("Hook失败,重试");
setTimeout(Hook_pricehistory_zoomDays,1000)
}
}
function Hook_pricehistory_zoomLifetime(){
if (typeof pricehistory_zoomLifetime === 'function') {
console.log("Hook pricehistory_zoomLifetime成功");
const originalPricehistoryZoomLifetime = pricehistory_zoomLifetime;
pricehistory_zoomLifetime = function(arg1, arg2, arg3) {
console.log('pricehistory_zoomLifetime:', arg1, arg2, arg3);
return originalPricehistoryZoomLifetime.apply(this, [arg1, arg2, arg3]);
};
} else {
console.log("Hook失败,重试");
setTimeout(Hook_pricehistory_zoomLifetime,1000)
}
}
function myCreatePriceHistoryGraph(numYAxisTicks){
generatelineoption()
g_plotPriceHistory.destroy()
g_plotPriceHistory = null
var plot = $J.jqplot('pricehistory', lineoption.lines, {
title:{text: '售价中位数', textAlign: 'left' },
gridPadding:{left: 45, right:45, top:25},
axesDefaults:{ showTickMarks:true },
axes:{
xaxis:{
renderer:$J.jqplot.DateAxisRenderer,
tickOptions:{formatString:'%b %#d %#I%p'},
pad: 1
},
yaxis: {
pad: 1.1,
tickOptions:{formatString:strFormatPrefix + '%0.2f' + strFormatSuffix, labelPosition:'start', showMark: false},
numberTicks: numYAxisTicks
}
},
grid: {
gridLineColor: '#1b2939',
borderColor: '#1b2939',
background: '#101822'
},
cursor: {
show: true,
zoom: true,
showTooltip: false
},
highlighter: {
show: true,
lineWidthAdjust: 2.5,
sizeAdjust: 5,
showTooltip: true,
tooltipLocation: 'n',
tooltipOffset: 20,
fadeTooltip: true,
yvalues: 2,
formatString: '%s
%s
已售出 %d 件'
},
series:lineoption.series,
seriesColors: lineoption.seriesColors
});
plot.defaultNumberTicks = numYAxisTicks;
g_plotPriceHistory = plot
pricehistory_zoomDays( g_plotPriceHistory, g_timePriceHistoryEarliest, g_timePriceHistoryLatest, 7 )
return plot;
}
function calculateAverage(prices, days) {
const result = [];
const oneHour = 60 * 60 * 1000;
const hoursInDay = 24;
const totalHours = days * hoursInDay;
prices.forEach((item, index) => {
const [dateStr, price, quantityStr] = item;
const date = new Date(dateStr.substring(0, 11) + " " + dateStr.substring(12, 14) + ":00:00");
const priceNumber = parseFloat(price);
const quantity = parseInt(quantityStr, 10);
let totalWeightedPrice = priceNumber * quantity;
let totalQuantity = quantity;
let earliestDate = new Date(date.getTime() - totalHours * oneHour);
let actualHours = 1;
for (let j = index - 1; j >= 0; j--) {
const [prevDateStr, prevPrice, prevQuantityStr] = prices[j];
const prevDate = new Date(prevDateStr.substring(0, 11) + " " + prevDateStr.substring(12, 14) + ":00:00");
if (prevDate >= earliestDate) {
const prevPriceNumber = parseFloat(prevPrice);
const prevQuantity = parseInt(prevQuantityStr, 10);
totalWeightedPrice += prevPriceNumber * prevQuantity;
totalQuantity += prevQuantity;
const diffHours = Math.abs((date - prevDate) / oneHour);
actualHours += diffHours;
} else {
break;
}
}
const averagePrice = totalWeightedPrice / totalQuantity;
const averageDailyQuantity = totalQuantity / (actualHours / hoursInDay);
result.push([dateStr, averagePrice.toFixed(3), averageDailyQuantity.toFixed(3)]);
});
return result;
}
function createButton(){
const button = document.createElement('button');
button.innerHTML = ``;
button.style.position = 'fixed';
button.style.top = saves.buttonposition;
button.style.zIndex = '9999'
button.style.transition = 'transform 0.3s ease';
button.style.opacity = '0.5';
button.style.backgroundColor = 'transparent';
button.style.border = 'none';
button.style.cursor = 'pointer';
button.style.left = '0'
button.style.clipPath = 'polygon(100% 50%, 85% 100%, 0 100%,0 0, 85% 0)';
button.style.transform = 'translate(0%, -50%)';
button.style.background = 'linear-gradient(to bottom right, pink, lightblue)';
var isDragging = false;
button.onmousedown = function(e) {
isDragging = true;
function onMouseMove(e) {
if (!isDragging) return;
button.style.top = `${e.clientY}px`;
}
function onMouseUp() {
isDragging = false;
saves.buttonposition = button.style.top
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
savestorage()
}
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
};
button.onmouseover = function() {
this.style.opacity = '1';
this.style.transform = 'translate(0%, -50%)';
};
button.onmouseleave = function() {
if(isDragging) return;
this.style.opacity = '0.5';
this.style.transform = 'translate(-50%, -50%)';
};
button.addEventListener('click', showsettingdialog);
button.ondragstart = function() {
return false;
};
document.body.appendChild(button);
return button
}
function showsettingdialog(){
const colorPickers = document.getElementById('colorPickers');
colorPickers.innerHTML = '';
saves.linelist.forEach(item => {
const colorPickerContainer = document.createElement('div');
colorPickerContainer.innerHTML = `
`;
colorPickers.appendChild(colorPickerContainer);
});
settingdialog.style.display = 'block'
}
function initsettingdialog(){
const modal = document.createElement('div');
modal.style.position = 'fixed';
modal.style.top = '20%';
modal.style.left = '5%';
modal.style.transform = 'translate(0, 0%)';
modal.style.backgroundColor = '#fff';
modal.style.padding = '20px';
modal.style.zIndex = '9999';
modal.style.display = 'none';
modal.style.border = '1px solid #ccc';
modal.style.boxShadow = '0 4px 6px rgba(0,0,0,.1)';
document.body.appendChild(modal);
modal.innerHTML = `
均线设置
`;
document.getElementById('confirmBtn').addEventListener('click', function() {
const numberInputs = document.querySelectorAll('.numberInput');
const colorInputs = document.querySelectorAll('.colorInput');
const lineList = [];
numberInputs.forEach((input, index) => {
const days = parseInt(input.value);
const color = colorInputs[index].value;
if(days < 1) days=1
if(days > 120) days =120
lineList.push({ days, color });
});
saves.linelist=lineList
savestorage()
modal.style.display = 'none';
try{document.getElementById('modalBG').style.display = 'none'}catch(e){}
myCreatePriceHistoryGraph( 5 )
g_plotPriceHistory.redraw()
});
document.getElementById('cancelBtn').addEventListener('click', function() {
modal.style.display = 'none';
try{document.getElementById('modalBG').style.display = 'none'}catch(e){}
});
return modal
}
function readstorage(){
var saves = GM_getValue('saves')
if(saves) return saves
saves = {
buttonposition:'50%',
linelist:[{
days:5,
color:"#FFFFFF",
},{
days:10,
color:"#FFFF0B",
},{
days:20,
color:"#FF80FF",
},{
days:30,
color:"#00E600",
}]
}
return saves
}
function savestorage(){
GM_setValue('saves',saves)
}
})();