输入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()