拆解OpenBB架构,搭建机构级的“全自动”投研系统

168 阅读12分钟

拆解OpenBB架构,搭建机构级的“全自动”投研系统

OpenBB 是一个开源的金融数据平台,它解决了一个长期存在的问题:金融数据源高度碎片化,每个数据提供商都有自己的 API 规范、认证机制和数据格式。这种碎片化让开发者需要为每个数据源编写适配代码,维护成本极高。OpenBB 通过构建统一的抽象层,让开发者用同一套 API 访问上百个数据源。 项目地址


OpenBB 技术细节

核心架构:Terminal 到 Platform 重构

最初的 OpenBB Terminal 采用的是典型的 Python CLI 架构。它使用 argparse 解析命令行参数,通过 cmd2 框架实现交互式终端,数据处理依赖 pandas,可视化使用 matplotlibplotly。这个架构的问题在于扩展性差,每增加一个数据源都需要修改核心代码,而且命令行界面限制了用户群体。

2023 年发布的 OpenBB Platform 进行了彻底的架构重写。新架构分为三层:Provider Layer、Data Layer 和 Interface Layer。Provider Layer 定义了标准化的数据获取接口,每个数据源实现为独立的 Provider。Data Layer 负责数据验证、缓存和转换。Interface Layer 提供多种访问方式,包括 Python SDK、REST API 和 Web UI。

from openbb import obb

obb.account.login(pat="your_personal_access_token")

result = obb.equity.price.historical(
    symbol="AAPL",
    start_date="2024-01-01",
    end_date="2024-12-31",
    provider="yfinance"
)

df = result.to_dataframe()

Provider 系统的技术细节

Provider 的实现基于 Pydantic 模型。每个数据端点定义两个模型:QueryParams 和 Data。QueryParams 描述输入参数及其验证规则,Data 描述输出数据的结构。这种强类型定义既能在运行时进行参数验证,又能自动生成 API 文档。

from pydantic import BaseModel, Field
from typing import Optional
from datetime import date

class EquityHistoricalQueryParams(BaseModel):
    symbol: str = Field(description="Stock symbol")
    start_date: Optional[date] = Field(default=None)
    end_date: Optional[date] = Field(default=None)
    interval: str = Field(default="1d")

class EquityHistoricalData(BaseModel):
    date: date
    open: float
    high: float
    low: float
    close: float
    volume: int
    adj_close: Optional[float] = None

每个 Provider 实现一个 Fetcher 类,包含三个核心方法:

  • transform_query() 将标准化的查询参数转换为特定 Provider 的格式。
  • extract_data() 执行实际的 API 调用并获取原始数据。
  • transform_data() 将原始数据转换为标准化的输出格式。
from openbb_core.provider.abstract.fetcher import Fetcher
import requests

class YFinanceEquityHistoricalFetcher(Fetcher):
    
    @staticmethod
    def transform_query(params: EquityHistoricalQueryParams):
        return {
            "ticker": params.symbol,
            "period1": int(params.start_date.timestamp()),
            "period2": int(params.end_date.timestamp()),
            "interval": params.interval
        }
    
    @staticmethod
    def extract_data(query: dict):
        url = f"https://query1.finance.yahoo.com/v8/finance/chart/{query['ticker']}"
        response = requests.get(url, params=query)
        return response.json()
    
    @staticmethod
    def transform_data(data: dict):
        chart = data['chart']['result'][0]
        timestamps = chart['timestamp']
        quotes = chart['indicators']['quote'][0]
        
        return [
            EquityHistoricalData(
                date=date.fromtimestamp(ts),
                open=quotes['open'][i],
                high=quotes['high'][i],
                low=quotes['low'][i],
                close=quotes['close'][i],
                volume=quotes['volume'][i]
            )
            for i, ts in enumerate(timestamps)
        ]

数据标准化的解决方案

不同数据源对同一概念使用不同的命名和格式。Yahoo Finance 使用 adj_close 表示调整后收盘价,Alpha Vantage 使用 adjusted_close,Polygon.io 使用 adjusted。OpenBB 需要将这些差异统一为标准字段名。

更复杂的是数据类型的差异。某些 Provider 返回的时间戳是 Unix epoch 格式的整数,某些返回 ISO 8601 字符串,还有些返回已经解析好的 datetime 对象。OpenBB 使用 Pydantic 的 validator 机制进行类型转换。

from pydantic import field_validator
from datetime import datetime

class EquityHistoricalData(BaseModel):
    date: datetime
    
    @field_validator('date', mode='before')
    @classmethod
    def parse_date(cls, v):
        if isinstance(v, int):
            return datetime.fromtimestamp(v)
        elif isinstance(v, str):
            return datetime.fromisoformat(v)
        return v

财务数据的单位转换也需要处理。有的 Provider 返回的市值单位是美元,有的是千美元,有的是百万美元。OpenBB 统一转换为基本单位(美元),并在元数据中标注原始单位。

@field_validator('market_cap', mode='before')
@classmethod
def normalize_market_cap(cls, v, info):
    unit = info.data.get('market_cap_unit', 'USD')
    if unit == 'K':
        return v * 1000
    elif unit == 'M':
        return v * 1000000
    elif unit == 'B':
        return v * 1000000000
    return v


REST API 的实现与性能优化

OpenBB Platform 使用 FastAPI 构建 REST API。FastAPI 基于 Starlette 和 Pydantic,提供了自动的请求验证、序列化和 OpenAPI 文档生成。API 的路由结构直接映射到 SDK 的模块结构,例如 obb.equity.price.historical() 对应 /api/v1/equity/price/historical

from fastapi import FastAPI, Query, Depends
from typing import Optional

app = FastAPI()

@app.get("/api/v1/equity/price/historical")
async def get_historical_price(
    symbol: str = Query(..., description="Stock symbol"),
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
    provider: str = Query("yfinance", description="Data provider")
):
    result = obb.equity.price.historical(
        symbol=symbol,
        start_date=start_date,
        end_date=end_date,
        provider=provider
    )
    return result.to_dict()

扩展性设计:自定义Provider的开发

OpenBB 的扩展性体现在开发者可以创建自己的 Provider。假设你需要集成一个企业内部的数据源,流程包括定义数据模型、实现 Fetcher 类、注册 Provider。

from openbb_core.provider.abstract.fetcher import Fetcher
from pydantic import BaseModel
import httpx

class InternalEquityData(BaseModel):
    symbol: str
    price: float
    timestamp: datetime

class InternalDataFetcher(Fetcher):
    
    @staticmethod
    def transform_query(params):
        return {"ticker": params.symbol}
    
    @staticmethod
    async def extract_data(query):
        async with httpx.AsyncClient() as client:
            response = await client.get(
                "https://internal-api.company.com/equity",
                params=query,
                headers={"Authorization": f"Bearer {API_TOKEN}"}
            )
            return response.json()
    
    @staticmethod
    def transform_data(data):
        return [
            InternalEquityData(
                symbol=item['ticker'],
                price=item['last_price'],
                timestamp=datetime.fromisoformat(item['ts'])
            )
            for item in data['results']
        ]

并发控制与资源管理

当同时处理多个请求时,OpenBB 使用 asyncio 进行并发控制。每个 Provider 有独立的连接池,避免单个慢速 Provider 阻塞其他请求。

import asyncio
from typing import List

async def fetch_multiple_symbols(symbols: List[str], provider: str):
    semaphore = asyncio.Semaphore(10)
    
    async def fetch_with_limit(symbol):
        async with semaphore:
            return await obb.equity.price.historical(
                symbol=symbol,
                provider=provider
            )
    
    tasks = [fetch_with_limit(symbol) for symbol in symbols]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    return results

对于外部 API 调用,使用 httpx.AsyncClient 配置连接池和超时参数。连接池大小设置为 100,每个连接的超时时间为 30 秒。这样可以在高并发场景下保持稳定的性能。

import httpx

client = httpx.AsyncClient(
    limits=httpx.Limits(max_keepalive_connections=100, max_connections=100),
    timeout=httpx.Timeout(30.0)
)

async def fetch_data(url: str, params: dict):
    try:
        response = await client.get(url, params=params)
        response.raise_for_status()
        return response.json()
    except httpx.HTTPStatusError as e:
        logger.error(f"HTTP error {e.response.status_code}: {e.response.text}")
        raise
    except httpx.TimeoutException:
        logger.error(f"Request timeout for {url}")
        raise

安装与使用

安装OpenBB与ODPCLI

可以通过运行以下命令从PyPI 包安装 ODP Python 包

pip install openbb

或者直接克隆存储库

git clone https://github.com/OpenBB-finance/OpenBB.git。

有关安装过程的更多信息,可以参阅OpenBB 文档

ODP CLI 安装 ODP CLI 是一个命令行界面,允许您直接从命令行访问 ODP。

可以通过运行以下命令进行安装。

pip install openbb-cli

或者直接克隆存储库

Linux 系统 Linux 用户在安装前需要采取一些额外的步骤。

RustCargo 必须安装到系统级别,并添加到 PATH 环境变量中。请按照屏幕上的说明进行安装,并将其添加到 shell 配置文件的 PATH 环境变量中。

curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh

OpenBB工作区使用说明

OpenBB Workspace 是一款面向企业级 AI 工作流程的安全应用程序。它将灵活的数据集成、可定制的 UI 组件和 AI 功能整合到一个解决方案中。

pro.openbb.co探索 OpenBB 工作区。

将开放数据平台集成到 OpenBB 工作区 在 Python (3.9.21 - 3.12) 环境中,使用几个简单的命令即可将此库连接到 OpenBB 工作区。

运行 ODP 后端

pip install "openbb[all]"

在本地主机上启动 API 服务器。

openbb-api

这将通过 Uvicorn 在 启动一个 FastAPI 服务器127.0.0.1:6900

将 ODP 后端集成到 OpenBB 工作区 登录OpenBB 工作区,并按照以下步骤操作:

  • 转到“应用”选项卡
  • 点击“连接后端”
  • 请填写以下表格:名称:开放数据平台网址:http ://127.0.0.1:6900
  • 点击“测试”。您应该会看到“测试成功”的提示,并显示找到的应用数量。
  • 点击“添加”。

OpenBB Workspace 现在以渐进式 Web 应用 (PWA) 的形式提供,可在您的所有设备上提供无缝、原生般的体验。

OpenBB 的开放数据平台 (ODP)

OpenBB 的开放数据平台 (ODP) 是一套开源工具集,可帮助数据工程师将专有、授权和公共数据源集成到下游应用程序(例如 AI 助手和研究仪表板)中。ODP 作为“一次连接,到处使用”的基础设施层,可同时将数据整合并暴露给多个平台:

  • 面向量化分析师的Python环境
  • 面向分析师的 OpenBB Workspace 和 Excel
  • 用于人工智能代理的 MCP 服务器
  • 其他应用程序的 REST API

API密钥 默认情况下,初始化和使用核心服务不需要授权。但是,大多数数据提供商需要 API 密钥才能访问其数据。密钥存储在本地对象的~/.openbb_platform/user_settings.json文件中credentials

本地 ~/.openbb_platform/凭据和用户偏好设置以 JSON 文件的形式存储在本地user_settings.json。该文件会在初始化 Python 客户端或 Fast API 获得授权时读取。如果该文件不存在,则会在首次运行时创建。以下架构可以复制/粘贴为模板:


{
  "credentials": {
    "fmp_api_key": "REPLACE",
    "polygon_api_key": "REPLACE",
    "benzinga_api_key": "REPLACE",
    "fred_api_key": "REPLACE",
    "nasdaq_api_key": "REPLACE",
    "intrinio_api_key": "REPLACE",
    "alpha_vantage_api_key": "REPLACE",
    "biztoc_api_key": "REPLACE",
    "tradier_api_key": "REPLACE",
    "tradier_account_type": "sandbox OR live",
    "tradingeconomics_api_key": "REPLACE",
    "tiingo_token": "REPLACE"
  }
}

REST API:HTTP 协议的标准化访问

用作0.0.0.0容器化或网络部署的主机。

uvicorn openbb_core.api.rest_api:app --host 0.0.0.0 --port 8000

默认设置提供了两种风格的交互式 API 文档:

Swagger: http://127.0.0.1:8000/docs

Redoc: http://127.0.0.1:8000/redoc

两者均提供详细的说明,且可以直接在页面上执行相关功能。


MCP Server

安装该软件包即可开始使用 ODP Python 包作为 MCP 服务器。

pip install openbb-mcp-server

服务器是通过命令行可执行文件启动的:

openbb-mcp

这将通过传输层在http://127.0.0.1:8001上启动服务器streamable-http,并将所有 GET 端点公开为 MCP 工具。

请按照以下步骤将服务器连接到WorkSpace


Workspace Integration:可视化分析平台 Workspace 是 OpenBB 的图形化前端,它通过内部 API 与 ODP 通信,为用户提供拖拽式的分析界面。

Dashboard 由多个 Widget 组成,每个 Widget 对应一个数据查询和可视化组件。Dashboard 的配置存储为 JSON 格式。

{
  "id": "dashboard-001",
  "name": "Market Overview",
  "layout": "grid",
  "widgets": [
    {
      "id": "widget-price-chart",
      "type": "equity.price.chart",
      "title": "AAPL Price Chart",
      "position": { "x": 0, "y": 0, "w": 6, "h": 4 },
      "config": {
        "symbol": "AAPL",
        "interval": "1d",
        "provider": "yfinance",
        "chart_type": "candlestick",
        "indicators": ["sma_20", "sma_50"]
      }
    },
    {
      "id": "widget-financials",
      "type": "equity.fundamental.table",
      "title": "Financial Metrics",
      "position": { "x": 6, "y": 0, "w": 6, "h": 4 },
      "config": {
        "symbol": "AAPL",
        "metrics": ["revenue", "net_income", "eps", "roe"],
        "period": "annual",
        "years": 5
      }
    }
  ]
}

自定义 Widget 需要实现三个核心方法:fetchDatatransformDatarender

import { Widget, WidgetConfig } from '@openbb/workspace-sdk'

interface CustomWidgetConfig extends WidgetConfig {
  symbol: string
  metric: string
}

export class CustomMetricWidget implements Widget<CustomWidgetConfig> {
  async fetchData(config: CustomWidgetConfig) {
    const response = await fetch(
      `http://localhost:6900/api/v1/equity/fundamental/metrics?symbol=${config.symbol}`,
      {
        headers: {
          'Authorization': `Bearer ${this.token}`
        }
      }
    )
    
    return response.json()
  }
  
  transformData(rawData: any, config: CustomWidgetConfig) {
    const results = rawData.results
    return results.map(item => ({
      date: item.date,
      value: item[config.metric]
    }))
  }
  
  render(data: any[], config: CustomWidgetConfig) {
    return (
      <div className="widget-container">
        <h3>{config.title}</h3>
        <LineChart
          data={data}
          width={600}
          height={300}
        >
          <Line dataKey="value" stroke="#8884d8" />
          <XAxis dataKey="date" />
          <YAxis />
          <Tooltip />
        </LineChart>
      </div>
    )
  }
}

OpenBB的商业化路径

变现路径一:量化研究服务的底层设施

个人量化交易者和小型对冲基金面临一个困境:他们需要专业级的数据分析能力,但无法承担 Bloomberg 或 FactSet 的费用。OpenBB 恰好填补了这个市场空白。

具体操作路径是将 OpenBB 包装为 API 服务。你可以在 AWS 或 DigitalOcean 上部署 OpenBB Platform,然后向客户提供接口调用服务。定价模型可以参考 Financial Modeling Prep 的分层结构:基础套餐每月 29 美元(每日 250 次调用),专业套餐每月 99 美元(每日 750 次调用)。

关键区别在于数据整合能力。单一数据源往往存在覆盖面不足的问题。OpenBB 允许你在同一个查询中聚合多个源的数据,然后提供统一的输出格式。比如某客户需要追踪特定行业的财务指标,你可以组合使用 Yahoo Finance 的价格数据、FRED 的宏观经济数据、SEC API 的财报数据,形成一个定制化的数据流。

技术门槛体现在数据清洗和标准化。不同数据源的字段命名、时间戳格式、货币单位都可能不一致。OpenBB 的 Provider 系统已经处理了大部分标准化工作,但针对特定客户需求,你仍需要编写转换逻辑。这种定制化服务正是利润来源。

from fastapi import FastAPI
from openbb import obb

app = FastAPI()

@app.get("/api/stock/{symbol}/financials")
async def get_financials(symbol: str, period: str = "annual"):
    income = obb.equity.fundamental.income(symbol, period=period, provider="fmp")
    balance = obb.equity.fundamental.balance(symbol, period=period, provider="fmp")
    cash = obb.equity.fundamental.cash(symbol, period=period, provider="fmp")
    
    return {
        "income_statement": income.to_dict(),
        "balance_sheet": balance.to_dict(),
        "cash_flow": cash.to_dict()
    }

变现路径二:投资研究报告自动化生成

卖方分析师每周需要产出大量行业研报。这个过程高度模式化:收集数据、计算财务比率、生成图表、撰写分析文字。OpenBB 可以自动化前三个步骤。

实现方式是构建一个报告生成引擎。用户输入股票代码和分析维度,系统自动拉取数据、计算指标、渲染图表,最后输出 PDF 或 Markdown 格式的报告。

这种服务可以按报告数量收费,每份报告 50-200 美元不等,取决于复杂度。 技术实现涉及几个模块:

  • 数据获取层调用 OpenBB 的 SDK;
  • 计算层使用 pandas 进行财务比率计算(P/E、ROE、Debt-to-Equity 等);
  • 可视化层使用 plotly 生成交互式图表;
  • 文档生成层使用 ReportLab 或 WeasyPrint 输出 PDF。
import pandas as pd
from openbb import obb

def generate_valuation_report(symbol: str):
    price_data = obb.equity.price.historical(symbol, provider="yfinance")
    income_stmt = obb.equity.fundamental.income(symbol, provider="fmp")
    balance_sheet = obb.equity.fundamental.balance(symbol, provider="fmp")
    
    eps = income_stmt['eps'].iloc[0]
    current_price = price_data['close'].iloc[-1]
    pe_ratio = current_price / eps
    
    total_debt = balance_sheet['total_debt'].iloc[0]
    total_equity = balance_sheet['total_equity'].iloc[0]
    debt_to_equity = total_debt / total_equity
    
    return {
        "symbol": symbol,
        "current_price": current_price,
        "pe_ratio": pe_ratio,
        "debt_to_equity": debt_to_equity
    }


结语

OpenBB 提供了技术基础,但技术本身不产生收入。真正的变现需要理解目标客户的痛点,然后用技术解决这些痛点。

OpenBB 的价值在于它降低了金融数据分析的准入门槛。过去需要几十万美元年费才能获得的能力,现在个人开发者也能掌握。这种技术趋势创造了大量商业机会。关键是找到自己的差异化定位,然后持续积累领域专业知识和客户资源。

💬 你用过哪款 AI 编程工具?欢迎评论区分享你的体验! ⭐ 觉得有用?点个「在看」让更多开发者看到这篇对比!