新球体育网亚盘统计

新球体育网(球探)手机端网页,在赛程页面加入“统计”按钮,点击后统计当前页面有完场比分的比赛。在分析页面右上角加入“表格”按钮,点击后生成各公司的统计表格。误差越低的公司越准确,预测出来的只是一个相对方向。

// ==UserScript==
// @name         新球体育网亚盘统计
// @namespace    http://dol.freevar.com/
// @version      0.91
// @description  新球体育网(球探)手机端网页,在赛程页面加入“统计”按钮,点击后统计当前页面有完场比分的比赛。在分析页面右上角加入“表格”按钮,点击后生成各公司的统计表格。误差越低的公司越准确,预测出来的只是一个相对方向。
// @author       Dolphin
// @match        https://m.titan007.com/info/*
// @match        https://m.titan007.com/Analy/Analysis/*
// @match        https://m.titan007.com/analy/Analysis/*
// @run-at       document-idle
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 检查当前URL并执行相应操作
    const currentUrl = window.location.href;

    if (currentUrl.match(/https:\/\/m\.titan007\.com\/info\//)) {
        handleInfoPage();
    } else if (currentUrl.match(/https:\/\/m\.titan007\.com\/analy\/Analysis\//i)) {
        handleAnalysisPage();
    }

    /**
     * 处理info页面:添加统计和清除按钮
     */
    function handleInfoPage() {
        // 在.matchname中添加按钮
        const matchnameDiv = document.querySelector('.matchname');
        if (matchnameDiv) {
            // 创建统计按钮
            const statsBtn = document.createElement('button');
            statsBtn.textContent = '统计';
            statsBtn.style.margin = '0 5px';
            statsBtn.style.padding = '3px 8px';
            statsBtn.style.cursor = 'pointer';
            statsBtn.style.background = '#bfb';
            statsBtn.addEventListener('click', analyzeMatches);

            // 创建清除按钮
            const clearBtn = document.createElement('button');
            clearBtn.textContent = '清除';
            clearBtn.style.margin = '0 5px';
            clearBtn.style.padding = '3px 8px';
            clearBtn.style.cursor = 'pointer';
            clearBtn.style.background = '#fbb';
            clearBtn.addEventListener('click', clearStatistics);

            matchnameDiv.appendChild(clearBtn);
            matchnameDiv.appendChild(statsBtn);
        }
    }

    /**
     * 处理Analysis页面:添加表格按钮
     */
    function handleAnalysisPage() {
        // 在.btns中添加表格按钮
        const btnsDiv = document.querySelector('.btns');
        if (btnsDiv) {
            const tableBtn = document.createElement('div');
            tableBtn.className = 'btn';
            tableBtn.textContent = '表格';
            tableBtn.style.background = '#4c8';
            tableBtn.addEventListener('click', showStatisticsTable);
            btnsDiv.appendChild(tableBtn);
        }
    }

    /**
     * 分析所有比赛数据
     */
    async function analyzeMatches() {
        // 找到所有包含红色比分的<tr>元素
        const matchRows = document.querySelectorAll('tr:has(span.red, span[style*="color:red"])');

        if (matchRows.length === 0) {
            alert('未找到符合条件的比赛数据');
            return;
        }

        let processedCount = 0;

        for (const row of matchRows) {
            try {
                // 获取比赛ID
                const onclickAttr = row.getAttribute('onclick');
                const matchIdMatch = onclickAttr.match(/ToAnaly\((\d+)\s*,\s*-1\)/);
                if (!matchIdMatch) continue;

                const matchId = matchIdMatch[1];

                // 获取实际比分并计算比分差
                const scoreSpan = row.querySelector('span.red, span[style*="color:red"]');
                if (!scoreSpan) continue;

                const scoreText = scoreSpan.textContent.trim();
                const scoreParts = scoreText.split(':').map(Number);
                if (scoreParts.length !== 2) continue;

                const [homeScore, awayScore] = scoreParts;
                const actualDiff = homeScore - awayScore;

                // 获取赔率数据
                const oddsData = await fetchOddsData(matchId);
                if (!oddsData) continue;

                // 处理赔率数据并计算误差
                processOddsData(oddsData, actualDiff, matchId);

                processedCount++;

            } catch (error) {
                console.error('处理比赛数据时出错:', error);
            }
        }

        alert(`已统计 ${processedCount} 场比赛`);
    }

    /**
     * 获取赔率数据
     */
    async function fetchOddsData(scheduleId) {
        try {
            const response = await fetch(`/HandicapDataInterface.ashx?scheid=${scheduleId}&type=1&oddskind=0&isHalf=0`);
            if (!response.ok) return null;

            const data = await response.json();
            return data;
        } catch (error) {
            console.error('获取赔率数据失败:', error);
            return null;
        }
    }

    /**
     * 处理赔率数据并计算误差
     */
    function processOddsData(oddsData, actualDiff, matchId) {
        if (!oddsData.companies || !oddsData.companies.length) return;

        // 收集所有预期比分差用于找到标准值
        const allExpectedDiffs = [];

        // 先遍历所有公司收集数据
        oddsData.companies.forEach(company => {
            // 只取num为1的数据
            const detail = company.details.find(d => d.num === 1);
            if (!detail) return;

            // 计算初盘预期比分差
            const firstDrawOdds = detail.firstDrawOdds !== undefined ? detail.firstDrawOdds : 0;
            const firstExpectedDiff = firstDrawOdds + (detail.firstAwayOdds - detail.firstHomeOdds) / 2;
            allExpectedDiffs.push(firstExpectedDiff);

            // 计算即时盘预期比分差(如果有数据)
            if (detail.homeOdds !== undefined && detail.awayOdds !== undefined) {
                const drawOdds = detail.drawOdds !== undefined ? detail.drawOdds : 0;
                const liveExpectedDiff = drawOdds + (detail.awayOdds - detail.homeOdds) / 2;
                allExpectedDiffs.push(liveExpectedDiff);
            }
        });

        if (allExpectedDiffs.length === 0) return;

        // 找到最接近实际比分差的预期比分差作为标准
        const standardDiff = findClosestValue(allExpectedDiffs, actualDiff);

        // 再次遍历公司计算误差并保存
        oddsData.companies.forEach(company => {
            const companyName = company.nameCn;
            const detail = company.details.find(d => d.num === 1);
            if (!detail) return;

            // 处理初盘数据
            const firstDrawOdds = detail.firstDrawOdds !== undefined ? detail.firstDrawOdds : 0;
            const firstExpectedDiff = firstDrawOdds + (detail.firstAwayOdds - detail.firstHomeOdds) / 2;
            const firstError = firstExpectedDiff - standardDiff;

            // 处理即时盘数据(如果有)
            let liveError = null;
            if (detail.homeOdds !== undefined && detail.awayOdds !== undefined) {
                const drawOdds = detail.drawOdds !== undefined ? detail.drawOdds : 0;
                const liveExpectedDiff = drawOdds + (detail.awayOdds - detail.homeOdds) / 2;
                liveError = liveExpectedDiff - standardDiff;
            }

            // 保存到localStorage
            saveToLocalStorage(companyName, '初盘', matchId, firstError);
            if (liveError !== null) {
                saveToLocalStorage(companyName, '即时盘', matchId, liveError);
            }
        });
    }

    /**
     * 找到数组中最接近目标值的元素
     */
    function findClosestValue(arr, target) {
        return arr.reduce((prev, curr) => {
            return (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev);
        });
    }

    /**
     * 保存数据到localStorage
     */
    function saveToLocalStorage(companyName, oddsType, matchId, error) {
        let stats = JSON.parse(localStorage.getItem('footballOddsStats') || '{}');

        // 初始化公司数据
        if (!stats[companyName]) {
            stats[companyName] = {
                '初盘': {},
                '即时盘': {}
            };
        }

        // 保存误差数据(如果该比赛ID尚未记录)
        if (!stats[companyName][oddsType][matchId]) {
            stats[companyName][oddsType][matchId] = error;
        }

        // 保存回localStorage
        localStorage.setItem('footballOddsStats', JSON.stringify(stats));
    }

    /**
     * 清除所有统计数据
     */
    function clearStatistics() {
        if (confirm('确定要清除所有统计数据吗?')) {
            localStorage.removeItem('footballOddsStats');
        }
    }

    /**
     * 在Analysis页面显示统计表格
     */
    async function showStatisticsTable() {
        // 获取当前比赛ID(假设页面已定义scheduleId变量)
        if (!scheduleId) {
            alert('无法获取比赛ID');
            return;
        }

        // 获取赔率数据
        const oddsData = await fetchOddsData(scheduleId);
        if (!oddsData || !oddsData.companies || !oddsData.companies.length) {
            alert('无法获取赔率数据');
            return;
        }

        // 获取本地存储的统计数据
        const stats = JSON.parse(localStorage.getItem('footballOddsStats') || '{}');

        // 计算当前比赛各公司的预期比分差
        const currentMatchOdds = {};

        oddsData.companies.forEach(company => {
            const companyName = company.nameCn;
            const detail = company.details.find(d => d.num === 1);
            if (!detail) return;

            currentMatchOdds[companyName] = {};

            // 初盘
            const firstDrawOdds = detail.firstDrawOdds !== undefined ? detail.firstDrawOdds : 0;
            currentMatchOdds[companyName]['初盘'] = firstDrawOdds + (detail.firstAwayOdds - detail.firstHomeOdds) / 2;

            // 即时盘(如果有)
            if (detail.homeOdds !== undefined && detail.awayOdds !== undefined) {
                const drawOdds = detail.drawOdds !== undefined ? detail.drawOdds : 0;
                currentMatchOdds[companyName]['即时盘'] = drawOdds + (detail.awayOdds - detail.homeOdds) / 2;
            }
        });

        // 清除已存在的表格(如果有)
        const existingTables = document.querySelectorAll('.odds-stats-table');
        existingTables.forEach(table => table.remove());

        // 生成初盘和即时盘的统计表格
        generateTable('即时盘', stats, currentMatchOdds);
        generateTable('初盘', stats, currentMatchOdds);
    }

    /**
     * 生成统计表格
     */
    function generateTable(oddsType, stats, currentOdds) {
        // 创建表格容器
        const tableContainer = document.createElement('div');
        tableContainer.className = 'odds-stats-table';
        tableContainer.style.border = '1px solid #ccc';
        tableContainer.style.borderRadius = '10px';

        // 创建标题
        const title = document.createElement('h3');
        title.textContent = `${oddsType}统计`;
        title.style.padding = '5px';
        title.style.background = '#bdf';
        tableContainer.appendChild(title);

        // 创建表格
        const table = document.createElement('table');
        table.style.borderCollapse = 'collapse';
        table.style.textAlign = 'center';
        table.style.margin = '0 auto';
        table.style.fontSize = '16px';

        // 创建表头
        const thead = document.createElement('thead');
        const headerRow = document.createElement('tr');
        headerRow.style.backgroundColor = '#eee';

        ['公司', '开盘', '平均误差', '预期比分差'].forEach(text => {
            const th = document.createElement('th');
            th.textContent = text;
            th.style.border = '1px solid #ccc';
            headerRow.appendChild(th);
        });
        thead.appendChild(headerRow);
        table.appendChild(thead);

        // 创建表格内容
        const tbody = document.createElement('tbody');

        // 收集并处理数据
        const tableData = [];

        for (const companyName in stats) {
            const companyData = stats[companyName];
            if (!companyData[oddsType]) continue;

            const matchIds = Object.keys(companyData[oddsType]);
            const count = matchIds.length;

            // 计算平均误差(绝对值的平均值)
            const totalError = matchIds.reduce((sum, matchId) => {
                return sum + Math.abs(companyData[oddsType][matchId]);
            }, 0);
            const avgError = totalError / count;

            // 获取当前比赛的预期比分差
            const currentExpectedDiff = currentOdds[companyName] && currentOdds[companyName][oddsType]
                ? currentOdds[companyName][oddsType]
                : '无数据';

            tableData.push({
                company: companyName,
                count: count,
                avgError: avgError,
                currentDiff: currentExpectedDiff
            });
        }

        // 按平均误差排序
        tableData.sort((a, b) => a.avgError - b.avgError);

        // 填充表格
        tableData.forEach((item, index) => {
            const row = document.createElement('tr');
            row.style.backgroundColor = index % 2 === 0 ? '#fff' : '#eeeeee';

            const companyCell = document.createElement('td');
            companyCell.textContent = item.company;
            companyCell.style.border = '1px solid #ccc';
            row.appendChild(companyCell);

            const countCell = document.createElement('td');
            countCell.textContent = item.count;
            countCell.style.border = '1px solid #ccc';
            row.appendChild(countCell);

            const avgErrorCell = document.createElement('td');
            avgErrorCell.textContent = item.avgError.toFixed(4);
            avgErrorCell.style.border = '1px solid #ccc';
            row.appendChild(avgErrorCell);

            const diffCell = document.createElement('td');
            diffCell.textContent = typeof item.currentDiff === 'number' ? item.currentDiff.toFixed(3) : item.currentDiff;
            diffCell.style.border = '1px solid #ccc';
            row.appendChild(diffCell);

            tbody.appendChild(row);
        });

        table.appendChild(tbody);
        tableContainer.appendChild(table);

        // 插入到页面中
        const contentDiv = document.querySelector('#content');
        if (contentDiv && contentDiv.firstChild) {
            contentDiv.insertBefore(tableContainer, contentDiv.firstChild);
        }
    }
})();