用Python构建交易日志:从随手记录到量化复盘

发布时间:2026/8/31 3:53:49
用Python构建交易日志:从随手记录到量化复盘 这条交易记录看起来很像盘中的随手备忘录早盘开盘后价格沉在水下卖出部分仓位换成网约车概念股最后补一句“还好没太多要不然又没了”。用长期复盘的眼光看这句话其实压缩了大量信息时间是早盘位置是水下动作是卖出和换股结果是不满意但可承受。问题在于如果每次复盘都停留在这种情绪化表达就很难回答三个关键问题这笔交易赚钱或亏钱的原因是什么当时买入和卖出的依据是什么同样的场景下次应该怎么处理。这篇文章会把这条记录当作一个起点演示如何把口语化的交易感想改造成结构化交易日志再用 Python 进行基础统计。你会看到一种可复用的复盘流程先拆解交易记录中的关键动作再为记录设计数据模型接着用 pandas 计算胜率、盈亏比和总盈亏最后把复盘结论落成可执行的交易规则。整个过程不指向任何具体股票也不提供任何买卖建议只讨论交易记录和复盘方法本身。注意股票交易存在真实风险本文只讨论复盘工具和方法论不构成投资建议。任何规则、检查清单和统计数据都不能替代独立的风险判断。1. 从一条交易记录拆出关键信息1.1 把口语记录翻译成结构化字段先看原始记录里出现了哪些信息“早盘开盘”——交易发生的时段可能是 9:30 到 10:00 左右。“水下”——当前价格低于买入成本或者低于当日开盘价。“走了一些”——卖出了一部分仓位不是全部清仓。“换成了网约车”——卖出后买入了一个新的概念板块相关标的。“还好没太多”——买入时的仓位不高或者整个账户暴露比例不大。“要不然又没了”——如果仓位大亏得会更多隐含了对风险后果的后怕。如果要把这条记录放进表格至少可以抽出这些字段字段示例值说明交易日期2025-06-18记录发生日期交易时段早盘可细分到具体时间原持仓方向卖出减仓还是清仓卖出原因价格水下触发风控止损、减仓、调仓买入方向网约车概念板块不一定是具体个股仓位占比少量可记录实际金额或比例结果评价避免更大回撤主观但有用情绪状态庆幸需要后续跟踪是否影响决策这一步看起来简单但它决定了后续能否做统计。很多时候交易者不是不会复盘而是复盘时只写“今天亏了市场不好”或“换了个票还好仓位低”缺少结构化字段。没有结构化字段历史数据就无法聚合也就谈不上量化自己的交易行为。1.2 “水下”和“换仓”背后的交易决策模型“水下”不是一个严谨的量化指标。它可能指股价低于成本价账户显示浮亏。股价低于当日开盘价分时图上在水下运行。股价低于关键均线技术形态走弱。同一个词在不同交易者那里含义不一样。所以在设计交易日志时不能只写“水下”要写清楚当时的参照标准例如“跌破 5 日均线”“低于成本价 3%”“早盘 30 分钟内未能翻红”。这样才能在复盘时判断当初设置的止损条件是价格触发还是情绪触发。“换仓”意味着两个动作同时发生卖出旧标的买入新标的。但换仓并不天然等于优化。换仓是否合理要看卖出逻辑和买入逻辑是否独立成立。如果卖出是因为“这只不行了”买入是因为“那只涨得好”那么两次决策都可能处于情绪高波动状态。复盘时要特别标记这类“调仓型交易”因为它们最容易掩盖亏损原因。1.3 为什么复盘记录不能只有“还好”“还好没太多”反映的是一种结果导向的侥幸。但复盘要关注的不只是结果而是决策过程。如果这次真的避免了更大的亏损就要追问为什么当初买入时只买了一点是因为有计划地控制仓位还是因为犹豫、没敢买如果买入时就知道风险很大为什么还要买如果卖出后它继续上涨这个“换仓”动作会不会变成一次踏空这些问题很难靠一条碎碎念式记录回答。把记录结构化之后可以把同类型操作放到一起比较多少次“水下卖出”事后是正确的多少次是错误的平均仓位是多少正确和错误操作之间有没有共性这才是复盘的价值。2. 先建立交易日志的数据模型2.1 一张交易记录表需要哪些字段从实用角度出发建议先建一张交易流水表。字段不用太多但每个字段都必须有明确用途。下面是一组适合个人交易者使用的最小字段集。字段名示例用途trade_time2025-06-18 09:45:00精确到分钟便于按时间排序stock_code600000唯一识别标的stock_name示例股份辅助阅读directionbuy / sell买卖方向price10.25成交价格volume500成交数量commission5.10佣金等费用reason跌破均线止损决策原因复盘核心sector网约车板块标签用于归因position_ratio0.15仓位占比after_thought换仓时情绪紧张主观备注事后补充reason和after_thought是容易被忽略的字段但恰恰是最有价值的。reason用来记录决策时的依据after_thought用来记录事后的回顾和情绪状态。统计交易纪律是否执行到位主要靠这两个文本字段而不是靠价格数字。2.2 用 CSV 定义最小数据结构在还没有搭建数据库之前先用 CSV 落地最直接。CSV 的好处是结构简单、可以被 Excel 打开、也可以被 pandas 直接读取。下面是一个最小示例的头部。trade_time,stock_code,stock_name,direction,price,volume,commission,reason,sector,position_ratio,after_thought 2025-06-18 09:32:00,600000,示例股份,sell,10.20,500,5.10,跌破均线止损,银行,0.20,执行了预设止损 2025-06-18 09:35:00,300000,示例网约车,buy,12.50,400,5.20,早盘板块走强计划内建仓,网约车,0.12,仓位较轻反弹明显这段 CSV 里有两个关键点。第一原因是“跌破均线止损”而不是“感觉要跌”第二买入原因是“计划内建仓”而不是“追热点”。真实记录中很多原因并不会这么清晰但有了字段栏位就会倒逼自己写清楚。2.3 用 Excel 或 Google Sheets 记录两张表交易日志不建议只建一张流水表还建议加一张持仓快照表。持仓快照表记录每天收盘后的持仓状态用于计算账户层面的盈亏避免只关注单笔交易。日期股票代码持股数量当日收盘价累计成本市值2025-06-18600000010.15002025-06-1830000040012.8050005120快照表不一定每天都要手工更新也可以每周更新一次。它的作用是让复盘从“单笔交易赢没赢”扩展到“账户整体回撤发生在哪个阶段”。2.4 记录时最容易缺失的信息实际复盘最常缺的不是价格和数量而是“当时为什么这么做”。价格可以从券商软件的历史成交里拉出来但决策原因不会自动生成。建议在每笔交易结束后 24 小时内补充一次reason和after_thought避免隔太久之后把真实的决策动机遗忘或美化。另一个容易缺的是仓位占比。很多人只记买了多少股不记这笔占账户总资金的比例。少了position_ratio就无法判断“这次回撤了 10%”对整体账户的影响到底是 1% 还是 5%也无法准确评估仓位管理是否达标。3. 用 Python 分析交易记录告别“感觉型复盘”3.1 环境准备推荐使用 Python 3.10 以上版本配合 pandas 和 Jupyter Notebook。首次使用前安装依赖。pip install pandas jupyter如果平时用其他虚拟环境或 conda也可以按自己的环境管理方式安装。下面示例代码中的路径和字段名要结合自己的 CSV 文件调整。3.2 读取和清洗数据先把 CSV 读进来统一时间列类型并按时间排序。import pandas as pd df pd.read_csv(trades.csv, encodingutf-8-sig) df[trade_time] pd.to_datetime(df[trade_time]) df df.sort_values(trade_time).reset_index(dropTrue) print(df.head())这里用encodingutf-8-sig是因为很多从 Excel 另存为的 CSV 会带 BOM直接读取时列表头会出现乱码。使用 pandas 时时间列尽量统一成datetime类型否则后面排序、分组会得到错误顺序。3.3 用简化版 FIFO 配对计算已实现盈亏对个人交易者来说一个够用的方法是对每只股票独立计算累计持仓量和平均成本卖出时用平均成本计算已实现盈亏。下面这段代码只用于演示核心逻辑实际项目中还要考虑分红、送股、手续费税种和成交滑点。import pandas as pd def calculate_realized_pnl(group): position 0 avg_cost 0.0 records [] for row in group.itertuples(): if row.direction buy: total_cost_before avg_cost * position total_cost_after total_cost_before row.price * row.volume row.commission position row.volume avg_cost total_cost_after / position if position 0 else 0 realized 0.0 else: realized (row.price - avg_cost) * row.volume - row.commission position - row.volume if position 0: position 0 records.append({ trade_time: row.trade_time, stock_code: row.stock_code, direction: row.direction, price: row.price, volume: row.volume, realized_pnl: round(realized, 2), position: position, avg_cost: round(avg_cost, 4), }) if position 0: avg_cost 0.0 return pd.DataFrame(records) result_df df.groupby(stock_code, group_keysFalse).apply(calculate_realized_pnl).reset_index(dropTrue) print(result_df)这段代码按股票代码分组每组内部保持时间递增顺序。买入时把佣金计入成本卖出时把佣金从已实现盈亏里扣掉。需要说明的是A 股卖出还会涉及印花税和过户费真实场景里应在commission字段外单独设计费用字段或者在commission里合并记录。3.4 计算胜率、盈亏比和总盈亏得到每笔交易后的realized_pnl后可以计算一组基础指标。为了获得“每笔交易”而不是“每次成交”的结果一般需要把方向为 sell 且卖出后持仓归零的一段区间视为一笔完整交易。简单起见下面按每次卖出成交产生的已实现盈亏作为统计样本。sells result_df[result_df[direction] sell].copy() total_trades len(sells) wins len(sells[sells[realized_pnl] 0]) losses len(sells[sells[realized_pnl] 0]) win_rate wins / total_trades * 100 avg_win sells[sells[realized_pnl] 0][realized_pnl].mean() avg_loss sells[sells[realized_pnl] 0][realized_pnl].mean() profit_loss_ratio abs(avg_win / avg_loss) if avg_loss else 0 total_pnl sells[realized_pnl].sum() print(f卖出成交次数: {total_trades}) print(f盈利次数: {wins}) print(f亏损次数: {losses}) print(f胜率: {win_rate:.2f}%) print(f平均盈利: {avg_win:.2f}) print(f平均亏损: {avg_loss:.2f}) print(f盈亏比: {profit_loss_ratio:.2f}) print(f总已实现盈亏: {total_pnl:.2f})输出结果示例卖出成交次数: 23 盈利次数: 9 亏损次数: 14 胜率: 39.13% 平均盈利: 385.20 平均亏损: -212.66 盈亏比: 1.81 总已实现盈亏: 482.30只看胜率39% 似乎不高但盈亏比 1.81 说明亏的时候亏得少赚的时候赚得多整体仍可能为正。这就是统计的意义单独一次操作让人感到沮丧放到更长周期里才会看到真实结构。3.5 按原因字段做归因统计数字指标之外还应该统计不同原因类型带来的盈亏分布。reason_stats sells.groupby(reason)[realized_pnl].agg([count, sum, mean]) print(reason_stats.sort_values(sum, ascendingFalse))输出结果示例count sum mean reason 跌破均线止损 8 620.0 77.50 板块轮动换仓 5 -180.0 -36.00 临时追涨 4 -450.0 -112.50 计划内建仓 3 300.0 100.00这种统计能直接揭示哪类原因在贡献亏损。例如“临时追涨”虽然次数少但平均亏损最大“跌破均线止损”虽然笔数多但总盈亏为正说明止损执行到位反而保护了账户。归因统计让复盘从纠结单笔对错转向识别系统性问题。4. 早盘交易为什么最容易冲动4.1 早盘价格波动大信息不完整早盘开盘后的 30 分钟是很多短线交易者最活跃的时间。集合竞价结束后前一晚的消息、外围市场影响和当日资金情绪会集中释放价格波动往往较大。此时成交量快速放大价格可能在几分钟内偏离自己的心理价位很远。从交易日志的角度看早盘产生的reason字段值得重点关注。如果一个人大多数交易都发生在 9:30 到 10:00且原因大多写的是“快速拉升”“急跌”“翻红”那说明决策高度依赖短期情绪。复盘时可以专门统计这个时间段的胜率与尾盘交易的胜率对比看自己的交易优势到底在哪个时段。4.2 水下卖出止损还是恐慌“水下卖出”本身没有对错。问题在于这究竟是按计划止损还是因为看着浮亏而恐慌减仓。区分方法很简单交易日志里是否提前记录了卖出条件。如果卖出条件是“跌破 5 日均线”“亏损达到 3%”“盘中创出日内新低”那么价格到水下只是触发了规则如果卖出条件只是“太难受了”“怕继续跌”那这就是情绪决策。复盘时把这两类卖出分开统计通常会发现规则止损的平均亏损更小持仓体验也更稳定。4.3 换股操作追热点还是换逻辑“换成网约车”这类操作要拆成两部分来看卖出旧标的的逻辑是否成立买入新标的的逻辑是否成立。两者不能互相抵消。如果卖出的原因是“网约车板块反弹更强”那就不是在止损而是在切换逻辑。切换逻辑本身可以但需要确认新标的的买入是否在自己的能力圈和计划内。如果只是因为“它涨了所以我要换”那这次买卖很可能变成一个情绪化链条卖出是被动买入是追涨。复盘时看到原因字段里同时出现“止损”“换仓”“追热点”时要特别审视当时的仓位和手续费损耗。4.4 仓位管理为什么“没太多”是幸存而不是策略“还好没太多”说明风险暴露不大所以结果可承受。这是一种好运气但不一定是好策略。如果每次都是因为买得少而避免大亏那应该把“买得少”升级成“基于风险预算的仓位管理”而不是每次都凭感觉控制比例。一个可操作的做法是单笔交易的最大亏损金额先设定好再根据止损距离反推仓位。例如账户资金 10 万元单笔最大亏损预算 2000 元止损距离 5%那么最大买入金额就是 4 万元对应的仓位比例是 40%。这样的仓位不是靠“少买点”来模糊控制而是由风险预算精确计算出来的。5. 把复盘结论变成可执行的交易纪律5.1 从一次操作提炼一条规则复盘不能只停留在“下次注意”。每条复盘结论都应该能被写成一条可执行、可验证的规则。例如原结论“早盘不要冲动。”可执行规则“早盘 9:30 到 10:00 内不执行没有提前挂单的买入操作如果行情触发计划内条件必须先在日志里写明触发原因再下单。”原结论“仓位不要太高。”可执行规则“单笔买入计划仓位不超过账户总资金的 20%且单笔最大亏损不超过账户的 2%。”原结论“水下不要乱卖。”可执行规则“每笔买入后必须在当日收盘前写下止损价卖出决策以触发止损价为准盘中不修改。”这些规则看上去简单但只有写成这样才可以在复盘时逐条检查。5.2 用复盘数据验证规则而不是验证情绪规则制定后需要放到历史数据里验证。比如设定“跌破 5 日均线止损”这条规则后可以回看过去半年的交易记录看看如果严格执行这条规则会躲开多少亏损又会错失多少上涨。这不是做历史回测那么简单但即便只是用已有的交易日志做假设性复盘也比凭感觉判断规则有效性更可靠。验证时要注意幸存者偏差。如果只挑出“幸好当时卖了”的案例而忽略“卖了之后继续上涨”的案例结论就会失真。正确做法是把同一规则下的所有案例都列出来计算平均收益和最大不利情况。5.3 建立一份发布前检查清单在每天开盘前可以准备一份类似发布前检查清单的东西逐项确认自己的交易计划是否完整。下面是一份参考样例。检查项是否完成备注今天是否只想观察不强制交易是无计划可以空仓关注的标的买入条件是什么明确写出具体价格或形态条件买入仓位上限是多少20%按风险预算计算买入后的止损依据是什么写清价格不盘中随意修改卖出的仓位对应多少资金记录卖出后才能明白换仓成本当前账户回撤是否已经超过阈值否超过时停止开新仓这份清单不是用来保证赚钱而是用来减少临场决策。交易者一旦进入盘中注意力会被价格快速消耗前期写下的条件可以帮助回到计划轨道。注意规则清单只有在真实执行时才有价值。如果连续三次没有执行需要先研究执行失败的原因而不是继续添加更多规则。5.4 模拟盘优先实盘前先跑通流程交易日志和数据统计方法也适用于模拟盘。建议先用模拟盘跑一个月验证自己的规则和记录流程是否顺畅。期间要关注的是有没有漏记、有没有拖延记录、统计代码有没有 bug。只有数据采集流程稳定后续统计结论才可信。实盘的额外风险来自真实资金带来的心理压力这无法在模拟盘中完全模拟但复盘框架可以先在模拟阶段建立起来。6. 数据处理常见问题与排查6.1 CSV 日期格式不一致现象同一列里既有2025-06-18又有2025/6/18还有06-18-2025。原因不同券商导出格式不同手工补录时也容易混用。检查方式打印原始时间列的前 20 个值观察格式差异。处理建议统一使用pd.to_datetime(df[trade_time], formatmixed)或手动指定格式。对于无效时间设置errorscoerce再检查空值。6.2 同一天多笔交易无法按单笔交易合并现象一天内先买 200 股又买 300 股再卖出 400 股。统计卖出盈亏时无法确定这 400 股对应哪一次买入。原因单条流水只记录了成交没有建立交易配对关系。检查方式查看同一stock_code下连续买卖的时间顺序和数量。处理建议使用 FIFO 配对逻辑也就是先买到的先卖出。程序上按时间排序后用一个队列保存每次买入的数量和成本卖出时从队首开始扣除。如果有真实交割单也可以直接用券商提供的“已实现盈亏”字段避免手工配对出错。6.3 佣金和印花税处理位置不对现象买入和卖出记录的realized_pnl都偏高。原因只在卖出时扣了佣金没有计算印花税或买入佣金没有计入成本。检查方式抽查一笔成交用券商交割单手工核对费用。处理建议买入时把佣金加入平均成本卖出时从卖出收入里扣除佣金和印花税。建议在 CSV 中分别记录commission和tax字段避免混在一起影响归因。6.4 除权除息导致平均成本计算错误现象某只股票分红后明明价格下跌但系统仍显示盈利。原因没有处理除权除息股本增加和现金分红没有调整成本或持仓数量。检查方式对比分红除权日前后持仓数量和成本的连续性。处理建议在数据中增加corporate_action事件表记录分红、送股、配股等操作。计算平均成本时在除权日当天对持仓数量和成本做调整。如果券商已经提供了复权因子也可以先使用复权价格进行简化分析。6.5 板块标签变化导致归因偏差现象同一只股票在不同时间段被标记为不同板块汇总后归因结果不稳定。原因个人手工填写的sector标签没有统一词典。检查方式统计sector列的唯一值数量观察是否有同义词例如“网约车”“出行”“网约车概念”混用。处理建议提前编写一张板块映射表把同义标签合并成统一词条。统计前先执行标准化再按sector分组。7. 最佳实践复盘的最小闭环和扩展方向7.1 复盘的最小闭环一次完整的复盘不是收盘后看一眼账户就结束。最小闭环可以这样设计盘中或收盘后记录每一笔交易的reason和after_thought。每周把成交流水导入 CSV用脚本计算出胜率、盈亏比和总盈亏。按reason和sector做归因找出亏损集中点。把发现写成一条可执行规则放入下一个交易周期的检查清单。下周执行后在下一次复盘里检查该规则是否被执行、是否有效。这个闭环的关键不是追求一次写一个很规范的系统而是保持低成本持续运行。只要两周不记录中间的数据就会断裂复盘逻辑也随之失效。7.2 从 pandas 扩展到 SQLite 和可视化当 CSV 越来越大查询和聚合会变慢。可以迁移到 SQLite格式仍然轻量但支持 SQL 查询。SELECT sector, COUNT(*) AS trade_count, SUM(realized_pnl) AS total_pnl FROM trades GROUP BY sector ORDER BY total_pnl;可视化方面可以用 pandas 配合 matplotlib 绘制累计盈亏曲线、按周统计的胜率柱状图以及按原因分类的箱线图。这些图表不适合用于预测行情但适合观察自己的交易行为是否有结构性偏差。7.3 从手工记录走向自动化采集手工记录容易中断更理想的方案是定期从券商交割单导出成交数据再用脚本自动转换成 CSV 或 SQLite。如果对代码熟悉还可以通过券商 API 或量化平台获取交易数据但要注意接口权限、数据时效和合规要求。自动化的目的是减少记录负担而不是增加对数据的过度信任。自动化采集之后reason字段仍然需要手工补充这是系统无法自动生成的部分。7.4 复盘数据的边界和正确心态交易复盘工具能帮助看清自己过去的决策分布但它不提供未来涨跌的答案。统计结果显示“这种操作亏钱”不等于“下次一定不能做”统计结果显示“这种操作赚钱”也不等于“下次一定能复制”。真正能长期使用的是记录习惯、规则意识和仓位管理。从那条“还好没太多”的随手记录出发一次完整的复盘可以让三个问题变得清晰水下卖出的依据是什么换仓买入的逻辑是否独立成立仓位控制在整体账户中的风险预算是否明确。把这三点长期记录下来再配合简单的统计脚本就能逐步把情绪化复盘变成基于数据的决策改进流程。新手最值得做的第一件事不是找更复杂的指标而是从今天开始把交易记录写成 CSV。等到积累几十条记录后再用 Python 跑一遍上面所示的统计代码你会第一次看到自己的交易行为全景。