账本里的 price 指令是离散的:某天有一条
2013-06-01 price USD 1.10 CAD,之后可能几周没有新报价,而估值要回答"任意一天
1 单位 X 值多少 Y"。prices.py 把散落的 Price
记录整理成按币种对索引、按日期排序的
PriceMap,再提供查询函数把任意日期映射到表中某一条。
模块只做建表与查表。取价(联网抓行情)不在范围内:docstring
说价格来自 Price
指令或脚本写入账本(prices.py:5-7),抓价代码后来迁往独立仓库
beanprice(CHANGES:184-186 记录 2020-07-25
的迁移准备工作);估值口径属于 convert.py(05 篇)。
| 名称 | 位置 | 作用 |
|---|---|---|
get_last_price_entries(entries, date) |
prices.py:25-43 |
取给定日期前每个币种对最后一条 Price
指令,非数值 |
class PriceMap(dict) |
:46-57 |
dict 子类,__slots__ = ("forward_pairs",)
多挂一属性 |
build_price_map(entries) |
:60-135 |
建表:过滤、合并反向、去重、生成倒数 |
project(price_map, from, to, base_currencies=None) |
:138-244 |
把 from 计价的价格投影成 to 计价 |
normalize_base_quote(base_quote) |
:247-262 |
"USD/CAD" 与 ("USD","CAD") 归一 |
_lookup_price_and_inverse(price_map, base_quote) |
:265-291 |
内部查键,兼管异常 |
get_all_prices / get_latest_price /
get_price |
:294-309 / :312-344 /
:347-382 |
整条序列 / 最后一条 / 不晚于给定日期的最后一条 |
表的形状:(base, quote) -> [(date, Decimal), ...],按日期升序,每个日期只出现一次(:76-81);("USD","CAD") -> 1.10
即 1 USD = 1.10 CAD。
Price 指令price_entries = [entry for entry in entries if isinstance(entry, Price)]
prices.py:83-84。交易里的 @ 1.10 CAD 与
{564.20 USD} 不会自动进表——它们挂在
Posting.price / Posting.cost 上,不是
Price 指令,补齐靠可选插件
implicit_prices.py(第 6 节)。键取
(price.currency, price.amount.currency)、值取
(price.date, price.amount.number),defaultdict(list)
承接(:86-91)。
for base, quote in inversed_units:
bq_prices = price_map[(base, quote)]
qb_prices = price_map[(quote, base)]
remove = (base, quote) if len(bq_prices) < len(qb_prices) else (quote, base)
base, quote = remove
remove_list = price_map[remove]
insert_list = price_map[(quote, base)]
del price_map[remove]
inverted_list = [(date, ONE / rate) for (date, rate) in remove_list if rate != ZERO]
insert_list.extend(inverted_list)
prices.py:100-113。账本同时写过
price USD ... CAD 和 price CAD ... USD
时两个方向各自成键,合并规则是条数少的一方取倒数并入条数多的一方,只留一个方向(:67-70)。条数是去重前的原始条数;相等时
remove 取
(quote, base),即先在账本里出现的方向被保留(inversed_units
按插入序遍历且同时含两个方向,第二遍循环是依赖 defaultdict
才无害的空转,:93-98)。
sorted_price_map = PriceMap(
{
base_quote: list(
misc_utils.sorted_uniquify(date_rates, lambda x: x[0], last=True)
)
for (base_quote, date_rates) in price_map.items()
}
)
prices.py:115-123。sorted_uniquify(misc_utils.py:203-231)先按
key 排序再去重,last=True 取同 key 的最后一个。key
只取日期、Python 排序稳定,"最后一个"即列表里最后出现的那条;加载器已按
data.entry_sortkey(data.py:708-719,同日按
lineno
排)排序,等价于"账本里写在最后的那条生效",prices_test.py:212-231 (test_ordering_same_date)
以注释 "The last one to appear in the file should be selected"
锁定。理由写在 Price 指令的 docstring 里:beancount
不存时刻只存日期,同日多价本身无意义,且声明不打算解决(data.py:377-381)。
forward_pairs = list(sorted_price_map.keys())
for (base, quote), price_list in list(sorted_price_map.items()):
# Note: You have to filter out zero prices for zero-cost postings, like
# gifted options.
sorted_price_map[(quote, base)] = [
(date, ONE / price) for date, price in price_list if price != ZERO
]
prices.py:125-132。每个存活方向自动生成反向键,逐条取倒数;Decimal
除法不量化,默认 28
位有效数字,反向汇率通常是长尾小数(prices_test.py:264-270
的期望值即 28 位)。
零价格在两处被过滤,都是补丁而非一次性设计::112(合并分支)来自
2018-04-18 commit 2dd25c8b,修
#157(CHANGES:908-909);:131(反向生成)来自
2015-04-21 commit 68f73905,提交信息写 "Now that zero-cost
is supported, we have to make sure to ignore those price
entries"。两处都跳过该条而非填哨兵,后果是正反向列表长度可以不等。
forward_pairs 记录反向键自动生成前、经 3.2
正反向合并后仍存活的方向键(:46-57,126,134),只是生成前那一刻键列表的快照,不保存指令来源信息——同一个方向下的价格可能是账本原文,也可能来自
implicit_prices 等插件合成或脚本写入,PriceMap
无从区分。它由 2014-05-18 commit 79c9ff34 引入,供当时的
web 界面枚举价格时排除反向对;v3 删掉 web 后只剩写入点。
normalize_base_quote(:247-262)接受
("USD","CAD") 或 "USD/CAD",字符串按
/ 切分并 assert
恰好两段(prices_test.py:54-58 锁定三段抛
AssertionError)。
try:
return price_map[base_quote]
except KeyError:
base, quote = base_quote
prices = price_map.get((quote, base), None)
if prices:
return prices
else:
raise
prices.py:283-291 (_lookup_price_and_inverse)。正向键缺失时回退查反向键,但返回的列表不取倒数,数值方向是错的,docstring
用大写 INTERNAL
标注它只供内部使用(:269-270)。它无害是因为
build_price_map
保证两个方向同时存在,回退分支对正常构建的表不命中;真正目的是异常信息——正反都查不到时用裸
raise 重抛正向键的
KeyError,这是 2014-05-18 commit f8cc61bf
的意图,prices_test.py:107-119 断言异常文本含
('EUR', 'USD')。
get_price
的日期语义if date is None:
return get_latest_price(price_map, base_quote)
base_quote = normalize_base_quote(base_quote)
base, quote = base_quote
if quote is None or base == quote:
return (None, ONE)
try:
price_list = _lookup_price_and_inverse(price_map, base_quote)
index = bisect_key.bisect_right_with_key(price_list, date, key=lambda x: x[0])
if index == 0:
return None, None
else:
return price_list[index - 1]
except KeyError:
return None, None
prices.py:364-382。四条语义:date=None
退化为 get_latest_price 取最后一条;给定日期用
bisect_right_with_key(bisect_key.py:55-78)取不晚于该日期的最后一条——价格是阶梯函数,报价在下一条出现前一直有效,中间不插值;早于首条报价(index == 0)与键不存在都返回
(None, None);base == quote(或
quote is None)短路返回
(None, ONE),不查表。
线性插值曾有接口但从未实现:2014 年的 get_price 带
interpolation=PREVIOUS 参数与
PREVIOUS, LINEAR = object(), object() 哨兵,2014-05-13
commit 59416619 连同测试末尾的
__incomplete__ = True
一并删除,此后阶梯是唯一语义。prices_test.py:166-210 (test_get_price)
用 7
个日期点锁定:查询日落在两条报价之间时返回前一条及前一条的日期。
get_latest_price(:312-344)除同样的短路分支外,把
KeyError 与空列表统一成
(None, None)(:337-344)。get_all_prices(:294-309)不短路也不吞异常,键查不到即抛
KeyError(prices_test.py:146-148),且返回表里那个列表本身而非副本。
get_last_price_entriesfor entry in entries:
if date is not None and entry.date >= date:
break
if isinstance(entry, Price):
price_entry_map[(entry.currency, entry.amount.currency)] = entry
return sorted(price_entry_map.values(), key=data.entry_sortkey)
prices.py:36-43(原文分两行取
base_quote)。它不碰 PriceMap,返回
Price 指令,供
ops/summarize.py:506
截断账本时把截断日之前每个币种对的最后一条价格插回,使截断后仍能估值。上界是开区间(>=
即 break),date=None 取到最后(该分支来自 2014-07-10
commit dfae4a27)。
project(:138-244)解决"表里有
(HOOL, USD) 与 (USD, CAD),想直接查
(HOOL, CAD)":遍历 quote == from_currency
的键,对每条价格查一次 from/to 汇率,相乘后插入
(base, to_currency),同时插入取倒数的
(to_currency, base)(:216-242)。原表不改,先做浅复制(:193-194);from == to
直接返回原对象(:190-191,prices_test.py:246-247
用 assertIs 锁定)。
docstring 列了四条限制(:157-177),关键一条带标记
{c1bd24f8d4b7}:只在基础品种已有报价点的日期上投影。(USD,CAD)
在 A、C 两天变动而 HOOL 只在 B 天有报价时,不会在 A、C
合成新的 HOOL/CAD 点;替代方案是多次查表,即
convert.convert_amount 的
via(convert.py:215-229)。这类花括号标记(另有
{b2b23353275d}、{97a5703ac517}、{4bb702d82c8a})在源码与测试里成对出现。
它与 build_price_map
在不变量上不一致,两处:日期不再唯一——后者承诺每个日期只出现一次(:79-80),project
只做 extend + sort
不去重(:233-242),目标键已有同日价格时投影那条照样加入,prices_test.py:298-317 (test_project_collisions)
里 ("HOOL","CAD") 就有两条
2013-06-15;forward_pairs
丢失——:194 新建的 PriceMap
从未赋该属性。
防冲突判断本身也不成立:
rate_date, rate = get_price(price_map, currency_pair, date)
...
if rate_date in existing_prices:
# Skip collisions in date. {97a5703ac517}
continue
prices.py:219-226。existing_prices
是目标键上已有价格的日期集合(:209-214),rate_date
却是那条汇率的日期,而非当前被投影价格的日期
date——这段判断实际比较的是"目标日期是否等于所用汇率的生效日",与"是否等于当前投影价格的日期"是两回事,因此不能可靠拦住同日冲突:test_project_collisions
里 rate_date 是 2013-06-01、date
是 2013-06-15,投影照样插入。这段判断表达式自 2020-11-01
commit effb4701 引入起未变。forward_pairs
缺失也是同一来源:effb4701 引入时 :194
返回的就是普通 dict,读取该属性已抛
AttributeError;2024-12-23 commit
0f879811(标题 "enable mypy")把该容器改成
PriceMap(:194),但仍未初始化
forward_pairs,只是把原因换成未赋值的
slot,外部报错行为不变,其余改动属格式化与类型标注。docstring
说"投影价与已有价同日时不覆盖后者"(:164-165),实际两条并存,查询时
bisect_right 取同日最后一条,而列表按
(date, number)
排序,胜出的是数值较大那条;测试里原有价
1125.00 大于投影价
1120.0000,结论才恰好成立。
另有几处偏差见第 8 节:base_currencies
传空集合时短路(:206)、零价格在反向表里填
ZERO 而非跳过(:240)、遍历反向键时产出
(to, to) 自反键、对同一结果重复调用不去重。仓库内除测试外无
project 的调用点。
交易里的 @ 与 {} 要经
beancount/plugins/implicit_prices.py 合成为
Price
指令才能进表。规则(implicit_prices.py:65-89):写了
price(@)就用 units.currency 与
posting.price 造 Price,元数据
__implicit_prices__ = "from_price";没写 price
但有 cost,且该腿在该账户 add_position()
的判定结果不是 MatchResult.REDUCED,则用成本造,元数据值
"from_cost"(:44-57,72-89)。紧邻代码留有
TODO(:77-79):账户没有 booking strategy
时减仓判断是否仍会失效、从而把成本插成价格,作者标注待查,未收窄这一边界。
插件不在 loader.py
默认插件列表(loader.py:53-68)中;显式配置
beancount.plugins.implicit_prices 会启用该插件,配置
beancount.plugins.auto 也会通过
loader.combine_plugins(auto_accounts, implicit_prices)
间接包含它(auto.py:13-17)。2015-07-11
的变更把它移出默认列表并迁到 beancount.plugins,理由见
CHANGES:3549-3560。元数据字段是 2020-09-12 commit
c142e339 加的(CHANGES:155-156)。
去重键是
(date, currency, number, cost-currency)(:92-97),含数值,所以同日同货币对的多个不同价格全部保留、一条都不报错,只有数值也相同的重复才折叠。代码里留着被注释掉的报错逻辑与解释:股票拆分、同日两笔不同成交价都是合法场景,"Keeping
both for
now",并指出理想情况下去重键不该含数值(:101-119),这段自
2014-07-05 commit d9d65159 就在。严格模式另有可选插件
plugins/unique_prices.py,同日同货币对出现不同数值即报
UniquePricesError,其 docstring 称这是 "very strict
mode"、可能不现实,故只做成插件(unique_prices.py:1-13,46-60)。这些重复最终由
3.3 步收敛成每日一条。
| 决策 | 理由 | 证据 |
|---|---|---|
| 表里同时存正向与反向键 | 查询方不必关心账本写哪个方向 | prices.py:49-51,125-132 |
| 两个方向都有报价时合并,保留点多的一方 | docstring 称目标是把两套序列"reconciled into a single one",避免查询结果因方向不同而不一致;为何具体选点少的一方取倒数并入,源码未说明 | :67-70,100-113,105 |
| 同一天多条只留最后一条 | 不存时刻,同日多价无从排序,作者声明不解决 | :63-65;data.py:377-381 |
| 取值用阶梯而非插值 | 曾有 interpolation/LINEAR
接口但从未实现,59416619 直接删除;提交信息("Removed
linear interpolation feature.")未说明选阶梯而非插值的动机 |
59416619;prices.py:376-380 |
base == quote 短路返回 ONE |
源码注释:处理"currency priced into its own"这一退化情形,不查表 | :330,369 |
| 零价格在生成倒数时跳过 | 零成本持仓(赠予期权)合法,取倒数会除零 | :112,128-132;68f73905、2dd25c8b |
交易里的 @/{} 不自动进表 |
build_price_map 只消费已存在的 Price
指令,成交价与收盘价语义可能不同 |
:83-84;unique_prices.py:6-12 |
implicit_prices 不设为默认插件 |
由用户显式选择成交价是否进价格库 | CHANGES:3549-3560 |
| 隐含价格重复不报错 | 拆分、同日多笔成交都合法,暂无更好方案 | implicit_prices.py:101-107 |
查表失败返回 (None, None) |
缺价是常态,估值路径要能退回 | :337-344,377-382;convert.py:154-156 |
get_all_prices 反而抛 KeyError |
语义是取出该序列,不存在即调用错误 | :294-309;prices_test.py:146-148 |
| 现象 | 后果 | 证据 |
|---|---|---|
| 正反向列表长度可不等 | 某日报价为 0 时正向留 (date, 0),反向缺这天 |
prices.py:130-132 |
base == quote 的报价(如
price EFA 57.53 EFA)自成反向对 |
该键进入 inversed_units,remove 与
insert 指向同一列表且随后被
del,整个币种对从表里消失 |
:97-113;prices_test.py:96-105 只断言非
None |
| 反向汇率不量化 | 1/1.12 展开成 28 位有效数字,逐级换算累积长尾 |
:131;prices_test.py:264-270 |
| 合并按去重前条数比较,且合并早于去重 | 一方 3 条同日报价按 3 点计;被并入的反向条目排在尾部,同日两向都有报价时点少一方胜出 | :103-105,113,115-123 |
_lookup_price_and_inverse 回退不取倒数 |
手工拼的单向表经 get_*
查询得到方向相反的数值,正常构建的表不命中 |
:283-291,269-270 |
get_all_prices 返回表内列表本身 |
调用方修改会改到表,project 故显式浅复制 |
:309;:194 |
get_price
返回报价日期而非查询日期,base == quote 时日期位是
None |
convert.get_value 的 output_date_prices
记的即报价日;自反查询无法判断新鲜度 |
:333,372,380;convert.py:149-151 |
【文档漂移】build_price_map docstring 说输入 "hopefully
including some Price and/or Transaction entries" |
代码只过滤 Price;这是 2014-06-07 commit
c9dd4062 前的遗留,那时本模块自己从交易合成价格 |
:73-74,83-84 |
【文档漂移】project docstring
说同日冲突不覆盖已有价格 |
实际两条并存,查询取排序后最后一条即数值较大那条 | :164-165,233-242 |
project 的冲突判断比较 rate_date 而非
date |
判断几乎不成立,同日重复照常插入 | :219-226;prices_test.py:298-317 |
project 返回值无 forward_pairs,且产出
(to, to) 自反键 |
前者读属性抛 AttributeError;后者被
get_price/get_latest_price 的
base == quote 短路忽略,但 get_all_prices
没有该短路,直接查询会返回该键下的合成列表而非报错——project
除测试外无调用点(第 5 节),此差异只影响直接组合调用两者的场景 |
:57,194,198-242,294-309,330-333,369-372 |
project(..., base_currencies=set()) 不限制;零价格填
ZERO 而非跳过;对同一结果重复调用相同参数不去重 |
空集合是假值致条件短路;零价填充与 build_price_map
不一致,反向表出现
(date, 0);extend+sort
不做按日期去重,二次调用会把同批点再插一遍 |
:206;:240 vs
:131;:233-242 |
normalize_base_quote
只在字符串输入路径断言切分结果恰为两段 |
非字符串输入既不检查长度也不检查元素类型,一元/三元 tuple
或非字符串元素均原样透传;assert 在 python -O
下整体失效 |
:257-262 |
forward_pairs
无读取点,get_last_price_entries 用 break |
前者在 v3 删除 web 后成只写属性;后者依赖输入按日期有序,上界为开区间 | prices.py:51,54,57,126,134;:37-39 |
同日多条的"最后一条"由 lineno 决定 |
跨 include 时行号不反映全局顺序 |
data.py:719;prices.py:119 |
prices_test.py 共 12 个测试,分
TestPriceEntries(:13)与
TestPriceMap(:53)两类。除
test_normalize_base_quote 外全部用
@loader.load_doc() 从 docstring
加载账本,该装饰器默认断言加载无错误(loader.py:760-794)。
| 测试 | 行号 | 锁定的行为 |
|---|---|---|
test_get_last_price_entries |
15-50 | 上界开区间;晚于全部报价取最后一条;早于全部则空 |
test_normalize_base_quote |
54-58 | 元组透传;"USD/CAD" 切分;三段抛
AssertionError |
test_build_price_map |
60-94 | 键集恰是正反两个;同日三条取最后;合并后 5 个点;反向同长 |
test_build_price_map_zero_prices |
96-105 | 含零价格的账本不抛异常(只断言非 None) |
test_lookup_price_and_inverse |
107-119 | 正反都查不到时异常文本含正向键 |
test_get_all_prices |
121-148 | 序列完整有序;反向等长;未知键 KeyError |
test_get_latest_price |
150-164 | 取最后一条;未知键 (None, None) |
test_get_price |
166-210 | 7 个日期点的阶梯语义;返回报价日期;date=None
取最新;未知键 (None, None) |
test_ordering_same_date |
212-231 | 同日多条按文件顺序取最后一条 |
test_project |
233-279 | from == to
返回原对象;原表不改;不合成中间日期点;反向表同步;base_currencies
生效;有序 |
test_project_missing |
281-296 | 汇率首条前的基础价格不投影 |
test_project_collisions |
298-317 | 同日冲突两条并存 |
未覆盖:quote is None
短路分支;get_latest_price() 的 base == quote
分支由 convert_test.py:test_convert_amount__noop 经
get_price(date=None) 间接覆盖,get_price()
在给定具体日期时的同名短路分支未覆盖;get_last_price_entries(entries, date=None)
分支,本文件四次调用均传入具体日期;_lookup_price_and_inverse
反向回退的成功路径;零价格账本建表后的实际键集与内容;forward_pairs
的取值;get_latest_price()
键存在但列表为空的分支(:150-164
只测非空列表与缺键两种情形,两者结果恰好都是
(None, None));normalize_base_quote
非字符串输入的边界——一元/三元
tuple、非字符串元素、python -O 下 assert
失效(:54-58 只测合法二元 tuple
与字符串路径);project 缺
forward_pairs、产出自反键、生成反向投影时零价格填
ZERO(:238-241,全部 project
测试数据无零价格,零价格只见于 :97-105
的建表测试)、base_currencies=set()、连续调用两次的重复投影;test_project_collisions
只用 get_all_prices
断言两条同日报价并存,未锁定文档所述"随后 get_price()
取数值较大者胜出"这一组合行为;get_all_prices
返回列表可被外部修改。
2017-04-30 的 commit 859f341e 之前仓库布局带
src/python/ 前缀;下表中 2016-12-24 5a818e47
之前的提交对应路径是
src/python/beancount/ops/prices.py,5a818e47
之后到 859f341e 之前是
src/python/beancount/core/prices.py。
| 日期 | 提交 / 记录 | 变化 |
|---|---|---|
| 2014-05-13 | 59416619 |
删除线性插值:去掉 interpolation 参数与
PREVIOUS, LINEAR 哨兵,该功能从未实现 |
| 2014-05-18 | f8cc61bf |
加入 _lookup_price_and_inverse,保证异常报正向键 |
| 2014-05-18 | 79c9ff34 |
加入 PriceMap 子类与 forward_pairs,供 web
界面区分正向与反向对 |
| 2014-06-07 | c9dd4062 |
build_price_map 改为只过滤 Price
指令;从交易合成价格拆成独立函数 |
| 2014-07-05 | d9d65159 |
合成逻辑迁出本模块成为插件
ops.implicit_prices,"重复价格暂不报错"注释同时写下 |
| 2014-07-10 | dfae4a27 |
get_last_price_entries 支持 date=None |
| 2015-04-21 | 68f73905 |
生成反向汇率时过滤零价格,修除零 |
| 2015-07-10 | 02d42864、86b775d5;CHANGES:3543-3560(归入
2015-07-11 记录段) |
implicit_prices 移出默认列表并迁到
beancount.plugins |
| 2016-12-24 | 5a818e47 |
模块从 beancount.ops.prices 迁到
beancount.core.prices,以便 core.convert
依赖 |
| 2017-01-15 | 722d09fa、df2cf311;CHANGES:1515-1524 |
删除本模块的
convert_amount、get_position_market_value、get_inventory_market_value,估值职责交给
convert.py |
| 2018-04-18 | 2dd25c8b;CHANGES:908-909 |
合并反向报价时也过滤零价格,修 #157 |
| 2020-09-12 | c142e339;CHANGES:155-156 |
隐含价格指令带 __implicit_prices__ 元数据 |
| 2020-11-01 | effb4701;CHANGES:124-125 |
新增 project(),修 #255 |
| 2020-11-15 | 2e8291cd |
inventory.Booking 改名
MatchResult,插件的减仓判断随之改写 |
| 2024-12-23 | 0f879811 |
project() 内部返回容器由 dict 改为
PriceMap(属 "enable mypy"
一次性改动的一部分),未同步初始化 forward_pairs |
data.py 提供
Price(:372-395)、Currency(:30)、entry_sortkey(:708-719);number.py
提供
ONE、ZERO;bisect_key.py:55-78 带
key 的二分;misc_utils.py:203-231 排序去重。不依赖
amount.py、position.py、inventory.py,只取
Price.amount 的 number 与
currency 两个标量。convert.py(05
篇):get_value 从
cost/price 推断目标币种后调
prices.get_price(price_map, base_quote, date),查到返回换算后的
Amount,查不到返回原
units(convert.py:140-156)。"写了
@ 却查不到价"的根因在本模块只收 Price
指令,表现却在 convert
的估值结果里。convert_amount 的
via(convert.py:215-229)用两次查表串成隐含汇率,是
project 之外的另一条跨币种路径,不需预先建表。ops/summarize.py:506
截断账本时补插历史价格指令;projects/export.py:174-186 遍历
price_map.keys()(含自动反向对)取最新价;scripts/doctor.py:401
仅在 conversion == "value"
时建表;scripts/example.py:1711
从生成的价格指令建表,:675,840,871
按样例交易日期查询历史价格用于生成投资交易。plugins/implicit_prices.py
是唯一把 @/{}
送进价格表的途径,plugins/unique_prices.py
是配套的严格校验插件。api.py:68-71,135,142,155-156 只导出
PriceMap、build_price_map、get_latest_price、get_price。beancount/core/prices.py:1-8 docstring;13-22
导入;25-43 get_last_price_entries(36-39
上界与提前退出;43 排序返回);46-57 PriceMap(49-51
键含正反向;57 __slots__);60-135
build_price_map(62-70 合并与去重规则;73-74
输入描述;76-81 返回描述;83-84 只收 Price;86-91
累加;93-98 找反向对;100-113 合并;105 保留判定;112 零价过滤;115-123
排序去重;125-132 生成反向;128-129 零价注释;134-135
forward_pairs 与返回);138-244
project(144-156 用途;157-177 四条限制;189-191
退化;193-194 浅复制;196-207 遍历与限制;209-214 已有日期集合;216-230
逐条投影;219 查汇率;224-226 冲突跳过;232-242 排序与反向;240 零价填
ZERO);247-262 normalize_base_quote;265-291
_lookup_price_and_inverse(269-270 INTERNAL;283-291
回退与重抛);294-309 get_all_prices;312-344
get_latest_price(330-333 短路;337-344
异常与空表);347-382 get_price(364-365 无日期;369-372
短路;374-382 二分与失败)。
beancount/core/prices_test.py:13、15-50、53、54-58、60-94、96-105、107-119、121-148、150-164、166-210、212-231、233-279、281-296、298-317。
其它:beancount/core/data.py:30,372-395,377-381,705,708-719;beancount/core/number.py:22,24;beancount/core/convert.py:109-156,149-151,190-232,215-229;beancount/utils/bisect_key.py:55-78;beancount/utils/misc_utils.py:203-231;beancount/plugins/implicit_prices.py:15,21,65-89,92-97,101-119;beancount/plugins/unique_prices.py:1-13,46-60;beancount/ops/summarize.py:505-506;beancount/projects/export.py:174-186,188-200;beancount/scripts/doctor.py:401;beancount/scripts/example.py:1711;beancount/api.py:68-71,135,142,155-156;beancount/loader.py:53-67,760-794;CHANGES:124-125,155-156,184-186,908-909,1515-1524,3543-3560。
commit:59416619(2014-05-13)、f8cc61bf(2014-05-18)、79c9ff34(2014-05-18)、c9dd4062(2014-06-07)、d9d65159(2014-07-05)、dfae4a27(2014-07-10)、68f73905(2015-04-21)、02d42864、86b775d5(2015-07-10)、5a818e47(2016-12-24)、722d09fa、df2cf311(2017-01-15)、859f341e(2017-04-30)、2dd25c8b(2018-04-18)、c142e339(2020-09-12)、effb4701(2020-11-01)、2e8291cd(2020-11-15)、0f879811(2024-12-23)。