VibeCoding[1]:用Python做一个Windows长期ETF持仓监控器

需求

最近需要一个能在 Windows 下长期运行的轻量级 ETF 持仓监控工具。需求并不复杂:不接入券商接口,不做自动买卖,只负责读取本地持仓、获取实时行情、计算组合盈亏,并在达到设定阈值时弹出提醒。

相比完整的量化交易系统,这个工具更适合作为个人持仓辅助看板。它的目标是少打扰、可长期运行、关键时刻提醒,而不是频繁刷屏或者自动交易。


实现

  1. 准备项目环境;

项目建议使用 Python 3.10 或更高版本。由于程序主要运行在 Windows 下,可以直接使用 Conda 的 base 环境:

1
2
3
cd portfolio_alert
conda activate base
python -m pip install -r requirements.txt

当前版本依赖 AKShare 获取 ETF 实时行情,并使用 win11toast 发送 Windows 通知。如果通知组件不可用,程序会自动退回到命令行输出。

  1. 新建本地持仓文件;

项目提供了 holdings.example.yaml 作为示例文件,真实持仓建议写在本地私有的 holdings.yaml 中,不要直接写进 Python 代码,也不要提交到 Git 仓库。

第一次使用时复制示例文件:

1
Copy-Item holdings.example.yaml holdings.yaml

然后根据自己的实际持仓编辑 holdings.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
holdings:
- code: "510300"
name: "沪深300ETF"
shares: 1200
cost_price: 4.881
enabled: true
remark: "手动填写成本价"

- code: "562500"
name: "机器人ETF"
shares: 5300
cost_price: 1.116
enabled: true
remark: "手动填写成本价"

- code: "159819"
name: "人工智能ETF"
shares: 2600
cost_price: 1.922
enabled: true
remark: "手动填写成本价"

其中 code 建议加引号,避免证券代码前导零丢失;shares 表示持有份额;cost_price 表示持仓成本价;enabledtrue 时参与组合计算,为 false 时临时忽略该标的。

如果程序启动时提示:

1
2
未找到 holdings.yaml。
请复制 holdings.example.yaml 为 holdings.yaml,并填写真实持仓信息。

说明还没有创建本地真实持仓文件,按上面的方式复制并填写即可。

  1. 编辑监控配置;

程序的刷新频率、交易时间、盈亏阈值、日报时间等配置都写在 config.yaml 中。如果该文件不存在,程序会自动生成默认配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
refresh_interval_sec: 900

trading_time:
morning_start: "09:30"
morning_end: "11:30"
afternoon_start: "13:00"
afternoon_end: "15:00"
skip_weekends: true

alert:
total_loss_threshold: -500
single_loss_threshold: -300
total_profit_threshold: 500
single_profit_threshold: 300
cooldown_sec: 300
notify_on_recover: true

console:
silent: true
show_startup_summary: true
show_non_trading_message: true
show_portfolio_each_refresh: false
startup_quote_timeout_sec: 8

daily_report:
enabled: true
report_time: "15:05"

其中亏损阈值使用负数,盈利阈值使用正数。例如组合总浮亏小于等于 -500 元时触发组合亏损提醒;组合浮盈大于等于 500 元时触发组合盈利提醒。

  1. 启动持仓监控;

完成持仓和配置文件后,执行:

1
2
3
cd portfolio_alert
conda activate base
python run.py

程序启动后会先打印一次当前监控状态,包括是否处于交易时间、启用持仓数量、刷新频率、当前组合状态和下一次刷新时间。

之后程序默认静默运行,不会每轮刷新都刷屏。只有当组合或单只标的达到亏损、盈利阈值时,才会通过 Windows 通知弹出提醒。运行明细会写入:

1
logs/portfolio_alert.log
  1. 使用一次性查看模式;

如果只是想看一次当前组合状态,不想进入长期后台循环,可以执行:

1
python run.py --once

如果希望强制请求实时行情,不读取本地缓存,可以执行:

1
python run.py --once --no-cache

调试时如果需要显示更详细的控制台日志,可以执行:

1
python run.py --debug
  1. 使用缓存快照;

每次成功获取行情并完成组合计算后,程序会把最近一次组合状态保存到:

1
data/latest_snapshot.json

非交易时间启动、行情接口失败或启动行情请求超时时,程序会优先显示这份缓存。这样即使行情接口暂时不可用,也可以看到最近一次组合状态。

如果缓存超过 7 天,程序会提示缓存较旧,仅供参考。

  1. 设置 Windows 开机自启动;

可以创建一个 PowerShell 脚本,例如 start_portfolio_alert.ps1

1
2
3
cd E:\CodeLocal\StakeTool\portfolio_alert
conda activate base
python run.py

然后按 Win + R,输入:

1
shell:startup

把该脚本的快捷方式放入启动文件夹即可。

也可以使用 Windows “任务计划程序”创建登录时运行的任务,程序选择 powershell.exe,参数示例为:

1
-ExecutionPolicy Bypass -File "E:\CodeLocal\StakeTool\portfolio_alert\start_portfolio_alert.ps1"
  1. 运行测试;

项目修改后可以执行单元测试:

1
2
3
cd portfolio_alert
conda activate base
python -m pytest tests -q

如果测试通过,说明基础配置读取、持仓解析和核心计算逻辑没有明显问题。


常见问题

行情获取失败怎么办?

先确认网络可用,并升级 AKShare:

1
pip install -U akshare

AKShare 的接口偶尔可能字段变化或短暂不可用,程序会把异常写入日志,并在下一轮继续重试。

为什么非交易时间显示的是最近一次状态?

因为非交易时间没有必要频繁请求实时行情。程序会优先读取 data/latest_snapshot.json 中最近一次成功获取的组合快照。

通知不弹出怎么办?

先确认 Windows 系统通知没有被关闭,然后检查 win11toast 是否安装成功:

1
pip install -U win11toast

如果通知库不可用,程序会自动在命令行打印重要提醒内容。

如何新增持仓?

holdings.yamlholdings: 下新增一项,填写 codenamesharescost_price,并把 enabled 设置为 true

如何临时忽略某只 ETF?

把对应标的的 enabled 改成 false 即可,程序会在组合计算中跳过该标的。

如何修改亏损或止盈提醒阈值?

修改 config.yaml 中的阈值配置:

1
2
3
4
5
alert:
total_loss_threshold: -500
single_loss_threshold: -300
total_profit_threshold: 500
single_profit_threshold: 300

保存后重启程序即可生效。


小结

这个工具的定位不是自动交易系统,而是一个个人持仓监控助手。它把真实持仓、运行配置和程序逻辑分开管理,通过 AKShare 获取 ETF 行情,通过本地配置控制提醒阈值,并在 Windows 下以较低打扰的方式长期运行。

对于只想监控 ETF 持仓、不想接入券商接口、也不希望程序自动买卖的场景,这种方案相对简单、透明,也更容易维护。