导出您的投资组合

Martin Blais,2015 年 12 月(第 2 版)

http://furius.ca/beancount/doc/export

概述

本文档说明如何将 Beancount 中的投资组合持仓导出到 Google 金融投资组合(并最终导出到其他投资组合跟踪网站)。

注意:本文档是第二版,于 2015 年 12 月重写,大幅简化了投资组合导出流程,并完全分离了价格下载所需的股票代码配置。新版本仅使用一个元数据字段名称:“export”。旧版本可在此查看

投资组合跟踪工具

互联网上有多个网站允许用户创建投资组合(或上传交易列表以生成投资组合),并报告因价格变动带来的投资组合变化、显示未实现资本收益等。其中一个网站是Google 金融门户。另一个例子是Yahoo 金融。这些工具非常方便,因为它们允许您在一天中的任何时间监控跨所有账户的全部资产价格变动影响。

然而,这些网站都要求用户通过它们的界面和工作流程,逐一手动输入每一个持仓位置。使用 Beancount 的巨大优势在于,你永远不需要手动输入这类信息;相反,你应该能够提取这些数据并上传到这些网站中。你可以摆脱对特定投资组合跟踪服务的依赖,并在它们之间自由切换而不会丢失任何数据;随着需求的变化,Beancount 可以作为你持仓列表的纯净数据源。

Google Finance 支持一种“导入”功能,用于创建投资组合数据,该功能支持微软 OFX 金融数据交换格式。本文将展示我们如何构建一个 Beancount 报告,将持仓数据导出为 OFX 格式,以便在 Google Finance 中创建投资组合。

导出到 Google Finance

将持仓导出为 OFX

首先,创建一个与你的 Beancount 持仓相对应的 OFX 文件。你可以使用以下命令来完成此操作:

bean-report file.beancount export_portfolio > portfolio.ofx

查看该报告的内置帮助以获取选项:

bean-report file.beancount export_portfolio --help

在 Google Finance 中导入 OFX 文件

然后,我们需要在基于网页的投资组合平台中导入该 OFX 文件。

  1. 访问 http://finance.google.com,点击左侧的“投资组合”(或直接访问 https://www.google.com/finance/portfolio,此方法在 2015 年 1 月仍有效)

  2. 如果你已有之前导入的投资组合,请点击“删除投资组合”以移除它。

  3. 点击“导入交易”,然后选择“选择文件”,选取你导出的 portfolio.ofx 文件,再点击“预览导入”。

  4. 你应该会看到一个导入的持仓列表,包含熟悉的股票代码和名称,类型为“买入”,并有合理的“股数”和“价格”列。如果未显示,请参阅下方说明。否则,滚动到页面底部并点击“导入”。

  5. 你的投资组合现在应已显示。操作完成。

你永远不应直接通过网站更新此投资组合……相反,应更新你的 Beancount 账本文件,重新导出为新的 OFX 文件,删除旧的投资组合,然后重新导入全新的版本。你的纯净数据源始终是 Beancount 文件;理想情况下,你无需担心任何外部网站上的投资组合数据被损坏或删除。

控制导出的资产

声明你的资产

通常,我们建议你明确声明输入文件中使用的每一种资产。这是一个很好的位置,用于附加关于这些资产的信息,例如元数据,这些数据日后可通过 bean-query 或你自己编写的脚本使用。例如,你可以像这样声明资产的可读描述和其他属性:

2001-09-06 commodity XIN
  name: "iShares MSCI EAFE Index ETF (CAD-Hedged)"
  class: "Stock"
  type: "ETF"
  ...

即使没有这些声明,Beancount 也能正常工作(若未提供,它会自动生成 Commodity 指令)。如果你希望更严格并保持一定的规范性,可以使用一个插件来强制要求每种资产都必须声明:当出现未声明的资产时,该插件会报错。

plugin "beancount.plugins.check_commodity"

您可以为该商品指令使用任何日期。我建议使用该商品的起始日期,或者如果是货币,则使用发行国家首次推出该货币的日期。您可以在维基百科或发行方的网站上找到合适的日期。Google Finance 也可能直接提供该日期。

默认情况下会发生什么

默认情况下,所有持仓都会以与您用于定义它们的 Beancount 商品名称相同的股票代码导出为持仓。如果您持有“AAPL”单位,系统将生成一个名为“AAPL”的导出条目。导出代码默认会尝试导出所有持仓。

然而,在任何非最简单且无歧义的情况下,这通常不足以生成一个可在 Google Finance 中正常工作的投资组合。您在 Beancount 输入文件中使用的每个商品名称,可能与 Google Finance 数据库中的某个金融工具相对应,也可能不对应;由于其数据库支持的符号数量极为庞大,仅指定股票代码往往会产生歧义。Google Finance 会尝试将模糊的符号字符串解析为其数据库中最可能的金融工具,但该工具可能并非您原本意图的那一个。因此,即使您使用了交易所使用的相同基本符号,通常仍需通过指定其所属的交易所或符号体系来消除歧义。Google 提供了一份这些符号空间的列表

以下是一个真实案例:iShares/Blackrock 发行的“CAD 对冲 MSCI EAFE 指数”ETF 产品在多伦多证券交易所(TSE)上的代码为“XIN”。如果您仅在 Google Finance 中搜索“XIN”,系统默认会将其解析为更可能的“NYSE:XIN”符号(纽约证券交易所上的新源地产公司)。因此,您需要明确指定该金融工具的正确 ETF 代码为“TSE:XIN”。

显式指定导出符号

您可以通过在每个商品指令上附加一个“export”元数据字段,来指定用于导出该商品的交易所特定符号,如下所示:

2001-09-06 commodity XIN
  ...
  export: "TSE:XIN"

export”字段用于将您的商品名称映射到 Google Finance 系统中的相应金融工具。如果需要导出该商品的持仓,系统将使用此代码,而非 Beancount 的货币名称。

Google Finance 所使用的符号体系遵循以下语法:

交易所:代码

其中 交易所 是股票交易所在的代码,或为其他金融数据来源的代码,例如“MUTF”表示“美国共同基金”,更多详情符号 是在该交易所内唯一的名称。我建议您在 Google Finance 中逐一搜索您的每个金融工具,确认其对应关系(通过检查完整名称、描述和价格),然后插入相应的代码,如下所示。

导出为现金等价物

为了处理 Google Finance 不支持的持仓,导出报告可将持仓转换为其现金等价物价值。这对于现金类持仓(例如存放在储蓄或支票账户中的闲置现金)也非常有用。

例如,我持有某保险政策投资工具的份额(在加拿大,例如伦敦人寿公司,这种情况很常见)。这是一种金融工具,但每一份保单发行都有其自身的特定价值——这些产品的数据没有公开来源,信息相当不透明,我只能通过年度对账单获取其价值,而绝不可能在谷歌财经上找到。但我仍希望该资产的价值能反映在我的投资组合中。

你通过在“export”字段中指定特殊值“CASH”来告诉导出代码执行此转换,如下所示:

1878-01-01 commodity LDNLIFE
  export: "CASH"

这将在导出时,使用最接近导出日期的价格,将 LDNLIFE 商品的持仓转换为对应的报价价值。请注意,如果某种商品没有可用的成本或价格信息以确定其价值,则尝试将其转换为现金将导致错误。必须存在价格才能完成转换。

简单的货币也应标记为现金以便导出:

1999-01-01 commodity EUR
  name: "European Union Euro currency"
  export: "CASH"

最后,所有已转换的持仓将合并为一个单一的现金头寸。将这些现金条目导出为独立的 OFX 条目毫无意义,因为谷歌财经的导入代码无论如何都会将它们合并为一个。

声明货币工具

这个现金转换流程中有一个小问题:谷歌财经导入器似乎无法正确识别 OFX 中的“现金”头寸;我认为这可能是谷歌财经导入代码中的一个 bug(或者我尚未找到正确的 OFX 字段值来实现此功能)。

因此,导出器使用一种现金等价商品来插入现金头寸,该商品始终以每单位货币定价为 1,例如美元为 $1.00。例如,对于美元,我使用 VMMXX,即先锋 Prime 货币市场基金;对于加元,我使用 IGI806。适合此用途的商品类型通常是某种货币市场基金。具体使用哪一种并不重要,只要其价格非常接近 1 即可。请找到一个合适的。

如果你希望包含现金类商品,你需要为账簿中每种现金货币找到这样的商品,并在 Beancount 中进行声明。通常,你只需要处理一到两种货币。

你通过在“export”字段中附加特殊值“MONEY”并指定该商品所代表的货币来声明它们,如下所示:

1900-01-01 commodity VMMXX
  export: "MUTF:VMMXX (MONEY:USD)"

1900-01-01 commodity IGI806
  export: "MUTF_CA:IGI806 (MONEY:CAD)"

忽略商品

最后,某些账簿中持有的商品应被忽略。例如,用于镜像记账的虚拟商品,用于追踪未归属的员工股票计划股份,或用于追踪退休账户缴款金额的商品,如下所示:

1996-01-01 commodity RSPCAD
  name: "Canada Registered Savings Plan Contributions"

你通过在“export”字段中指定特殊值“IGNORE”来告诉导出代码忽略某个商品,如下所示:

1996-01-01 commodity RSPCAD
  name: "Canada Registered Savings Plan Contributions"
  export: "IGNORE"

因此,所有 RSPCAD 单位的持仓将不会被导出。

某些商品是否应该被出口的问题有时会带来有趣的抉择。以下是一个例子:我将累积的休假时数记录在一个资产账户中,单位为“VACHR”。我为该商品关联了一个大致等于我净时薪的价格。这让我大致了解账面上累积了多少休假时间价值,例如,如果我辞职,我能获得多少补偿。我是否希望将这些休假时数包含在我的净资产总额中?这些时数的价值是否应反映在我导出的投资组合价值中?我认为这主要取决于我是否计划在离职前用完这些休假,以及我是否希望这一累积价值出现在我的资产负债表上。

与净资产比较

最终结果是,您导出的所有头寸加上现金头寸的总和,应大致等于您全部资产的价值,而 Google 金融网站计算的总值应与此报告所报告的值非常接近:

bean-report file.beancount networth

作为参考,我自己的投资组合价值通常与实际值相差仅数百美元左右。

OFX 导出详情

导入失败

导出包含 Google 金融无法识别的符号的投资组合会致命地导致 Google 的导入功能失败。Google 金融随后将无法识别您的整个文件。为避免此问题,我建议您在所有导出的商品上使用明确的交易所:符号名称,具体方法见本文后续说明。

Google 金融对您提供的 OFX 文件格式也可能比较挑剔。export_portfolio命令试图避免使用会破坏导入功能的 OFX 特性,但该过程仍很脆弱,您的投资组合内容的某些特定组合仍可能导致输出无法导入。如果是这种情况,在上述第(4)步中,您将不会看到股票符号列表,而是看到一长串类似 XML 标签的持仓信息(这是失败的表现形式)。如果出现这种情况,请发送邮件至邮件列表(最好能隔离出导致失败的持仓,并具备比较文件差异和排查问题的能力)。

共同基金与股票

OFX 格式在股票和共同基金之间有所区分。实际上,Google 金融导入器似乎并不区分这两者(至少其行为表现相同),因此这很可能只是一个无关紧要的实现细节。尽管如此,导出代码仍能遵循 OFX 规范,区分“BUYMF”与“BUYSTOCK”XML 元素。

为此,导出代码通过检查与商品关联的 ticker 是否以字母“MUTF”开头并紧跟一个冒号,来判断哪些商品代表共同基金。例如,“MUTF:RGAGX”和“MUTF_CA:RBF1005”都会被识别为共同基金。

调试导出过程

为了调试您的每项持仓如何被导出,请使用--debug标志,该标志会将导出脚本处理每项持仓的详细信息打印到stderr

bean-report file.beancount export_portfolio --debug 2>&1 >/dev/null | more

脚本应打印导出的持仓及其对应头寸列表,然后是转换后的持仓及其对应头寸列表(通常许多现金头寸会被合并在一起),最后是被忽略的头寸列表。这足以解释导出投资组合的全部内容。

购买日期

目前 Beancount 并未包含所有仓位的购买日期,因此导出时将购买日期设为导出前一天。

最终,当 Beancount 中可用购买日期(待 库存记账变更)时,导出格式可能会使用实际的仓位购买日期。然而,目前尚不明确使用正确日期是否合适,因为 Google Finance 可能会因报告的购买日期而强制插入现金股息……但 Beancount 已经自动处理了为现金插入特殊仓位的逻辑,该仓位应已包含这部分内容。我们等到那时再看。

禁用股息

在“编辑投资组合”选项中,有一个复选框可用于禁用股息计算。最好能在导入时自动取消勾选此复选框。

自动化上传

若能通过 Python 脚本自动替换投资组合将非常方便。遗憾的是,Google Finance API 已被弃用。或许有人可以编写一个网页抓取脚本来实现此功能。

摘要

每个头寸的导出行为可通过其商品的处理方式来控制,具体如下:

  1. 导出为投资组合中的持仓。这是默认行为,但您应使用 “ticker” 或 “export” 元数据字段指定股票代码,格式为 “交易所代码:股票代码”。

  2. 转换为现金,并导出为货币市场现金等价物持仓,方法是将 “export” 元数据字段的值设为特殊值 “CASH”。

  3. 忽略,方法是将 “export” 元数据字段的值设为特殊值 “IGNORE”。

  4. 作为 货币工具 提供,用于标识每个计划转换为现金并纳入投资组合的头寸的现金等价物价值。这些工具通过 “export” 元数据字段中的特殊值 “(MONEY:<货币>)” 来识别。