gs-quant Portfolio 指南:用 Python 构建、定价与风险管理投资组合
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
gs-quant 的Portfolio类是量化交易中处理一篮子工具的入口,它允许你将多只工具(如利率互换、期权)聚合在一起进行批量定价、风险计算与组合管理。阅读本文后,你将掌握 Portfolio 的构造方式、嵌套组合与路径寻址、定价上下文(PricingContext)配合使用、风险度量计算、序列化转换以及从 Marquee 平台加载与保存组合的完整实战能力。
Portfolio 在 gs-quant 中的定位
Portfolio是 gs-quant 中「工具的集合」,其官方文档 Portfolio.rst 中通过自动生成的 API 文档列出了它的全部方法与属性。它是PriceableImpl的子类,因而继承了price()、dollar_price()、calc()、resolve()等通用定价能力。从源码看,其定义于 gs_quant/markets/portfolio.py:
@dataclass class Portfolio(PriceableImpl): """A collection of instruments Portfolio holds a collection of instruments in order to run pricing and risk scenarios """与单只工具不同,Portfolio 可以嵌套(一个组合内可以再包含子组合),并且所有 pricing/risk 操作都可以一次性地作用于组合内全部工具,这在批量定价和风险敞口汇总场景中非常实用。
构造一个 Portfolio
基本构造
Portfolio的构造函数签名如下(见 portfolio.py):
def __init__(self, priceables=(), name=None):priceables:可以是单只工具(PriceableImpl)、工具的迭代器(list/tuple/numpy 数组等),也可以是一个 dict——字典的 key 会被用作工具的名称。name:组合名称,用于__repr__展示、to_frame的索引等。
源码中构造时的关键逻辑(portfolio.py):
- 传入 dict 时,会遍历
{name: priceable},将每个 key 写入对应priceable.name,再转换为列表存入self.priceables; - 否则直接把传入的 priceable 或可迭代对象赋值给
self.priceables(内部 setter 会统一转为 tuple)。
from gs_quant.instrument import IRSwap from gs_quant.markets.portfolio import Portfolio swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.001, name='swap_10y@10bp') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.002, name='swap_10y@20bp') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.003, name='swap_10y@30bp') # 传入元组 portfolio = Portfolio((swap1, swap2, swap3)) print(portfolio) # Portfolio(3 instrument(s))字典构造方式(key 会被用作工具名称):
portfolio = Portfolio({'swap_5': swap1, 'swap_6': swap2, 'swap_7': swap3}) assert len(portfolio) == 3测试用例 test_portfolio.py 验证了 list、tuple、numpy 数组三种可迭代容器构造的结果完全等价(p1 == p2 == p3)。
组合操作:append / extend / pop / add
文档列出的方法中,组合的动态修改由以下方法完成(portfolio.py):
# 追加单个或一批工具 portfolio.append(swap4) # 用另一个组合或工具列表扩展 portfolio.extend(new_portfolio) # 传入 Portfolio portfolio.extend([IRSwap(...), ...]) # 传入工具列表 # 按索引/名称弹出工具 extracted = portfolio.pop('swap_10y@20bp')此外,两个 Portfolio 之间还支持+运算符直接合并(portfolio.py),合并结果是一个新的组合。
组合的遍历与查询属性
文档 Attributes 中列出的属性,其语义如下(portfolio.py):
| 属性 | 说明 |
|---|---|
instruments | 组合直接持有的Instrument(去重后) |
all_instruments | 递归收集所有子组合的工具(深度优先) |
portfolios | 组合直接持有的子Portfolio |
all_portfolios | 递归收集的所有子组合 |
priceables | 组合直接持有的全部元素(含子组合) |
id/quote_id | Marquee 平台上的组合 ID / quote ID |
同时支持 Python 容器协议:len(portfolio)、portfolio[i](整数索引或切片)、instrument in portfolio、name in portfolio,以及基于全路径的相等性比较portfolio == other(portfolio.py)。
嵌套组合与 PortfolioPath 寻址
Portfolio 允许嵌套,即组合内部可以再包含子组合,从而表达层级化的簿记结构。此时,PortfolioPath用于定位组合内的某个工具,其定义见 gs_quant/risk/results.py。路径由整数序列组成,例如PortfolioPath((1, 1, 0))表示「索引 1 的子组合 → 其索引 1 的子组合 → 索引 0 的工具」。
paths(key)方法可按名称或工具对象返回其在组合内的所有路径(portfolio.py):
swap6 = IRSwap('Pay', '10y', 'CHF', name='CHF-swap') portfolio2_1 = Portfolio((swap1, swap2, swap3), name='portfolio2_1') portfolio1_1 = Portfolio((swap4, portfolio2_1), name='portfolio1_1') portfolio = Portfolio((swap6, portfolio1_1), name='portfolio') assert portfolio.paths('CHF-swap') == (PortfolioPath(0),)测试 test_nested_portfolios 展示了重复名称场景:'USD-swap'在多层嵌套组合中会命中PortfolioPath(2)、PortfolioPath((1, 1, 0))、PortfolioPath((2, 1, 0))三个路径,说明paths会递归搜索全部层级。
其他路径相关方法/属性:
all_paths:组合内所有工具的完整路径(叶子路径),见 portfolio.py;subset(paths, name=None):按一组路径抽取工具形成新的子组合(portfolio.py);如果路径恰好指向单个子组合则直接返回该子组合;__getitem__:除了整数索引,也支持用PortfolioPath、名称字符串或工具对象直接取值。
在 PricingContext 中定价与风险计算
定价上下文
gs-quant 的定价操作通常需要配合PricingContext使用,它决定定价日期、市场数据等环境。在with PricingContext(pricing_date=...)代码块内调用定价方法时,返回的是PricingFuture,代码块结束时一次性批量提交计算;在代码块外直接调用则同步返回结果。dollar_price()与price()定义在基类 gs_quant/priceable.py:
def dollar_price(self): # 返回美元现值(PV) return self.calc(DollarPrice) def price(self, currency=None): # 返回本币现值(可选货币) return self.calc(Price(currency=currency)) if currency else self.calc(Price)calc 与 PortfolioRiskResult
Portfolio.calc(risk_measure, fn=None)是整个风险计算的核心入口(portfolio.py),它返回一个PortfolioRiskResult:
from gs_quant.markets import PricingContext from gs_quant import risk with PricingContext(pricing_date=dt.date(2020, 10, 15)): prices = portfolio.dollar_price() result = portfolio.calc((risk.DollarPrice, risk.IRDelta))PortfolioRiskResult(results.py)是一个可组合的结果对象,支持非常灵活的切片:
- 按工具切片:
prices[swap2]、prices['swap_10y@30bp']; - 按风险度量切片:
result[risk.DollarPrice],再按工具切片result[risk.DollarPrice]['swap_10y@30bp']; - 按日期切片(HistoricalPricingContext 场景):
results[dt.date(2021, 2, 9)][risk.DollarPrice]; - 聚合:
prices.aggregate()返回整个组合的汇总值。
测试 test_portfolio.py 完整演示了这些用法,包括result[risk.DollarPrice].aggregate()的汇总结果,以及历史多日期切片在任意顺序下取值等价(L141-L164)。
历史与情景定价
HistoricalPricingContext:在多个历史日期上批量定价,返回按日期组织的SeriesWithInfo(见 test_historical_pricing);BackToTheFuturePricingContext:围绕基准日前后偏移若干交易日定价(test_backtothefuture_pricing);resolve():在定价上下文内解析工具(如根据市场数据把相对利率、到期日落实为具体数值),支持in_place原地修改或返回新组合(portfolio.py)。测试 test_results_with_resolution 验证了 resolve 前后工具对象发生变化,且解析后的结果仍然可以按原工具检索。
market()
Portfolio.market()返回组合内所有工具的市场数据坐标与值的映射(portfolio.py)。对于同一坐标出现冲突值的情况,会抛出ValueError提示冲突(容差 1e-6),保证组合层面市场数据的一致性。
序列化与转换:dict / JSON / DataFrame / CSV
文档列出的as_dict、to_dict、from_dict、to_json、from_json、to_frame、from_frame、to_csv、from_csv提供了组合与常见数据格式之间的双向转换。
DataFrame 与 CSV
to_frame(mappings=None)将组合转为 pandas DataFrame,其中mappings可用于把已有列映射成新列(支持字符串取值或 callable 计算),见 portfolio.py。to_csv(csv_file, mappings=None, ignored_cols=None)则进一步写出为 CSV 文件。
反向导入由类方法完成:
from_frame(data, mappings=None):从 DataFrame 构造组合。它优先尝试asset_class+type组合,其次尝试$type键,通过Instrument.from_dict重建工具(portfolio.py);两者都缺失时抛出ValueError('Neither asset_class/type nor $type specified')。from_csv(csv_file, mappings=None):读取 CSV 后调用from_frame,并会校验重复列(如type.1这类带数字后缀的列会触发ValueError),见 portfolio.py。
工具对象与其他构造函数
Portfolio还提供了若干从不同数据源构建组合的类方法/静态方法:
| 方法 | 用途 |
|---|---|
from_asset_id(asset_id, date=None) | 从资产 ID 加载持仓(可指定日期,默认取最新) |
from_asset_name(name) | 从资产名称加载 |
from_quote(quote_id) | 从 Marquee quote 加载工具 |
from_eti(eti) | 从 ETI(电子交易标识)加载内部持仓 |
from_book(book, book_type='risk', activity_type='position') | 从内部簿记加载持仓 |
get(portfolio_id=None, portfolio_name=None, query_instruments=False) | 从 Marquee 平台按 ID 或名称加载组合(当前推荐的入口) |
其中from_portfolio_id与from_portfolio_name自版本0.8.293起已被标记为 deprecated,官方建议改用Portfolio.get(portfolio_id=...)或Portfolio.get(portfolio_name=...)(见 portfolio.py)。get还接受query_instruments参数,置为True时会在加载组合的同时拉取工具详情。
保存到 Marquee:save / save_as_quote / save_to_shadowbook
Portfolio可以直接把工具上传到 Marquee 平台或保存为 quote / shadowbook,便于后续在平台侧复用或与他人共享。
save(overwrite=False)(portfolio.py):- 若组合包含子组合,抛出
ValueError('Cannot save portfolios with nested portfolios'); - 已有
id且未指定overwrite=True时抛错,避免覆盖; - 无
id时要求设置name,否则抛ValueError('name not set'); - 通过
GsPortfolioApi.create_portfolio创建 Marquee 组合,再以当前 PositionContext 的持仓日期把工具转成Position列表上传。
- 若组合包含子组合,抛出
save_as_quote(overwrite=False) -> str(portfolio.py):把组合连同定价日期与市场数据封装为RiskRequest保存为 quote,返回 quote ID;已有 quote_id 时同样需要overwrite=True才能覆盖。save_to_shadowbook(name)(portfolio.py):以给定名称保存到 shadowbook(影子簿记),返回保存状态并打印。
这三个方法都要求组合不含嵌套子组合,且都依赖PricingContext.current的定价日期与市场数据来确定保存快照的环境。相关 API 封装位于 gs_quant/api/gs/portfolios.py。
其他辅助能力:clone / scale / properties
clone(clone_instruments=False)(portfolio.py):深拷贝组合结构;clone_instruments=True时连同工具一起克隆,同时保留id与quote_id。该能力也被calc内部使用——PortfolioRiskResult持有的是组合的克隆而非引用,避免后续原地修改(如 resolve)污染结果对象(见 portfolio.py 的注释)。scale(scaling, in_place=True)(portfolio.py):对组合内所有工具按比例缩放。properties、as_dict、to_dict等来自InstrumentBase/PriceableImpl的通用协议,用于把工具或组合序列化为字典结构,与from_dict形成对称转换。
另外,同一模块还定义了Grid类(portfolio.py),它也是Portfolio的子类,通过给定基准工具、两个参数轴(x/y)与各自的取值列表,自动克隆并构建一个二维参数扫描网格——在敏感性分析场景(如同时扫描期限与利率水平)中可以直接复用 Portfolio 的定价与风险能力。
组合使用模式速览
把前面各节串起来,一个典型的完整工作流如下:
import datetime as dt from gs_quant.instrument import IRSwap from gs_quant.markets import PricingContext from gs_quant.markets.portfolio import Portfolio from gs_quant import risk # 1. 构造组合 swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.001, name='swap_10y@10bp') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.002, name='swap_10y@20bp') portfolio = Portfolio((swap1, swap2), name='my_book') # 2. 在定价上下文中批量计算风险 with PricingContext(pricing_date=dt.date(2020, 10, 15)): dollar_prices = portfolio.dollar_price() deltas = portfolio.calc(risk.IRDelta) # 3. 按工具与度量切片、聚合 print(dollar_prices.aggregate()) print(deltas[risk.IRDelta]['swap_10y@10bp']) # 4. 导出为 DataFrame / CSV,再反向重建 df = portfolio.to_frame() portfolio2 = Portfolio.from_frame(df) portfolio.to_csv('my_book.csv')小结与进一步阅读
Portfolio是 gs-quant 中聚合工具、执行批量定价与风险计算、与 Marquee 平台交互的核心容器。本文覆盖了其全部文档方法与属性:构造、动态修改、嵌套寻址、定价上下文内的calc/resolve/market、dict/JSON/DataFrame/CSV 序列化、平台保存(save / save_as_quote / save_to_shadowbook)以及 clone / scale 等辅助能力,并结合 test_portfolio.py 中的测试用例验证了各 API 的实际行为。
- 核心实现:gs_quant/markets/portfolio.py
- 路径与结果类型:gs_quant/risk/results.py
- 定价基类:gs_quant/priceable.py
- 定价上下文:
PricingContext/HistoricalPricingContext,参见 docs/markets.rst - 测试用例:gs_quant/test/markets/test_portfolio.py
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考