股票当日成交的分价表

查看 62|回复 11
作者:psqladm   
股票当日成交的分价表,对于判断次日走势有一定的辅助作用。某财软件的分价表没有包含竞价阶段的成交情况,原因不得而知。
通过调用Akshare提供的接口,可以得到当日的分笔成交明细,据此绘制当日的成交分价表,信息全面一些。
单独开一帖,希望能帮到那些需要的人。
看到有人需要打包后的软件,请自取。说明:下载的csv文件保存在D:\STOCKDETAIL目录下,生成的图像保存在:D:\DRAW目录下。
一个简单的界面,输入股票代码和股票名称,点击按钮“开始生成”即可。
通过网盘分享的文件:股票分价表软件
链接: https://pan.baidu.com/s/18CL60g49UX7xaOaT2djfMA?pwd=52pj 提取码: 52pj[i]
[Python] 纯文本查看 复制代码# !/usr/bin/env python
# coding=utf-8`

import asyncio
import os
import warnings
from datetime import datetime

import akshare as ak
import kaleido
import pandas as pd
import plotly.express as px

warnings.filterwarnings("ignore")

def get_stock_intraday_data(code, name):

    stock_intraday_em_df = ak.stock_intraday_em(symbol=code)
    pivot = pd.pivot_table(
        stock_intraday_em_df,
        values='手数',
        index='成交价',
        columns='买卖盘性质',
        aggfunc='sum',
        fill_value=0
    )
    pivot['总手数'] = pivot.sum(axis=1)
    pivot.columns.name = None
    if '中性盘' not in pivot.columns:
        pivot.insert(3, '中性盘', 0)

    if '买盘' not in pivot.columns:
        pivot.insert(2, '买盘', 0)
    result = pivot.reset_index()[['成交价', '总手数', '买盘', '中性盘', '卖盘']]
    result.insert(0, '日期', datetime.now().strftime("%Y-%m-%d"))

    file_name = f"D:\\stockdata\\stockdetail\\{code}.csv"
    result.to_csv(file_name, mode="w", header=True, encoding="gbk", index=False)

    return


def plot_stock_intraday(code, name):
    file_name = f"D:\\stockdata\\stockdetail\\{code}.csv"
    df = pd.read_csv(file_name, encoding="gbk", dtype={'成交价': float}, engine='pyarrow')
    df['成交价'] = df['成交价'].round(2)
    df['成交价'] = df['成交价'].apply(lambda x: ('%g' % x))
    total_buy = df['买盘'].sum()
    total_sell = df['卖盘'].sum()
    total_neutral = df['中性盘'].sum()
    # 根据买盘和卖盘占比大小,确定bar显示顺序
    if total_buy >= total_sell:
        df_long = df.melt(
            id_vars='成交价',
            value_vars=['买盘', '中性盘', '卖盘'],
            var_name='买卖盘性质',
            value_name='手数'
        )
    else:
        df_long = df.melt(
            id_vars='成交价',
            value_vars=['卖盘', '中性盘', '买盘'],
            var_name='买卖盘性质',
            value_name='手数'
        )
    price_totals = (
        df_long.groupby('成交价')['手数']
        .sum()
        .reset_index(name='price_hand')
    )

    day_total_hand = price_totals['price_hand'].sum()
    buy_pct = round(total_buy/day_total_hand * 100, 2)
    sell_pct = round(total_sell/day_total_hand * 100, 2)
    neutral_pct = round(total_neutral/day_total_hand * 100, 2)

    price_totals['ratio'] = price_totals['price_hand'] / day_total_hand * 100
    price_totals['ratio_str'] = price_totals['ratio'].map(lambda x: f"{x:.2f}%")
    price_totals = price_totals.sort_values(
        by="成交价", key=lambda s: s.astype(float), ascending=False
    ).reset_index(drop=True)   
    valid_prices = sorted(price_totals['成交价'].unique())
    price_order_str = price_totals['成交价'].astype(str).tolist()

    fig = px.bar(
        df_long,
        y='成交价',  # Y轴为成交价
        x='手数',  # X轴为手数
        color='买卖盘性质',
        orientation='h',  # 水平方向
        color_discrete_map={
            '买盘': '#FF4500',  # 红色
            '中性盘': '#00BFFF',  # 蓝色
            '卖盘': '#228B22'  # 绿色
        },
        category_orders={'成交价': price_order_str},
        labels={'手数': '手数', '成交价': '成交价'},
        title=f"{code}  {name}" + ' 分价表 日期;' + df['日期'].iloc[-1].strftime('%Y-%m-%d') +
        " 卖盘占比:" + str(sell_pct) + "%, 中性盘占比:" + str(neutral_pct) + "%, 买盘占比:" + str(buy_pct) + "%",
    )

    fig.update_yaxes(
        type='category',
        categoryorder='array',
        categoryarray=price_order_str,
    )

    ratio_text_aligned = price_totals.set_index('成交价').loc[valid_prices, 'ratio_str'].tolist()
    # 根据买盘和卖盘占比大小,确定文字显示位置
    if total_buy >= total_sell:
        fig.update_traces(
            selector=dict(name='卖盘'),
            text=ratio_text_aligned,
            textposition='outside',
            textfont=dict(color='black')
        )
    else:
        fig.update_traces(
            selector=dict(name='买盘'),
            text=ratio_text_aligned,
            textposition='outside',
            textfont=dict(color='black')
        )
    fig.update_layout(
        xaxis_title='手数',
        yaxis_title='成交价',
        bargap=0.15,
        bargroupgap=0.1,
        barmode='stack',
        font=dict(family='SimHei', color="black"),
        # yaxis_tickfont_size=6,
        hovermode='y unified',
    )

    # fig.show() # 显示图片
    # 输出目录, 请自行调整
    output_path = os.path.join("D:\\akstock\\draw\\板块\\", f"{code}{name} 分价表.png")
    # width, height 根据显示器的显示分辨率调整
    asyncio.run(kaleido.write_fig(fig, path=output_path,
                                  opts={"format": "png", "width": 3840, "height": 2160, "scale": 3}))

    return


if __name__ == '__main__':

    get_stock_intraday_data("002952", "亚世光电")
    plot_stock_intraday("002952", "亚世光电")

成交价, 买盘

nywthy   

刚刚把编码发给“元宝”,能不能打包?她这样回复我:
目前最常用且功能强大的工具是​​PyInstaller​​,它支持跨平台,能将Python脚本及其所有依赖项打包成独立的可执行文件,方便在没有Python环境的电脑上运行
。以下是详细的步骤和注意事项。
🛠️ 使用PyInstaller打包exe的步骤
​​安装PyInstaller​​
在命令行中使用pip安装(建议在虚拟环境中操作,以避免依赖冲突):
pip install pyinstaller
如果网络较慢,可以使用国内镜像源加速,例如清华源:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller

​​准备你的Python脚本​​
确保你的Python脚本(例如 your_script.py)在本地开发环境中可以正常运行,所有依赖库已正确安装

​​执行打包命令​​
打开命令行,切换到你的Python脚本所在目录,然后执行打包命令。最常用的命令格式如下:
pyinstaller --onefile --noconsole your_script.py
--onefile:将所有文件打包成一个单独的exe文件,方便分发

--noconsole(或 -w):如果你的程序是图形界面(GUI)应用,使用此参数可以避免运行时弹出命令行黑框

如果想为exe文件添加自定义图标,可以使用 --icon=your_icon.ico参数。图标文件需为.ico格式

​​查找生成的可执行文件​​
打包成功后,会在当前目录下生成 build和 dist两个文件夹。你需要的 your_script.exe文件就在 ​​dist​​ 文件夹中
。你可以将此exe文件复制到其他没有Python环境的Windows电脑上运行。
⚙️ 高级配置与自定义选项
​​处理资源文件​​:如果你的程序需要用到图片、配置文件等额外资源,需要使用 --add-data参数指定这些文件,确保它们被打包进去
。在代码中,需要使用特殊方法来获取这些资源在打包后的正确路径

​​使用spec文件​​:对于复杂的项目,可以先生成一个spec配置文件(首次打包后会自动生成),然后在该文件中详细定义需要包含的模块、数据文件等,最后通过 pyinstaller your_script.spec命令进行打包

​​减小文件体积​​:生成的exe文件通常较大,因为它包含了Python解释器和所有依赖库。你可以尝试使用UPX压缩工具(通过 --upx-dir参数指定路径),或在虚拟环境中仅安装必要的包来减小体积

⚠️ 常见问题与解决方案
​​程序一闪而过或闪退​​:可以暂时去掉 --noconsole参数打包,这样运行时能看到控制台输出的错误信息,便于调试

​​提示缺少模块​​:有些模块可能是动态导入的,PyInstaller无法自动检测。可以使用 --hidden-import参数手动指定这些模块

​​杀毒软件误报​​:部分打包后的exe文件可能会被杀毒软件误报为病毒,这有时是加壳压缩导致的。可以尝试不使用UPX压缩,或申请代码签名证书

🔄 其他打包工具简介
除了PyInstaller,还有其他工具可供选择:
​​cx_Freeze​​:另一个跨平台打包工具,需要通过编写 setup.py文件进行配置

​​Nuitka​​:它将Python代码编译成C语言,再生成可执行文件,理论上能获得更好的运行性能

​​auto-py-to-exe​​:一个带有图形界面的工具,底层基于PyInstaller,对新手非常友好

总的来说,​​PyInstaller​​ 因其易用性和强大的功能,是大多数情况下的首选。如果在打包过程中遇到具体问题,例如如何处理特定的资源文件或解决某个库的兼容性问题,可以提供更详细的信息,我可以给出更具针对性的建议。
psqladm
OP
  


nywthy 发表于 2025-10-21 10:58
刚刚把编码发给“元宝”,能不能打包?她这样回复我:
目前最常用且功能强大的工具是​​PyInst ...

打包完成的exe文件链接,在一楼
用AI跑了一遍,加了一个简单的界面,添加了一些注释,方便理解。
为了打包方便,把用到的Akshare两个函数,直接写入程序了。因为程序简单,没做多少容错处理。
愿意修改优化的,也请分享成果。
[Python] 纯文本查看 复制代码#!/usr/bin/env python# -*- coding: utf-8 -*-
import asyncio
import json
import warnings
from datetime import datetime
from pathlib import Path
from tkinter import Tk, Label, Entry, Button, StringVar, messagebox
import kaleido
import pandas as pd
import plotly.express as px
import requests
warnings.filterwarnings("ignore")
# ==============================================================
# 1️⃣ 常量(根目录统一为 D:\,只改这里即可切换盘符)
# ==============================================================
BASE_DIR = Path("D:/")                     # 只改这里即可统一根路径
CSV_DIR  = BASE_DIR / "stockdetail"        # CSV 保存位置 → D:\stockdetail\
PNG_DIR  = BASE_DIR / "draw"      # PNG 保存位置 → D:\draw\
# ==============================================================
# 2️⃣ 核心业务函数
# ==============================================================
def __event_stream(url, params):
    # 使用 stream=True 参数来启用流式请求
    response = requests.get(url, params=params, stream=True)
    event_data = ""
    for line in response.iter_lines():
        # 过滤掉保持连接的空行
        if line:
            event_data += line.decode() + "\n"
        elif event_data:
            yield event_data
            event_data = ""
def stock_intraday_em(symbol: str = "000001") -> pd.DataFrame:
    """
    某财-分时数据
    https://quote.eastmoney.com/f1.html?newcode=0.000001
    :param symbol: 股票代码
    :type symbol: str
    :return: 分时数据
    :rtype: pandas.DataFrame
    """
    market_code = 1 if symbol.startswith("6") else 0
    url = "https://70.push2.eastmoney.com/api/qt/stock/details/sse"
    params = {
        "fields1": "f1,f2,f3,f4",
        "fields2": "f51,f52,f53,f54,f55",
        "mpi": "2000",
        "ut": "bd1d9ddb04089700cf9c27f6f7426281",
        "fltt": "2",
        "pos": "-0",
        "secid": f"{market_code}.{symbol}",
        "wbp2u": "|0|0|0|web",
    }
    big_df = pd.DataFrame()  # 创建一个空的 DataFrame
    for event in __event_stream(url, params):
        # 从每个事件的数据行中删除 "data: ",然后解析 JSON
        event_json = json.loads(event.replace("data: ", ""))
        # 将 JSON 数据转换为 DataFrame,然后添加到主 DataFrame 中
        temp_df = pd.DataFrame(
            [item.split(",") for item in event_json["data"]["details"]]
        )
        big_df = pd.concat(objs=[big_df, temp_df], ignore_index=True)
        break
    big_df.columns = ["时间", "成交价", "手数", "-", "买卖盘性质"]
    big_df["买卖盘性质"] = big_df["买卖盘性质"].map(
        {"2": "买盘", "1": "卖盘", "4": "中性盘"}
    )
    big_df = big_df[["时间", "成交价", "手数", "买卖盘性质"]]
    big_df["成交价"] = pd.to_numeric(big_df["成交价"], errors="coerce")
    big_df["手数"] = pd.to_numeric(big_df["手数"], errors="coerce")
    return big_df
def get_stock_intraday_data(code: str, name: str) -> Path:
    r"""
    下载当日分笔成交数据,保存为 CSV(D:\stockdetail\)。
    返回 CSV 文件的完整路径(Path 对象)。
    """
    # ------------------- 1️⃣ 拉取数据 -------------------
    stock_intraday_em_df = stock_intraday_em(symbol=code)
    # ------------------- 2️⃣ 透视表 → 手数汇总 -------------------
    pivot = pd.pivot_table(
        stock_intraday_em_df,
        values='手数',
        index='成交价',
        columns='买卖盘性质',
        aggfunc='sum',
        fill_value=0,
    )
    pivot['总手数'] = pivot.sum(axis=1)
    pivot.columns.name = None
    # ------------------- 3️⃣ 确保必须列存在 -------------------
    for col in ['买盘', '中性盘', '卖盘']:
        if col not in pivot.columns:
            pivot[col] = 0
    # ------------------- 4️⃣ 组织结果 -------------------
    result = pivot.reset_index()[['成交价', '总手数', '买盘', '中性盘', '卖盘']]
    result.insert(0, '日期', datetime.now().strftime("%Y-%m-%d"))
    # ------------------- 5️⃣ 写入 CSV(GBK 编码) -------------------
    CSV_DIR.mkdir(parents=True, exist_ok=True)
    csv_path = CSV_DIR / f"{code}.csv"
    result.to_csv(csv_path, mode="w", header=True, encoding="gbk", index=False)
    return csv_path
def plot_stock_intraday(code: str, name: str, csv_path: Path) -> Path:
    r"""
    读取 CSV,绘制分价表并保存为 PNG(D:\draw\)。
    返回 PNG 文件的完整路径(Path 对象)。
    """
    # ------------------- 1️⃣ 读取 CSV -------------------
    df = pd.read_csv(csv_path, encoding="gbk", dtype={'成交价': float})
    df['成交价'] = df['成交价'].round(2).apply(lambda x: ('%g' % x))
    # ------------------- 2️⃣ 统计总手数 -------------------
    total_buy = df['买盘'].sum()
    total_sell = df['卖盘'].sum()
    total_neutral = df['中性盘'].sum()
    # ------------------- 3️⃣ 根据买卖占比决定 melt 顺序 -------------------
    melt_cols = ['买盘', '中性盘', '卖盘'] if total_buy >= total_sell else ['卖盘', '中性盘', '买盘']
    df_long = df.melt(
        id_vars='成交价',
        value_vars=melt_cols,
        var_name='买卖盘性质',
        value_name='手数'
    )
    # ------------------- 4️⃣ 价位手数占比(用于条形图外侧文字) -------------------
    price_totals = (
        df_long.groupby('成交价')['手数']
        .sum()
        .reset_index(name='price_hand')
    )
    day_total_hand = price_totals['price_hand'].sum()
    price_totals['ratio'] = price_totals['price_hand'] / day_total_hand * 100
    price_totals['ratio_str'] = price_totals['ratio'].map(lambda x: f"{x:.2f}%")
    price_totals = price_totals.sort_values(
        by="成交价", key=lambda s: s.astype(float), ascending=False
    )
    price_order_str = price_totals['成交价'].astype(str).tolist()
    # ------------------- 5️⃣ 整体买/卖/中性占比(放在标题里) -------------------
    buy_pct = round(total_buy / day_total_hand * 100, 2)
    sell_pct = round(total_sell / day_total_hand * 100, 2)
    neutral_pct = round(total_neutral / day_total_hand * 100, 2)
    # ------------------- 6️⃣ 绘图 -------------------
    fig = px.bar(
        df_long,
        y='成交价',
        x='手数',
        color='买卖盘性质',
        orientation='h',
        color_discrete_map={
            '买盘': '#FF4500',      # 红
            '中性盘': '#00BFFF',    # 蓝
            '卖盘': '#228B22'      # 绿
        },
        category_orders={'成交价': price_order_str},
        labels={'手数': '手数', '成交价': '成交价'},
        title=(
            f"{code}  {name}  分价表  日期:{df['日期'].iloc[-1]}  "
            f"卖盘占比:{sell_pct}%  中性盘占比:{neutral_pct}%  买盘占比:{buy_pct}%"
        ),
    )
    fig.update_yaxes(type='category', categoryorder='array', categoryarray=price_order_str)
    # ------------------- 7️⃣ 把占比文字放到弱势方向的条形外侧 -------------------
    ratio_text_aligned = price_totals['ratio_str'].tolist()
    if total_buy >= total_sell:
        fig.update_traces(
            selector=dict(name='卖盘'),
            text=ratio_text_aligned,
            textposition='outside',
            textfont=dict(color='black')
        )
    else:
        fig.update_traces(
            selector=dict(name='买盘'),
            text=ratio_text_aligned,
            textposition='outside',
            textfont=dict(color='black')
        )
    fig.update_layout(
        xaxis_title='手数',
        yaxis_title='成交价',
        bargap=0.15,
        bargroupgap=0.1,
        barmode='stack',
        font=dict(family='SimHei', color="black"),
        hovermode='y unified',
    )
    # ------------------- 8️⃣ 保存 PNG(Kaleido) -------------------
    PNG_DIR.mkdir(parents=True, exist_ok=True)
    png_path = PNG_DIR / f"{code}_{name}_分价表.png"
    async def _write():
        await kaleido.write_fig(
            fig,
            path=str(png_path),
            opts={"format": "png", "width": 3840, "height": 2160, "scale": 3},
        )
    asyncio.run(_write())
    return png_path
# ==============================================================
# 3️⃣ Tkinter UI
# ==============================================================
class StockApp:
    def __init__(self, master):
        self.master = master
        master.title("股票分价表生成工具")
        master.geometry("420x150")
        master.resizable(False, False)
        # 变量
        self.var_code = StringVar()
        self.var_name = StringVar()
        # 布局
        Label(master, text="股票代码 (如 002952):").grid(row=0, column=0, sticky="e", padx=5, pady=8)
        Entry(master, textvariable=self.var_code, width=30).grid(row=0, column=1, padx=5)
        Label(master, text="股票名称:").grid(row=1, column=0, sticky="e", padx=5, pady=8)
        Entry(master, textvariable=self.var_name, width=30).grid(row=1, column=1, padx=5)
        # 按钮
        Button(master, text="开始生成", command=self.start,
               width=14, bg="#4CAF50", fg="white").grid(row=3, column=1, pady=20, sticky="e")
        Button(master, text="退出", command=master.quit,
               width=8, bg="#F44336", fg="white").grid(row=3, column=1, pady=20, sticky="w")
    def start(self):
        """校验 → 下载 CSV → 绘图 PNG → 完成提示"""
        code = self.var_code.get().strip()
        name = self.var_name.get().strip()
        if not code:
            messagebox.showerror("错误", "请填写股票代码")
            return
        if not name:
            messagebox.showerror("错误", "请填写股票名称")
            return
        # 1️⃣ 下载 CSV
        try:
            csv_path = get_stock_intraday_data(code, name)
        except Exception as e:
            messagebox.showerror("下载失败", f"获取行情时出错:\n{e}")
            return
        # 2️⃣ 绘图 PNG
        try:
            png_path = plot_stock_intraday(code, name, csv_path)
        except Exception as e:
            messagebox.showerror("绘图失败", f"绘制分价表时出错:\n{e}")
            return
        # 3️⃣ 完成提示
        messagebox.showinfo(
            "完成",
            f"CSV 已保存至:\n{csv_path}\n\n"
            f"分价表图片已保存至:\n{png_path}"
        )
# ==============================================================
# 4️⃣ 程序入口
# ==============================================================
if __name__ == "__main__":
    root = Tk()
    app = StockApp(root)
    root.mainloop()
sammonkey819   

谢谢分享。我来研究下有没有用。
HA19683   

感谢分享,akshare这个库挺有用的
jiukou   

我想要当日机构的期指多空单净值,有一些主播每天都根据这个判断机构是加仓还是减仓,可用于参考。
nywthy   

非常幸运看到,谢谢
nywthy   

能不能发个包,谢谢
skzhaixing   

不玩股票的我表示看不懂
alphagis   

我也来研究一下Akshare,看看能不能学到啥
您需要登录后才可以回帖 登录 | 立即注册

返回顶部