class FirstBoardTradingStrategy { constructor() { this.stocks = []; this.settings = this.loadSettings(); this.init(); } init() { this.bindEvents(); this.loadSampleData(); this.updateDashboard(); } bindEvents() { document.getElementById('analyzeBtn').addEventListener('click', () => { this.analyzeStocks(); }); document.getElementById('settingsBtn').addEventListener('click', () => { this.showSettingsModal(); }); document.getElementById('closeSettings').addEventListener('click', () => { this.hideSettingsModal(); }); document.getElementById('saveSettings').addEventListener('click', () => { this.saveSettings(); }); document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'Enter') { this.analyzeStocks(); } }); } loadSettings() { const defaultSettings = { market: 'all', minGain: 9.5, volumeRatio: 2, holdDays: 3, stopLoss: 5, takeProfit: 20 }; const saved = localStorage.getItem('firstBoardSettings'); return saved ? {...defaultSettings, ...JSON.parse(saved)} : defaultSettings; } saveSettings() { this.settings = { market: document.getElementById('marketSelect').value, minGain: parseFloat(document.getElementById('gainSelect').value), volumeRatio: parseFloat(document.getElementById('volumeRatio').value), holdDays: parseInt(document.getElementById('holdDays').value), stopLoss: parseInt(document.getElementById('stopLoss').value), takeProfit: parseInt(document.getElementById('takeProfit').value) }; localStorage.setItem('firstBoardSettings', JSON.stringify(this.settings)); this.hideSettingsModal(); this.showNotification('设置已保存', 'success'); } showSettingsModal() { const modal = document.getElementById('settingsModal'); modal.classList.remove('hidden'); modal.classList.add('modal-enter'); document.getElementById('holdDays').value = this.settings.holdDays; document.getElementById('stopLoss').value = this.settings.stopLoss; document.getElementById('takeProfit').value = this.settings.takeProfit; } hideSettingsModal() { document.getElementById('settingsModal').classList.add('hidden'); } loadSampleData() { this.stocks = [ { code: '600519', name: '贵州茅台', gain: 10.02, volumeRatio: 3.2, marketCap: 2.1, boardType: '首板' }, { code: '000858', name: '五粮液', gain: 9.95, volumeRatio: 2.8, marketCap: 0.8, boardType: '首板' }, { code: '300750', name: '宁德时代', gain: 10.0, volumeRatio: 4.1, marketCap: 1.2, boardType: '首板' }, { code: '601318', name: '中国平安', gain: 9.87, volumeRatio: 2.3, marketCap: 1.5, boardType: '首板' }, { code: '000001', name: '平安银行', gain: 9.92, volumeRatio: 3.5, marketCap: 0.6, boardType: '首板' } ]; } analyzeStocks() { this.showLoading(); setTimeout(() => { const filteredStocks = this.filterStocks(); this.displayStocks(filteredStocks); this.updateBacktestResults(); this.hideLoading(); this.showNotification('分析完成', 'success'); }, 1500); } filterStocks() { const marketFilter = document.getElementById('marketSelect').value; const minGain = parseFloat(document.getElementById('gainSelect').value); const volumeRatio = parseFloat(document.getElementById('volumeRatio').value); return this.stocks.filter(stock => { const codePrefix = stock.code.substring(0, 1); const matchesMarket = marketFilter === 'all' || (marketFilter === '6' && codePrefix === '6' && !stock.code.startsWith('688')) || (marketFilter === '0' && codePrefix === '0') || (marketFilter === '3' && codePrefix === '3') || (marketFilter === '688' && stock.code.startsWith('688')); return matchesMarket && stock.gain >= minGain && stock.volumeRatio >= volumeRatio; }); } displayStocks(stocks) { const tbody = document.getElementById('stockTableBody'); tbody.innerHTML = ''; stocks.forEach(stock => { const row = document.createElement('tr'); row.className = 'border-b border-gray-200 hover:bg-gray-50 transition-colors'; row.innerHTML = ` ${stock.code} ${stock.name} ${stock.gain.toFixed(2)}% ${stock.volumeRatio.toFixed(1)}倍 ${stock.marketCap}万亿 `; tbody.appendChild(row); }); document.getElementById('todayFirstBoard').textContent = stocks.length; } updateDashboard() { const successRate = Math.random() * 20 + 60; const avgGain = Math.random() * 5 + 8; document.getElementById('successRate').textContent = `${successRate.toFixed(1)}%`; document.getElementById('avgGain').textContent = `${avgGain.toFixed(1)}%`; } updateBacktestResults() { const totalReturn = (Math.random() * 50 + 50).toFixed(1); const annualReturn = (Math.random() * 30 + 15).toFixed(1); const maxDrawdown = (Math.random() * 10 + 10).toFixed(1); const sharpeRatio = (Math.random() * 1.5 + 0.5).toFixed(2); document.getElementById('totalReturn').textContent = `${totalReturn}%`; document.getElementById('annualReturn').textContent = `${annualReturn}%`; document.getElementById('maxDrawdown').textContent = `${maxDrawdown}%`; document.getElementById('sharpeRatio').textContent = sharpeRatio; const totalTrades = Math.floor(Math.random() * 50 + 50); const winRate = (Math.random() * 20 + 60).toFixed(1); const profitLossRatio = (Math.random() * 2 + 1).toFixed(2); document.getElementById('totalTrades').textContent = totalTrades; document.getElementById('winRate').textContent = `${winRate}%`; document.getElementById('profitLossRatio').textContent = profitLossRatio; this.updatePerformanceChart(); } updatePerformanceChart() { const ctx = document.getElementById('performanceChart').getContext('2d'); const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']; const strategyReturns = Array.from({length: 12}, () => Math.random() * 10 - 2); const benchmarkReturns = Array.from({length: 12}, () => Math.random() * 8 - 4); new Chart(ctx, { type: 'line', data: { labels: months, datasets: [ { label: '首板策略', data: this.calculateCumulativeReturns(strategyReturns), borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1), tension: 0.4, fill: true }, { label: '沪深300', data: this.calculateCumulativeReturns(benchmarkReturns), borderColor: '#94a3b8', backgroundColor: 'rgba(148, 163, 184, 0.1), tension: 0.4, fill: true } ] }, options: { responsive: true, plugins: { legend: { position: 'top', }, title: { display: true, text: '策略 vs 基准收益对比' } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value + '%'; } } } } } }); } calculateCumulativeReturns(returns) { let cumulative = 100; return returns.map(return_ => { cumulative = cumulative * (1 + return_ / 100); return (cumulative - 100).toFixed(1); }); } addToWatchlist(stockCode) { let watchlist = JSON.parse(localStorage.getItem('firstBoardWatchlist') || '[]'); if (!watchlist.includes(stockCode)) { watchlist.push(stockCode); localStorage.setItem('firstBoardWatchlist', JSON.stringify(watchlist)); this.showNotification('已加入自选股', 'success'); } else { this.showNotification('已在自选股中', 'info'); } } showLoading() { const analyzeBtn = document.getElementById('analyzeBtn'); analyzeBtn.innerHTML = '分析中...'; analyzeBtn.disabled = true; } hideLoading() { const analyzeBtn = document.getElementById('analyzeBtn'); analyzeBtn.innerHTML = '开始分析'; analyzeBtn.disabled = false; } showNotification(message, type = 'info') { const notification = document.createElement('div'); const bgColor = type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500'; notification.className = `fixed top-4 right-4 ${bgColor} text-white px-6 py-3 rounded-lg shadow-lg transform transition-all duration-300 z-50`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.classList.add('translate-x-0', 'opacity-100'); notification.classList.remove('translate-x-full', 'opacity-0'); }, 100); setTimeout(() => { notification.classList.remove('translate-x-0', 'opacity-100'); notification.classList.add('translate-x-full', 'opacity-0'); setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } } const strategy = new FirstBoardTradingStrategy(); # 股票首板交易策略Web应用依赖说明 # 通过CDN引入的外部资源: # - TailwindCSS 2.2.19:现代化CSS框架 # - Font Awesome 6.0.0:图标库 # - Chart.js:数据可视化图表库 # 所有依赖通过CDN引入,无需本地安装