吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 350|回复: 12
上一主题 下一主题
收起左侧

[Python 转载] 用AI做了一个股票交易记录系统

[复制链接]
跳转到指定楼层
楼主
snyang 发表于 2026-5-12 19:52 回帖奖励
总觉得股票交易软件持仓和交易记录情况不好用,所以自己用AI做了一个股票交易记录系统,不连接网络,全部手动输入,自动计算持仓成本和亏盈,添加备注后底色变为黄色,后来又增加了导出打印功能。目前只能想到这些,代码附上,有需要的自取,有好的建议和新增功能欢迎提一提,谢谢。



[Python] 纯文本查看 复制代码
import json
import os
from datetime import datetime
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from tkinter import font as tkfont
from collections import defaultdict
import webbrowser


class StockTradingGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("股票交易记录系统")
        self.root.geometry("1350x750")

        # 数据文件
        self.data_file = "trading_records.json"
        self.current_prices_file = "current_prices.json"  # 保存当前股价
        self.records = []
        self.current_prices = {}  # 存储当前股价
        self.load_records()
        self.load_current_prices()

        # 设置样式
        self.setup_styles()

        # 创建界面
        self.create_widgets()

        # 刷新显示
        self.refresh_display()

    def setup_styles(self):
        """设置样式和颜色"""
        # 定义颜色
        self.colors = {
            'buy': '#ff4444',  # 红色 - 买入
            'sell': '#4444ff',  # 蓝色 - 卖出
            'profit': '#ff4444',  # 红色 - 盈利
            'loss': '#00cc00',  # 绿色 - 亏损
            'bg': '#f0f0f0',
            'header': '#2c3e50',
            'button': '#3498db',
            'note_bg': '#ffff99'  # 黄色 - 有备注的行
        }

        # 设置字体
        self.title_font = tkfont.Font(family="Microsoft YaHei", size=12, weight="bold")
        self.normal_font = tkfont.Font(family="Microsoft YaHei", size=10)

    def create_widgets(self):
        """创建界面组件"""
        # 主框架
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 左侧输入区域
        left_frame = ttk.LabelFrame(main_frame, text="交易记录输入", padding=10)
        left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))

        # 输入框
        ttk.Label(left_frame, text="股票代码:", font=self.normal_font).grid(row=0, column=0, sticky=tk.W, pady=5)
        self.stock_code_entry = ttk.Entry(left_frame, width=20, font=self.normal_font)
        self.stock_code_entry.grid(row=0, column=1, pady=5, padx=(5, 10))

        ttk.Label(left_frame, text="股票名称:", font=self.normal_font).grid(row=1, column=0, sticky=tk.W, pady=5)
        self.stock_name_entry = ttk.Entry(left_frame, width=20, font=self.normal_font)
        self.stock_name_entry.grid(row=1, column=1, pady=5, padx=(5, 10))

        ttk.Label(left_frame, text="交易数量:", font=self.normal_font).grid(row=2, column=0, sticky=tk.W, pady=5)
        self.quantity_entry = ttk.Entry(left_frame, width=20, font=self.normal_font)
        self.quantity_entry.grid(row=2, column=1, pady=5, padx=(5, 10))

        ttk.Label(left_frame, text="成交价:", font=self.normal_font).grid(row=3, column=0, sticky=tk.W, pady=5)
        self.price_entry = ttk.Entry(left_frame, width=20, font=self.normal_font)
        self.price_entry.grid(row=3, column=1, pady=5, padx=(5, 10))

        ttk.Label(left_frame, text="成交金额:", font=self.normal_font).grid(row=4, column=0, sticky=tk.W, pady=5)
        self.amount_var = tk.StringVar(value="0.00")
        self.amount_entry = ttk.Entry(left_frame, width=20, font=self.normal_font, textvariable=self.amount_var,
                                      state="readonly")
        self.amount_entry.grid(row=4, column=1, pady=5, padx=(5, 10))

        # 绑定数量和价格变化事件
        self.quantity_entry.bind('<KeyRelease>', self.calculate_amount)
        self.price_entry.bind('<KeyRelease>', self.calculate_amount)

        ttk.Label(left_frame, text="交易时间:", font=self.normal_font).grid(row=5, column=0, sticky=tk.W, pady=5)
        self.time_var = tk.StringVar(value=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
        self.time_entry = ttk.Entry(left_frame, width=20, font=self.normal_font, textvariable=self.time_var)
        self.time_entry.grid(row=5, column=1, pady=5, padx=(5, 10))

        ttk.Label(left_frame, text="备注:", font=self.normal_font).grid(row=6, column=0, sticky=tk.W, pady=5)
        self.note_entry = ttk.Entry(left_frame, width=20, font=self.normal_font)
        self.note_entry.grid(row=6, column=1, pady=5, padx=(5, 10))

        # 按钮
        ttk.Button(left_frame, text="&#128993; 买入记录", command=self.add_buy_record,
                   width=20, style="Buy.TButton").grid(row=7, column=0, columnspan=2, pady=10)

        ttk.Button(left_frame, text="&#128309; 卖出记录", command=self.add_sell_record,
                   width=20, style="Sell.TButton").grid(row=8, column=0, columnspan=2, pady=5)

        ttk.Button(left_frame, text="&#128465;&#65039; 删除选定记录", command=self.delete_record,
                   width=20).grid(row=9, column=0, columnspan=2, pady=5)

        ttk.Button(left_frame, text="&#9999;&#65039; 编辑备注", command=self.edit_note,
                   width=20).grid(row=10, column=0, columnspan=2, pady=5)

        ttk.Button(left_frame, text="&#128190; 保存数据", command=self.save_records,
                   width=20).grid(row=11, column=0, columnspan=2, pady=5)

        ttk.Button(left_frame, text="&#128424;&#65039; 打印记录", command=self.print_records,
                   width=20).grid(row=12, column=0, columnspan=2, pady=5)

        # 统计信息框
        stats_frame = ttk.LabelFrame(left_frame, text="统计信息", padding=10)
        stats_frame.grid(row=13, column=0, columnspan=2, pady=10, sticky=tk.W + tk.E)

        self.stats_text = tk.Text(stats_frame, height=8, width=25, font=self.normal_font)
        self.stats_text.pack()

        # 右侧显示区域(标签页)
        right_frame = ttk.Frame(main_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        # 创建标签页
        self.notebook = ttk.Notebook(right_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)

        # 动态创建股票交易记录标签页
        self.stock_notebooks = {}

        # 持仓情况标签页
        self.positions_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.positions_frame, text="&#128188; 持仓情况")

        # 创建持仓表格
        self.create_positions_table()

        # 设置按钮样式
        self.setup_button_styles()

    def setup_button_styles(self):
        """设置按钮样式"""
        style = ttk.Style()
        style.configure("Buy.TButton", foreground="#ff4444", font=("Microsoft YaHei", 10, "bold"))
        style.configure("Sell.TButton", foreground="#4444ff", font=("Microsoft YaHei", 10, "bold"))

    def calculate_amount(self, event=None):
        """计算成交金额"""
        try:
            quantity = float(self.quantity_entry.get()) if self.quantity_entry.get() else 0
            price = float(self.price_entry.get()) if self.price_entry.get() else 0
            amount = quantity * price
            self.amount_var.set(f"{amount:.2f}")
        except ValueError:
            self.amount_var.set("0.00")

    def print_records(self):
        """打印交易记录"""
        if not self.records:
            messagebox.showwarning("警告", "没有交易记录可打印!")
            return

        # 创建打印选项窗口
        print_window = tk.Toplevel(self.root)
        print_window.title("打印选项")
        print_window.geometry("400x300")
        print_window.transient(self.root)
        print_window.grab_set()

        ttk.Label(print_window, text="选择打印内容:", font=self.title_font).pack(pady=10)

        # 打印选项
        print_option = tk.StringVar(value="all")

        ttk.Radiobutton(print_window, text="打印所有交易记录", variable=print_option,
                        value="all").pack(pady=5, anchor=tk.W, padx=20)
        ttk.Radiobutton(print_window, text="打印当前股票交易记录", variable=print_option,
                        value="current_stock").pack(pady=5, anchor=tk.W, padx=20)
        ttk.Radiobutton(print_window, text="打印持仓情况", variable=print_option,
                        value="positions").pack(pady=5, anchor=tk.W, padx=20)

        ttk.Label(print_window, text="输出格式:", font=self.title_font).pack(pady=10)

        format_option = tk.StringVar(value="html")
        ttk.Radiobutton(print_window, text="HTML格式(在浏览器中打开)", variable=format_option,
                        value="html").pack(pady=5, anchor=tk.W, padx=20)
        ttk.Radiobutton(print_window, text="文本格式", variable=format_option,
                        value="txt").pack(pady=5, anchor=tk.W, padx=20)

        def do_print():
            print_type = print_option.get()
            output_format = format_option.get()
            print_window.destroy()

            if print_type == "all":
                self.print_all_records(output_format)
            elif print_type == "current_stock":
                self.print_current_stock_records(output_format)
            elif print_type == "positions":
                self.print_positions(output_format)

        ttk.Button(print_window, text="打印", command=do_print, width=15).pack(pady=20)

    def print_all_records(self, output_format):
        """打印所有交易记录"""
        if output_format == "html":
            self.generate_html_report()
        else:
            self.generate_text_report()

    def print_current_stock_records(self, output_format):
        """打印当前股票的记录"""
        # 获取当前选中的标签页
        current_tab = self.notebook.select()
        tab_text = self.notebook.tab(current_tab, "text")

        if tab_text == "&#128188; 持仓情况":
            messagebox.showwarning("警告", "请先在股票交易记录标签页中选择要打印的股票!")
            return

        # 查找对应的股票key
        stock_key = None
        for key, data in self.stock_notebooks.items():
            if self.notebook.tab(current_tab, "text") == f"&#128202; {key}":
                stock_key = key
                break

        if not stock_key:
            messagebox.showwarning("警告", "请先选择要打印的股票!")
            return

        if output_format == "html":
            self.generate_stock_html_report(stock_key)
        else:
            self.generate_stock_text_report(stock_key)

    def print_positions(self, output_format):
        """打印持仓情况"""
        if output_format == "html":
            self.generate_positions_html_report()
        else:
            self.generate_positions_text_report()

    def generate_html_report(self):
        """生成所有记录的HTML报告"""
        html_content = """
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>股票交易记录报告</title>
            <style>
                body { font-family: Microsoft YaHei, Arial, sans-serif; margin: 20px; }
                h1 { color: #2c3e50; text-align: center; }
                h2 { color: #34495e; margin-top: 30px; }
                table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
                th, td { border: 1px solid #ddd; padding: 8px; text-align: center; }
                th { background-color: #2c3e50; color: white; }
                .buy { color: #ff4444; }
                .sell { color: #4444ff; }
                .profit { color: #ff4444; }
                .loss { color: #00cc00; }
                .date { color: #666; font-size: 12px; }
                .summary { background-color: #f0f0f0; padding: 15px; margin-top: 20px; border-radius: 5px; }
            </style>
        </head>
        <body>
            <h1>股票交易记录报告</h1>
            <p class="date">生成时间: {}</p>
        """.format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

        # 按股票分组
        stocks = defaultdict(list)
        for record in self.records:
            key = f"{record['stock_code']} - {record['stock_name']}"
            stocks[key].append(record)

        # 为每个股票生成表格
        for stock_key, stock_records in stocks.items():
            html_content += f"<h2>{stock_key}</h2>"
            html_content += """
            <table>
                <thead>
                    <tr>
                        <th>序号</th>
                        <th>交易时间</th>
                        <th>类型</th>
                        <th>成交数量</th>
                        <th>成交价</th>
                        <th>成交金额</th>
                        <th>备注</th>
                    </tr>
                </thead>
                <tbody>
            """

            # 按时间排序
            stock_records.sort(key=lambda x: x['trade_date'])
            for idx, record in enumerate(stock_records, 1):
                trade_type = "买入" if record['trade_type'] == 'buy' else "卖出"
                color_class = "buy" if record['trade_type'] == 'buy' else "sell"
                note = record.get('note', '')

                html_content += f"""
                    <tr class="{color_class}">
                        <td>{idx}</td>
                        <td>{record['trade_date']}</td>
                        <td>{trade_type}</td>
                        <td>{int(record['quantity']) if record['quantity'].is_integer() else record['quantity']}</td>
                        <td>&#165;{record['price']:.2f}</td>
                        <td>&#165;{record['amount']:,.2f}</td>
                        <td>{note}</td>
                    </tr>
                """

            html_content += "</tbody></table>"

        # 添加统计信息
        holdings, _ = self.calculate_positions()
        total_buy_amount = sum(r['amount'] for r in self.records if r['trade_type'] == 'buy')
        total_sell_amount = sum(r['amount'] for r in self.records if r['trade_type'] == 'sell')

        html_content += f"""
            <div class="summary">
                <h3>统计摘要</h3>
                <p>总买入金额: &#165;{total_buy_amount:,.2f}</p>
                <p>总卖出金额: &#165;{total_sell_amount:,.2f}</p>
                <p>总交易次数: {len(self.records)}</p>
                <p>当前持仓数: {len(holdings)}</p>
                <p>持仓成本: &#165;{sum(h['total_cost'] for h in holdings):,.2f}</p>
            </div>
        </body></html>
        """

        # 保存HTML文件
        filename = f"trading_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(html_content)

        webbrowser.open(filename)
        messagebox.showinfo("成功", f"报告已生成并打开:{filename}")

    def generate_stock_html_report(self, stock_key):
        """生成单只股票的HTML报告"""
        stock_records = [r for r in self.records
                         if f"{r['stock_code']} - {r['stock_name']}" == stock_key]
        stock_records.sort(key=lambda x: x['trade_date'])

        html_content = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>{stock_key} 交易记录</title>
            <style>
                body {{ font-family: Microsoft YaHei, Arial, sans-serif; margin: 20px; }}
                h1 {{ color: #2c3e50; text-align: center; }}
                table {{ border-collapse: collapse; width: 100%; margin-top: 20px; }}
                th, td {{ border: 1px solid #ddd; padding: 8px; text-align: center; }}
                th {{ background-color: #2c3e50; color: white; }}
                .buy {{ color: #ff4444; }}
                .sell {{ color: #4444ff; }}
                .date {{ color: #666; font-size: 12px; }}
            </style>
        </head>
        <body>
            <h1>{stock_key} 交易记录</h1>
            <p class="date">生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
            <table>
                <thead>
                    <tr>
                        <th>序号</th>
                        <th>交易时间</th>
                        <th>类型</th>
                        <th>成交数量</th>
                        <th>成交价</th>
                        <th>成交金额</th>
                        <th>备注</th>
                    </tr>
                </thead>
                <tbody>
        """

        for idx, record in enumerate(stock_records, 1):
            trade_type = "买入" if record['trade_type'] == 'buy' else "卖出"
            color_class = "buy" if record['trade_type'] == 'buy' else "sell"
            note = record.get('note', '')

            html_content += f"""
                <tr class="{color_class}">
                    <td>{idx}</td>
                    <td>{record['trade_date']}</td>
                    <td>{trade_type}</td>
                    <td>{int(record['quantity']) if record['quantity'].is_integer() else record['quantity']}</td>
                    <td>&#165;{record['price']:.2f}</td>
                    <td>&#165;{record['amount']:,.2f}</td>
                    <td>{note}</td>
                </tr>
            """

        html_content += "</tbody></table></body></html>"

        filename = f"{stock_key.replace(' - ', '_')}_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(html_content)

        webbrowser.open(filename)
        messagebox.showinfo("成功", f"报告已生成并打开:{filename}")

    def generate_positions_html_report(self):
        """生成持仓HTML报告"""
        holdings, _ = self.calculate_positions()

        html_content = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>持仓情况报告</title>
            <style>
                body {{ font-family: Microsoft YaHei, Arial, sans-serif; margin: 20px; }}
                h1 {{ color: #2c3e50; text-align: center; }}
                table {{ border-collapse: collapse; width: 100%; margin-top: 20px; }}
                th, td {{ border: 1px solid #ddd; padding: 8px; text-align: center; }}
                th {{ background-color: #2c3e50; color: white; }}
                .profit {{ color: #ff4444; }}
                .loss {{ color: #00cc00; }}
                .date {{ color: #666; font-size: 12px; }}
            </style>
        </head>
        <body>
            <h1>持仓情况报告</h1>
            <p class="date">生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
            <table>
                <thead>
                    <tr>
                        <th>代码</th>
                        <th>名称</th>
                        <th>持仓数量</th>
                        <th>平均成本</th>
                        <th>当前股价</th>
                        <th>持仓成本</th>
                        <th>当前市值</th>
                        <th>浮动盈亏</th>
                        <th>盈亏比例</th>
                    </tr>
                </thead>
                <tbody>
        """

        for holding in holdings:
            stock_key = f"{holding['stock_code']} - {holding['stock_name']}"
            current_price = self.current_prices.get(stock_key, holding['avg_cost'])
            current_value = current_price * holding['quantity']
            profit_loss = current_value - holding['total_cost']
            profit_loss_percent = (profit_loss / holding['total_cost']) * 100 if holding['total_cost'] != 0 else 0

            profit_class = "profit" if profit_loss > 0 else "loss" if profit_loss < 0 else ""

            avg_cost_display = f"&#165;{holding['avg_cost']:.2f}" if holding[
                                                                    'avg_cost'] >= 0 else f"-&#165;{abs(holding['avg_cost']):.2f}"

            html_content += f"""
                <tr class="{profit_class}">
                    <td>{holding['stock_code']}</td>
                    <td>{holding['stock_name']}</td>
                    <td>{int(holding['quantity']) if holding['quantity'].is_integer() else holding['quantity']}</td>
                    <td>{avg_cost_display}</td>
                    <td>&#165;{current_price:.2f}</td>
                    <td>&#165;{holding['total_cost']:,.2f}</td>
                    <td>&#165;{current_value:,.2f}</td>
                    <td>&#165;{profit_loss:,.2f}</td>
                    <td>{profit_loss_percent:+.2f}%</td>
                </tr>
            """

        html_content += "</tbody></table></body></html>"

        filename = f"positions_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html"
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(html_content)

        webbrowser.open(filename)
        messagebox.showinfo("成功", f"报告已生成并打开:{filename}")

    def generate_text_report(self):
        """生成文本格式报告"""
        filename = f"trading_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"

        with open(filename, 'w', encoding='utf-8') as f:
            f.write("=" * 80 + "\n")
            f.write("股票交易记录报告\n")
            f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write("=" * 80 + "\n\n")

            # 按股票分组
            stocks = defaultdict(list)
            for record in self.records:
                key = f"{record['stock_code']} - {record['stock_name']}"
                stocks[key].append(record)

            for stock_key, stock_records in stocks.items():
                f.write(f"\n【{stock_key}】\n")
                f.write("-" * 80 + "\n")
                f.write(
                    f"{'序号':<6}{'交易时间':<20}{'类型':<6}{'成交数量':<10}{'成交价':<10}{'成交金额':<15}{'备注':<20}\n")
                f.write("-" * 80 + "\n")

                stock_records.sort(key=lambda x: x['trade_date'])
                for idx, record in enumerate(stock_records, 1):
                    trade_type = "买入" if record['trade_type'] == 'buy' else "卖出"
                    note = record.get('note', '')
                    f.write(f"{idx:<6}{record['trade_date']:<20}{trade_type:<6}"
                            f"{int(record['quantity']) if record['quantity'].is_integer() else record['quantity']:<10}"
                            f"&#165;{record['price']:<9.2f}&#165;{record['amount']:<14,.2f}{note:<20}\n")

            # 统计信息
            holdings, _ = self.calculate_positions()
            total_buy_amount = sum(r['amount'] for r in self.records if r['trade_type'] == 'buy')
            total_sell_amount = sum(r['amount'] for r in self.records if r['trade_type'] == 'sell')

            f.write("\n" + "=" * 80 + "\n")
            f.write("统计摘要\n")
            f.write("=" * 80 + "\n")
            f.write(f"总买入金额: &#165;{total_buy_amount:,.2f}\n")
            f.write(f"总卖出金额: &#165;{total_sell_amount:,.2f}\n")
            f.write(f"总交易次数: {len(self.records)}\n")
            f.write(f"当前持仓数: {len(holdings)}\n")
            f.write(f"持仓成本: &#165;{sum(h['total_cost'] for h in holdings):,.2f}\n")

        # 打开文件
        os.startfile(filename)
        messagebox.showinfo("成功", f"报告已生成:{filename}")

    def generate_stock_text_report(self, stock_key):
        """生成单只股票文本报告"""
        stock_records = [r for r in self.records
                         if f"{r['stock_code']} - {r['stock_name']}" == stock_key]
        stock_records.sort(key=lambda x: x['trade_date'])

        filename = f"{stock_key.replace(' - ', '_')}_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"

        with open(filename, 'w', encoding='utf-8') as f:
            f.write("=" * 80 + "\n")
            f.write(f"{stock_key} 交易记录\n")
            f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write("=" * 80 + "\n\n")
            f.write(
                f"{'序号':<6}{'交易时间':<20}{'类型':<6}{'成交数量':<10}{'成交价':<10}{'成交金额':<15}{'备注':<20}\n")
            f.write("-" * 80 + "\n")

            for idx, record in enumerate(stock_records, 1):
                trade_type = "买入" if record['trade_type'] == 'buy' else "卖出"
                note = record.get('note', '')
                f.write(f"{idx:<6}{record['trade_date']:<20}{trade_type:<6}"
                        f"{int(record['quantity']) if record['quantity'].is_integer() else record['quantity']:<10}"
                        f"&#165;{record['price']:<9.2f}&#165;{record['amount']:<14,.2f}{note:<20}\n")

        os.startfile(filename)
        messagebox.showinfo("成功", f"报告已生成:{filename}")

    def generate_positions_text_report(self):
        """生成持仓文本报告"""
        holdings, _ = self.calculate_positions()

        filename = f"positions_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"

        with open(filename, 'w', encoding='utf-8') as f:
            f.write("=" * 100 + "\n")
            f.write("持仓情况报告\n")
            f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write("=" * 100 + "\n\n")
            f.write(
                f"{'代码':<10}{'名称':<12}{'持仓数量':<10}{'平均成本':<12}{'当前股价':<12}{'持仓成本':<15}{'当前市值':<15}{'浮动盈亏':<15}{'盈亏比例':<10}\n")
            f.write("-" * 100 + "\n")

            for holding in holdings:
                stock_key = f"{holding['stock_code']} - {holding['stock_name']}"
                current_price = self.current_prices.get(stock_key, holding['avg_cost'])
                current_value = current_price * holding['quantity']
                profit_loss = current_value - holding['total_cost']
                profit_loss_percent = (profit_loss / holding['total_cost']) * 100 if holding['total_cost'] != 0 else 0

                avg_cost_display = f"&#165;{holding['avg_cost']:.2f}" if holding[
                                                                        'avg_cost'] >= 0 else f"-&#165;{abs(holding['avg_cost']):.2f}"

                f.write(f"{holding['stock_code']:<10}{holding['stock_name']:<12}"
                        f"{int(holding['quantity']) if holding['quantity'].is_integer() else holding['quantity']:<10}"
                        f"{avg_cost_display:<12}&#165;{current_price:<11.2f}"
                        f"&#165;{holding['total_cost']:<14,.2f}&#165;{current_value:<14,.2f}"
                        f"&#165;{profit_loss:<14,.2f}{profit_loss_percent:+.2f}%\n")

        os.startfile(filename)
        messagebox.showinfo("成功", f"报告已生成:{filename}")

    def create_stock_records_tab(self, stock_key):
        """为股票创建独立的交易记录标签页"""
        if stock_key in self.stock_notebooks:
            return

        # 创建新标签页
        stock_frame = ttk.Frame(self.notebook)
        self.notebook.add(stock_frame, text=f"&#128202; {stock_key}")

        # 添加右键菜单
        self.create_context_menu(stock_frame, stock_key)

        # 创建表格 - 不包含平均成本列
        columns = ("序号", "交易时间", "类型", "成交数量", "成交价", "成交金额", "持仓数量", "备注")
        tree = ttk.Treeview(stock_frame, columns=columns, show="headings", height=15)

        # 设置列标题和宽度
        col_widths = [50, 180, 80, 100, 100, 120, 100, 150]
        col_titles = ["序号", "交易时间", "类型", "成交数量", "成交价", "成交金额", "持仓数量", "备注"]
        for col, width, title in zip(columns, col_widths, col_titles):
            tree.heading(col, text=title)
            tree.column(col, width=width, anchor=tk.CENTER)

        # 添加滚动条
        vsb = ttk.Scrollbar(stock_frame, orient="vertical", command=tree.yview)
        hsb = ttk.Scrollbar(stock_frame, orient="horizontal", command=tree.xview)
        tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

        # 布局
        tree.grid(row=0, column=0, sticky="nsew")
        vsb.grid(row=0, column=1, sticky="ns")
        hsb.grid(row=1, column=0, sticky="ew")

        stock_frame.grid_rowconfigure(0, weight=1)
        stock_frame.grid_columnconfigure(0, weight=1)

        # 保存相关信息
        self.stock_notebooks[stock_key] = {
            "frame": stock_frame,
            "tree": tree,
            "stock_code": stock_key.split(" - ")[0],
            "stock_name": stock_key.split(" - ")[1]
        }

        # 绑定双击事件编辑备注
        tree.bind('<Double-Button-1>', self.on_double_click)
        # 绑定右键菜单
        tree.bind('<Button-3>', self.show_context_menu)

    def create_context_menu(self, frame, stock_key):
        """创建右键菜单"""
        menu = tk.Menu(frame, tearoff=0)
        menu.add_command(label="删除该股票的所有记录",
                         command=lambda: self.delete_all_stock_records(stock_key))
        menu.add_command(label="打印该股票记录",
                         command=lambda: self.print_current_stock_records("html"))
        frame.menu = menu

    def show_context_menu(self, event):
        """显示右键菜单"""
        # 获取当前选中的标签页
        current_tab = self.notebook.select()

        # 查找对应的股票key
        for key, data in self.stock_notebooks.items():
            if self.notebook.tab(current_tab, "text") == f"&#128202; {key}":
                if hasattr(data["frame"], 'menu'):
                    data["frame"].menu.post(event.x_root, event.y_root)
                break

    def delete_all_stock_records(self, stock_key):
        """删除指定股票的所有记录"""
        # 确认删除
        result = messagebox.askyesno(
            "确认删除",
            f"确定要删除股票 [{stock_key}] 的所有交易记录吗?\n此操作不可恢复!",
            icon='warning'
        )

        if not result:
            return

        # 删除该股票的所有记录
        stock_code = stock_key.split(" - ")[0]
        stock_name = stock_key.split(" - ")[1]

        # 过滤掉该股票的所有记录
        self.records = [r for r in self.records
                        if not (r['stock_code'] == stock_code and r['stock_name'] == stock_name)]

        # 删除该股票的当前股价记录
        if stock_key in self.current_prices:
            del self.current_prices[stock_key]

        # 重新编号
        for i, record in enumerate(self.records):
            record["id"] = i + 1

        # 保存数据
        self.save_records()
        self.save_current_prices()

        # 刷新显示
        self.refresh_display()

        messagebox.showinfo("成功", f"股票 [{stock_key}] 的所有记录已删除!")

    def on_double_click(self, event):
        """双击表格行编辑备注"""
        # 获取当前选中的标签页
        current_tab = self.notebook.select()

        # 查找对应的树形控件和股票key
        tree = None
        stock_key = None
        for key, data in self.stock_notebooks.items():
            if self.notebook.tab(current_tab, "text") == f"&#128202; {key}":
                tree = data["tree"]
                stock_key = key
                break

        if not tree:
            return

        # 获取选中的行
        selection = tree.selection()
        if not selection:
            return

        # 获取记录序号
        item = tree.item(selection[0])
        sequence_num = int(item['values'][0])

        # 查找该股票的所有记录,按时间排序后找到对应的记录
        stock_records = [r for r in self.records
                         if f"{r['stock_code']} - {r['stock_name']}" == stock_key]
        # 按交易时间排序
        stock_records.sort(key=lambda x: x['trade_date'])

        if sequence_num <= len(stock_records):
            record = stock_records[sequence_num - 1]
            self.edit_note_for_record(record)

    def edit_note_for_record(self, record):
        """编辑指定记录的备注"""
        dialog = tk.Toplevel(self.root)
        dialog.title("编辑备注")
        dialog.geometry("400x250")
        dialog.transient(self.root)
        dialog.grab_set()

        # 创建输入框
        ttk.Label(dialog, text=f"股票: {record['stock_name']} ({record['stock_code']})",
                  font=self.normal_font).pack(pady=10)
        ttk.Label(dialog, text=f"交易类型: {'买入' if record['trade_type'] == 'buy' else '卖出'}",
                  font=self.normal_font).pack(pady=5)
        ttk.Label(dialog, text="备注:", font=self.normal_font).pack(pady=5)

        note_text = tk.Text(dialog, height=5, width=50)
        note_text.pack(pady=5, padx=10)

        # 插入现有备注
        current_note = record.get('note', '')
        note_text.insert('1.0', current_note)

        def save_note():
            new_note = note_text.get('1.0', tk.END).strip()
            if new_note:
                record['note'] = new_note
            else:
                # 如果备注为空,删除note字段
                if 'note' in record:
                    del record['note']

            # 保存记录到文件
            self.save_records()
            # 刷新显示
            self.refresh_display()
            dialog.destroy()
            messagebox.showinfo("成功", "备注已保存!")

        ttk.Button(dialog, text="保存", command=save_note, width=15).pack(pady=10)

    def edit_note(self):
        """编辑备注按钮功能"""
        # 获取当前选中的标签页
        current_tab = self.notebook.select()
        tab_text = self.notebook.tab(current_tab, "text")

        # 如果是持仓标签页,提示先选择交易记录标签页
        if tab_text == "&#128188; 持仓情况":
            messagebox.showwarning("警告", "请在具体的股票交易记录标签页中选择要编辑备注的记录!")
            return

        # 查找对应的树形控件
        tree = None
        stock_key = None
        for key, data in self.stock_notebooks.items():
            if self.notebook.tab(current_tab, "text") == f"&#128202; {key}":
                tree = data["tree"]
                stock_key = key
                break

        if not tree:
            return

        # 获取选中的行
        selection = tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择要编辑备注的交易记录!")
            return

        # 获取记录序号
        item = tree.item(selection[0])
        sequence_num = int(item['values'][0])

        # 查找该股票的所有记录,按时间排序后找到对应的记录
        stock_records = [r for r in self.records
                         if f"{r['stock_code']} - {r['stock_name']}" == stock_key]
        # 按交易时间排序
        stock_records.sort(key=lambda x: x['trade_date'])

        if sequence_num <= len(stock_records):
            record = stock_records[sequence_num - 1]
            self.edit_note_for_record(record)

    def create_positions_table(self):
        """创建持仓表格"""
        # 创建主框架
        positions_main_frame = ttk.Frame(self.positions_frame)
        positions_main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 当前股价输入区域
        price_input_frame = ttk.LabelFrame(positions_main_frame, text="当前股价设置", padding=5)
        price_input_frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Label(price_input_frame, text="选择股票:").pack(side=tk.LEFT, padx=5)
        self.stock_selector = ttk.Combobox(price_input_frame, width=20)
        self.stock_selector.pack(side=tk.LEFT, padx=5)
        self.stock_selector.bind('<<ComboboxSelected>>', self.on_stock_select)

        ttk.Label(price_input_frame, text="当前股价:").pack(side=tk.LEFT, padx=5)
        self.current_price_entry = ttk.Entry(price_input_frame, width=15)
        self.current_price_entry.pack(side=tk.LEFT, padx=5)

        ttk.Button(price_input_frame, text="更新股价", command=self.update_current_price,
                   width=12).pack(side=tk.LEFT, padx=5)

        ttk.Button(price_input_frame, text="批量更新", command=self.batch_update_prices,
                   width=12).pack(side=tk.LEFT, padx=5)

        # 持仓表格
        table_frame = ttk.Frame(positions_main_frame)
        table_frame.pack(fill=tk.BOTH, expand=True)

        columns = ("代码", "名称", "持仓数量", "平均成本", "当前股价", "持仓成本", "当前市值", "浮动盈亏", "盈亏比例")
        self.positions_tree = ttk.Treeview(table_frame, columns=columns, show="headings", height=18)

        # 设置列标题和宽度
        col_widths = [100, 120, 100, 100, 100, 120, 120, 120, 100]
        col_titles = ["代码", "名称", "持仓数量", "平均成本", "当前股价", "持仓成本", "当前市值", "浮动盈亏",
                      "盈亏比例"]
        for col, width, title in zip(columns, col_widths, col_titles):
            self.positions_tree.heading(col, text=title)
            self.positions_tree.column(col, width=width, anchor=tk.CENTER)

        # 添加滚动条
        vsb = ttk.Scrollbar(table_frame, orient="vertical", command=self.positions_tree.yview)
        hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=self.positions_tree.xview)
        self.positions_tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

        # 布局
        self.positions_tree.grid(row=0, column=0, sticky="nsew")
        vsb.grid(row=0, column=1, sticky="ns")
        hsb.grid(row=1, column=0, sticky="ew")

        table_frame.grid_rowconfigure(0, weight=1)
        table_frame.grid_columnconfigure(0, weight=1)

        # 添加右键菜单到持仓表格
        self.create_positions_context_menu()
        self.positions_tree.bind('<Button-3>', self.show_positions_context_menu)

        # 绑定双击事件查看详细记录
        self.positions_tree.bind('<Double-Button-1>', self.show_stock_records)

    def create_positions_context_menu(self):
        """创建持仓表格右键菜单"""
        self.positions_menu = tk.Menu(self.positions_frame, tearoff=0)
        self.positions_menu.add_command(label="删除该股票的所有记录",
                                        command=self.delete_selected_stock_from_positions)
        self.positions_menu.add_command(label="打印持仓报告",
                                        command=lambda: self.print_positions("html"))

    def show_positions_context_menu(self, event):
        """显示持仓表格右键菜单"""
        # 获取选中的行
        selection = self.positions_tree.selection()
        if selection:
            self.positions_menu.post(event.x_root, event.y_root)

    def delete_selected_stock_from_positions(self):
        """从持仓表格删除选中的股票"""
        selection = self.positions_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择要删除的股票!")
            return

        item = self.positions_tree.item(selection[0])
        stock_code = item['values'][0]
        stock_name = item['values'][1]
        stock_key = f"{stock_code} - {stock_name}"

        self.delete_all_stock_records(stock_key)

    def load_current_prices(self):
        """加载当前股价"""
        if os.path.exists(self.current_prices_file):
            try:
                with open(self.current_prices_file, 'r', encoding='utf-8') as f:
                    self.current_prices = json.load(f)
            except:
                self.current_prices = {}

    def save_current_prices(self):
        """保存当前股价"""
        try:
            with open(self.current_prices_file, 'w', encoding='utf-8') as f:
                json.dump(self.current_prices, f, ensure_ascii=False, indent=2)
        except:
            pass

    def update_stock_selector(self):
        """更新股票选择器"""
        holdings, _ = self.calculate_positions()
        stock_list = [f"{h['stock_code']} - {h['stock_name']}" for h in holdings]
        self.stock_selector['values'] = stock_list
        if stock_list:
            self.stock_selector.set(stock_list[0])
            self.update_current_price_display()

    def update_current_price_display(self):
        """更新当前股价显示"""
        selected = self.stock_selector.get()
        if selected and selected in self.current_prices:
            self.current_price_entry.delete(0, tk.END)
            self.current_price_entry.insert(0, str(self.current_prices[selected]))
        else:
            self.current_price_entry.delete(0, tk.END)

    def on_stock_select(self, event):
        """股票选择事件"""
        self.update_current_price_display()

    def update_current_price(self):
        """更新当前股价"""
        selected = self.stock_selector.get()
        if not selected:
            messagebox.showwarning("警告", "请选择股票!")
            return

        try:
            current_price = float(self.current_price_entry.get())
            if current_price <= 0:
                messagebox.showerror("错误", "股价必须大于0!")
                return

            self.current_prices[selected] = current_price
            self.save_current_prices()
            self.refresh_positions_table()
            messagebox.showinfo("成功", f"{selected} 当前股价已更新为 &#165;{current_price:.2f}")
        except ValueError:
            messagebox.showerror("错误", "请输入有效的股价!")

    def batch_update_prices(self):
        """批量更新股价"""
        holdings, _ = self.calculate_positions()
        if not holdings:
            messagebox.showwarning("警告", "暂无持仓!")
            return

        # 创建批量更新窗口
        batch_window = tk.Toplevel(self.root)
        batch_window.title("批量更新股价")
        batch_window.geometry("400x500")

        # 创建滚动框架
        canvas = tk.Canvas(batch_window)
        scrollbar = ttk.Scrollbar(batch_window, orient="vertical", command=canvas.yview)
        scrollable_frame = ttk.Frame(canvas)

        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )

        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        # 创建输入框
        price_entries = {}
        for i, holding in enumerate(holdings):
            stock_key = f"{holding['stock_code']} - {holding['stock_name']}"
            frame = ttk.Frame(scrollable_frame)
            frame.pack(fill=tk.X, padx=10, pady=5)

            ttk.Label(frame, text=stock_key, width=25).pack(side=tk.LEFT, padx=5)

            current_price = self.current_prices.get(stock_key, holding['avg_cost'])
            entry = ttk.Entry(frame, width=15)
            entry.insert(0, str(current_price))
            entry.pack(side=tk.LEFT, padx=5)

            price_entries[stock_key] = entry

        def save_batch_prices():
            for stock_key, entry in price_entries.items():
                try:
                    price = float(entry.get())
                    if price > 0:
                        self.current_prices[stock_key] = price
                except:
                    pass

            self.save_current_prices()
            self.refresh_positions_table()
            batch_window.destroy()
            messagebox.showinfo("成功", "批量更新完成!")

        ttk.Button(scrollable_frame, text="保存所有", command=save_batch_prices,
                   width=20).pack(pady=20)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

    def show_stock_records(self, event):
        """双击持仓显示该股票的详细交易记录"""
        selection = self.positions_tree.selection()
        if selection:
            item = self.positions_tree.item(selection[0])
            stock_name = item['values'][1]
            stock_code = item['values'][0]
            stock_key = f"{stock_code} - {stock_name}"

            # 切换到对应的标签页
            if stock_key in self.stock_notebooks:
                # 查找标签页索引
                for i, tab_id in enumerate(self.notebook.tabs()):
                    if self.notebook.tab(tab_id, "text") == f"&#128202; {stock_key}":
                        self.notebook.select(i)
                        break

    def load_records(self):
        """加载交易记录"""
        if os.path.exists(self.data_file):
            try:
                with open(self.data_file, 'r', encoding='utf-8') as f:
                    self.records = json.load(f)
            except:
                self.records = []

    def save_records(self):
        """保存交易记录"""
        try:
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(self.records, f, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("错误", f"保存失败:{str(e)}")

    def add_buy_record(self):
        """添加买入记录"""
        self.add_record('buy')

    def add_sell_record(self):
        """添加卖出记录"""
        self.add_record('sell')

    def add_record(self, trade_type):
        """添加交易记录"""
        # 获取输入值
        stock_code = self.stock_code_entry.get().strip()
        stock_name = self.stock_name_entry.get().strip()

        try:
            quantity = float(self.quantity_entry.get())
            price = float(self.price_entry.get())
            amount = quantity * price
        except ValueError:
            messagebox.showerror("错误", "数量和价格必须是数字!")
            return

        trade_date = self.time_var.get()
        note = self.note_entry.get().strip()

        # 验证输入
        if not stock_code or not stock_name:
            messagebox.showerror("错误", "请输入股票代码和名称!")
            return

        if quantity <= 0 or price <= 0:
            messagebox.showerror("错误", "数量和价格必须大于0!")
            return

        # 创建记录(使用全局ID,但显示时会按股票重新编号)
        record = {
            "id": len(self.records) + 1,
            "stock_code": stock_code,
            "stock_name": stock_name,
            "trade_type": trade_type,
            "quantity": quantity,
            "price": price,  # 成交价
            "amount": round(amount, 2),  # 成交金额
            "trade_date": trade_date
        }

        # 添加备注(如果有)
        if note:
            record["note"] = note

        self.records.append(record)
        self.save_records()

        # 清空输入框
        self.stock_code_entry.delete(0, tk.END)
        self.stock_name_entry.delete(0, tk.END)
        self.quantity_entry.delete(0, tk.END)
        self.price_entry.delete(0, tk.END)
        self.note_entry.delete(0, tk.END)
        self.time_var.set(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

        # 刷新显示
        self.refresh_display()

        trade_type_text = "买入" if trade_type == 'buy' else "卖出"
        messagebox.showinfo("成功", f"{trade_type_text}记录添加成功!")

    def delete_record(self):
        """删除选中的记录"""
        # 获取当前选中的标签页
        current_tab = self.notebook.select()
        tab_text = self.notebook.tab(current_tab, "text")

        # 如果是持仓标签页,提示先选择交易记录标签页
        if tab_text == "&#128188; 持仓情况":
            messagebox.showwarning("警告", "请在具体的股票交易记录标签页中选择要删除的记录!")
            return

        # 查找对应的树形控件和股票key
        tree = None
        stock_key = None
        for key, data in self.stock_notebooks.items():
            if self.notebook.tab(current_tab, "text") == f"&#128202; {key}":
                tree = data["tree"]
                stock_key = key
                break

        if not tree:
            return

        selected = tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择要删除的记录!")
            return

        if messagebox.askyesno("确认", "确定要删除选中的记录吗?"):
            # 获取记录序号
            item = tree.item(selected[0])
            sequence_num = int(item['values'][0])

            # 查找该股票的所有记录,按时间排序后找到对应的记录
            stock_records = [r for r in self.records
                             if f"{r['stock_code']} - {r['stock_name']}" == stock_key]
            # 按交易时间排序
            stock_records.sort(key=lambda x: x['trade_date'])

            if sequence_num <= len(stock_records):
                record_to_delete = stock_records[sequence_num - 1]

                # 删除记录
                for i, record in enumerate(self.records):
                    if record["id"] == record_to_delete["id"]:
                        self.records.pop(i)
                        break

            self.save_records()
            self.refresh_display()
            messagebox.showinfo("成功", "记录已删除!")

    def calculate_positions(self):
        """计算持仓 - 卖出时直接从总成本中减去卖出金额"""
        positions = {}

        # 按股票分组
        for record in self.records:
            key = f"{record['stock_code']} - {record['stock_name']}"

            if key not in positions:
                positions[key] = {
                    "stock_code": record['stock_code'],
                    "stock_name": record['stock_name'],
                    "transactions": []
                }

            positions[key]["transactions"].append(record)

        # 计算每只股票的持仓和平均成本
        holdings = []
        for key, pos in positions.items():
            # 按时间顺序排序交易记录
            sorted_transactions = sorted(pos["transactions"], key=lambda x: x['trade_date'])

            # 初始化变量
            current_quantity = 0  # 当前持仓数量
            current_cost = 0  # 当前持仓成本

            # 逐笔交易计算
            for transaction in sorted_transactions:
                if transaction['trade_type'] == 'buy':
                    # 买入:增加持仓数量和成本
                    current_quantity += transaction['quantity']
                    current_cost += transaction['amount']

                else:  # 卖出
                    sell_quantity = transaction['quantity']
                    sell_amount = transaction['amount']

                    if current_quantity >= sell_quantity:
                        # 卖出:直接减去卖出金额
                        current_cost -= sell_amount
                        current_quantity -= sell_quantity

                        # 如果全部卖出,成本归零
                        if current_quantity == 0:
                            current_cost = 0
                    else:
                        # 卖出数量超过持仓(理论上不应该发生)
                        current_quantity = 0
                        current_cost = 0

            # 只显示有持仓的股票
            if current_quantity > 0:
                avg_cost = current_cost / current_quantity if current_quantity > 0 else 0
                holdings.append({
                    "stock_code": pos["stock_code"],
                    "stock_name": pos["stock_name"],
                    "quantity": current_quantity,
                    "avg_cost": round(avg_cost, 2),
                    "total_cost": round(current_cost, 2),
                    "stock_key": key
                })

        return holdings, positions

    def update_stats(self):
        """更新统计信息"""
        if not self.records:
            self.stats_text.delete(1.0, tk.END)
            self.stats_text.insert(1.0, "暂无交易数据")
            return

        total_buy_amount = sum(r['amount'] for r in self.records if r['trade_type'] == 'buy')
        total_sell_amount = sum(r['amount'] for r in self.records if r['trade_type'] == 'sell')
        total_trades = len(self.records)
        holdings, _ = self.calculate_positions()
        total_position_value = sum(h['total_cost'] for h in holdings)

        # 计算总浮动盈亏
        total_profit_loss = 0
        for holding in holdings:
            stock_key = f"{holding['stock_code']} - {holding['stock_name']}"
            current_price = self.current_prices.get(stock_key, holding['avg_cost'])
            current_value = current_price * holding['quantity']
            profit_loss = current_value - holding['total_cost']
            total_profit_loss += profit_loss

        stats = f"总买入金额: &#165;{total_buy_amount:,.2f}\n"
        stats += f"总卖出金额: &#165;{total_sell_amount:,.2f}\n"
        stats += f"交易次数: {total_trades}\n"
        stats += f"当前持仓数: {len(holdings)}\n"
        stats += f"持仓成本: &#165;{total_position_value:,.2f}\n"
        stats += f"浮动盈亏: "
        if total_profit_loss >= 0:
            stats += f"&#165;{total_profit_loss:,.2f} (盈利)\n"
        else:
            stats += f"&#165;{total_profit_loss:,.2f} (亏损)\n"

        self.stats_text.delete(1.0, tk.END)
        self.stats_text.insert(1.0, stats)

    def refresh_display(self):
        """刷新所有显示"""
        self.refresh_stock_tabs()
        self.refresh_positions_table()
        self.update_stats()
        self.update_stock_selector()

    def refresh_stock_tabs(self):
        """刷新各股票的交易记录标签页"""
        # 获取所有有交易的股票
        stocks_with_records = set()
        for record in self.records:
            stock_key = f"{record['stock_code']} - {record['stock_name']}"
            stocks_with_records.add(stock_key)

        # 删除不再需要的标签页
        for stock_key in list(self.stock_notebooks.keys()):
            if stock_key not in stocks_with_records:
                # 移除标签页
                tab_data = self.stock_notebooks[stock_key]
                self.notebook.forget(tab_data["frame"])
                del self.stock_notebooks[stock_key]

        # 为每个股票创建或更新标签页
        for stock_key in stocks_with_records:
            if stock_key not in self.stock_notebooks:
                self.create_stock_records_tab(stock_key)

            # 更新表格数据
            self.refresh_stock_table(stock_key)

    def refresh_stock_table(self, stock_key):
        """刷新指定股票的表格,计算持仓数量"""
        if stock_key not in self.stock_notebooks:
            return

        tree = self.stock_notebooks[stock_key]["tree"]

        # 清空现有数据
        for item in tree.get_children():
            tree.delete(item)

        # 获取该股票的所有交易记录
        stock_records = [r for r in self.records
                         if f"{r['stock_code']} - {r['stock_name']}" == stock_key]

        # 按交易时间排序
        stock_records.sort(key=lambda x: x['trade_date'])

        # 计算每次交易后的持仓数量(使用相同逻辑)
        current_quantity = 0
        current_cost = 0
        records_with_holding = []

        for record in stock_records:
            if record['trade_type'] == 'buy':
                # 买入:增加持仓数量和成本
                current_quantity += record['quantity']
                current_cost += record['amount']

            else:  # 卖出
                sell_quantity = record['quantity']
                sell_amount = record['amount']

                if current_quantity >= sell_quantity:
                    # 卖出:直接减去卖出金额
                    current_cost -= sell_amount
                    current_quantity -= sell_quantity
                    if current_quantity == 0:
                        current_cost = 0
                else:
                    current_quantity = 0
                    current_cost = 0

            records_with_holding.append({
                'record': record,
                'holding_quantity': current_quantity
            })

        # 添加数据到表格,序号从1开始递增
        for idx, item_data in enumerate(records_with_holding, start=1):
            record = item_data['record']
            holding_qty = item_data['holding_quantity']

            trade_type = "买入" if record['trade_type'] == 'buy' else "卖出"

            # 格式化显示
            if record['trade_type'] == 'buy':
                trade_type_display = f"买入"
            else:
                trade_type_display = f"卖出"

            # 格式化持仓数量
            holding_qty_display = int(holding_qty) if holding_qty.is_integer() else holding_qty

            # 获取备注
            note = record.get('note', '')

            values = (
                idx,  # 使用股票内部的序号
                record["trade_date"],
                trade_type_display,
                int(record["quantity"]) if record["quantity"].is_integer() else record["quantity"],
                f"&#165;{record['price']:.2f}",
                f"&#165;{record['amount']:,.2f}",
                holding_qty_display,
                note
            )

            item = tree.insert("", tk.END, values=values)

            # 设置行样式:先设置文字颜色,如果有备注再添加黄色背景
            if record['trade_type'] == 'buy':
                # 买入:红色字体
                if note:
                    # 有备注:黄色背景 + 红色字体
                    tree.tag_configure('buy_with_note', foreground='#ff4444', background='#ffff99')
                    tree.item(item, tags=('buy_with_note',))
                else:
                    # 无备注:红色字体
                    tree.tag_configure('buy', foreground='#ff4444')
                    tree.item(item, tags=('buy',))
            else:
                # 卖出:蓝色字体
                if note:
                    # 有备注:黄色背景 + 蓝色字体
                    tree.tag_configure('sell_with_note', foreground='#4444ff', background='#ffff99')
                    tree.item(item, tags=('sell_with_note',))
                else:
                    # 无备注:蓝色字体
                    tree.tag_configure('sell', foreground='#4444ff')
                    tree.item(item, tags=('sell',))

    def refresh_positions_table(self):
        """刷新持仓表格"""
        # 清空现有数据
        for item in self.positions_tree.get_children():
            self.positions_tree.delete(item)

        # 计算持仓
        holdings, _ = self.calculate_positions()

        # 添加数据
        for holding in holdings:
            stock_key = f"{holding['stock_code']} - {holding['stock_name']}"
            current_price = self.current_prices.get(stock_key, holding['avg_cost'])
            current_value = current_price * holding['quantity']
            profit_loss = current_value - holding['total_cost']
            profit_loss_percent = (profit_loss / holding['total_cost']) * 100 if holding['total_cost'] != 0 else 0

            # 设置盈亏颜色标记(红色盈利,绿色亏损)
            profit_loss_text = f"&#165;{profit_loss:,.2f}"
            if profit_loss > 0:
                profit_loss_text = f"&#128308; &#165;{profit_loss:,.2f}"  # 红色表示盈利
            elif profit_loss < 0:
                profit_loss_text = f"&#128994; &#165;{profit_loss:,.2f}"  # 绿色表示亏损

            percent_text = f"{profit_loss_percent:+.2f}%"
            if profit_loss_percent > 0:
                percent_text = f"&#128308; {profit_loss_percent:+.2f}%"  # 红色表示盈利
            elif profit_loss_percent < 0:
                percent_text = f"&#128994; {profit_loss_percent:+.2f}%"  # 绿色表示亏损

            # 平均成本可能为负数,正常显示
            if holding['avg_cost'] >= 0:
                avg_cost_display = f"&#165;{holding['avg_cost']:.2f}"
            else:
                avg_cost_display = f"-&#165;{abs(holding['avg_cost']):.2f}"

            values = (
                holding["stock_code"],
                holding["stock_name"],
                int(holding["quantity"]) if holding["quantity"].is_integer() else holding["quantity"],
                avg_cost_display,
                f"&#165;{current_price:.2f}",
                f"&#165;{holding['total_cost']:,.2f}",
                f"&#165;{current_value:,.2f}",
                profit_loss_text,
                percent_text
            )

            item = self.positions_tree.insert("", tk.END, values=values)

            # 设置行颜色(红色盈利,绿色亏损)
            if profit_loss > 0:
                self.positions_tree.tag_configure('profit', foreground='#ff4444')  # 盈利红色
                self.positions_tree.item(item, tags=('profit',))
            elif profit_loss < 0:
                self.positions_tree.tag_configure('loss', foreground='#00cc00')  # 亏损绿色
                self.positions_tree.item(item, tags=('loss',))


def main():
    root = tk.Tk()
    app = StockTradingGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
aag233 发表于 2026-5-13 00:25
我觉得不连接网络很麻烦,我的建议是你输入股票代码之后直接就连接网络而且获得实时数据,还有一个问题就是自己输入那个价格那个点位很麻烦你一次两次你是可以接受的但是如果你频繁交易,而且还要做各种分析你就会觉得这个特别麻烦股票一多尤其是,所以我的建议就是你可以保留你手动输入这个但是同时还要自动获取价格。
沙发
Rachel筠 发表于 2026-5-12 21:42
3#
白日依山尽 发表于 2026-5-12 22:01
4#
mw26973 发表于 2026-5-12 22:12
厉害,感谢分享,感谢大佬!
5#
dxgw123 发表于 2026-5-12 22:19
厉害,感谢分享,感谢老铁
6#
jtjt68 发表于 2026-5-12 22:28
咋回事、怎么修改
7#
wei363516609 发表于 2026-5-12 22:33
看着还不错
8#
kindbigbear0 发表于 2026-5-12 22:36
建议加入备注:买入理由/卖出理由。方便进行自我剖析,保持交易一致性,反省非计划交易行为。
9#
忘情的城市 发表于 2026-5-12 22:39
感觉没什么特色,用EXCEL电子表格做区别也不大
表1做个检索,表2录入数据,
10#
gallon 发表于 2026-5-12 23:13
能看实时价格与量化指标吗
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-13 02:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表