推广 热搜: 采购方式  甲带  滤芯  气动隔膜泵  带式称重给煤机  减速机型号  无级变速机  链式给煤机  履带  减速机 

同花顺概念行业板块分析及成分股展示

   日期:2026-02-28 12:27:26     来源:网络整理    作者:本站编辑    评论:0    
同花顺概念行业板块分析及成分股展示
春节期间有好几个同学问我,怎么获取同花顺概念板块及成分股, 这里写一写。 把之前写的streamlit代码用pyside6重构下。
这个小工具,能够一键获取所有概念板块和行业板块的实时涨跌幅,点击板块即可查看成分股列表,并且涨跌幅用红绿颜色清晰标识,数据一目了然。提供整个代码,你可以自由修改、扩展,打造属于自己的看盘利器!

一、工具功能速览

启动工具后,你会看到一个简洁的双栏界面:

  • 左侧:通过标签页切换“概念板块”和“行业板块”,每个板块名称后都紧跟实时涨跌幅(红色代表上涨,绿色代表下跌),让你快速定位强势板块。

  • 右侧:点击左侧任一板块,右侧立即展示该板块的成分股列表,包含股票代码、名称、最新价和涨跌幅,同样用红绿颜色区分涨跌。

二、代码简单介绍

整个程序分为三大模块:

  • 数据模型(StockTableModel):负责管理股票表格数据及颜色渲染。

  • 工作线程(DataLoader):处理所有网络请求,通过信号与主线程通信。

  • 主窗口(MainWindow):搭建界面,连接信号槽,响应用户交互。

这种设计符合MVC(模型-视图-控制器)模式,如果你想增加新的数据维度(如换手率、市盈率),只需修改模型和对应的数据解析部分即可。

三、如何使用?

环境准备

确保你的Python版本≥3.7,然后安装依赖:

pip install pyside6 pandas requests pywencai
pywencai需要依赖node, 建议node18, 不知道怎么操作的可以翻一翻我之前的文章。 或者进群交流。

最后这里贴一下完整代码,参考下思路, 具体根据自己的实际情况改造。 备注:如果发现格式有多余的特殊字符,用普通浏览器打开复制应该没问题。 希望我的分享对大家有所帮助。  

import sysimport jsonimport pandas as pdimport requestsimport pywencaifrom functools import lru_cachefrom PySide6.QtWidgets import (    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,    QTabWidget, QListWidget, QListWidgetItem, QTableView, QSplitter,    QMessageBox, QStatusBar, QProgressBar, QHeaderView, QLabel)from PySide6.QtCore import Qt, QThread, Signal, QObject, QTimer, QAbstractTableModel, QModelIndex, QSizefrom PySide6.QtGui import QFont, QColor, QBrush# -------------------- 数据模型 --------------------class StockTableModel(QAbstractTableModel):    """成分股表格模型,涨跌幅列按正负显示颜色"""    def __init__(self):        super().__init__()        self._data = pd.DataFrame(columns=['股票代码''股票名称''最新价''涨跌幅'])        self._headers = ['股票代码''股票名称''最新价''涨跌幅']    def update_data(self, df: pd.DataFrame):        self.beginResetModel()        self._data = df        self.endResetModel()    def rowCount(self, parent=QModelIndex()):        return self._data.shape[0]    def columnCount(self, parent=QModelIndex()):        return self._data.shape[1]    def data(self, index, role=Qt.DisplayRole):        if not index.isValid():            return None        row, col = index.row(), index.column()        value = self._data.iloc[row, col]        if role == Qt.DisplayRole:            if col == 3 and isinstance(value, (intfloat)):                return f"{value:.2f}%"            return str(value)        elif role == Qt.ForegroundRole:            if col == 3:                try:                    if isinstance(value, str):                        val_str = value.replace('%''').strip()                        val = float(val_str) if val_str else 0.0                    else:                        val = float(value)                    if val > 0:                        return QBrush(QColor(2558080))   # 红色                    elif val < 0:                        return QBrush(QColor(01500))     # 绿色                    else:                        return QBrush(Qt.black)                except:                    return QBrush(Qt.gray)            return QBrush(Qt.black)        return None    def headerData(self, section, orientation, role=Qt.DisplayRole):        if orientation == Qt.Horizontal and role == Qt.DisplayRole:            return self._headers[section]        return None# -------------------- 工作线程 --------------------class DataLoader(QObject):    concept_blocks_ready = Signal(pd.DataFrame)    industry_blocks_ready = Signal(pd.DataFrame)    stock_list_ready = Signal(pd.DataFrame, str)    error_occurred = Signal(str)    def __init__(self):        super().__init__()        self._cache_concept = None        self._cache_industry = None    @lru_cache(maxsize=128)    def _fetch_concept_blocks(self):        return pywencai.get(query="概念板块,涨跌幅排序", query_type="zhishu", sort_order='desc', loop=True)    @lru_cache(maxsize=128)    def _fetch_industry_blocks(self):        return pywencai.get(query="行业板块,涨跌幅排序", query_type="zhishu", sort_order='desc', loop=True)    def load_concept_blocks(self):        try:            df = self._fetch_concept_blocks()            self.concept_blocks_ready.emit(df)        except Exception as e:            self.error_occurred.emit(f"获取概念板块失败: {str(e)}")    def load_industry_blocks(self):        try:            df = self._fetch_industry_blocks()            self.industry_blocks_ready.emit(df)        except Exception as e:            self.error_occurred.emit(f"获取行业板块失败: {str(e)}")    def load_stock_list(self, block_code: str):        try:            url = f"https://d.10jqka.com.cn/v2/blockrank/{block_code}/199112/d1000.js"            headers = {                'Referer''http://q.10jqka.com.cn/',                'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) '                               'AppleWebKit/537.36 (KHTML, like Gecko) '                               'Chrome/119.0.0.0 Safari/537.36')            }            resp = requests.get(url, headers=headers, timeout=10)            if resp.status_code != 200:                self.error_occurred.emit(f"请求失败,状态码:{resp.status_code}")                return            json_str = resp.text.split('('1)[1].rsplit(')'1)[0]            data = json.loads(json_str)            items = data.get('items', [])            if not items:                self.stock_list_ready.emit(pd.DataFrame(), block_code)                return            rows = []            for s in items:                rows.append([                    s.get('5''').zfill(6),                    s.get('55'''),                    s.get('8'''),                    s.get('199112'0)                ])            df = pd.DataFrame(rows, columns=['股票代码''股票名称''最新价''涨跌幅'])            self.stock_list_ready.emit(df, block_code)        except Exception as e:            self.error_occurred.emit(f"获取成分股失败: {str(e)}")# -------------------- 主窗口 --------------------class MainWindow(QMainWindow):    def __init__(self):        super().__init__()        self.setWindowTitle("同花顺板块分析")        self.setMinimumSize(1100650)        self._setup_ui()        self._setup_loader()        self._load_blocks()    def _setup_ui(self):        central = QWidget()        self.setCentralWidget(central)        main_layout = QHBoxLayout(central)        main_layout.setContentsMargins(5555)        splitter = QSplitter(Qt.Horizontal)        main_layout.addWidget(splitter)        left_widget = QWidget()        left_layout = QVBoxLayout(left_widget)        left_layout.setContentsMargins(0000)        self.tab_widget = QTabWidget()        left_layout.addWidget(self.tab_widget)        # 概念板块列表        self.concept_list = QListWidget()        self.concept_list.setAlternatingRowColors(True)        self.concept_list.itemClicked.connect(self._on_block_selected)        self.tab_widget.addTab(self.concept_list, "概念板块")        # 行业板块列表        self.industry_list = QListWidget()        self.industry_list.setAlternatingRowColors(True)        self.industry_list.itemClicked.connect(self._on_block_selected)        self.tab_widget.addTab(self.industry_list, "行业板块")        splitter.addWidget(left_widget)        # 右侧表格        self.table_view = QTableView()        self.table_model = StockTableModel()        self.table_view.setModel(self.table_model)        self.table_view.setAlternatingRowColors(True)        self.table_view.horizontalHeader().setStretchLastSection(True)        self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)        splitter.addWidget(self.table_view)        splitter.setSizes([350750])        self._progress = QProgressBar()        self._progress.setVisible(False)        self.statusBar().addPermanentWidget(self._progress)        self.setStyleSheet("""            QListWidget::item { border-bottom: 1px solid #e0e0e0; }            QListWidget::item:selected { background-color: #c0e0ff; }            QTabWidget::pane { border: 1px solid #cccccc; }            QHeaderView::section { background-color: #f0f0f0; padding: 4px; }        """)    def _setup_loader(self):        self._loader = DataLoader()        self._thread = QThread()        self._loader.moveToThread(self._thread)        self._thread.start()        self._loader.concept_blocks_ready.connect(self._on_concept_blocks_loaded)        self._loader.industry_blocks_ready.connect(self._on_industry_blocks_loaded)        self._loader.stock_list_ready.connect(self._on_stock_list_loaded)        self._loader.error_occurred.connect(self._show_error)    def _load_blocks(self):        self._progress.setVisible(True)        self._progress.setRange(00)        QTimer.singleShot(100self._loader.load_concept_blocks)        QTimer.singleShot(200self._loader.load_industry_blocks)    def _on_concept_blocks_loaded(self, df):        self._populate_list_widget(self.concept_list, df)        self._check_all_loaded()    def _on_industry_blocks_loaded(self, df):        self._populate_list_widget(self.industry_list, df)        self._check_all_loaded()    def _get_change_column(self, df: pd.DataFrame) -> str:        possible_names = ['涨跌幅''涨跌幅(%)''涨幅''涨幅%''涨跌幅度']        for col in df.columns:            if col in possible_names:                return col        for col in df.columns:            if '涨跌幅' in col or '涨幅' in col:                return col        return None    def _format_change(self, value):        if pd.isna(value):            return '--'        try:            if isinstance(value, str):                value = value.replace('%''').strip()            val = float(value)            return f"{val:+.2f}%"        except:            return str(value)    def _get_change_color_html(self, change_str: str) -> str:        try:            if change_str.endswith('%'):                num_str = change_str[:-1].strip()            else:                num_str = change_str            if num_str.startswith('+'):                num_str = num_str[1:]            val = float(num_str)            if val > 0:                return 'red'            elif val < 0:                return 'green'            else:                return 'black'        except:            return 'gray'    def _populate_list_widget(self, list_widget: QListWidget, df: pd.DataFrame):        """填充左侧列表,使用QLabel实现颜色区分,并增加项高度"""        list_widget.clear()        if '指数简称' not in df.columns or 'code' not in df.columns:            self._show_error("数据格式错误:缺少指数简称或code列")            return        change_col = self._get_change_column(df)        if change_col is None:            self._show_error("未找到涨跌幅列,将显示默认值")            changes = ['--'] * len(df)        else:            changes = df[change_col].values        for idx, row in df.iterrows():            name = row['指数简称']            code = str(row['code'])            change_val = changes[idx]            change_str = self._format_change(change_val)            color = self._get_change_color_html(change_str)            # 创建列表项            item = QListWidgetItem(list_widget)            item.setData(Qt.UserRole, code)            # 创建QLabel显示HTML,增加内边距以提高项高度            label = QLabel()            html = f"""            <div style="display: flex; justify-content: space-between; font-family: Arial; width: 100%;">                <span style="font-weight: normal;">{name} <span style="color: #666;">({code})</span></span>                <span style="font-weight: bold; color: {color};">{change_str}</span>            </div>            """            label.setText(html)            label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)            # 增加内边距,使项更高            label.setStyleSheet("padding: 8px 4px; background-color: transparent;")            label.setAutoFillBackground(False)            # 让label根据内容和内边距调整大小            label.adjustSize()            # 设置项的大小提示,使其高度适应label            item.setSizeHint(label.sizeHint())            # 将label设置为item的widget            list_widget.setItemWidget(item, label)    def _check_all_loaded(self):        if self.concept_list.count() > 0 and self.industry_list.count() > 0:            self._progress.setVisible(False)    def _on_block_selected(self, item: QListWidgetItem):        block_code = item.data(Qt.UserRole)        self.statusBar().showMessage("正在加载成分股...")        self._progress.setVisible(True)        self._progress.setRange(00)        QTimer.singleShot(0lambdaself._loader.load_stock_list(block_code))    def _on_stock_list_loaded(self, df: pd.DataFrame, block_code: str):        self._progress.setVisible(False)        self.statusBar().showMessage("就绪"3000)        if df.empty:            QMessageBox.information(self"提示""该板块暂无成分股数据")            self.table_model.update_data(pd.DataFrame())        else:            self.table_model.update_data(df)            self.table_view.resizeColumnsToContents()    def _show_error(self, msg: str):        self._progress.setVisible(False)        self.statusBar().showMessage("错误"3000)        QMessageBox.critical(self"错误", msg)    def closeEvent(self, event):        self._thread.quit()        self._thread.wait()        event.accept()# -------------------- 启动应用 --------------------def main():    app = QApplication(sys.argv)    window = MainWindow()    window.show()    sys.exit(app.exec())if __name__ == "__main__":    main()
如果我的分享对你有所帮助, 不吝啬给个点赞呗
 
打赏
 
更多>同类资讯
0相关评论

推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  皖ICP备20008326号-18
Powered By DESTOON