[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="🟡 买入记录", command=self.add_buy_record,
width=20, style="Buy.TButton").grid(row=7, column=0, columnspan=2, pady=10)
ttk.Button(left_frame, text="🔵 卖出记录", command=self.add_sell_record,
width=20, style="Sell.TButton").grid(row=8, column=0, columnspan=2, pady=5)
ttk.Button(left_frame, text="🗑️ 删除选定记录", command=self.delete_record,
width=20).grid(row=9, column=0, columnspan=2, pady=5)
ttk.Button(left_frame, text="✏️ 编辑备注", command=self.edit_note,
width=20).grid(row=10, column=0, columnspan=2, pady=5)
ttk.Button(left_frame, text="💾 保存数据", command=self.save_records,
width=20).grid(row=11, column=0, columnspan=2, pady=5)
ttk.Button(left_frame, text="🖨️ 打印记录", 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="💼 持仓情况")
# 创建持仓表格
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 == "💼 持仓情况":
messagebox.showwarning("警告", "请先在股票交易记录标签页中选择要打印的股票!")
return
# 查找对应的股票key
stock_key = None
for key, data in self.stock_notebooks.items():
if self.notebook.tab(current_tab, "text") == f"📊 {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>¥{record['price']:.2f}</td>
<td>¥{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>总买入金额: ¥{total_buy_amount:,.2f}</p>
<p>总卖出金额: ¥{total_sell_amount:,.2f}</p>
<p>总交易次数: {len(self.records)}</p>
<p>当前持仓数: {len(holdings)}</p>
<p>持仓成本: ¥{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>¥{record['price']:.2f}</td>
<td>¥{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"¥{holding['avg_cost']:.2f}" if holding[
'avg_cost'] >= 0 else f"-¥{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>¥{current_price:.2f}</td>
<td>¥{holding['total_cost']:,.2f}</td>
<td>¥{current_value:,.2f}</td>
<td>¥{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"¥{record['price']:<9.2f}¥{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"总买入金额: ¥{total_buy_amount:,.2f}\n")
f.write(f"总卖出金额: ¥{total_sell_amount:,.2f}\n")
f.write(f"总交易次数: {len(self.records)}\n")
f.write(f"当前持仓数: {len(holdings)}\n")
f.write(f"持仓成本: ¥{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"¥{record['price']:<9.2f}¥{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"¥{holding['avg_cost']:.2f}" if holding[
'avg_cost'] >= 0 else f"-¥{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}¥{current_price:<11.2f}"
f"¥{holding['total_cost']:<14,.2f}¥{current_value:<14,.2f}"
f"¥{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"📊 {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"📊 {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"📊 {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 == "💼 持仓情况":
messagebox.showwarning("警告", "请在具体的股票交易记录标签页中选择要编辑备注的记录!")
return
# 查找对应的树形控件
tree = None
stock_key = None
for key, data in self.stock_notebooks.items():
if self.notebook.tab(current_tab, "text") == f"📊 {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} 当前股价已更新为 ¥{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"📊 {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 == "💼 持仓情况":
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"📊 {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"总买入金额: ¥{total_buy_amount:,.2f}\n"
stats += f"总卖出金额: ¥{total_sell_amount:,.2f}\n"
stats += f"交易次数: {total_trades}\n"
stats += f"当前持仓数: {len(holdings)}\n"
stats += f"持仓成本: ¥{total_position_value:,.2f}\n"
stats += f"浮动盈亏: "
if total_profit_loss >= 0:
stats += f"¥{total_profit_loss:,.2f} (盈利)\n"
else:
stats += f"¥{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"¥{record['price']:.2f}",
f"¥{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"¥{profit_loss:,.2f}"
if profit_loss > 0:
profit_loss_text = f"🔴 ¥{profit_loss:,.2f}" # 红色表示盈利
elif profit_loss < 0:
profit_loss_text = f"🟢 ¥{profit_loss:,.2f}" # 绿色表示亏损
percent_text = f"{profit_loss_percent:+.2f}%"
if profit_loss_percent > 0:
percent_text = f"🔴 {profit_loss_percent:+.2f}%" # 红色表示盈利
elif profit_loss_percent < 0:
percent_text = f"🟢 {profit_loss_percent:+.2f}%" # 绿色表示亏损
# 平均成本可能为负数,正常显示
if holding['avg_cost'] >= 0:
avg_cost_display = f"¥{holding['avg_cost']:.2f}"
else:
avg_cost_display = f"-¥{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"¥{current_price:.2f}",
f"¥{holding['total_cost']:,.2f}",
f"¥{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()