# 数据存储机制
## 一、概述
ibkr-mcp 是一个基于 Interactive Brokers API 的期权交易辅助系统,主要功能包括期权链数据获取、股票历史数据下载、策略分析等。本文档详细介绍系统如何存储和管理这些市场数据。
系统的数据存储采用**双层存储架构**:关系型数据库用于结构化查询和高效检索,文件存储用于数据备份和分析用途。这种设计兼顾了数据完整性和查询性能,满足交易决策和历史分析的双重需求。
## 二、存储架构概览
### 2.1 双层存储设计
系统采用数据库与文件相结合的存储策略,两种存储方式并行运作,各司其职。数据库存储负责高频查询和结构化数据管理,文件存储则提供数据备份、分析导出和跨平台迁移能力。这种分层设计使得系统既能快速响应实时交易决策,又能保留完整的历史数据供后续分析使用。
数据库层基于 SQLAlchemy 2.0 ORM 构建,支持 SQLite、PostgreSQL 和 MySQL 三种数据库引擎。默认配置使用 SQLite,其轻量级特性适合个人交易者的使用场景,无需额外数据库服务即可运行。对于需要多用户访问或更高并发性能的用户,可以通过配置环境变量切换到 PostgreSQL 或 MySQL。
文件层使用 Parquet 列式存储格式和 CSV 文本格式。Parquet 格式具有优秀的压缩比和查询性能,适合存储大量历史数据;CSV 格式则便于数据导出和第三方工具处理。两种文件格式的命名遵循统一的约定,便于数据管理和检索。
### 2.2 存储目录结构
项目根目录下的数据存储结构如下所示:
```
ibkr-mcp/
├── optiondata/ # 期权链数据快照(Parquet 格式)
│ ├── AAPL_20251217_145506.parquet
│ ├── NFLX_20251217_150022.parquet
│ └── ...
│
├── historydata/ # 历史数据(CSV + Parquet)
│ ├── 20251217.csv # 每日汇总数据
│ ├── 20251226_stocks.csv # 股票每日数据
│ ├── AAPL_20251226_025823.parquet
│ └── ...
│
├── results/ # 分析结果
│ └── fetch_option_chain_live/
│ ├── snapshots/ # 期权链快照
│ └── history/ # 历史数据
│
└── ibkr_data.db # SQLite 数据库文件
```
## 三、数据库存储
### 3.1 数据库配置与管理
数据库管理由 `DatabaseManager` 类负责,该类封装了数据库连接、连接池和会话管理等核心功能。默认配置下,系统使用 SQLite 数据库,数据库文件名为 `ibkr_data.db`,存储在项目根目录。
```python
# 数据库配置参数
DB_PATH = "ibkr_data.db" # 数据库文件路径
DB_POOL_SIZE = 10 # 连接池大小
DB_MAX_OVERFLOW = 20 # 连接池最大溢出
DB_ECHO = false # 是否输出 SQL 日志
```
数据库连接支持通过环境变量进行自定义配置。设置 `DB_URL` 环境变量可以指定完整的数据库连接字符串,格式如下:
```
# SQLite(默认)
sqlite+aiosqlite:///ibkr_data.db
# PostgreSQL
postgresql+asyncpg://user:password@host:port/dbname
# MySQL
mysql+aiomysql://user:password@host:port/dbname
```
`DatabaseManager` 在初始化时会自动构建数据库 URL,并根据配置的数据库类型选择合适的异步引擎。连接池配置包括池大小、最大溢出连接数、超时时间和连接回收策略,确保系统在高并发场景下仍能稳定运行。
### 3.2 数据库模型定义
系统定义了两个核心数据模型,均采用扁平化设计,直接对应 CSV 格式,避免复杂的嵌套结构。这种设计理念使数据模型简洁直观,便于理解和维护,同时优化了数据库查询性能。
#### 3.2.1 期权链快照模型(OptionChainSnapshot)
`OptionChainSnapshot` 模型用于存储期权链的实时快照数据,每条记录代表一个期权合约。模型字段直接映射 CSV 列格式,一行记录一个合约,结构清晰明了。
```python
class OptionChainSnapshot(Base):
"""Option chain snapshot - one row per option contract."""
__tablename__ = "option_chain_snapshots"
# 核心标识字段
id = Column(Integer, primary_key=True, autoincrement=True)
symbol = Column(String(20), nullable=False, index=True) # 标的股票代码
expiry = Column(Date, nullable=False, index=True) # 到期日
strike = Column(PriceType, nullable=False) # 行权价
option_type = Column(String(4), nullable=False, index=True) # CALL 或 PUT
# 价格数据
bid = Column(PriceType) # 买价
ask = Column(PriceType) # 卖价
mark = Column(PriceType) # 中间价
price = Column(PriceType) # 最后成交价
# 希腊字母参数
delta = Column(Float) # Delta 值
gamma = Column(Float) # Gamma 值
vega = Column(Float) # Vega 值
theta = Column(Float) # Theta 值
rho = Column(Float) # Rho 值
implied_volatility = Column(Float) # 隐含波动率
# 元数据
underlying_price = Column(PriceType) # 标的资产价格
timestamp = Column(DateTime, nullable=False, index=True) # 快照时间(UTC)
created_at = Column(DateTime, default=datetime.utcnow) # 记录创建时间
```
该模型包含以下优化索引,支持常见查询模式:
| 索引名称 | 字段组合 | 用途 |
|---------|---------|------|
| idx_option_symbol_timestamp | symbol + timestamp | 按标的和时间查询 |
| idx_option_expiry | expiry | 按到期日查询 |
| idx_option_strike | strike | 按行权价查询 |
| idx_option_type | option_type | 筛选期权类型 |
| idx_option_symbol_expiry_type | symbol + expiry + type | 特定期权链查询 |
#### 3.2.2 股票数据模型(StockData)
`StockData` 模型用于存储股票的历史 OHLCV 数据,每条记录代表一个交易日的数据点。该模型遵循标准的金融数据格式,便于与其他分析工具兼容。
```python
class StockData(Base):
"""Stock price data - OHLCV format."""
__tablename__ = "stock_data"
# 核心标识字段
id = Column(Integer, primary_key=True, autoincrement=True)
symbol = Column(String(20), nullable=False, index=True) # 股票代码
timestamp = Column(DateTime, nullable=False, index=True) # 时间戳
# OHLCV 数据
open_price = Column(PriceType) # 开盘价
high_price = Column(PriceType) # 最高价
low_price = Column(PriceType) # 最低价
close_price = Column(PriceType) # 收盘价
volume = Column(Integer) # 成交量
adjusted_close = Column(PriceType) # 调整后收盘价
# 元数据
created_at = Column(DateTime, default=datetime.utcnow)
```
股票数据模型的优化索引如下:
| 索引名称 | 字段组合 | 用途 |
|---------|---------|------|
| idx_stock_symbol_timestamp | symbol + timestamp | 按股票和时间查询 |
| idx_stock_timestamp | timestamp | 按时间范围查询 |
| idx_stock_symbol | symbol | 按股票代码查询 |
## 四、期权链数据存储机制
### 4.1 数据获取流程
期权链数据的获取采用分层架构设计,从底层到顶层依次为:IBKR API 通信层、数据获取器(Fetcher)、服务层(Service)。这种分层设计使得各模块职责清晰,便于测试和维护。
#### 4.1.1 IBKRDataFetcher 数据获取器
`IBKRDataFetcher` 类是从 IBKR API 获取期权链数据的核心组件,位于 `core/option_data.py` 文件中。该类封装了与 IBKR TWS/Gateway 通信的所有细节,包括合约构建、API 调用和数据转换。
数据获取的主要流程包括以下步骤:
首先,获取标的资产信息。创建股票合约 `Stock(symbol, "SMART", "USD")`,并获取标的资产的实时价格,这一步骤为后续期权合约筛选提供基准。
其次,加载期权链元数据。调用 `reqSec_defOptParamsAsync` API 获取期权链的交易所、到期日和行权价信息。系统支持特定股票的交易所覆盖配置,可以根据不同股票的特性选择最优交易所。
第三,选择到期日和行权价。到期日选择策略优先选取近月合约和 LEAPS 远期合约,每个标的最多保留 32 个到期日。行权价选择以 ATM(价平)为中心,向两侧各延伸 8 档,总计约 16 个行权价。
第四,构建和限定合约。为每个到期日和行权价组合构建 CALL 和 PUT 合约,然后使用 `qualifyContractsAsync` 限定合约,确保合约信息的准确性。
最后,请求实时报价。调用 `reqTickersAsync` 并发获取所有合约的实时价格和 Greeks 数据,返回的每个 Ticker 对象包含买卖价、中间价和希腊字母参数。
#### 4.1.2 OptionDataService 服务层
`OptionDataService` 类是期权数据的服务层封装,提供 MCP 工具接口和调度任务支持。该类将底层数据获取器的复杂性隐藏,向上层提供简洁的服务接口。
```python
class OptionDataService(IBService):
"""Service wrapper for option chain retrieval."""
async def fetch_option_chains(symbols, mode="live"):
"""获取期权链数据(live 或 local 模式)。"""
async def run_scheduled_update():
"""执行定时更新任务。"""
async def run_once():
"""手动触发单次更新。"""
```
### 4.2 数据持久化机制
期权链数据采用三路存储策略,同时写入 Parquet 文件、CSV 文件和 SQLite 数据库。这种设计确保数据的可靠性、可用性和可分析性。
#### 4.2.1 Parquet 文件存储
Parquet 文件存储在 `optiondata/` 目录下,文件名格式为 `{SYMBOL}_{YYYYMMDD}_{HHMMSS}.parquet`。Parquet 是列式存储格式,具有优秀的压缩比和查询性能,特别适合存储大量结构化数据。
```python
# Parquet 文件路径示例
optiondata/AAPL_20251217_145506.parquet
optiondata/NFLX_20251217_150022.parquet
```
#### 4.2.2 CSV 文件存储
CSV 文件存储在 `historydata/` 目录下,追加写入每日汇总文件。CSV 格式便于数据导出和查看,格式如下:
```csv
symbol,expiry,strike,option_type,bid,ask,mark,delta,gamma,vega,theta,rho,implied_volatility,underlying_price,timestamp,price
AAPL,2026-01-16,220.0,CALL,5.50,5.80,5.65,0.65,0.03,0.25,-0.05,0.12,0.28,215.50,2025-12-23T10:30:00Z,5.65
AAPL,2026-01-16,220.0,PUT,5.20,5.50,5.35,-0.35,0.03,0.22,-0.08,-0.10,0.30,215.50,2025-12-23T10:30:00Z,5.35
```
#### 4.2.3 SQLite 数据库存储
每条期权合约记录插入 `option_chain_snapshots` 表,通过 Repository 层提供的数据访问接口进行管理。数据库存储支持复杂的条件查询和聚合操作,是实时交易决策的主要数据来源。
### 4.3 字段说明
期权链快照的完整字段说明如下:
| 字段名 | 类型 | 说明 |
|-------|------|------|
| symbol | String(20) | 标的股票代码,如 AAPL、MSFT |
| expiry | Date | 期权到期日,格式 YYYY-MM-DD |
| strike | Numeric(12,6) | 行权价 |
| option_type | String(4) | 期权类型,CALL 或 PUT |
| bid | Numeric(12,6) | 买一价 |
| ask | Numeric(12,6) | 卖一价 |
| mark | Numeric(12,6) | 中间价,计算方式为 (bid + ask) / 2 |
| price | Numeric(12,6) | 最后成交价 |
| delta | Float | Delta 值,衡量标的价格变化对期权价格的影响 |
| gamma | Float | Gamma 值,衡量 Delta 对标的价格变化的敏感度 |
| vega | Float | Vega 值,衡量隐含波动率变化对期权价格的影响 |
| theta | Float | Theta 值,衡量时间流逝对期权价格的影响 |
| rho | Float | Rho 值,衡量无风险利率变化对期权价格的影响 |
| implied_volatility | Float | 隐含波动率,通过期权价格反推 |
| underlying_price | Numeric(12,6) | 标的资产的当前价格 |
| timestamp | DateTime | 快照时间,UTC 时区 |
| created_at | DateTime | 记录创建时间 |
## 五、股票数据存储机制
### 5.1 数据获取流程
股票历史数据的获取同样采用分层架构设计。`StockDataFetcher` 类封装了 IBKR 历史数据 API 的调用逻辑,`StockDataService` 类提供上层服务接口。
#### 5.1.1 StockDataFetcher 数据获取器
`StockDataFetcher` 类负责从 IBKR API 获取股票的 OHLCV 历史数据,支持批量获取和增量更新。
```python
class StockDataFetcher:
"""Fetches historical stock data from IBKR."""
async def fetch_history(symbol, duration, bar_size, what_to_show):
"""获取单只股票的历史数据。"""
async def fetch_history_many(symbols, **kwargs):
"""批量获取多只股票的历史数据。"""
```
API 调用参数说明:
| 参数 | 默认值 | 说明 |
|-----|-------|------|
| duration | "90 D" | 数据时间范围,支持 D(天)、W(周)、M(月)、Y(年) |
| bar_size | "1 day" | K 线周期,支持 1 secs 到 1 month |
| what_to_show | "ADJUSTED_LAST" | 数据类型,ADJUSTED_LAST 为复权后价格 |
| use_rth | True | 是否仅使用常规交易时段数据 |
#### 5.1.2 增量更新机制
系统支持增量更新模式,避免重复下载已存在的数据。数据获取时首先查询数据库中该股票的最新时间戳,然后仅下载该时间戳之后的增量数据。这种机制显著减少了数据获取时间和 API 调用次数。
```python
# 增量更新逻辑示例
latest_timestamp = await stock_repo.get_latest_timestamp(symbol)
if latest_timestamp:
end_time = latest_timestamp # 从最新时间开始
else:
end_time = "" # 首次下载,从 90 天前开始
bars = await ib.reqHistoricalDataAsync(contract, endDateTime=end_time, ...)
```
### 5.2 数据持久化机制
股票数据的持久化同样采用数据库加文件的双层存储策略。
#### 5.2.1 数据库存储
每条股票日数据记录插入 `stock_data` 表,包含完整的 OHLCV 信息和调整后收盘价。数据按股票代码和时间戳建立复合索引,支持高效的范围查询和聚合计算。
#### 5.2.2 文件存储
股票数据的文件存储分为两种格式:
CSV 格式用于每日汇总,文件名格式为 `{YYYYMMDD}_stocks.csv`,追加写入:
```csv
symbol,timestamp,open,high,low,close,volume,snapshot_time
AAPL,2025-12-26,230.50,232.00,229.00,231.50,52000000,2025-12-26T03:00:00
```
Parquet 格式用于单只股票的独立快照,文件名格式为 `{SYMBOL}_{YYYYMMDD}_{HHMMSS}.parquet`。
### 5.3 字段说明
股票数据的完整字段说明如下:
| 字段名 | 类型 | 说明 |
|-------|------|------|
| symbol | String(20) | 股票代码,如 AAPL、GOOGL |
| timestamp | DateTime | 数据时间戳,UTC 时区 |
| open_price | Numeric(12,6) | 开盘价 |
| high_price | Numeric(12,6) | 最高价 |
| low_price | Numeric(12,6) | 最低价 |
| close_price | Numeric(12,6) | 收盘价 |
| volume | Integer | 成交量 |
| adjusted_close | Numeric(12,6) | 调整后收盘价,已复权处理 |
| created_at | DateTime | 记录创建时间 |
## 六、Repository 数据访问层
### 6.1 Repository 模式设计
系统采用 Repository 模式封装数据访问逻辑,将数据存储细节与业务逻辑分离。`BaseRepository` 提供通用的 CRUD 操作,`OptionChainRepository` 和 `StockDataRepository` 分别实现特定实体的数据访问方法。
这种设计模式的优势在于:统一的数据访问接口、便于单元测试、支持数据源切换。业务层代码通过 Repository 接口访问数据,无需关心底层存储细节。
### 6.2 OptionChainRepository 方法
`OptionChainRepository` 提供期权链数据的查询和管理接口:
| 方法名 | 功能说明 |
|-------|---------|
| save_contract() | 保存单个期权合约 |
| get_latest_contracts() | 获取最新合约快照 |
| get_contracts_by_expiry() | 按到期日查询合约 |
| get_historical_contracts() | 查询历史合约数据 |
| get_calls_and_puts() | 分别获取看涨和看跌合约 |
| get_by_strike_range() | 按行权价范围查询 |
| get_average_iv() | 计算平均隐含波动率 |
| cleanup_old_contracts() | 清理过期数据(默认保留 90 天) |
### 6.3 StockDataRepository 方法
`StockDataRepository` 提供股票数据的查询和管理接口:
| 方法名 | 功能说明 |
|-------|---------|
| save_stock_data() | 保存股票数据 |
| get_latest_price() | 获取最新价格 |
| get_historical_data() | 获取历史数据 |
| get_price_change() | 计算价格变化 |
| get_multiple_symbols_latest() | 批量获取多只股票最新价 |
| cleanup_old_data() | 清理旧数据(默认保留 365 天) |
## 七、配置与环境变量
### 7.1 配置文件
主配置文件 `config.yaml` 包含调度任务和数据获取的运行时配置:
```yaml
schedule:
enabled: true # 是否启用定时任务
times:
- "13:30" # 定时执行时间(美东时间)
timezone: "America/New_York" # 时区配置
symbols: [] # 监控的股票列表,空数组表示自动从持仓获取
mode: "live" # 数据获取模式:live(实时)或 local(本地)
market_data_type: "FROZEN" # 市场数据类型
```
### 7.2 环境变量配置
数据库和存储路径通过环境变量配置:
| 环境变量 | 默认值 | 说明 |
|---------|-------|------|
| DB_URL | - | 完整数据库连接 URL |
| DB_PATH | ibkr_data.db | SQLite 数据库文件路径 |
| DB_ECHO | false | 是否输出 SQL 日志 |
| DB_POOL_SIZE | 10 | 连接池大小 |
| DB_MAX_OVERFLOW | 20 | 连接池最大溢出连接数 |
## 八、数据管理最佳实践
### 8.1 数据清理策略
系统内置数据清理机制,防止数据库无限增长。期权链快照默认保留 90 天,股票数据默认保留 365 天。可以通过 Repository 方法自定义保留天数:
```python
# 清理 30 天前的期权合约数据
await option_repo.cleanup_old_contracts(days_to_keep=30)
# 清理 180 天前的股票数据
await stock_repo.cleanup_old_data(days_to_keep=180)
```
### 8.2 数据备份建议
对于重要的交易数据,建议定期执行以下备份操作:
第一,定期导出 CSV 文件。CSV 文件格式通用,可以导入到 Excel、Google Sheets 或其他数据分析工具中进行备份。
第二,保留 Parquet 快照。Parquet 文件保留了数据的完整结构,便于后续分析和数据迁移。
第三,数据库文件备份。可以直接复制 SQLite 数据库文件(`ibkr_data.db`)进行完整备份,注意备份时确保没有正在进行的写入操作。
### 8.3 查询性能优化
针对不同的查询场景,系统已配置相应的索引优化:
按标的和时间查询期权链时,系统会使用 `idx_option_symbol_timestamp` 索引,该复合索引覆盖了最常见的查询模式。查询时建议同时指定 symbol 和 timestamp 条件,以获得最佳查询性能。
按到期日筛选合约时,系统会使用 `idx_option_expiry` 索引。如果需要查询某一特定到期日的所有合约,该索引可以显著提升查询速度。
按股票代码和时间范围查询历史数据时,系统会使用 `idx_stock_symbol_timestamp` 索引。历史数据查询是策略分析中最常见的操作,该索引经过优化可以支持高效的范围扫描。