Xueqiu Follow Helper

在雪球组合上显示最近一个交易日调仓的成交价。允许为每个组合设置预算,并根据预算计算应买卖的股数。

  1. // ==UserScript==
  2. // @name Xueqiu Follow Helper
  3. // @namespace https://github.com/henix/userjs/xueqiu_helper
  4. // @description 在雪球组合上显示最近一个交易日调仓的成交价。允许为每个组合设置预算,并根据预算计算应买卖的股数。
  5. // @author henix
  6. // @version 20200704.1
  7. // @match http://xueqiu.com/P/*
  8. // @match https://xueqiu.com/P/*
  9. // @license MIT License
  10. // @require https://cdn.jsdelivr.net/npm/domo@0.5.9/lib/domo.js
  11. // ==/UserScript==
  12.  
  13. /**
  14. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign
  15. */
  16. Math.sign = Math.sign || function(x) {
  17. return ((x > 0) - (x < 0)) || +x;
  18. };
  19.  
  20. function insertSheet(ruleString, atstart) {
  21. var head = document.getElementsByTagName("head")[0];
  22. var style = document.createElement("style");
  23. var rules = document.createTextNode(ruleString);
  24. style.type = "text/css";
  25. if(style.styleSheet) {
  26. style.styleSheet.cssText = rules.nodeValue;
  27. } else {
  28. style.appendChild(rules);
  29. }
  30. if (atstart) {
  31. head.insertBefore(style, head.children[0]);
  32. } else {
  33. head.appendChild(style);
  34. }
  35. }
  36.  
  37. function ajaxGetJson(url, onSuccess) {
  38. var xhr = new XMLHttpRequest();
  39. xhr.open("GET", url);
  40. xhr.responseType = "json";
  41. xhr.onload = function() {
  42. if (this.status == 200) {
  43. onSuccess(this.response);
  44. }
  45. };
  46. xhr.send();
  47. }
  48.  
  49. function myround(x) {
  50. return Math.sign(x) * Math.round(Math.abs(x));
  51. }
  52.  
  53. function pad2(x) {
  54. return x >= 10 ? x : "0" + x;
  55. }
  56.  
  57. var symbol = location.pathname.substring("/P/".length);
  58.  
  59. function FollowDetails(elem) {
  60. this.elem = elem;
  61. this.symbol = elem.getAttribute("symbol");
  62. }
  63.  
  64. FollowDetails.prototype.repaint = function(data) {
  65. var $this = this;
  66. var rebalances = data.rebalances;
  67. var budget = data.budget;
  68. var buyfactor = data.buyfactor;
  69. var cur_prices = data.cur_prices;
  70.  
  71. // 过滤掉系统分红
  72. rebalances.list = rebalances.list.filter(function(o) { return o.category == "user_rebalancing"; });
  73.  
  74. var frag = document.createDocumentFragment();
  75.  
  76. var now = new Date(rebalances.list[0].updated_at);
  77. var lastday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
  78.  
  79. var trs = [TR(TH("名称"), TH("百分比"), TH("参考成交价" + (buyfactor != 1 ? " / 挂买价" : "")), TH("买卖股数" + (buyfactor != 1 ? " / 挂买股数" : "")))];
  80. for (var a of rebalances.list.filter(function(o) { return o.updated_at > lastday && (o.status == "success" || o.status == "pending"); })) {
  81. var utime = new Date(a.updated_at);
  82. trs.push(TR(TD({ colspan: 4 }, utime.getFullYear() + "-" + (utime.getMonth()+1) + "-" + utime.getDate() + " " + utime.getHours() + ":" + pad2(utime.getMinutes()) + ":" + pad2(utime.getSeconds()) + (a.status == "pending" ? "(待成交)" : ""))));
  83. a.rebalancing_histories.forEach(function(r) {
  84. var prev_weight = r.prev_weight_adjusted || 0;
  85. var delta = r.target_weight - prev_weight;
  86. var price = r.price || cur_prices[r.stock_symbol];
  87. if (delta && !price) {
  88. // 开盘前无价格,使用当前价格
  89. ajaxGetJson("/stock/quotep.json?stockid=" + r.stock_id, function(info) {
  90. cur_prices[r.stock_symbol] = info[r.stock_id].current; // TODO: immutable map
  91. $this.repaint(data);
  92. });
  93. }
  94. var quantity = budget * delta / 100 / price;
  95. trs.push(TR(
  96. TD(A({ target: "_blank", href: "/S/" + r.stock_symbol }, r.stock_name), "(" + r.stock_symbol.replace(/^SH|^SZ/, "$&.") + ")"),
  97. TD(prev_weight + "% → " + r.target_weight + "%"),
  98. TD(delta ? (price ? (price + (r.price ? ((buyfactor != 1 && delta > 0) ? (" / " + Math.round(price * buyfactor * 1000) / 1000) : "") : "(当前价)")) : "正在获取") : "无"),
  99. TD(delta ? (price ? (myround(quantity) + ((buyfactor != 1 && delta > 0) ? (" / " + Math.round(quantity / buyfactor)) : "")) : "正在获取") : "无")
  100. ));
  101. });
  102. }
  103. frag.appendChild(TABLE.apply(null, trs));
  104.  
  105. var budgetInput = INPUT({ value: budget, size: 10 });
  106. var budgetSave = INPUT({ type: "button", value: "保存" });
  107. budgetSave.addEventListener("click", function() {
  108. budget = parseInt(budgetInput.value, 10);
  109. localStorage.setItem("follow.budget." + $this.symbol, budget);
  110. data.budget = budget; // TODO: immutable map
  111. $this.repaint(data);
  112. });
  113. var buyfactorInput = INPUT({ value: buyfactor, size: 4 });
  114. var buyfactorSave = INPUT({ type: "button", value: "保存" });
  115. buyfactorSave.addEventListener("click", function() {
  116. localStorage.setItem("follow.buyfactor." + $this.symbol, buyfactorInput.value);
  117. data.buyfactor = parseFloat(buyfactorInput.value); // TODO: immutable map
  118. $this.repaint(data);
  119. });
  120. frag.appendChild(DIV({ "class": "budget-setting" }, "预算 ", budgetInput, " 元 ", budgetSave, " 挂买价 = 参考成交价 * ", buyfactorInput, " ", buyfactorSave));
  121.  
  122. this.elem.innerHTML = "";
  123. this.elem.appendChild(frag);
  124. };
  125.  
  126. ajaxGetJson("/cubes/rebalancing/history.json?cube_symbol=" + symbol + "&count=20&page=1", function(histories) {
  127. var cubeAction = document.getElementById("cube-action");
  128. var div = DIV({ "class": "-FollowDetails", "symbol": symbol });
  129. cubeAction.parentNode.insertBefore(div, cubeAction.nextSibling);
  130.  
  131. var followDetails = new FollowDetails(div);
  132. followDetails.repaint({
  133. rebalances: histories,
  134. budget: parseInt(localStorage.getItem("follow.budget." + symbol) || 10000, 10),
  135. buyfactor: parseFloat(localStorage.getItem("follow.buyfactor." + symbol) || 1),
  136. cur_prices: {}
  137. });
  138. });
  139.  
  140. insertSheet(
  141. ".-FollowDetails table { width: 100%; margin: 10px auto; }" +
  142. ".-FollowDetails th { font-weight: bold; }" +
  143. ".-FollowDetails th, .-FollowDetails td { border: 1px solid black; padding: 0.5em; }" +
  144. ".-FollowDetails .budget-setting { margin: 10px 0 20px 0; }"
  145. );

QingJ © 2025

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