Steam 市场价格均线

在Steam市场的历史成交价格上显示任意日期内的均线。

  1. // ==UserScript==
  2. // @name Steam 市场价格均线
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-03-29
  5. // @description 在Steam市场的历史成交价格上显示任意日期内的均线。
  6. // @author Cliencer Goge
  7. // @match https://steamcommunity.com/market/listings/*/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=steamcommunity.com
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_deleteValue
  12. // @license GPLv3
  13. // ==/UserScript==
  14. (function() {
  15. 'use strict';
  16. Hook_pricehistory_zoomLifetime()
  17. Hook_pricehistory_zoomDays()
  18. var settingdialog=initsettingdialog()
  19. var saves = readstorage()
  20. const url = window.location.href;
  21. const parts = new URL(url).pathname.split('/');
  22. const appId = parts[3];
  23. const itemName = parts[4];
  24. var lineoption={lines:[],series:[],seriesColors:[]}
  25. var strFormatPrefix = "¥"
  26. var strFormatSuffix = ""
  27. var mainbutton = createButton()
  28. var prices = g_plotPriceHistory.data[0]
  29. myCreatePriceHistoryGraph( 5 )
  30. function generatelineoption(){
  31. lineoption ={lines:[prices],series:[{lineWidth:3, markerOptions:{show: false, style:'circle'}}],seriesColors:["#688F3E"]}
  32. for(var line of saves.linelist){
  33. var lineav = calculateAverage(prices, line.days)
  34. lineoption.lines.push(lineav)
  35. 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 件'}})
  36. lineoption.seriesColors.push(line.color)
  37. }
  38. }
  39. function Hook_pricehistory_zoomDays(){
  40. if (typeof pricehistory_zoomDays === 'function') {
  41. console.log("Hook pricehistory_zoomDays成功");
  42. const originalPricehistoryZoomDays = pricehistory_zoomDays;
  43. pricehistory_zoomDays = function(arg1, arg2, arg3, arg4) {
  44. console.log('pricehistory_zoomDays:', arg1, arg2, arg3, arg4);
  45. return originalPricehistoryZoomDays.apply(this, [arg1, arg2, arg3, arg4]);
  46. };
  47. } else {
  48. console.log("Hook失败,重试");
  49. setTimeout(Hook_pricehistory_zoomDays,1000)
  50. }
  51. }
  52. function Hook_pricehistory_zoomLifetime(){
  53. if (typeof pricehistory_zoomLifetime === 'function') {
  54. console.log("Hook pricehistory_zoomLifetime成功");
  55. const originalPricehistoryZoomLifetime = pricehistory_zoomLifetime;
  56. pricehistory_zoomLifetime = function(arg1, arg2, arg3) {
  57. console.log('pricehistory_zoomLifetime:', arg1, arg2, arg3);
  58. return originalPricehistoryZoomLifetime.apply(this, [arg1, arg2, arg3]);
  59. };
  60. } else {
  61. console.log("Hook失败,重试");
  62. setTimeout(Hook_pricehistory_zoomLifetime,1000)
  63. }
  64. }
  65. function myCreatePriceHistoryGraph(numYAxisTicks){
  66. generatelineoption()
  67. g_plotPriceHistory.destroy()
  68. g_plotPriceHistory = null
  69. var plot = $J.jqplot('pricehistory', lineoption.lines, {
  70. title:{text: '售价中位数', textAlign: 'left' },
  71. gridPadding:{left: 45, right:45, top:25},
  72. axesDefaults:{ showTickMarks:true },
  73. axes:{
  74. xaxis:{
  75. renderer:$J.jqplot.DateAxisRenderer,
  76. tickOptions:{formatString:'%b %#d<span class="priceHistoryTime"> %#I%p<span>'},
  77. pad: 1
  78. },
  79. yaxis: {
  80. pad: 1.1,
  81. tickOptions:{formatString:strFormatPrefix + '%0.2f' + strFormatSuffix, labelPosition:'start', showMark: false},
  82. numberTicks: numYAxisTicks
  83. }
  84. },
  85. grid: {
  86. gridLineColor: '#1b2939',
  87. borderColor: '#1b2939',
  88. background: '#101822'
  89. },
  90. cursor: {
  91. show: true,
  92. zoom: true,
  93. showTooltip: false
  94. },
  95. highlighter: {
  96. show: true,
  97. lineWidthAdjust: 2.5,
  98. sizeAdjust: 5,
  99. showTooltip: true,
  100. tooltipLocation: 'n',
  101. tooltipOffset: 20,
  102. fadeTooltip: true,
  103. yvalues: 2,
  104. formatString: '<strong>%s</strong><br>%s<br>已售出 %d 件'
  105. },
  106. series:lineoption.series,
  107. seriesColors: lineoption.seriesColors
  108. });
  109. plot.defaultNumberTicks = numYAxisTicks;
  110. g_plotPriceHistory = plot
  111. pricehistory_zoomDays( g_plotPriceHistory, g_timePriceHistoryEarliest, g_timePriceHistoryLatest, 7 )
  112. return plot;
  113. }
  114. function calculateAverage(prices, days) {
  115. const result = [];
  116. const oneHour = 60 * 60 * 1000;
  117. const hoursInDay = 24;
  118. const totalHours = days * hoursInDay;
  119. prices.forEach((item, index) => {
  120. const [dateStr, price, quantityStr] = item;
  121. const date = new Date(dateStr.substring(0, 11) + " " + dateStr.substring(12, 14) + ":00:00");
  122. const priceNumber = parseFloat(price);
  123. const quantity = parseInt(quantityStr, 10);
  124. let totalWeightedPrice = priceNumber * quantity;
  125. let totalQuantity = quantity;
  126. let earliestDate = new Date(date.getTime() - totalHours * oneHour);
  127. let actualHours = 1;
  128. for (let j = index - 1; j >= 0; j--) {
  129. const [prevDateStr, prevPrice, prevQuantityStr] = prices[j];
  130. const prevDate = new Date(prevDateStr.substring(0, 11) + " " + prevDateStr.substring(12, 14) + ":00:00");
  131. if (prevDate >= earliestDate) {
  132. const prevPriceNumber = parseFloat(prevPrice);
  133. const prevQuantity = parseInt(prevQuantityStr, 10);
  134. totalWeightedPrice += prevPriceNumber * prevQuantity;
  135. totalQuantity += prevQuantity;
  136. const diffHours = Math.abs((date - prevDate) / oneHour);
  137. actualHours += diffHours;
  138. } else {
  139. break;
  140. }
  141. }
  142. const averagePrice = totalWeightedPrice / totalQuantity;
  143. const averageDailyQuantity = totalQuantity / (actualHours / hoursInDay);
  144. result.push([dateStr, averagePrice.toFixed(3), averageDailyQuantity.toFixed(3)]);
  145. });
  146. return result;
  147. }
  148. function createButton(){
  149. const button = document.createElement('button');
  150. 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>`;
  151. button.style.position = 'fixed';
  152. button.style.top = saves.buttonposition;
  153. button.style.zIndex = '9999'
  154. button.style.transition = 'transform 0.3s ease';
  155. button.style.opacity = '0.5';
  156. button.style.backgroundColor = 'transparent';
  157. button.style.border = 'none';
  158. button.style.cursor = 'pointer';
  159. button.style.left = '0'
  160. button.style.clipPath = 'polygon(100% 50%, 85% 100%, 0 100%,0 0, 85% 0)';
  161. button.style.transform = 'translate(0%, -50%)';
  162. button.style.background = 'linear-gradient(to bottom right, pink, lightblue)';
  163. var isDragging = false;
  164. button.onmousedown = function(e) {
  165. isDragging = true;
  166. function onMouseMove(e) {
  167. if (!isDragging) return;
  168. button.style.top = `${e.clientY}px`;
  169. }
  170. function onMouseUp() {
  171. isDragging = false;
  172. saves.buttonposition = button.style.top
  173. window.removeEventListener('mousemove', onMouseMove);
  174. window.removeEventListener('mouseup', onMouseUp);
  175. savestorage()
  176. }
  177. window.addEventListener('mousemove', onMouseMove);
  178. window.addEventListener('mouseup', onMouseUp);
  179. };
  180. button.onmouseover = function() {
  181. this.style.opacity = '1';
  182. this.style.transform = 'translate(0%, -50%)';
  183. };
  184. button.onmouseleave = function() {
  185. if(isDragging) return;
  186. this.style.opacity = '0.5';
  187. this.style.transform = 'translate(-50%, -50%)';
  188. };
  189. button.addEventListener('click', showsettingdialog);
  190. button.ondragstart = function() {
  191. return false;
  192. };
  193. document.body.appendChild(button);
  194. return button
  195. }
  196. function showsettingdialog(){
  197. const colorPickers = document.getElementById('colorPickers');
  198. colorPickers.innerHTML = '';
  199. saves.linelist.forEach(item => {
  200. const colorPickerContainer = document.createElement('div');
  201. colorPickerContainer.innerHTML = `
  202. <input type="number" min="1" max="120" class="numberInput" value="${item.days}">
  203. <input type="color" class="colorInput" value="${item.color}">
  204. `;
  205. colorPickers.appendChild(colorPickerContainer);
  206. });
  207. settingdialog.style.display = 'block'
  208. }
  209. function initsettingdialog(){
  210. const modal = document.createElement('div');
  211. modal.style.position = 'fixed';
  212. modal.style.top = '20%';
  213. modal.style.left = '5%';
  214. modal.style.transform = 'translate(0, 0%)';
  215. modal.style.backgroundColor = '#fff';
  216. modal.style.padding = '20px';
  217. modal.style.zIndex = '9999';
  218. modal.style.display = 'none';
  219. modal.style.border = '1px solid #ccc';
  220. modal.style.boxShadow = '0 4px 6px rgba(0,0,0,.1)';
  221. document.body.appendChild(modal);
  222. modal.innerHTML = `
  223. <div> 均线设置</div>
  224. <div id="colorPickers"></div>
  225. <button id="confirmBtn">确定</button>
  226. <button id="cancelBtn">取消</button>
  227. `;
  228. document.getElementById('confirmBtn').addEventListener('click', function() {
  229. const numberInputs = document.querySelectorAll('.numberInput');
  230. const colorInputs = document.querySelectorAll('.colorInput');
  231. const lineList = [];
  232. numberInputs.forEach((input, index) => {
  233. const days = parseInt(input.value);
  234. const color = colorInputs[index].value;
  235. if(days < 1) days=1
  236. if(days > 120) days =120
  237. lineList.push({ days, color });
  238. });
  239. saves.linelist=lineList
  240. savestorage()
  241. modal.style.display = 'none';
  242. try{document.getElementById('modalBG').style.display = 'none'}catch(e){}
  243. myCreatePriceHistoryGraph( 5 )
  244. g_plotPriceHistory.redraw()
  245. });
  246. document.getElementById('cancelBtn').addEventListener('click', function() {
  247. modal.style.display = 'none';
  248. try{document.getElementById('modalBG').style.display = 'none'}catch(e){}
  249. });
  250. return modal
  251. }
  252. function readstorage(){
  253. var saves = GM_getValue('saves')
  254. if(saves) return saves
  255. saves = {
  256. buttonposition:'50%',
  257. linelist:[{
  258. days:5,
  259. color:"#FFFFFF",
  260. },{
  261. days:10,
  262. color:"#FFFF0B",
  263. },{
  264. days:20,
  265. color:"#FF80FF",
  266. },{
  267. days:30,
  268. color:"#00E600",
  269. }]
  270. }
  271. return saves
  272. }
  273. function savestorage(){
  274. GM_setValue('saves',saves)
  275. }
  276. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址