用Python搭建交易复盘工具:自动计算盈亏、回撤与胜率
2026/8/31 2:53:40 网站建设 项目流程

8月13日,26岁,账户本金600万,满仓梭哈,当日亏损3万。这不是段子,而是非常典型的个人交易记录。多数人看到这类记录会先关注金额,但真正值得关注的其实是另一件事:交易发生了、盈亏结算了,可如果没有日志、没有复盘、没有风险指标,下一笔交易和这一笔几乎没有区别。

这次我们来看一个能直接落地的技术方案:用 Python 从零搭建一套个人交易复盘工具。它能读取你导出的交易流水,自动计算每日盈亏、账户净值、最大回撤、胜率、盈亏比,再把资金曲线画出来。工具本身不依赖 GPU、不需要显存、不调用任何付费服务,装好 Python 环境就能跑。文章末尾还会给出 API 接口和批量任务目录扫描脚本,方便你把多账户、多周期的交易记录统一管理。

标题里的“被骗炮”,在交易语境里通常指被某个突破信号吸引进场、随后行情反转导致追高被套。这种事在手工交易里非常典型,而它恰恰说明:问题往往不完全出在信号,而是出在“没有一套可追溯、可复盘、可约束的流程”。本文会用代码把这条流程搭起来,让你每一笔操作都能被数据解释。

1. 核心能力速览

能力项说明
项目类型个人交易日志分析与复盘工具(示例工程)
运行环境Python 3.9 及以上,普通笔记本即可运行
主要功能交易流水导入、单笔盈亏计算、每日盈亏聚合、净值曲线绘制、胜率统计、盈亏比统计、最大回撤计算
是否支持 API支持,内置 FastAPI 接口,可上传 CSV 返回分析结果
是否支持批量任务支持,可按目录递归扫描 CSV 并汇总报表
GPU / 显存需求不依赖 GPU,显存需求为 0
启动方式命令行运行,或启动 API 服务后通过接口调用
输入格式CSV 交易流水表
输出内容终端汇总报告 + PNG 净值曲线 + 汇总 CSV
适合场景个人手工交易复盘、量化策略回测结果初步分析、财经数据教学、小团队内部资金曲线管理

需要说明的是,这是一个基于公开 CSV 格式构建的示例工具,核心代码在本文中完整给出。如果你有自己的交易软件导出格式,只需要把字段映射到统一格式即可。

2. 适用场景与使用边界

这个工具适合四类人:

第一类,手工交易者。每次下单后把开仓时间、方向、价格、数量、手续费记下来,复盘时不再靠记忆和截图,直接看数据和曲线。

第二类,量化策略初学者。策略回测得到一堆成交记录后,用这套脚本快速计算胜率、盈亏比和最大回撤,比在 Excel 里手动拉公式高效得多。

第三类,财经数据分析方向的学生。用真实或模拟数据练习 pandas 数据处理、matplotlib 可视化和指标计算,是一套完整的小型数据分析项目。

第四类,小团队资金管理场景。多个交易员各自维护一张 CSV 表,用批量脚本统一汇总成一张整体报表。

同时必须明确它的边界:

  • 它不做自动下单,不接交易所接口,不能直接用于实盘交易。
  • 它不做高频行情处理,只处理交易流水,不处理 tick 级数据。
  • 它不产生买卖信号,不负责告诉你“下一步该买还是该卖”。
  • 它不能替代专业风控系统,最大回撤只是事后统计指标。

合规方面也要说清楚。本文所有代码仅用于个人交易记录整理和统计分析,不构成投资建议。如果使用模拟盘或真实账户数据,注意隐私保护,不要公开含敏感账户信息的文件。涉及他人数据时,必须确认数据来源合法、已获得授权。另外,不建议在真实账户中使用高杠杆、满仓等极端操作,复盘工具的作用是帮助你认识风险,而不是鼓励冒险。

3. 环境准备与项目结构

环境要求很低:一台能运行 Python 的电脑,Windows、macOS、Linux 都行。建议使用 Python 3.9 或更高版本,依赖库包括 pandas、matplotlib、fastapi、uvicorn、python-multipart 和 openpyxl。

先创建项目目录:

mkdir trade_review cd trade_review

创建依赖文件requirements.txt

pandas>=2.0.0 matplotlib>=3.7.0 fastapi>=0.110.0 uvicorn>=0.29.0 python-multipart>=0.0.9 openpyxl>=3.1.0

安装依赖:

pip install -r requirements.txt

如果 pip 安装速度慢,可以替换为国内镜像源:

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

项目文件结构如下:

trade_review/ ├── requirements.txt ├── trade_log.csv ├── review.py ├── api_server.py └── batch_review.py

trade_log.csv存放交易流水,review.py是核心分析脚本,api_server.py提供接口服务,batch_review.py负责批量任务。

4. 搭建交易复盘工具

4.1 准备交易流水表

创建一个trade_log.csv,字段建议包含:日期、时间、交易品种、方向、数量、开仓价、平仓价、手续费、交易前账户权益。其中方向字段只填longshort,分别表示做多和做空。

下面是一份演示数据,用于验证代码流程,里面特意包含了“8月13日单日亏损3万”的场景:

date,time,symbol,direction,quantity,entry_price,exit_price,fee,capital_before 2024-08-07,09:30,TEST,long,100000,10.0,10.5,0.0,6000000.0 2024-08-08,14:20,TEST,short,50000,12.0,12.5,0.0,6050000.0 2024-08-09,09:35,TEST,long,100000,9.5,9.8,0.0,6025000.0 2024-08-12,10:00,TEST,long,100000,9.8,9.9,0.0,6055000.0 2024-08-13,09:35,TEST,long,100000,10.0,9.7,0.0,6065000.0

说明一下字段含义:

  • quantity:交易数量,这里是股数或合约份数。
  • entry_price:开仓价格。
  • exit_price:平仓价格。
  • fee:手续费。
  • capital_before:该笔交易发生前的账户权益。

4.2 编写核心分析脚本 review.py

在项目目录下创建review.py

import argparse import pandas as pd import matplotlib.pyplot as plt def load_csv(path): df = pd.read_csv(path, encoding="utf-8-sig") df["date"] = pd.to_datetime(df["date"]) df = df.sort_values(["date", "time"]) return df def calc_pnl(row): direction = row["direction"] if direction == "long": return (row["exit_price"] - row["entry_price"]) * row["quantity"] - row["fee"] elif direction == "short": return (row["entry_price"] - row["exit_price"]) * row["quantity"] - row["fee"] else: return 0.0 def build_report(df): df = df.copy() df["pnl"] = df.apply(calc_pnl, axis=1) df["equity_after"] = df["capital_before"] + df["pnl"] daily = df.groupby(df["date"].dt.date)["pnl"].sum().reset_index() daily.columns = ["date", "daily_pnl"] last_equity = df.groupby(df["date"].dt.date)["equity_after"].last() daily["net_value"] = daily["date"].map(lambda x: float(last_equity.loc[x])) wins = df[df["pnl"] > 0] losses = df[df["pnl"] < 0] win_rate = len(wins) / len(df) * 100 if len(df) else 0.0 profit_factor = ( wins["pnl"].sum() / abs(losses["pnl"].sum()) if len(losses) else float("inf") ) max_drawdown = calc_max_drawdown(daily["net_value"].to_list()) return df, daily, { "total_pnl": float(df["pnl"].sum()), "trade_count": len(df), "win_rate": round(win_rate, 2), "profit_factor": round(profit_factor, 2) if profit_factor != float("inf") else None, "max_drawdown": round(max_drawdown, 4), } def calc_max_drawdown(net_values): peak = net_values[0] max_dd = 0.0 for value in net_values: if value > peak: peak = value dd = (value - peak) / peak if peak != 0 else 0.0 if dd < max_dd: max_dd = dd return max_dd def plot_report(daily, output_png): plt.figure(figsize=(10, 5)) plt.plot(daily["date"], daily["net_value"], marker="o", linestyle="-") plt.title("Equity Curve") plt.xlabel("date") plt.ylabel("net value") plt.grid(True) plt.xticks(rotation=45) plt.tight_layout() plt.savefig(output_png) print(f"[OK] equity curve saved to {output_png}") def main(): parser = argparse.ArgumentParser(description="Trade Review Tool") parser.add_argument("--input", required=True, help="path to trade log csv") parser.add_argument("--output", default="equity_curve.png", help="output png path") args = parser.parse_args() df = load_csv(args.input) report_df, daily, metrics = build_report(df) print("===== Trade Review Report =====") print(f"Trade Count: {metrics['trade_count']}") print(f"Total PnL: {metrics['total_pnl']}") print(f"Win Rate: {metrics['win_rate']}%") print(f"Profit Factor: {metrics['profit_factor']}") print(f"Max Drawdown: {metrics['max_drawdown']}") print(daily.to_string(index=False)) print(report_df[["date", "time", "symbol", "direction", "quantity", "entry_price", "exit_price", "pnl"]].to_string(index=False)) plot_report(daily, args.output) if __name__ == "__main__": main()

这里有几个计算细节需要看明白:

  • 做多盈亏公式是(平仓价 - 开仓价) * 数量 - 手续费
  • 做空盈亏公式是(开仓价 - 平仓价) * 数量 - 手续费
  • 每日净值取当天最后一笔交易完成后的账户权益,而不是简单的初始权益累加。
  • 最大回撤使用净值序列的峰值回撤算法,公式为(当前净值 - 历史峰值) / 历史峰值,取最小值。

4.3 运行核心脚本

打开终端,执行:

python review.py --input trade_log.csv --output equity_curve.png

运行后,终端会输出一份汇总报告,并生成equity_curve.png净值曲线图。

5. 功能测试与效果验证

5.1 测试数据完整性校验

先检查代码是否能正确识别 CSV 表头和字段。运行脚本时如果报KeyError,优先检查 CSV 列名是否和代码中的字段名完全一致。注意:表头不要带空格,capital_before不要写成capital before

5.2 验证单笔盈亏计算

用演示数据中的第一笔验证做多公式:

开仓价 10.0,平仓价 10.5,数量 100000,手续费 0 盈亏 = (10.5 - 10.0) * 100000 = 50000

对应日期 2024-08-07,账户权益从 6000000 变为 6050000。

再验证 8 月 13 日那笔:

开仓价 10.0,平仓价 9.7,数量 100000,手续费 0 盈亏 = (9.7 - 10.0) * 100000 = -30000

正好对应标题中的“当日 -3w”。这说明数据字段和公式是对得上的。

5.3 验证每日聚合

演示数据共有 5 个交易日,每日盈亏分别为:

  • 2024-08-07:+50000
  • 2024-08-08:-25000
  • 2024-08-09:+30000
  • 2024-08-12:+10000
  • 2024-08-13:-30000

按日期分组求和后,可以得到每日盈亏序列,再结合最后一笔交易的权益得到每日净值序列。

5.4 验证风险指标

基于以上演示数据,指标计算结果应该是:

  • 总盈亏:35000
  • 胜率:3 胜 2 负,60.0%
  • 盈亏比:盈利总和 90000,亏损总和 55000,盈亏比约 1.64
  • 最大回撤:净值序列为 6050000、6025000、6055000、6065000、6035000,最大回撤约 -0.46%

终端输出中,Profit Factor字段是盈亏比。这在实盘场景中很有参考价值:胜率再高,如果单笔亏损远大于单笔盈利,长期下来账户依然是亏的。

5.5 验证净值曲线

运行后项目目录下会生成equity_curve.png。打开图片,能看到一条从 6000000 附近波动向上的曲线,最后一天出现明显回落。曲线能直观反映账户的波动节奏,尤其是“连续盈利几天后一天打回部分利润”这类形态。

常见失败情况:

  • 如果图片中文乱码,是 matplotlib 默认字体不支持中文,可以在代码中设置中文字体,或者把图片标题改成英文。
  • 如果图片空白,检查 CSV 是否为空文件,或者日期列是否被解析成NaN

6. 接口 API 与批量任务

6.1 启动 FastAPI 服务

如果不想每次都在命令行里跑,可以把分析逻辑封装成接口。创建api_server.py

import os import tempfile import pandas as pd from fastapi import FastAPI, File, UploadFile from review import build_report app = FastAPI(title="Trade Review API", version="0.1.0") @app.get("/health") def health(): return {"status": "ok"} @app.post("/api/review/upload") async def review_upload(file: UploadFile = File(...)): suffix = os.path.splitext(file.filename)[-1] with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: tmp.write(await file.read()) tmp_path = tmp.name try: df = pd.read_csv(tmp_path, encoding="utf-8-sig") report_df, daily, metrics = build_report(df) return { "metrics": metrics, "daily": daily.to_dict(orient="records"), } finally: os.remove(tmp_path)

启动接口服务:

uvicorn api_server:app --host 127.0.0.1 --port 8000

启动后先验证健康检查:

curl http://127.0.0.1:8000/health

正常返回:

{"status":"ok"}

6.2 使用 curl 上传 CSV 调用分析接口

curl -X POST "http://127.0.0.1:8000/api/review/upload" \ -F "file=@trade_log.csv"

接口会返回 JSON 格式的指标结果和每日净值列表。这个接口可以直接接到自己的本地工具链里,比如定时任务收集交易记录后自动推送到复盘服务。

6.3 使用 Python 请求库调用接口

import requests url = "http://127.0.0.1:8000/api/review/upload" files = {"file": ("trade_log.csv", open("trade_log.csv", "rb"), "text/csv")} response = requests.post(url, files=files, timeout=30) print(response.json())

返回结果中:

  • metrics.total_pnl:总盈亏
  • metrics.win_rate:胜率
  • metrics.max_drawdown:最大回撤
  • daily:每日盈亏与净值列表

6.4 批量任务目录扫描

当账户变多或记录分散在多个目录时,手动逐个调用接口显然不现实。创建batch_review.py

import argparse import glob import os import pandas as pd from review import build_report def scan_directory(input_dir, output_dir): os.makedirs(output_dir, exist_ok=True) csv_files = glob.glob(os.path.join(input_dir, "**", "*.csv"), recursive=True) summary = [] if not csv_files: print(f"[WARN] no csv files found in {input_dir}") return for idx, path in enumerate(csv_files, 1): try: df = pd.read_csv(path, encoding="utf-8-sig") report_df, daily, metrics = build_report(df) summary.append({ "file": path, "total_pnl": float(report_df["pnl"].sum()), "trade_count": metrics["trade_count"], "win_rate": metrics["win_rate"], "profit_factor": metrics["profit_factor"], "max_drawdown": metrics["max_drawdown"], }) print(f"[{idx}/{len(csv_files)}] OK: {path}") except Exception as exc: print(f"[{idx}/{len(csv_files)}] FAIL: {path} -> {exc}") result = pd.DataFrame(summary) result.to_csv( os.path.join(output_dir, "summary.csv"), index=False, encoding="utf-8-sig", ) print(f"[DONE] summary saved to {output_dir}/summary.csv") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--input", required=True, help="input directory") parser.add_argument("--output", required=True, help="output directory") args = parser.parse_args() scan_directory(args.input, args.output)

运行批量任务:

python batch_review.py --input ./logs --output ./reports

它会递归扫描./logs目录下所有 CSV 文件,逐份计算指标,并生成一份summary.csv汇总表。单文件处理失败不会中断整个任务,日志会打印具体失败原因,方便排查。

批量任务的常见设计原则是:

  • 每个文件独立处理,互不影响。
  • 失败时记录日志并继续处理下一个文件。
  • 最后统一生成汇总表,便于人工复核。

7. 资源占用与性能观察

这个工具是纯 CPU 任务,运行时不依赖 GPU,显存需求为 0。第一次在普通笔记本上运行,主要消耗来自 pandas 读取 CSV 和 matplotlib 绘图。

从性能角度看:

  • 几千条到几万条交易记录,pandas 处理几乎没有压力。
  • 几十万条记录时,建议关注内存占用,不要把全部历史数据一次性频繁读入。
  • 如果数据量达到百万级,建议改用分块读取,或者把数据存储格式从 CSV 换成 Parquet,能明显减少读取和计算时间。

实际占用需要以本机配置和数据量为准。更稳妥的做法是:先跑一遍演示数据确认流程正常,再导入完整数据观察运行时间。如果单次处理超过几十秒,优先检查是否在循环里重复读取大文件,而不是怀疑机器性能。

启动 API 服务后,也可以观察端口占用情况:

lsof -i :8000

如果端口被占用,换一个端口启动即可:

uvicorn api_server:app --host 127.0.0.1 --port 8001

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
CSV 中文乱码文件编码不是 UTF-8用文本编辑器查看文件编码CSV 文件使用 UTF-8 编码;代码中读取时使用encoding="utf-8-sig"
运行时报 KeyErrorCSV 表头与代码字段名不一致打印df.columns检查字段名统一字段名,去掉空格
日期解析失败日期格式不统一检查 CSV 中日期列统一使用YYYY-MM-DD格式
最大回撤输出为 0净值序列只有一笔或者全是上涨检查聚合后的 daily 数据确认有多个交易日的净值数据
胜率计算异常direction 字段存在非 long/short 值统计 direction 值分布数据清洗时统一方向字段
API 上传 CSV 后返回 500上传文件不是标准 CSV查看服务日志检查 CSV 表头是否完整
uvicorn 启动端口被占用8000 端口已被其他进程占用执行lsof -i :8000换端口启动,或先结束占用进程
批量任务中途失败某个 CSV 文件格式损坏看日志中 FAIL 文件路径修复该文件后重新运行批量脚本
matplotlib 图片中文乱码系统缺少中文字体查看图片文字将图表标题改为英文,或配置中文字体
批量汇总表为空输入目录下没有 CSV 文件检查目录路径确认--input指向包含 CSV 的目录

9. 最佳实践与使用建议

第一,交易记录必须落地。无论是手动交易还是量化策略,每一笔操作的日期、方向、价格、数量、手续费都必须记录。没有记录,复盘就是靠记忆,而人的记忆会美化亏损、放大盈利。

第二,统一字段格式。建议把 CSV 字段固定为本文给出的格式,并在每次写入前做数据校验。方向字段只允许longshort,日期格式只允许YYYY-MM-DD

第三,记录交易理由。在 CSV 中额外增加一列note,写下当时为什么进场。复盘时盈亏和理由一起看,才能真正改进交易逻辑。这一步最容易被忽略,也最有价值。

第四,给账户设置风控红线。在代码中增加一个简单的全仓检测:当单笔交易金额占账户权益比例过高时,输出警示。例如单笔交易投入超过账户权益的 50% 时提醒风险。这个逻辑可以用几行代码实现,但能有效防止“梭哈后失控”的情况。

def check_concentration(row): if row["quantity"] * row["entry_price"] / row["capital_before"] > 0.5: print(f"[WARN] high concentration on {row['date']}") return True return False

第五,模拟盘先跑通。不要直接把策略和工具接到真实账户上,先用模拟数据验证代码逻辑,确认盈亏计算正确后再处理真实数据。

第六,不伪造记录。复盘工具有一个隐藏前提:输入数据必须真实。如果因为亏损就删除记录或调整价格,那这套工具就失去了意义。

第七,接口服务只允许本机访问。FastAPI 启动时绑定127.0.0.1,不要直接绑定0.0.0.0,避免其他人未经授权访问你的交易数据。

第八,涉及人脸、声音、个人交易隐私等数据时,务必确认授权和合规边界。如果你的交易工具后续扩展到自动化下单、信号推送等方向,更要控制权限和访问范围。

10. 总结与下一步

回到开头那条记录。8月13日梭哈600万、当日亏损3万,单独看是一条让人心痛的流水。但如果把它放进完整的交易日志里,它只是净值曲线上的一个点。真正重要的不是这一天的盈亏,而是它前后的交易逻辑、仓位比例和风险暴露是否被记录、被计算、被总结。

本文这个工具能直接解决三件事:把分散的成交记录变成结构化数据;用统一公式计算盈亏和风险指标;把复杂的资金变化画成一条清晰的曲线。先跑通 CSV 分析脚本,再启动 API 服务,最后用批量脚本管理多份记录,整个过程不需要 GPU,不需要付费工具,一台普通电脑就能完成。

下一步可以扩展的方向包括:

  • 接入 backtrader 等第三方回测库,把交易日志直接用于策略回测。
  • 增加持仓周期分析,统计每笔交易从开仓到平仓的平均时长。
  • 增加多账户汇总功能,在批量任务基础上生成团队整体资金曲线。
  • 增加异常波动预警,当单日亏损超过设定阈值时自动输出告警。

最容易踩的坑就两个:一是 CSV 字段名不统一导致 KeyError,二是把复盘工具当成自动交易系统来用。前者改字段名即可解决,后者需要重新理解工具的定位:它记录过去,不预测未来。

建议先把演示数据跑通,再用自己的交易记录替换。等累计记录超过几十笔之后,回看净值曲线和最大回撤,多数人会发现自己对风险的感知远比数据展现的更乐观。所以,别急着讨论下一笔买什么,先把交易日志跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询