news 2026/9/15 18:47:46

gs-quant Portfolio 指南:用 Python 构建、定价与风险管理投资组合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gs-quant Portfolio 指南:用 Python 构建、定价与风险管理投资组合

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_idMarquee 平台上的组合 ID / quote ID

同时支持 Python 容器协议:len(portfolio)portfolio[i](整数索引或切片)、instrument in portfolioname 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_dictto_dictfrom_dictto_jsonfrom_jsonto_framefrom_frameto_csvfrom_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_idfrom_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时连同工具一起克隆,同时保留idquote_id。该能力也被calc内部使用——PortfolioRiskResult持有的是组合的克隆而非引用,避免后续原地修改(如 resolve)污染结果对象(见 portfolio.py 的注释)。
  • scale(scaling, in_place=True)(portfolio.py):对组合内所有工具按比例缩放。
  • propertiesas_dictto_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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 18:46:39

分布式系统服务根目录安全防护体系设计与实践

1. 服务根目录的安全防护体系设计在分布式系统架构中,服务根目录作为核心入口节点,其安全性直接关系到整个系统的稳定运行。我曾在某大型电商平台的微服务架构改造项目中,亲历过因根目录配置不当导致的全局性服务雪崩。那次事故让我们团队深刻…

作者头像 李华
网站建设 2026/9/15 18:44:11

MyBatis-Plus时间字段自动更新的四种实现方式与性能对比

1. MyBatis-Plus 时间字段自动更新的四种实现方式在数据库操作中,记录修改时间是一个高频需求。MyBatis-Plus 作为 MyBatis 的增强工具,提供了多种实现时间字段自动更新的方案。下面我将结合实战经验,详细介绍四种主流实现方式及其适用场景。…

作者头像 李华