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();
}
}
});