您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
AGSV股市辅助收益计算
当前为
// ==UserScript== // @name AGSV股票持仓收益分析 // @namespace http://tampermonkey.net/ // @version 0.1.1 // @license MIT License // @description AGSV股市辅助收益计算 // @author PandaChan // @match https://stock.agsvpt.cn/ // @icon https://stock.agsvpt.cn/plugins/stock/favicon.svg // @grant GM_xmlhttpRequest // ==/UserScript== (async function () { 'use strict'; let token = localStorage.getItem('auth_token'); // console.log('TOKEN:', token); const getCurrentPrice = async function () { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', // 或 'POST', 'PUT', 'DELETE' 等 url: 'https://stock.agsvpt.cn/api/stocks/info', // 请求的URL headers: { // 可选:自定义请求头 'authorization': 'Bearer ' + token }, responseType: 'json', // 可选:指定响应类型,如 'json', 'text', 'arraybuffer', 'blob' timeout: 5000, // 可选:请求超时时间(毫秒) onload: function (response) { // 请求成功时的回调函数 // console.log('请求成功:', response); // console.log("当前价格:", JSON.stringify(response.response)) resolve(response.response) }, onerror: function (response) { // 请求失败时的回调函数 console.error('请求失败:', response); reject(response) }, ontimeout: function (response) { // 请求超时时的回调函数 console.warn('请求超时:', response); reject(response) } }); }) }; const getHistoryData = async function () { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', // 或 'POST', 'PUT', 'DELETE' 等 url: 'https://stock.agsvpt.cn/api/user/history?&page=1&page_size=10000', // 请求的URL headers: { // 可选:自定义请求头 'authorization': 'Bearer ' + token }, responseType: 'json', // 可选:指定响应类型,如 'json', 'text', 'arraybuffer', 'blob' timeout: 5000, // 可选:请求超时时间(毫秒) onload: function (response) { // 请求成功时的回调函数 // console.log('请求成功:', response); // console.log("历史数据:", JSON.stringify(response.response.data)) resolve(response.response.data) }, onerror: function (response) { // 请求失败时的回调函数 console.error('请求失败:', response); reject(response) }, ontimeout: function (response) { // 请求超时时的回调函数 console.warn('请求超时:', response); reject(response) } }); }) }; function calculatePortfolioPerformanceWithWeightedAverage(transactions, realTimePrices) { const holdings = {}; transactions.reverse().forEach(transaction => { const {stock_code, quantity, price, fee, type} = transaction; if (!holdings[stock_code]) { holdings[stock_code] = { name: transaction.name, current_quantity: 0, // 当前持有的数量 current_total_cost: 0.0 // 当前持有的股票的总成本 }; } const stockHolding = holdings[stock_code]; switch (type) { case 'BUY': stockHolding.current_quantity += quantity; stockHolding.current_total_cost += (price * quantity) + fee; break; case 'SELL': if (stockHolding.current_quantity > 0) { const averageCostPerShare = stockHolding.current_total_cost / stockHolding.current_quantity; const costOfSoldShares = averageCostPerShare * quantity; stockHolding.current_quantity -= quantity; stockHolding.current_total_cost -= costOfSoldShares; // 防止因浮点数运算或极端情况导致数量/成本为负数 if (stockHolding.current_quantity < 0) stockHolding.current_quantity = 0; if (stockHolding.current_total_cost < 0) stockHolding.current_total_cost = 0.0; } else { // 如果在没有持仓的情况下卖出,只减少数量(不影响成本) stockHolding.current_quantity -= quantity; } break; // 'BORROW' 和 'REPAY' 类型在此函数中继续被忽略 default: break; } }); const pricesMap = new Map(); realTimePrices.forEach(item => { pricesMap.set(item.code, item.price); }); const portfolioSummary = {}; for (const stock_code in holdings) { const stockHolding = holdings[stock_code]; const {name, current_quantity, current_total_cost} = stockHolding; let total_holding_cost = 0.0; let estimated_profit_loss = 0.0; let cost_per_share = 0.0; // 新增:持仓每股成本价格 const current_price = pricesMap.get(stock_code); if (current_quantity > 0) { total_holding_cost = current_total_cost; cost_per_share = total_holding_cost / current_quantity; // 计算每股成本 if (current_price !== undefined) { const current_market_value = current_price * current_quantity; estimated_profit_loss = current_market_value - total_holding_cost; } else { console.warn(`股票 ${name} (${stock_code}) 没有实时价格数据,无法计算预计收益。`); estimated_profit_loss = null; } } else { // 如果没有正向持仓(数量为0或负数),所有相关值都为0 stockHolding.current_quantity = 0; total_holding_cost = 0.0; estimated_profit_loss = 0.0; cost_per_share = 0.0; // 没有持仓,每股成本为0 } portfolioSummary[stock_code] = { name: name, current_holding_quantity: stockHolding.current_quantity, total_holding_cost: parseFloat(total_holding_cost.toFixed(2)), cost_per_share: parseFloat(cost_per_share.toFixed(2)), // 新增:持仓每股成本价格 estimated_profit_loss: estimated_profit_loss !== null ? parseFloat(estimated_profit_loss.toFixed(2)) : 'N/A' }; } return portfolioSummary; } const currentProce = await getCurrentPrice(); // console.log("currentProce:", currentProce) const historyData = await getHistoryData(); // console.log("historyData:", historyData) const calculatedHoldings = calculatePortfolioPerformanceWithWeightedAverage(historyData, currentProce); console.log("分析结果:", calculatedHoldings); const appendExpandInfoToTable = function (calculatedHoldings) { const positionTable = document.querySelector('div.positions-container').querySelector('table'); const stockNameToCodeMap = {}; for (const code in calculatedHoldings) { if (calculatedHoldings.hasOwnProperty(code)) { stockNameToCodeMap[calculatedHoldings[code].name] = code; } } const headerRow = positionTable.querySelector('tbody tr'); if (!headerRow || headerRow.children.length === 0) { console.warn('未找到表格的表头行。'); return; } // 检查是否已经添加过列,防止重复添加 if (headerRow.querySelector('.added-profit-loss-header')) { // 只需要检查一列即可 console.log('持仓成本、均价和预计收益列已存在,跳过添加。'); return; } // 增加新的表头列:持仓成本 const thHoldingCost = document.createElement('th'); thHoldingCost.textContent = '持仓成本'; thHoldingCost.classList.add('added-holding-cost-header'); headerRow.appendChild(thHoldingCost); // 增加新的表头列:持仓均价 const thCostPerShare = document.createElement('th'); thCostPerShare.textContent = '持仓均价'; thCostPerShare.classList.add('added-cost-per-share-header'); headerRow.appendChild(thCostPerShare); // 增加新的表头列:预计收益 const thEstimatedProfitLoss = document.createElement('th'); thEstimatedProfitLoss.textContent = '预计收益'; thEstimatedProfitLoss.classList.add('added-profit-loss-header'); headerRow.appendChild(thEstimatedProfitLoss); // 获取数据行所在的 tbody (第二个 tbody) const dataTbody = positionTable.querySelectorAll('tbody')[1]; if (!dataTbody) { console.warn('未找到包含数据行的tbody元素。'); return; } const processTableFun = function () { // 遍历数据行并添加相应的数据 dataTbody.querySelectorAll('tr').forEach(row => { const cells = row.querySelectorAll('td'); if (cells.length > 0) { const stockName = cells[0].textContent.trim(); const stockCode = stockNameToCodeMap[stockName]; if (stockCode && calculatedHoldings[stockCode]) { const stockData = calculatedHoldings[stockCode]; // 持仓成本TD const tdHoldingCost = document.createElement('td'); tdHoldingCost.textContent = stockData.total_holding_cost.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); row.appendChild(tdHoldingCost); // 持仓均价TD const tdCostPerShare = document.createElement('td'); if (stockData.cost_per_share === 0) { tdCostPerShare.textContent = '0.00'; } else { tdCostPerShare.textContent = stockData.cost_per_share.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } row.appendChild(tdCostPerShare); // 预计收益TD const tdEstimatedProfitLoss = document.createElement('td'); if (stockData.estimated_profit_loss === 'N/A') { tdEstimatedProfitLoss.textContent = 'N/A'; } else { tdEstimatedProfitLoss.textContent = stockData.estimated_profit_loss.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); // 可以根据盈亏为单元格添加样式 if (stockData.estimated_profit_loss > 0) { tdEstimatedProfitLoss.style.color = 'green'; } else if (stockData.estimated_profit_loss < 0) { tdEstimatedProfitLoss.style.color = 'red'; } } row.appendChild(tdEstimatedProfitLoss); } else { console.warn(`未找到股票 ${stockName} 的计算数据,或该股票无持仓/交易记录。`); // 如果没有找到数据或无持仓,添加空或 '--' TD for (let i = 0; i < 3; i++) { // 3列: 持仓成本, 持仓均价, 预计收益 const tdEmpty = document.createElement('td'); tdEmpty.textContent = '--'; row.appendChild(tdEmpty); } } } }); }; const waitTableRawDataProcessFinish = function () { const tableBody = dataTbody.innerHTML; if (tableBody.length == 0) { // console.log("表格原始数据尚未渲染, 延迟等待..."); setTimeout(waitTableRawDataProcessFinish, 200); } else { processTableFun(); } }; waitTableRawDataProcessFinish(); } appendExpandInfoToTable(calculatedHoldings); console.log("表格处理完成"); // Your code here... })();