- // ==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
- // ==/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: '<h5>'+line.days +'日线</h5><strong>%s</strong><br>'+strFormatPrefix +'%0.2f'+strFormatSuffix+'<br>日均售出 %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<span class="priceHistoryTime"> %#I%p<span>'},
- 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: '<strong>%s</strong><br>%s<br>已售出 %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 = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 256 256" preserveAspectRatio="xMidYMid"><path d="M127.779 0C60.42 0 5.24 52.412 0 119.014l68.724 28.674a35.812 35.812 0 0 1 20.426-6.366c.682 0 1.356.019 2.02.056l30.566-44.71v-.626c0-26.903 21.69-48.796 48.353-48.796 26.662 0 48.352 21.893 48.352 48.796 0 26.902-21.69 48.804-48.352 48.804-.37 0-.73-.009-1.098-.018l-43.593 31.377c.028.582.046 1.163.046 1.735 0 20.204-16.283 36.636-36.294 36.636-17.566 0-32.263-12.658-35.584-29.412L4.41 164.654c15.223 54.313 64.673 94.132 123.369 94.132 70.818 0 128.221-57.938 128.221-129.393C256 57.93 198.597 0 127.779 0zM80.352 196.332l-15.749-6.568c2.787 5.867 7.621 10.775 14.033 13.47 13.857 5.83 29.836-.803 35.612-14.799a27.555 27.555 0 0 0 .046-21.035c-2.768-6.79-7.999-12.086-14.706-14.909-6.67-2.795-13.811-2.694-20.085-.304l16.275 6.79c10.222 4.3 15.056 16.145 10.794 26.46-4.253 10.314-15.998 15.195-26.22 10.895zm121.957-100.29c0-17.925-14.457-32.52-32.217-32.52-17.769 0-32.226 14.595-32.226 32.52 0 17.926 14.457 32.512 32.226 32.512 17.76 0 32.217-14.586 32.217-32.512zm-56.37-.055c0-13.488 10.84-24.42 24.2-24.42 13.368 0 24.208 10.932 24.208 24.42 0 13.488-10.84 24.421-24.209 24.421-13.359 0-24.2-10.933-24.2-24.42z" fill="#1A1918"/></svg>`;
- 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 = `
- <input type="number" min="1" max="120" class="numberInput" value="${item.days}">
- <input type="color" class="colorInput" value="${item.color}">
- `;
- 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 = `
- <div> 均线设置</div>
- <div id="colorPickers"></div>
- <button id="confirmBtn">确定</button>
- <button id="cancelBtn">取消</button>
- `;
- 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)
- }
- })();