股票数据成本分析工具

输入excel格式

输出excel的格式

输出季度报表格式

输出总持仓报表格式

功能就是输入股票交易数据,自动输出期初期末核算

python 复制代码
import pandas as pd
import os
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QPushButton, QLabel, QFileDialog, QTextEdit)
from PyQt6.QtCore import Qt

# ==============================================
# 所有文件都放在 桌面/股票持仓分析结果 里面
# ==============================================
DESKTOP = os.path.join(os.path.expanduser("~"), "Desktop")
BASE_FOLDER = os.path.join(DESKTOP, "股票持仓分析结果")  # 桌面总文件夹
OUTPUT_FOLDER = os.path.join(BASE_FOLDER, "季度报表")   # 季度报表子文件夹
TOTAL_FILE = os.path.join(BASE_FOLDER, "总持仓汇总表.xlsx")  # 总表放在总文件夹里

# ==============================================
# 业务逻辑
# ==============================================
def read_data(input_file):
    df = pd.read_excel(input_file)
    required = ['证券名称', '成交日期', '成交价格', '成交数量', '发生金额', '业务名称']
    for col in required:
        if col not in df.columns:
            raise ValueError(f"缺少必要列:{col}")
    return df

def add_quarter_tag(df):
    df = df.copy()
    df['date_str'] = df['成交日期'].astype(str).str.strip()
    df['year'] = df['date_str'].str[:4]
    df['month'] = df['date_str'].str[4:6].astype(int)

    def get_quarter(y, m):
        if m <= 3:
            return f"{y}Q1"
        elif m <= 6:
            return f"{y}Q2"
        elif m <= 9:
            return f"{y}Q3"
        else:
            return f"{y}Q4"

    df['quarter_tag'] = df.apply(lambda row: get_quarter(row['year'], row['month']), axis=1)
    return df

def clean_data(df):
    df = df.copy()
    apply_no_data = df[(df['业务名称'].str.contains('申购配号', na=False)) & (df['发生金额'] == 0)]
    df = df[~((df['业务名称'].str.contains('申购配号', na=False)) & (df['发生金额'] == 0))]
    df = df[~((df['业务名称'].str.contains('配股', na=False)) & (df['成交数量'] == 0))]
    return df, apply_no_data

def load_initial(total_file):
    if not os.path.exists(total_file):
        return {}
    try:
        df = pd.read_excel(total_file)
        init_data = {}
        for _, row in df.iterrows():
            stock = str(row['证券名称'])
            qty = int(row['期末数量']) if pd.notna(row['期末数量']) else 0
            avg = float(row['期末均价']) if pd.notna(row['期末均价']) else 0
            profit = float(row['累计盈亏']) if pd.notna(row['累计盈亏']) else 0
            code = str(row['证券代码']) if '证券代码' in df.columns and pd.notna(row['证券代码']) else ''
            init_data[stock] = {'qty': qty, 'avg': avg, 'profit': profit, 'code': code}
        return init_data
    except:
        return {}

def process_single_data(df, apply_no_data, stock_prev_data, output_file):
    df = df.dropna(subset=['证券名称'])
    summary_list = []

    with pd.ExcelWriter(output_file) as writer:
        if not apply_no_data.empty:
            apply_no_data.to_excel(writer, sheet_name='申购配号汇总', index=False)

        for stock in df['证券名称'].unique():
            if not stock or stock == 'nan':
                continue

            data = df[df['证券名称'] == stock].copy()
            data = data.sort_values('成交日期').reset_index(drop=True)
            stock_code = data['证券代码'].iloc[0] if '证券代码' in data.columns else ''

            def get_operate(biz):
                if any(k in biz for k in ['买入', '中签', '配股', '债券', '申购']):
                    return '买入'
                elif '卖出' in biz:
                    return '卖出'
                elif any(k in biz for k in ['红股', '送股', '转增', '扩股']):
                    return '扩股'
                elif any(k in biz for k in ['股息', '分红', '派息']):
                    return '分红派息'
                else:
                    return '其他'

            data['操作方向'] = data['业务名称'].apply(get_operate)
            prev = stock_prev_data.get(stock, {'qty': 0, 'avg': 0, 'profit': 0, 'code': ''})

            total_share = prev['qty']
            avg_cost = prev['avg']
            total_cost = total_share * avg_cost
            current_profit = prev['profit']

            cost_list = []
            profit_list = []

            for _, row in data.iterrows():
                op = row['操作方向']
                qty = row['成交数量']
                amount = row['发生金额']

                if op == '买入':
                    total_share += qty
                    total_cost += abs(amount)
                    avg_cost = round(total_cost / total_share, 2) if total_share > 0 else 0

                elif op == '扩股':
                    total_share += qty
                    avg_cost = round(total_cost / total_share, 2) if total_share > 0 else 0

                elif op == '卖出':
                    total_share -= qty
                    current_profit += amount

                elif op == '分红派息':
                    current_profit += abs(amount)

                cost_list.append(avg_cost)
                profit_list.append(round(current_profit, 2))

            data['加权平均成本'] = cost_list
            data['在仓数'] = data['成交数量'].cumsum() + prev['qty']
            data['本次盈亏'] = data['发生金额'].cumsum().round(2)
            data['累计盈亏'] = profit_list
            data['累计持仓总金额'] = data['加权平均成本'] * data['在仓数']

            init_row = pd.DataFrame({
                '成交日期': ['期初'], '业务名称': [''], '操作方向': [''], '成交价格': [prev['avg']],
                '成交数量': [0], '发生金额': [0], '在仓数': [prev['qty']], '加权平均成本': [prev['avg']],
                '本次盈亏': [0], '累计盈亏': [prev['profit']]
            })

            data_with_init = pd.concat([init_row, data], ignore_index=True)
            cols = ['成交日期', '业务名称', '操作方向', '成交价格', '成交数量',
                    '发生金额', '在仓数', '加权平均成本', '本次盈亏', '累计盈亏']
            data_with_init[cols].to_excel(writer, sheet_name=stock[:30], index=False)

            buy = data[data['操作方向'] == '买入']
            sell = data[data['操作方向'] == '卖出']

            summary_list.append({
                '证券代码': stock_code, '证券名称': stock, '期初数量': prev['qty'],
                '期初成本': round(prev['avg'] * prev['qty'], 2), '期初均价': prev['avg'],
                '本期买入数量': buy['成交数量'].sum(), '本期买入金额': round(buy['发生金额'].abs().sum(), 2),
                '本期卖出数量': sell['成交数量'].abs().sum(), '本期卖出金额': round(sell['发生金额'].sum(), 2),
                '期末数量': total_share, '期末成本': round(total_cost, 2),
                '期末均价': avg_cost, '累计盈亏': current_profit
            })

            stock_prev_data[stock] = {
                'qty': total_share, 'avg': avg_cost,
                'profit': current_profit, 'code': stock_code
            }

        pd.DataFrame(summary_list).to_excel(writer, sheet_name='持仓汇总表', index=False)
    return stock_prev_data

def process_all_quarters(df, apply_no_data, prev_data):
    os.makedirs(OUTPUT_FOLDER, exist_ok=True)
    for q_tag in sorted(df['quarter_tag'].unique()):
        df_q = df[df['quarter_tag'] == q_tag]
        df_apply = apply_no_data[apply_no_data['quarter_tag'] == q_tag]
        if len(df_q) > 0:
            prev_data = process_single_data(df_q, df_apply, prev_data, f"{OUTPUT_FOLDER}/{q_tag}.xlsx")
    return prev_data

def save_total(prev_data):
    final = []
    for stock, info in prev_data.items():
        final.append({
            '证券代码': info.get('code', ''),
            '证券名称': stock,
            '期末数量': info['qty'],
            '期末均价': info['avg'],
            '累计盈亏': info['profit']
        })
    if final:
        pd.DataFrame(final).to_excel(TOTAL_FILE, index=False)

def run_process(input_file):
    os.makedirs(BASE_FOLDER, exist_ok=True)  # 自动创建根文件夹
    df = read_data(input_file)
    df = add_quarter_tag(df)
    df, apply_no_data = clean_data(df)
    prev_data = load_initial(TOTAL_FILE)
    prev_data = process_all_quarters(df, apply_no_data, prev_data)
    save_total(prev_data)

# ==============================================
# PyQt 界面
# ==============================================
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("股票持仓分析工具")
        self.setFixedSize(650, 450)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        layout.setSpacing(15)
        layout.setAlignment(Qt.AlignmentFlag.AlignTop)

        title = QLabel("📊 股票持仓季度分析工具")
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        title.setStyleSheet("font-size:16px; font-weight:bold; padding:10px;")

        self.file_label = QLabel("未选择文件")
        self.select_btn = QPushButton("1. 选择 Excel 文件")
        self.select_btn.clicked.connect(self.select_file)

        self.run_btn = QPushButton("2. 开始处理数据")
        self.run_btn.clicked.connect(self.run_task)
        self.run_btn.setEnabled(False)

        self.log = QTextEdit()
        self.log.setReadOnly(True)
        self.log.setPlaceholderText("日志将在这里显示...")

        layout.addWidget(title)
        layout.addWidget(self.select_btn)
        layout.addWidget(self.file_label)
        layout.addWidget(self.run_btn)
        layout.addWidget(QLabel("执行日志:"))
        layout.addWidget(self.log)

        self.file_path = None

    def select_file(self):
        path, _ = QFileDialog.getOpenFileName(filter="Excel Files (*.xlsx *.xls)")
        if path:
            self.file_path = path
            self.file_label.setText(f"已选择:{os.path.basename(path)}")
            self.run_btn.setEnabled(True)
            self.log.append("✅ 已选择文件:" + path)

    def run_task(self):
        try:
            self.run_btn.setEnabled(False)
            self.log.append("🔄 开始处理...")
            QApplication.processEvents()

            run_process(self.file_path)

            self.log.append("✅ 处理完成!")
            self.log.append(f"📂 所有文件保存在:{BASE_FOLDER}")
            self.log.append(f"📁 季度报表:{OUTPUT_FOLDER}")
            self.log.append(f"📄 总持仓表:{TOTAL_FILE}")
        except Exception as e:
            self.log.append(f"❌ 错误:{str(e)}")
        finally:
            self.run_btn.setEnabled(True)

# ====================== 启动 ======================
def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()
相关推荐
小蒜学长25 分钟前
校园社团招新网站的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·校园社团招新网站
布莱克6051 小时前
MySQL 增删改查(CRUD)语法详解
数据库·mysql·增删改查
Dovis(誓平步青云)2 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
2501_933670792 小时前
2027届大数据管理与应用求职数字化转型顾问:项目、SQL、业务理解怎么排优先级
数据库·sql
东方护航数据恢复(深圳)3 小时前
物流案例:勒索病毒 Mallox 加密 ERP 数据库,旧备份 + 残留页重组实录【东方护航数据恢复深圳店】
数据库·数据恢复·勒索病毒·二次开盘·开盘·数据镜像
.Hypocritical.3 小时前
Redis从入门到实战:核心原理、场景落地与避坑指南
数据库·redis·缓存
梦想画家4 小时前
SQLMesh Python 模型入门(二):依赖管理、引擎实战与内存优化
数据库·python·sqlmesh
Omics Pro4 小时前
经典多组学整合算法→商用云平台
数据库·人工智能·算法·机器学习·自然语言处理
xcLeigh4 小时前
【KingbaseES数据库教程】国产化信创背景下的数据库选型与初识
linux·数据库·windows·kes·数据选型
资深技术分享员5 小时前
Geejing WebBuilder 数据库连接配置与在线 SQL 工具,运维的左右手
运维·数据库·sql·低代码