Add files via upload
This commit is contained in:
497
gui2.py
Normal file
497
gui2.py
Normal file
@@ -0,0 +1,497 @@
|
||||
import sys
|
||||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||
QHBoxLayout, QLineEdit, QPushButton, QTextBrowser,
|
||||
QLabel, QTextEdit, QMessageBox, QProgressBar,
|
||||
QFrame, QSizePolicy)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QFont, QPalette, QColor
|
||||
import markdown2
|
||||
from stock_analyzer import StockAnalyzer
|
||||
|
||||
class ModernFrame(QFrame):
|
||||
"""现代化的面板组件"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Raised)
|
||||
self.setStyleSheet("""
|
||||
ModernFrame {
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
""")
|
||||
|
||||
class ModernButton(QPushButton):
|
||||
"""现代化的按钮组件"""
|
||||
def __init__(self, text, parent=None, primary=True):
|
||||
super().__init__(text, parent)
|
||||
self.setMinimumHeight(40)
|
||||
if primary:
|
||||
self.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #1a73e8;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 8px 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #1557b0;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #0d47a1;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
else:
|
||||
self.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #f8f9fa;
|
||||
color: #1a73e8;
|
||||
border: 1px solid #dadce0;
|
||||
border-radius: 5px;
|
||||
padding: 8px 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #f1f3f4;
|
||||
border-color: #d2e3fc;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #e8eaed;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
color: #5f6368;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
""")
|
||||
|
||||
class ModernLineEdit(QLineEdit):
|
||||
"""现代化的输入框组件"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setMinimumHeight(40)
|
||||
self.setStyleSheet("""
|
||||
QLineEdit {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
padding: 8px 12px;
|
||||
background-color: white;
|
||||
selection-background-color: #cce0ff;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border-color: #1a73e8;
|
||||
}
|
||||
QLineEdit:hover {
|
||||
border-color: #999999;
|
||||
}
|
||||
""")
|
||||
|
||||
class ModernTextEdit(QTextEdit):
|
||||
"""现代化的多行文本输入框组件"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setStyleSheet("""
|
||||
QTextEdit {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
padding: 8px;
|
||||
background-color: white;
|
||||
selection-background-color: #cce0ff;
|
||||
}
|
||||
QTextEdit:focus {
|
||||
border-color: #1a73e8;
|
||||
}
|
||||
QTextEdit:hover {
|
||||
border-color: #999999;
|
||||
}
|
||||
""")
|
||||
|
||||
class ModernProgressBar(QProgressBar):
|
||||
"""现代化的进度条组件"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setStyleSheet("""
|
||||
QProgressBar {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
background-color: #f0f0f0;
|
||||
height: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #1a73e8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
""")
|
||||
self.setTextVisible(False)
|
||||
|
||||
class AnalysisWorker(QThread):
|
||||
"""后台工作线程,用于执行分析任务"""
|
||||
finished = pyqtSignal(dict)
|
||||
error = pyqtSignal(str)
|
||||
progress = pyqtSignal(int)
|
||||
|
||||
def __init__(self, analyzer, stock_code):
|
||||
super().__init__()
|
||||
self.analyzer = analyzer
|
||||
self.stock_code = stock_code
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
report = self.analyzer.analyze_stock(self.stock_code)
|
||||
self.finished.emit(report)
|
||||
except Exception as e:
|
||||
self.error.emit(str(e))
|
||||
|
||||
class BatchAnalysisWorker(QThread):
|
||||
"""后台工作线程,用于执行批量分析任务"""
|
||||
finished = pyqtSignal(list)
|
||||
error = pyqtSignal(str)
|
||||
progress = pyqtSignal(int)
|
||||
|
||||
def __init__(self, analyzer, stock_list):
|
||||
super().__init__()
|
||||
self.analyzer = analyzer
|
||||
self.stock_list = stock_list
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
results = []
|
||||
total = len(self.stock_list)
|
||||
for i, stock_code in enumerate(self.stock_list):
|
||||
report = self.analyzer.analyze_stock(stock_code)
|
||||
results.append(report)
|
||||
self.progress.emit(int((i + 1) / total * 100))
|
||||
self.finished.emit(results)
|
||||
except Exception as e:
|
||||
self.error.emit(str(e))
|
||||
|
||||
class ModernStockAnalyzerGUI(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.analyzer = StockAnalyzer()
|
||||
self.init_ui()
|
||||
self.adjust_size_and_position()
|
||||
|
||||
def adjust_size_and_position(self):
|
||||
"""调整窗口大小和位置以适应不同分辨率"""
|
||||
screen = QApplication.primaryScreen()
|
||||
if screen:
|
||||
geometry = screen.availableGeometry()
|
||||
# 设置窗口大小为屏幕的75%
|
||||
width = int(geometry.width() * 0.75)
|
||||
height = int(geometry.height() * 0.75)
|
||||
self.resize(width, height)
|
||||
|
||||
# 居中显示
|
||||
center = geometry.center()
|
||||
frame = self.frameGeometry()
|
||||
frame.moveCenter(center)
|
||||
self.move(frame.topLeft())
|
||||
|
||||
def init_ui(self):
|
||||
self.setWindowTitle('现代股票分析系统')
|
||||
self.setStyleSheet("""
|
||||
QMainWindow {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
""")
|
||||
|
||||
# 创建中央部件和主布局
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
main_layout.setSpacing(20)
|
||||
main_layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
# 创建标题
|
||||
title_label = QLabel('股票分析系统')
|
||||
title_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #202124;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
""")
|
||||
main_layout.addWidget(title_label, alignment=Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# 创建输入区域容器
|
||||
input_container = ModernFrame()
|
||||
input_layout = QHBoxLayout(input_container)
|
||||
input_layout.setSpacing(20)
|
||||
input_layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
# 单只股票分析部分
|
||||
single_stock_frame = self.create_single_stock_section()
|
||||
input_layout.addWidget(single_stock_frame)
|
||||
|
||||
# 分隔线
|
||||
separator = QFrame()
|
||||
separator.setFrameShape(QFrame.Shape.VLine)
|
||||
separator.setStyleSheet("background-color: #e0e0e0;")
|
||||
input_layout.addWidget(separator)
|
||||
|
||||
# 批量分析部分
|
||||
batch_stock_frame = self.create_batch_stock_section()
|
||||
input_layout.addWidget(batch_stock_frame)
|
||||
|
||||
main_layout.addWidget(input_container)
|
||||
|
||||
# 进度条
|
||||
self.progress_bar = ModernProgressBar()
|
||||
self.progress_bar.setVisible(False)
|
||||
main_layout.addWidget(self.progress_bar)
|
||||
|
||||
# 结果显示区域
|
||||
result_frame = ModernFrame()
|
||||
result_layout = QVBoxLayout(result_frame)
|
||||
result_layout.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
result_label = QLabel('分析结果')
|
||||
result_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #202124;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
result_layout.addWidget(result_label)
|
||||
|
||||
self.result_browser = QTextBrowser()
|
||||
self.result_browser.setOpenExternalLinks(True)
|
||||
self.result_browser.setStyleSheet("""
|
||||
QTextBrowser {
|
||||
border: none;
|
||||
background-color: white;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
""")
|
||||
result_layout.addWidget(self.result_browser)
|
||||
|
||||
main_layout.addWidget(result_frame)
|
||||
|
||||
def create_single_stock_section(self):
|
||||
"""创建单只股票分析部分"""
|
||||
frame = QFrame()
|
||||
layout = QVBoxLayout(frame)
|
||||
layout.setSpacing(15)
|
||||
|
||||
label = QLabel('单只股票分析')
|
||||
label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #202124;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(label)
|
||||
|
||||
self.single_stock_input = ModernLineEdit()
|
||||
self.single_stock_input.setPlaceholderText('输入股票代码(如:600000)')
|
||||
layout.addWidget(self.single_stock_input)
|
||||
|
||||
self.analyze_btn = ModernButton('分析')
|
||||
self.analyze_btn.clicked.connect(self.analyze_single_stock)
|
||||
layout.addWidget(self.analyze_btn)
|
||||
|
||||
layout.addStretch()
|
||||
return frame
|
||||
|
||||
def create_batch_stock_section(self):
|
||||
"""创建批量分析部分"""
|
||||
frame = QFrame()
|
||||
layout = QVBoxLayout(frame)
|
||||
layout.setSpacing(15)
|
||||
|
||||
label = QLabel('批量股票分析')
|
||||
label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #202124;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(label)
|
||||
|
||||
self.batch_stock_input = ModernTextEdit()
|
||||
self.batch_stock_input.setPlaceholderText('输入多个股票代码,每行一个')
|
||||
self.batch_stock_input.setMinimumHeight(100)
|
||||
layout.addWidget(self.batch_stock_input)
|
||||
|
||||
self.batch_analyze_btn = ModernButton('批量分析')
|
||||
self.batch_analyze_btn.clicked.connect(self.analyze_multiple_stocks)
|
||||
layout.addWidget(self.batch_analyze_btn)
|
||||
|
||||
layout.addStretch()
|
||||
return frame
|
||||
|
||||
def format_report(self, report, is_single=True):
|
||||
"""将分析报告格式化为现代化的Markdown格式"""
|
||||
md = f"""# 股票分析报告 - {report['stock_code']}
|
||||
|
||||
## 基本信息
|
||||
- **分析日期:** {report['analysis_date']}
|
||||
- **当前价格:** ¥{report['price']:.2f}
|
||||
- **价格变动:** {report['price_change']:.2f}%
|
||||
|
||||
## 技术指标
|
||||
- **均线趋势:** {report['ma_trend']}
|
||||
- **RSI指标:** {report['rsi']:.2f}
|
||||
- **MACD信号:** {report['macd_signal']}
|
||||
- **成交量状态:** {report['volume_status']}
|
||||
|
||||
## 评分与建议
|
||||
- **综合评分:** {report['score']}分
|
||||
- **投资建议:** {report['recommendation']}
|
||||
|
||||
## AI分析
|
||||
{report['ai_analysis']}
|
||||
|
||||
---
|
||||
"""
|
||||
return md
|
||||
|
||||
def analyze_single_stock(self):
|
||||
"""分析单只股票"""
|
||||
stock_code = self.single_stock_input.text().strip()
|
||||
if not stock_code:
|
||||
self.show_warning('请输入股票代码')
|
||||
return
|
||||
|
||||
self.analyze_btn.setEnabled(False)
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(0)
|
||||
|
||||
self.worker = AnalysisWorker(self.analyzer, stock_code)
|
||||
self.worker.finished.connect(self.handle_single_analysis_result)
|
||||
self.worker.error.connect(self.handle_analysis_error)
|
||||
self.worker.progress.connect(self.progress_bar.setValue)
|
||||
self.worker.start()
|
||||
|
||||
def analyze_multiple_stocks(self):
|
||||
"""批量分析股票"""
|
||||
text = self.batch_stock_input.toPlainText().strip()
|
||||
if not text:
|
||||
self.show_warning('请输入股票代码')
|
||||
return
|
||||
|
||||
stock_list = [code.strip() for code in text.split('\n') if code.strip()]
|
||||
|
||||
self.batch_analyze_btn.setEnabled(False)
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(0)
|
||||
|
||||
self.batch_worker = BatchAnalysisWorker(self.analyzer, stock_list)
|
||||
self.batch_worker.finished.connect(self.handle_batch_analysis_result)
|
||||
self.batch_worker.error.connect(self.handle_analysis_error)
|
||||
self.batch_worker.progress.connect(self.progress_bar.setValue)
|
||||
self.batch_worker.start()
|
||||
|
||||
def handle_single_analysis_result(self, report):
|
||||
"""处理单只股票分析结果"""
|
||||
markdown_text = self.format_report(report)
|
||||
html_content = markdown2.markdown(markdown_text, extras=['tables', 'fenced-code-blocks'])
|
||||
self.result_browser.setHtml(html_content)
|
||||
self.analyze_btn.setEnabled(True)
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
def handle_batch_analysis_result(self, recommendations):
|
||||
"""处理批量分析结果"""
|
||||
markdown_text = "# 批量股票分析报告\n\n"
|
||||
for rec in recommendations:
|
||||
markdown_text += self.format_report(rec, False)
|
||||
|
||||
html_content = markdown2.markdown(markdown_text, extras=['tables', 'fenced-code-blocks'])
|
||||
self.result_browser.setHtml(html_content)
|
||||
self.batch_analyze_btn.setEnabled(True)
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
def handle_analysis_error(self, error_message):
|
||||
"""处理分析错误"""
|
||||
self.show_error(f'分析过程中出现错误:{error_message}')
|
||||
self.analyze_btn.setEnabled(True)
|
||||
self.batch_analyze_btn.setEnabled(True)
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
def show_warning(self, message):
|
||||
"""显示警告对话框"""
|
||||
warning = QMessageBox(self)
|
||||
warning.setIcon(QMessageBox.Icon.Warning)
|
||||
warning.setWindowTitle('警告')
|
||||
warning.setText(message)
|
||||
warning.setStandardButtons(QMessageBox.StandardButton.Ok)
|
||||
warning.setStyleSheet("""
|
||||
QMessageBox {
|
||||
background-color: white;
|
||||
}
|
||||
QMessageBox QLabel {
|
||||
color: #202124;
|
||||
min-width: 200px;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #1a73e8;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
min-width: 80px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #1557b0;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #0d47a1;
|
||||
}
|
||||
""")
|
||||
warning.exec()
|
||||
|
||||
def show_error(self, message):
|
||||
"""显示错误对话框"""
|
||||
error = QMessageBox(self)
|
||||
error.setIcon(QMessageBox.Icon.Critical)
|
||||
error.setWindowTitle('错误')
|
||||
error.setText(message)
|
||||
error.setStandardButtons(QMessageBox.StandardButton.Ok)
|
||||
error.setStyleSheet("""
|
||||
QMessageBox {
|
||||
background-color: white;
|
||||
}
|
||||
QMessageBox QLabel {
|
||||
color: #202124;
|
||||
min-width: 200px;
|
||||
}
|
||||
QPushButton {
|
||||
background-color: #1a73e8;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
min-width: 80px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #1557b0;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #0d47a1;
|
||||
}
|
||||
""")
|
||||
error.exec()
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
app.setStyle('Fusion')
|
||||
|
||||
# 创建并显示主窗口
|
||||
window = ModernStockAnalyzerGUI()
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user