第 9 章 · 03 getlatestprices 与输出 本节摘要:DiscreteAllocation 的三输入之一是「最新价 latestprices」,它必须是 pd.Series。怎么从你已有的价格 DataFrame 里取出每资产的最新价?这就是 工具函数的职责。本节讲清这个小工具的细节(ffill + iloc[-1])、它如何与 DiscreteAllocation 串联,以及离散分配最终输出的 怎么解读和落地。读完本节,你能完成端到端流水线的最后一步:权重 → 整数股数 → 实际下单建议。 内容来源:源码 、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 用 getlatestprices 从价格 DataFrame 取最新价。
本节摘要:DiscreteAllocation 的三输入之一是「最新价 latest_prices」,它必须是 pd.Series。怎么从你已有的价格 DataFrame 里取出每资产的最新价?这就是
get_latest_prices工具函数的职责。本节讲清这个小工具的细节(ffill + iloc[-1])、它如何与 DiscreteAllocation 串联,以及离散分配最终输出的(allocation, leftover)怎么解读和落地。读完本节,你能完成端到端流水线的最后一步:权重 → 整数股数 → 实际下单建议。
内容来源:源码
pypfopt/discrete_allocation.py、文档docs/Postprocessing.rst,汉化并套用体系化模板。
阅读完本节,你应当能够:
源码极简:
def get_latest_prices(prices): """ A helper tool which retrieves the most recent asset prices from a dataframe of asset prices, required in order to generate a discrete allocation. """ if not isinstance(prices, pd.DataFrame): raise TypeError("prices not in a dataframe") return prices.ffill().iloc[-1]
只两步:
ffill():forward fill,用前一个有效值填充 NaN。iloc[-1]:取最后一行(最新一天)。返回 pd.Series,索引是 ticker,值是最新价。
💡 为什么 ffill:金融数据常有缺失——某资产某天停牌、数据源漏报。
ffill()用「上一个有效收盘价」填充,避免 NaN 污染。如果不 ffill 直接iloc[-1],只要最后一天某资产没数据就会报错。
from pypfopt.discrete_allocation import get_latest_prices from pypfopt import DiscreteAllocation # prices 是从 yfinance 拉的价格 DataFrame,日期 × ticker latest_prices = get_latest_prices(prices) print(latest_prices) # AAPL 189.45 # MSFT 372.30 # AMZN 3051.20 # ... # 离散分配 da = DiscreteAllocation( weights=weights, latest_prices=latest_prices, total_portfolio_value=20000, ) allocation, leftover = da.greedy_portfolio()
DiscreteAllocation 的构造函数有严格校验:
if (not isinstance(latest_prices, pd.Series)) or any(np.isnan(latest_prices)): raise TypeError("latest_prices should be a pd.Series with no NaNs")
为什么必须 pd.Series 而不接受 dict?
any(np.isnan(...)) 直接判断;dict 不行。⚠️ 传 dict 会报错:如果你手动构造一个
{ticker: price}字典传给 DiscreteAllocation,会得到TypeError: latest_prices should be a pd.Series with no NaNs。正确做法:pd.Series({ticker: price})或直接用get_latest_prices(prices_df)。
greedy_portfolio() 或 lp_portfolio() 返回 (allocation, leftover) 二元组:
allocation, leftover = da.greedy_portfolio() print(allocation) # OrderedDict([('MA', 24), ('FB', 21), ('PFE', 95), ('BABA', 8), # ('AAPL', 9), ('BBY', 5), ('SBUX', 32), ('GOOG', 1)]) print(leftover) # 12.15
allocation 的特征:
OrderedDict,键是 ticker、值是整数股数。_remove_zero_positions:@staticmethod def _remove_zero_positions(allocation): return {k: v for k, v in allocation.items() if v != 0}
short_alloc = {t: -w for t, w in short_alloc.items()} # 翻成负数
leftover 是「未投出的剩余资金」:
leftover = total_portfolio_value - Σ(allocation[ticker] × latest_prices[ticker])
它的物理含义:
💡 leftover 的处理建议:实盘时,这点零头可以:① 留作现金缓冲(推荐);② 加到最大权重资产再凑整;③ 改用 lp_portfolio 让 leftover 最小化。留作现金最简单也最稳健。
allocation, leftover = da.greedy_portfolio(verbose=True)
Funds remaining: $12.15 MA: allocated 0.242, desired 0.246 FB: allocated 0.200, desired 0.199 PFE: allocated 0.183, desired 0.184 BABA: allocated 0.088, desired 0.096 AAPL: allocated 0.086, desired 0.092 AMZN: allocated 0.000, desired 0.072 BBY: allocated 0.064, desired 0.061 SBUX: allocated 0.036, desired 0.038 GOOG: allocated 0.102, desired 0.013 Allocation has RMSE: 0.038
每行含义:allocated(实际权重,= 股数 × 单价 / 总投入)vs desired(目标权重)。差距大说明该资产离散化失败。最后 RMSE 是全体偏差的均方根。
⚠️ AMZN allocated 0 的警示:这种「目标 7.2% 但实际 0%」是最严重的离散化失败——AMZN 单价太高,1 股都买不起。处理方案:① 增大 total_portfolio_value,② 移除 AMZN 改用 ETF 替代,③ 接受偏差并在重新平衡时补齐。
把第 4-8 章的优化器和本章后处理串起来:
import pandas as pd from pypfopt import ( EfficientFrontier, expected_returns, risk_models, DiscreteAllocation, ) from pypfopt.discrete_allocation import get_latest_prices # 1. 数据 prices = ... # 价格 DataFrame # 2. 优化 mu = expected_returns.mean_historical_return(prices) S = risk_models.CovarianceShrinkage(prices).ledoit_wolf() ef = EfficientFrontier(mu, S) ef.max_sharpe() weights = ef.clean_weights() # 3. 离散分配 latest_prices = get_latest_prices(prices) da = DiscreteAllocation(weights, latest_prices, total_portfolio_value=20000) allocation, leftover = da.lp_portfolio() # 4. 输出下单建议 print("=== 下单建议 ===") for ticker, shares in allocation.items(): print(f" {ticker}: 买入 {shares} 股 @ ${latest_prices[ticker]:.2f}" f" = ${shares * latest_prices[ticker]:.2f}") print(f"剩余资金: ${leftover:.2f}") print(f"实际投入: ${20000 - leftover:.2f} / $20000")
输出:
=== 下单建议 === MA: 买入 24 股 @ $246.10 = $5906.40 FB: 买入 21 股 @ $190.30 = $3996.30 ... 剩余资金: $0.42 实际投入: $19999.58 / $20000
get_latest_prices 用 ffill 填充缺失——如果某资产最近停牌多日,它的「最新价」其实是停牌前的旧价,可能严重失真。
⚠️ 检查项:跑
da之前,先看一眼prices.isna().sum()和最后 5 行,确认没有大段缺失。停牌资产最好剔除或用其他数据源补全。
prices.ffill().iloc[-1],从 DataFrame 取最新价 pd.Series;ffill 用前值填缺失,避免停牌 NaN。至此第 9 章讲完。下一章是收尾——把权重与分配可视化、自定义优化器、参与上游贡献。