app.js

// 超短线量化选股程序(插件化架构) - 网页交互逻辑

// DOM加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
    // 初始化日期输入框
    initDateInputs();
    
    // 绑定事件监听器
    bindEventListeners();
    
    // 加载配置信息
    loadConfig();
    
    // 检查是否有选股结果需要展示
    checkForRecentResults();
});

// 初始化日期输入框
function initDateInputs() {
    const today = new Date().toISOString().split('T')[0];
    const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
    
    // 选股日期默认值为昨日
    document.getElementById('trade-date').value = yesterday;
    
    // 回测开始日期默认值为2024年1月1日
    document.getElementById('start-date').value = '2024-01-01';
    
    // 回测结束日期默认值为昨日
    document.getElementById('end-date').value = yesterday;
    
    // 查询结果日期默认值为昨日
    document.getElementById('results-date').value = yesterday;
}

// 绑定事件监听器
function bindEventListeners() {
    // 选股按钮
    document.getElementById('select-stocks-btn').addEventListener('click', selectStocks);
    
    // 更新数据按钮
    document.getElementById('update-data-btn').addEventListener('click', updateData);
    
    // 回测按钮
    document.getElementById('backtest-btn').addEventListener('click', runBacktest);
    
    // 参数优化按钮
    document.getElementById('optimize-btn').addEventListener('click', runOptimization);
    
    // 查询结果按钮
    document.getElementById('query-results-btn').addEventListener('click', queryResults);
}

// 加载配置信息
async function loadConfig() {
    try {
        const response = await fetch('/api/config');
        const data = await response.json();
        
        if (data.success) {
            displayConfig(data.config);
        } else {
            showMessage('加载配置信息失败', 'error');
        }
    } catch (error) {
        console.error('加载配置信息失败:', error);
        showMessage('加载配置信息失败', 'error');
    }
}

// 显示配置信息
function displayConfig(config) {
    const configDisplay = document.getElementById('config-display');
    
    let configHTML = `
        <div class="row">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">策略配置</div>
                    <div class="card-body">
                        <ul class="list-group">
    `;
    
    config.strategies.forEach(strategy => {
        configHTML += `
            <li class="list-group-item">
                <strong>${strategy.name}:</strong> ${strategy.description}
            </li>
        `;
    });
    
    configHTML += `
                        </ul>
                    </div>
                </div>
            </div>
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">参数配置</div>
                    <div class="card-body">
                        <ul class="list-group">
    `;
    
    for (const [key, value] of Object.entries(config.parameters)) {
        configHTML += `
            <li class="list-group-item">
                <strong>${key}:</strong> ${value}
            </li>
        `;
    }
    
    configHTML += `
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    `;
    
    configDisplay.innerHTML = configHTML;
}

// 检查是否有选股结果需要展示
async function checkForRecentResults() {
    try {
        const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
        const formattedDate = yesterday.replace(/-/g, '');
        
        const response = await fetch(`/api/results?trade_date=${formattedDate}`);
        const data = await response.json();
        
        if (data.success && data.count > 0) {
            showMessage(`检测到${yesterday}的选股结果,共${data.count}只股票`, 'success');
        }
    } catch (error) {
        console.error('检查选股结果失败:', error);
    }
}

// 选股函数
async function selectStocks() {
    const strategySelect = document.getElementById('strategy-select');
    const strategies = Array.from(strategySelect.selectedOptions).map(option => option.value);
    const tradeDate = document.getElementById('trade-date').value;
    
    if (!tradeDate) {
        showMessage('请选择选股日期', 'error');
        return;
    }
    
    if (strategies.length === 0) {
        showMessage('请至少选择一个选股策略', 'error');
        return;
    }
    
    // 显示加载动画
    showLoading();
    
    try {
        const response = await fetch('/api/select', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                strategies: strategies,
                trade_date: tradeDate.replace(/-/g, '')
            })
        });
        
        const data = await response.json();
        
        // 隐藏加载动画
        hideLoading();
        
        if (data.success) {
            showMessage(`选股完成,共选出${data.count}只股票`, 'success');
            displaySelectionResults(data.results, strategies);
        } else {
            showMessage(data.error || '选股失败', 'error');
        }
    } catch (error) {
        // 隐藏加载动画
        hideLoading();
        
        console.error('选股失败:', error);
        showMessage('选股失败', 'error');
    }
}

// 显示选股结果
function displaySelectionResults(results, strategies) {
    const resultsDiv = document.getElementById('selection-results');
    
    if (results.length === 0) {
        resultsDiv.innerHTML = `
            <div class="alert alert-info" role="alert">
                未选出符合条件的股票
            </div>
        `;
        return;
    }
    
    let tableHTML = `
        <div class="table-responsive">
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        <th>代码</th>
                        <th>名称</th>
                        <th>行业</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
    `;
    
    results.forEach(stock => {
        tableHTML += `
            <tr>
                <td>${stock.ts_code}</td>
                <td>${stock.name}</td>
                <td>${stock.industry}</td>
                <td>
                    <button class="btn btn-sm btn-info" onclick="showStockDetails('${stock.ts_code}')">详情</button>
                    <button class="btn btn-sm btn-success" onclick="addToWatchlist('${stock.ts_code}')">加入自选</button>
                </td>
            </tr>
        `;
    });
    
    tableHTML += `
                </tbody>
            </table>
        </div>
    `;
    
    resultsDiv.innerHTML = tableHTML;
}

// 更新数据函数
async function updateData() {
    // 显示加载动画
    showLoading();
    
    try {
        const response = await fetch('/api/update_data', {
            method: 'POST'
        });
        
        const data = await response.json();
        
        // 隐藏加载动画
        hideLoading();
        
        if (data.success) {
            showMessage(data.message, 'success');
        } else {
            showMessage(data.error || '数据更新失败', 'error');
        }
    } catch (error) {
        // 隐藏加载动画
        hideLoading();
        
        console.error('数据更新失败:', error);
        showMessage('数据更新失败', 'error');
    }
}

// 回测函数
async function runBacktest() {
    const strategySelect = document.getElementById('backtest-strategy');
    const strategies = Array.from(strategySelect.selectedOptions).map(option => option.value);
    const startDate = document.getElementById('start-date').value;
    const endDate = document.getElementById('end-date').value;
    
    if (!startDate || !endDate) {
        showMessage('请选择回测日期范围', 'error');
        return;
    }
    
    if (new Date(startDate) > new Date(endDate)) {
        showMessage('开始日期不能晚于结束日期', 'error');
        return;
    }
    
    if (strategies.length === 0) {
        showMessage('请至少选择一个回测策略', 'error');
        return;
    }
    
    // 显示加载动画
    showLoading();
    
    try {
        const response = await fetch('/api/backtest', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                strategies: strategies,
                start_date: startDate.replace(/-/g, ''),
                end_date: endDate.replace(/-/g, '')
            })
        });
        
        const data = await response.json();
        
        // 隐藏加载动画
        hideLoading();
        
        if (data.success) {
            showMessage('回测完成', 'success');
            displayBacktestReport(data.report);
        } else {
            showMessage(data.error || '回测失败', 'error');
        }
    } catch (error) {
        // 隐藏加载动画
        hideLoading();
        
        console.error('回测失败:', error);
        showMessage('回测失败', 'error');
    }
}

// 显示回测报告
function displayBacktestReport(report) {
    const reportDiv = document.getElementById('backtest-report');
    
    let reportHTML = `
        <div class="card">
            <div class="card-header">回测报告</div>
            <div class="card-body">
                <div class="row">
                    <div class="col-md-6">
                        <div class="mb-3">
                            <label class="form-label">总交易天数</label>
                            <div class="form-control-plaintext">${report.total_days}</div>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">总交易次数</label>
                            <div class="form-control-plaintext">${report.total_trades}</div>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">胜率</label>
                            <div class="form-control-plaintext">${(report.win_rate * 100).toFixed(2)}%</div>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">盈亏比</label>
                            <div class="form-control-plaintext">${report.profit_loss_ratio.toFixed(2)}</div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="mb-3">
                            <label class="form-label">平均单只收益率</label>
                            <div class="form-control-plaintext">${(report.avg_return * 100).toFixed(2)}%</div>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">累计收益率</label>
                            <div class="form-control-plaintext">${(report.total_return * 100).toFixed(2)}%</div>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">每日平均收益率</label>
                            <div class="form-control-plaintext">${(report.avg_daily_return * 100).toFixed(2)}%</div>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">最大回撤</label>
                            <div class="form-control-plaintext">${(report.max_drawdown * 100).toFixed(2)}%</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `;
    
    reportDiv.innerHTML = reportHTML;
}

// 参数优化函数
async function runOptimization() {
    const strategy = document.getElementById('optimization-strategy').value;
    
    // 显示加载动画
    showLoading();
    
    try {
        const response = await fetch('/api/optimize', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                strategy: strategy
            })
        });
        
        const data = await response.json();
        
        // 隐藏加载动画
        hideLoading();
        
        if (data.success) {
            showMessage('参数优化完成', 'success');
            displayOptimizationResults(data.parameters);
        } else {
            showMessage(data.error || '参数优化失败', 'error');
        }
    } catch (error) {
        // 隐藏加载动画
        hideLoading();
        
        console.error('参数优化失败:', error);
        showMessage('参数优化失败', 'error');
    }
}

// 显示参数优化结果
function displayOptimizationResults(parameters) {
    const resultsDiv = document.getElementById('optimization-results');
    
    let parametersHTML = `
        <div class="card">
            <div class="card-header">最优参数</div>
            <div class="card-body">
                <ul class="list-group">
    `;
    
    for (const [key, value] of Object.entries(parameters)) {
        parametersHTML += `
            <li class="list-group-item">
                <strong>${key}:</strong> ${value}
            </li>
        `;
    }
    
    parametersHTML += `
                </ul>
            </div>
        </div>
    `;
    
    resultsDiv.innerHTML = parametersHTML;
}

// 查询结果函数
async function queryResults() {
    const strategySelect = document.getElementById('results-strategy');
    const strategies = Array.from(strategySelect.selectedOptions).map(option => option.value);
    const resultsDate = document.getElementById('results-date').value;
    
    if (!resultsDate) {
        showMessage('请选择查询日期', 'error');
        return;
    }
    
    if (strategies.length === 0) {
        showMessage('请至少选择一个策略', 'error');
        return;
    }
    
    try {
        const strategiesParam = strategies.join(',');
        const response = await fetch(`/api/results?strategies=${strategiesParam}&trade_date=${resultsDate.replace(/-/g, '')}`);
        const data = await response.json();
        
        if (data.success) {
            displayQueryResults(data.results, strategies);
        } else {
            showMessage(data.error || '查询失败', 'error');
        }
    } catch (error) {
        console.error('查询失败:', error);
        showMessage('查询失败', 'error');
    }
}

// 显示查询结果
function displayQueryResults(results, strategies) {
    const displayDiv = document.getElementById('results-display');
    
    if (results.length === 0) {
        displayDiv.innerHTML = `
            <div class="alert alert-info" role="alert">
                该日期无选股结果
            </div>
        `;
        return;
    }
    
    let tableHTML = `
        <div class="table-responsive">
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        <th>代码</th>
                        <th>名称</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
    `;
    
    results.forEach(stock => {
        tableHTML += `
            <tr>
                <td>${stock.ts_code}</td>
                <td>${stock.name}</td>
                <td>
                    <button class="btn btn-sm btn-info" onclick="showStockDetails('${stock.ts_code}')">详情</button>
                    <button class="btn btn-sm btn-success" onclick="addToWatchlist('${stock.ts_code}')">加入自选</button>
                </td>
            </tr>
        `;
    });
    
    tableHTML += `
                </tbody>
            </table>
        </div>
    `;
    
    displayDiv.innerHTML = tableHTML;
}

// 显示股票详情
function showStockDetails(ts_code) {
    // TODO: 实现显示股票详情功能
    alert(`股票详情功能尚未实现:${ts_code}`);
}

// 加入自选
function addToWatchlist(ts_code) {
    // TODO: 实现加入自选功能
    alert(`加入自选功能尚未实现:${ts_code}`);
}

// 显示消息
function showMessage(message, type) {
    const alertType = type === 'success' ? 'alert-success' : 'alert-danger';
    const alertHTML = `
        <div class="alert ${alertType} alert-dismissible fade show" role="alert">
            ${message}
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        </div>
    `;
    
    const container = document.querySelector('.container');
    const firstSection = container.querySelector('section');
    
    container.insertBefore(document.createRange().createContextualFragment(alertHTML), firstSection);
}

// 显示加载动画
function showLoading() {
    document.getElementById('loading-overlay').classList.remove('d-none');
}

// 隐藏加载动画
function hideLoading() {
    document.getElementById('loading-overlay').classList.add('d-none');
}

// 响应式导航栏
document.addEventListener('click', function(event) {
    const navbar = document.getElementById('navbarNav');
    
    if (navbar && !navbar.contains(event.target) && !event.target.closest('.navbar-brand') && !event.target.closest('.navbar-toggler')) {
        const collapse = bootstrap.Collapse.getInstance(navbar);
        if (collapse && collapse._isShown) {
            collapse.hide();
        }
    }
});