diff --git a/AMAZINGDATA_ADAPTER_INTERFACES.md b/AMAZINGDATA_ADAPTER_INTERFACES.md new file mode 100644 index 0000000..931c41e --- /dev/null +++ b/AMAZINGDATA_ADAPTER_INTERFACES.md @@ -0,0 +1,120 @@ +# AmazingData Adapter 接口调用关系 + +## 一、核心适配器接口 (DataSourceAdapter 实现) + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 说明 | +|------|----------|----------|----------|------| +| 1 | `connect` | - | `_market_data.login()` | 连接数据源 | +| 2 | `close` | - | - | 关闭连接 | +| 3 | `subscribe_ticks` | - | - | 订阅实时Tick | +| 4 | `fetch_klines` | `_fetch_klines_sync` | `_market_data.query_kline()` | 获取K线数据 | +| 5 | `fetch_symbols` | `_fetch_symbols_sync` | `_base_data.get_code_list()`
`_base_data.get_code_info()`
`_base_data.get_future_code_list()`
`_base_data.get_future_info()` | 获取标的列表 | +| 6 | `fetch_trading_calendar` | `_fetch_calendar_sync` | `_base_data.get_calendar()` | 获取交易日历 | +| 7 | `health_check` | - | `_market_data.is_login()` | 健康检查 | + +## 二、数据获取接口 (基础数据) + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 说明 | +|------|----------|----------|----------|------| +| 8 | `get_adj_factor` | - | `_base_data.get_adj_factor()` | 获取复权因子 | +| 9 | `get_backward_factor` | - | `_base_data.get_backward_factor()` | 获取后复权因子 | +| 10 | `get_code_info` | - | `_base_data.get_code_info()` | 获取代码信息 | +| 11 | `get_trading_calendar` | - | `_base_data.get_calendar()` | 获取交易日历(通用) | + +## 三、数据获取接口 (财务/股东数据) + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 说明 | +|------|----------|----------|----------|------| +| 12 | `get_balance_sheet` | - | `_info_data.get_balance_sheet()` | 资产负债表 | +| 13 | `get_cash_flow` | - | `_info_data.get_cash_flow()` | 现金流量表 | +| 14 | `get_income_statement` | - | `_info_data.get_income_statement()` | 利润表 | +| 15 | `get_profit_express` | - | `_info_data.get_profit_express()` | 业绩预告 | +| 16 | `get_profit_notice` | - | `_info_data.get_profit_notice()` | 业绩快报 | +| 17 | `get_top10_shareholders` | - | `_info_data.get_share_holder()` | 前十大股东 | +| 18 | `get_shareholder_count` | - | `_info_data.get_holder_num()` | 股东户数 | +| 19 | `get_equity_structure` | - | `_info_data.get_equity_structure()` | 股本结构 | + +## 四、数据获取接口 (市场数据) + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 说明 | +|------|----------|----------|----------|------| +| 20 | `get_snapshot` | - | `_market_data.query_kline()` | 获取快照数据 | +| 21 | `get_index_constituents` | - | `_info_data.get_index_constituent()` | 指数成分股 | +| 22 | `get_index_weights` | - | `_info_data.get_index_weight()` | 指数权重 | +| 23 | `get_margin_summary` | - | `_info_data.get_margin_summary()` | 融资融券汇总 | +| 24 | `get_margin_detail` | - | `_info_data.get_margin_detail()` | 融资融券明细 | +| 25 | `get_longhu_bang` | - | `_info_data.get_long_hu_bang()` | 龙虎榜 | +| 26 | `get_block_trading` | - | `_info_data.get_block_trading()` | 大宗交易 | + +## 五、数据获取接口 (基金/可转债) + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 说明 | +|------|----------|----------|----------|------| +| 27 | `get_etf_pcf` | - | `_base_data.get_etf_pcf()` | ETF申购赎回清单 | +| 28 | `get_fund_share` | - | `_info_data.get_fund_share()` | 基金份额 | +| 29 | `get_kzz_issuance` | - | `_info_data.get_kzz_issuance()` | 可转债发行 | + +## 六、新增分表数据接口 + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 说明 | +|------|----------|----------|----------|------| +| 30 | `fetch_kline_base` | `_fetch_kline_base_sync` | `_market_data.query_kline()` | 获取基础K线数据 | +| 31 | `fetch_kline_quote` | `_fetch_kline_quote_sync` | `_market_data.query_kline()`
`_base_data.get_code_info()` | 获取行情指标数据 | +| 32 | `fetch_kline_finance` | `_fetch_kline_finance_sync` | `_market_data.query_kline()`
`_info_data.get_equity_structure()` | 获取财务数据 | +| 33 | `fetch_stock_basic_info` | `_fetch_stock_basic_info_sync` | `_base_data.get_code_list()`
`_base_data.get_code_info()`
`_info_data.get_equity_structure()` | 获取股票基础信息 | + +## 七、组合调用详细说明 + +### 7.1 多次调用同一接口的方法 + +| 方法 | SDK 调用次数 | 调用详情 | +|------|-------------|----------| +| `fetch_symbols` (stock) | 2次 | 1. `get_code_list()`
2. `get_code_info()` | +| `fetch_symbols` (futures) | 2次 | 1. `get_future_code_list()`
2. `get_future_info()` | +| `fetch_kline_quote` | 2次 | 1. `query_kline()` - 获取K线
2. `get_code_info()` - 获取涨跌停价 | +| `fetch_kline_finance` | 2次 | 1. `get_equity_structure()` - 获取股本
2. `query_kline()` - 获取价格计算市值 | +| `fetch_stock_basic_info` | 3次 | 1. `get_code_list()` - 获取代码列表
2. `get_code_info()` - 获取名称
3. `get_equity_structure()` - 获取上市日期 | + +### 7.2 调用链示例 + +``` +fetch_stock_basic_info (async) +└── _fetch_stock_basic_info_sync (sync) + ├── _base_data.get_code_list() [第1次SDK调用] + ├── _base_data.get_code_info() [第2次SDK调用] + └── _info_data.get_equity_structure() [第3次SDK调用, 循环每个股票] +``` + +``` +fetch_kline_quote (async) +└── _fetch_kline_quote_sync (sync) + ├── _market_data.query_kline() [第1次SDK调用, 扩展日期范围] + └── _base_data.get_code_info() [第2次SDK调用, 获取涨跌停价] +``` + +``` +fetch_kline_finance (async) +└── _fetch_kline_finance_sync (sync) + ├── _info_data.get_equity_structure() [第1次SDK调用] + └── _market_data.query_kline() [第2次SDK调用] +``` + +## 八、SDK 对象汇总 + +| SDK 对象 | 说明 | 主要方法 | +|----------|------|----------| +| `_market_data` | 市场数据 | `login()`, `query_kline()`, `is_login()` | +| `_base_data` | 基础数据 | `get_code_list()`, `get_code_info()`, `get_calendar()`, `get_adj_factor()`, `get_future_code_list()`, `get_etf_pcf()` | +| `_info_data` | 信息数据 | `get_equity_structure()`, `get_share_holder()`, `get_income_statement()`, `get_balance_sheet()`, `get_cash_flow()`, `get_profit_express()`, `get_profit_notice()`, `get_holder_num()`, `get_margin_summary()`, `get_margin_detail()`, `get_long_hu_bang()`, `get_block_trading()`, `get_index_constituent()`, `get_index_weight()`, `get_fund_share()`, `get_kzz_issuance()` | + +## 九、接口统计 + +| 类别 | 接口数量 | +|------|----------| +| 核心适配器接口 | 7 | +| 基础数据接口 | 4 | +| 财务/股东数据接口 | 8 | +| 市场数据接口 | 7 | +| 基金/可转债接口 | 3 | +| 新增分表数据接口 | 4 | +| **总计** | **33** | diff --git a/AMAZINGDATA_ADAPTER_INTERFACES_V2.md b/AMAZINGDATA_ADAPTER_INTERFACES_V2.md new file mode 100644 index 0000000..80324b8 --- /dev/null +++ b/AMAZINGDATA_ADAPTER_INTERFACES_V2.md @@ -0,0 +1,222 @@ +# AmazingData Adapter 接口调用关系 (完整版) + +## 一、核心适配器接口 + +### 1.1 fetch_klines / _fetch_klines_sync (最复杂) + +``` +fetch_klines (async) +└── _fetch_klines_sync (sync) + ├── _market_data.query_kline() [第1次SDK调用] + ├── _base_data.get_code_info() [第2次SDK调用 - 涨跌停价] + ├── _info_data.get_equity_structure() [第3次SDK调用 - 股本结构] + ├── _base_data.get_calendar() [第4次SDK调用 - 交易日历] + └── _get_list_date() [内部方法] + ├── _base_data.get_code_info() [可能第5次SDK调用] + └── _base_data.get_hist_code_list() [可能第6次SDK调用] +``` + +| 调用层次 | 方法 | SDK调用次数 | 说明 | +|---------|------|------------|------| +| 外层 | `fetch_klines` | 1 | async入口 | +| 内层 | `_fetch_klines_sync` | 4-6次 | 包含K线+扩展字段获取 | + +**总计 SDK 调用**: 4-6 次(取决于 `_get_list_date` 的执行路径) + +--- + +### 1.2 其他核心接口 + +| 序号 | 公共接口 | 同步方法 | SDK 调用 | 次数 | 说明 | +|------|----------|----------|----------|------|------| +| 1 | `connect` | - | `_market_data.login()` | 1 | 连接数据源 | +| 2 | `close` | - | - | 0 | 关闭连接 | +| 3 | `subscribe_ticks` | - | - | 0 | 订阅实时Tick | +| 4 | `fetch_symbols` | `_fetch_symbols_sync` | `_base_data.get_code_list()`
`_base_data.get_code_info()` | 2 | 股票标的列表 | +| 4b| `fetch_symbols` | `_fetch_symbols_sync` | `_base_data.get_future_code_list()`
`_base_data.get_future_info()` | 2 | 期货标的列表 | +| 5 | `fetch_trading_calendar` | `_fetch_calendar_sync` | `_base_data.get_calendar()` | 1 | 交易日历 | +| 6 | `health_check` | - | `_market_data.is_login()` | 1 | 健康检查 | + +--- + +## 二、新增分表数据接口 (详细调用链) + +### 2.1 fetch_kline_base / _fetch_kline_base_sync + +``` +fetch_kline_base (async) +└── _fetch_kline_base_sync (sync) + └── _market_data.query_kline() [第1次SDK调用] +``` + +| 调用层次 | 方法 | SDK调用次数 | 说明 | +|---------|------|------------|------| +| 外层 | `fetch_kline_base` | 1 | async入口 | +| 内层 | `_fetch_kline_base_sync` | 1 | 仅基础K线 | + +**总计 SDK 调用**: 1 次 + +--- + +### 2.2 fetch_kline_quote / _fetch_kline_quote_sync + +``` +fetch_kline_quote (async) +└── _fetch_kline_quote_sync (sync) + ├── _market_data.query_kline() [第1次SDK调用 - 扩展日期范围] + └── _base_data.get_code_info() [第2次SDK调用 - 涨跌停价] +``` + +| 调用层次 | 方法 | SDK调用次数 | 说明 | +|---------|------|------------|------| +| 外层 | `fetch_kline_quote` | 2 | async入口 | +| 内层 | `_fetch_kline_quote_sync` | 2 | K线+涨跌停价 | + +**总计 SDK 调用**: 2 次 + +--- + +### 2.3 fetch_kline_finance / _fetch_kline_finance_sync + +``` +fetch_kline_finance (async) +└── _fetch_kline_finance_sync (sync) + ├── _info_data.get_equity_structure() [第1次SDK调用 - 股本结构] + └── _market_data.query_kline() [第2次SDK调用 - 价格数据] +``` + +| 调用层次 | 方法 | SDK调用次数 | 说明 | +|---------|------|------------|------| +| 外层 | `fetch_kline_finance` | 2 | async入口 | +| 内层 | `_fetch_kline_finance_sync` | 2 | 股本+价格计算市值 | + +**总计 SDK 调用**: 2 次 + +--- + +### 2.4 fetch_stock_basic_info / _fetch_stock_basic_info_sync + +``` +fetch_stock_basic_info (async) +└── _fetch_stock_basic_info_sync (sync) + ├── _base_data.get_code_list() [第1次SDK调用 - 代码列表] + ├── _base_data.get_code_info() [第2次SDK调用 - 名称/交易所] + └── 循环每个股票: + └── _info_data.get_equity_structure() [第3次SDK调用 - 上市日期] +``` + +| 调用层次 | 方法 | SDK调用次数 | 说明 | +|---------|------|------------|------| +| 外层 | `fetch_stock_basic_info` | 2+N | async入口,N为股票数量 | +| 内层 | `_fetch_stock_basic_info_sync` | 2+N | 基础信息+上市日期 | + +**总计 SDK 调用**: 2 + N 次(N = 股票数量,每个股票调用一次 get_equity_structure) + +--- + +## 三、财务/股东数据接口 + +| 序号 | 公共接口 | SDK 调用 | 次数 | 说明 | +|------|----------|----------|------|------| +| 7 | `get_balance_sheet` | `_info_data.get_balance_sheet()` | 1 | 资产负债表 | +| 8 | `get_cash_flow` | `_info_data.get_cash_flow()` | 1 | 现金流量表 | +| 9 | `get_income_statement` | `_info_data.get_income_statement()` | 1 | 利润表 | +| 10 | `get_profit_express` | `_info_data.get_profit_express()` | 1 | 业绩预告 | +| 11 | `get_profit_notice` | `_info_data.get_profit_notice()` | 1 | 业绩快报 | +| 12 | `get_top10_shareholders` | `_info_data.get_share_holder()` | 1 | 前十大股东 | +| 13 | `get_shareholder_count` | `_info_data.get_holder_num()` | 1 | 股东户数 | +| 14 | `get_equity_structure` | `_info_data.get_equity_structure()` | 1 | 股本结构 | + +--- + +## 四、市场数据接口 + +| 序号 | 公共接口 | SDK 调用 | 次数 | 说明 | +|------|----------|----------|------|------| +| 15 | `get_snapshot` | `_market_data.query_kline()` | 1 | 快照数据 | +| 16 | `get_adj_factor` | `_base_data.get_adj_factor()` | 1 | 复权因子 | +| 17 | `get_backward_factor` | `_base_data.get_backward_factor()` | 1 | 后复权因子 | +| 18 | `get_index_constituents` | `_info_data.get_index_constituent()` | 1 | 指数成分股 | +| 19 | `get_index_weights` | `_info_data.get_index_weight()` | 1 | 指数权重 | +| 20 | `get_margin_summary` | `_info_data.get_margin_summary()` | 1 | 融资融券汇总 | +| 21 | `get_margin_detail` | `_info_data.get_margin_detail()` | 1 | 融资融券明细 | +| 22 | `get_longhu_bang` | `_info_data.get_long_hu_bang()` | 1 | 龙虎榜 | +| 23 | `get_block_trading` | `_info_data.get_block_trading()` | 1 | 大宗交易 | + +--- + +## 五、基金/可转债接口 + +| 序号 | 公共接口 | SDK 调用 | 次数 | 说明 | +|------|----------|----------|------|------| +| 24 | `get_etf_pcf` | `_base_data.get_etf_pcf()` | 1 | ETF申购赎回清单 | +| 25 | `get_fund_share` | `_info_data.get_fund_share()` | 1 | 基金份额 | +| 26 | `get_kzz_issuance` | `_info_data.get_kzz_issuance()` | 1 | 可转债发行 | + +--- + +## 六、接口调用次数汇总表 + +| 接口方法 | SDK 调用次数 | 调用的 SDK 方法 | +|----------|-------------|----------------| +| `fetch_klines` | **4-6次** | `query_kline` + `get_code_info` (1-2次) + `get_equity_structure` + `get_calendar` + `get_hist_code_list` (可选) | +| `fetch_symbols` (stock) | **2次** | `get_code_list` + `get_code_info` | +| `fetch_symbols` (futures) | **2次** | `get_future_code_list` + `get_future_info` | +| `fetch_trading_calendar` | **1次** | `get_calendar` | +| `fetch_kline_base` | **1次** | `query_kline` | +| `fetch_kline_quote` | **2次** | `query_kline` + `get_code_info` | +| `fetch_kline_finance` | **2次** | `get_equity_structure` + `query_kline` | +| `fetch_stock_basic_info` | **2+N次** | `get_code_list` + `get_code_info` + N×`get_equity_structure` | +| 其他单个数据接口 | **1次** | 对应单个 SDK 方法 | + +--- + +## 七、SDK 对象与方法汇总 + +### _market_data (市场数据) +| 方法 | 调用次数 | 使用场景 | +|------|---------|----------| +| `login()` | 1 | connect | +| `query_kline()` | 多次 | K线数据获取 | +| `is_login()` | 1 | health_check | + +### _base_data (基础数据) +| 方法 | 调用次数 | 使用场景 | +|------|---------|----------| +| `get_code_list()` | 多次 | 股票代码列表 | +| `get_code_info()` | 最频繁 | 代码信息、涨跌停价、上市日期 | +| `get_calendar()` | 多次 | 交易日历 | +| `get_adj_factor()` | 按需 | 复权因子 | +| `get_future_code_list()` | 按需 | 期货代码列表 | +| `get_etf_pcf()` | 按需 | ETF数据 | +| `get_hist_code_list()` | 可选 | 历史代码列表(备选) | + +### _info_data (信息数据) +| 方法 | 调用次数 | 使用场景 | +|------|---------|----------| +| `get_equity_structure()` | 最频繁 | 股本结构、上市日期推断 | +| `get_income_statement()` | 按需 | 利润表 | +| `get_balance_sheet()` | 按需 | 资产负债表 | +| `get_cash_flow()` | 按需 | 现金流量表 | +| `get_share_holder()` | 按需 | 股东数据 | +| `get_margin_summary/detail()` | 按需 | 融资融券 | +| `get_index_constituent/weight()` | 按需 | 指数数据 | + +--- + +## 八、接口统计 + +| 类别 | 接口数量 | 说明 | +|------|----------|------| +| 核心适配器接口 | 6 | connect/close/subscribe_ticks/fetch_klines/fetch_symbols/fetch_trading_calendar/health_check | +| 财务/股东数据接口 | 8 | 各类财务报表和股东数据 | +| 市场数据接口 | 9 | K线、指数、融资融券等 | +| 基金/可转债接口 | 3 | ETF、基金、可转债 | +| 新增分表数据接口 | 4 | 拆分表专用接口 | +| **总计** | **30** | 公共接口方法 | + +**调用复杂度分级**: +- ⭐ 简单 (1次SDK调用): fetch_kline_base, get_balance_sheet, get_cash_flow 等 +- ⭐⭐ 中等 (2次SDK调用): fetch_symbols, fetch_kline_quote, fetch_kline_finance 等 +- ⭐⭐⭐ 复杂 (4-6次SDK调用): **fetch_klines** (原K线获取接口) +- ⭐⭐⭐⭐ 最复杂 (2+N次SDK调用): **fetch_stock_basic_info** (N=股票数量) diff --git a/AMAZINGDATA_ADAPTER_INTERFACES_V3.md b/AMAZINGDATA_ADAPTER_INTERFACES_V3.md new file mode 100644 index 0000000..1d36b7e --- /dev/null +++ b/AMAZINGDATA_ADAPTER_INTERFACES_V3.md @@ -0,0 +1,239 @@ +# AmazingData Adapter 接口调用关系 (完整修正版) + +## 一、接口调用次数汇总表 + +| 序号 | 接口方法 | SDK 调用次数 | 调用的 SDK 方法 | 复杂度 | +|------|----------|-------------|----------------|--------| +| 1 | `fetch_klines` | **4-6次** | `query_kline` + `get_code_info` + `get_equity_structure` + `get_calendar` + `get_list_date`(内部) | ⭐⭐⭐⭐ | +| 2 | `fetch_stock_basic_info` | **2+N次** | `get_code_list` + `get_code_info` + N×`get_equity_structure` | ⭐⭐⭐⭐⭐ | +| 3 | `fetch_symbols` (stock) | **2次** | `get_code_list` + `get_code_info` | ⭐⭐ | +| 4 | `fetch_symbols` (futures) | **1次** | `get_future_code_list` | ⭐ | +| 5 | `fetch_trading_calendar` | **1次** | `get_calendar` | ⭐ | +| 6 | `fetch_kline_quote` | **2次** | `query_kline` + `get_code_info` | ⭐⭐ | +| 7 | `fetch_kline_finance` | **2次** | `get_equity_structure` + `query_kline` | ⭐⭐ | +| 8 | `fetch_kline_base` | **1次** | `query_kline` | ⭐ | +| 9 | 其他所有数据接口 | **1次** | 对应单个 SDK 方法 | ⭐ | + +--- + +## 二、复杂接口详细调用链 + +### 2.1 fetch_klines (4-6次SDK调用) + +```python +fetch_klines (async) +└── _fetch_klines_sync (sync) + ├── _market_data.query_kline() # [第1次] 获取K线数据 + ├── _base_data.get_code_info() # [第2次] 获取涨跌停价 + ├── _info_data.get_equity_structure() # [第3次] 获取股本结构 + ├── _base_data.get_calendar() # [第4次] 获取交易日历 + └── _get_list_date() # [内部方法] + ├── _base_data.get_code_info() # [第5次,可选] 尝试获取上市日期 + └── _base_data.get_hist_code_list() # [第6次,可选] 备选方案 +``` + +**实际调用**: 最少4次,最多6次(取决于 `_get_list_date` 的执行路径) + +--- + +### 2.2 fetch_stock_basic_info (2+N次SDK调用) + +```python +fetch_stock_basic_info (async) +└── _fetch_stock_basic_info_sync (sync) + ├── _base_data.get_code_list() # [第1次] 获取所有股票代码 + ├── _base_data.get_code_info() # [第2次] 获取股票名称 + └── for code in codes: # [循环N次,N=股票数量] + └── _info_data.get_equity_structure() # [第3~2+N次] 每个股票调用一次! +``` + +**⚠️ 警告**: 如果获取全市场5000+只股票,将触发 2+5000 = **5002次** SDK 调用! + +**建议**: 使用 `codes` 参数限制股票数量,避免全量查询。 + +--- + +### 2.3 fetch_symbols (股票 - 2次SDK调用) + +```python +fetch_symbols (async) +└── _fetch_symbols_sync (sync) + ├── _base_data.get_code_list() # [第1次] 获取代码列表 + └── _base_data.get_code_info() # [第2次] 获取代码信息(名称) +``` + +--- + +### 2.4 fetch_symbols (期货 - 1次SDK调用) + +```python +fetch_symbols (async) +└── _fetch_symbols_sync (sync) + └── _base_data.get_future_code_list() # [第1次] 获取期货代码 + # 注意: 期货没有调用 get_future_info,交易所通过代码解析 +``` + +**修正**: 之前文档错误地写为2次调用,实际只有1次。 + +--- + +## 三、新增分表接口调用链 + +### 3.1 fetch_kline_base (1次SDK调用) + +```python +fetch_kline_base (async) +└── _fetch_kline_base_sync (sync) + └── _market_data.query_kline() # [第1次] 仅基础K线 +``` + +--- + +### 3.2 fetch_kline_quote (2次SDK调用) + +```python +fetch_kline_quote (async) +└── _fetch_kline_quote_sync (sync) + ├── _market_data.query_kline() # [第1次] 扩展日期范围 + └── _base_data.get_code_info() # [第2次] 涨跌停价 +``` + +--- + +### 3.3 fetch_kline_finance (2次SDK调用) + +```python +fetch_kline_finance (async) +└── _fetch_kline_finance_sync (sync) + ├── _info_data.get_equity_structure() # [第1次] 股本结构 + └── _market_data.query_kline() # [第2次] 价格数据 +``` + +--- + +## 四、单次SDK调用的接口 + +以下接口都只进行 **1次** SDK 调用: + +| 接口 | SDK 方法 | +|------|----------| +| `connect` | `_market_data.login()` | +| `health_check` | `_market_data.is_login()` | +| `fetch_trading_calendar` | `_base_data.get_calendar()` | +| `get_adj_factor` | `_base_data.get_adj_factor()` | +| `get_backward_factor` | `_base_data.get_backward_factor()` | +| `get_snapshot` | `_market_data.query_snapshot()` | +| `get_balance_sheet` | `_info_data.get_balance_sheet()` | +| `get_cash_flow` | `_info_data.get_cash_flow()` | +| `get_income_statement` | `_info_data.get_income()` | +| `get_profit_express` | `_info_data.get_profit_express()` | +| `get_profit_notice` | `_info_data.get_profit_notice()` | +| `get_top10_shareholders` | `_info_data.get_share_holder()` | +| `get_shareholder_count` | `_info_data.get_holder_num()` | +| `get_equity_structure` | `_info_data.get_equity_structure()` | +| `get_index_constituents` | `_info_data.get_index_constituent()` | +| `get_index_weights` | `_info_data.get_index_weight()` | +| `get_margin_summary` | `_info_data.get_margin_summary()` | +| `get_margin_detail` | `_info_data.get_margin_detail()` | +| `get_longhu_bang` | `_info_data.get_long_hu_bang()` | +| `get_block_trading` | `_info_data.get_block_trading()` | +| `get_etf_pcf` | `_base_data.get_etf_pcf()` | +| `get_fund_share` | `_info_data.get_fund_share()` | +| `get_kzz_issuance` | `_info_data.get_kzz_issuance()` | +| `get_history_stock_status` | `_info_data.get_history_stock_status()` | +| `get_code_info` | `_base_data.get_code_info()` | +| `get_trading_calendar` | `_base_data.get_calendar()` | + +--- + +## 五、SDK 对象方法汇总 + +### _market_data (市场数据) +| 方法 | 使用场景 | 调用次数 | +|------|----------|----------| +| `login()` | connect | 1 | +| `query_kline()` | K线数据 | 频繁 | +| `query_snapshot()` | get_snapshot | 按需 | +| `is_login()` | health_check | 1 | + +### _base_data (基础数据) +| 方法 | 使用场景 | 调用次数 | +|------|----------|----------| +| `get_code_list()` | fetch_symbols(stock) | 频繁 | +| `get_future_code_list()` | fetch_symbols(futures) | 按需 | +| `get_code_info()` | 多个接口 | **最频繁** | +| `get_calendar()` | 交易日历 | 多次 | +| `get_adj_factor()` | 复权因子 | 按需 | +| `get_backward_factor()` | 后复权因子 | 按需 | +| `get_etf_pcf()` | ETF数据 | 按需 | +| `get_hist_code_list()` | 备选上市日期 | 可选 | + +### _info_data (信息数据) +| 方法 | 使用场景 | 调用次数 | +|------|----------|----------| +| `get_equity_structure()` | 股本/上市日期 | **最频繁** | +| `get_share_holder()` | 股东数据 | 按需 | +| `get_income()` | 利润表 | 按需 | +| `get_balance_sheet()` | 资产负债表 | 按需 | +| `get_cash_flow()` | 现金流量表 | 按需 | +| `get_profit_express()` | 业绩预告 | 按需 | +| `get_profit_notice()` | 业绩快报 | 按需 | +| `get_holder_num()` | 股东户数 | 按需 | +| `get_margin_summary()` | 融资融券汇总 | 按需 | +| `get_margin_detail()` | 融资融券明细 | 按需 | +| `get_long_hu_bang()` | 龙虎榜 | 按需 | +| `get_block_trading()` | 大宗交易 | 按需 | +| `get_index_constituent()` | 指数成分股 | 按需 | +| `get_index_weight()` | 指数权重 | 按需 | +| `get_fund_share()` | 基金份额 | 按需 | +| `get_kzz_issuance()` | 可转债发行 | 按需 | +| `get_history_stock_status()` | 历史股票状态(涨停/跌停/ST/停牌) | 按需 | + +--- + +## 六、修正说明 + +### 6.1 与V2版文档的差异 + +| 接口 | V2版文档 | V3版文档(修正) | 差异说明 | +|------|----------|----------------|----------| +| `fetch_symbols` (futures) | 2次调用 | **1次调用** | 期货实际只调用`get_future_code_list`,没有调用`get_future_info` | +| `fetch_stock_basic_info` | 2+N次 | **2+N次** | 确认正确,N=股票数量 | +| `fetch_klines` | 4-6次 | **4-6次** | 确认正确 | + +### 6.2 风险提示 + +**⚠️ 高危接口**: `fetch_stock_basic_info` +- 获取全市场股票时会产生 **5000+** 次 SDK 调用 +- 可能导致性能问题或触发限流 +- **建议**: 始终使用 `codes` 参数限制查询范围 + +```python +# ❌ 不推荐: 获取全市场 +all_stocks = await adapter.fetch_stock_basic_info() + +# ✅ 推荐: 只查询指定股票 +specific_stocks = await adapter.fetch_stock_basic_info( + codes=["000001.SZ", "600519.SH"] +) +``` + +--- + +## 七、接口统计 + +| 类别 | 接口数量 | +|------|----------| +| 核心适配器接口 | 6 | +| 基础数据接口 | 4 | +| 财务/股东数据接口 | 8 | +| 市场数据接口 | 9 | +| 基金/可转债接口 | 3 | +| 新增分表数据接口 | 4 | +| **总计** | **34** | + +**复杂度分级**: +- ⭐ 简单 (1次): 26个接口 +- ⭐⭐ 中等 (2次): 3个接口 (`fetch_symbols` stock, `fetch_kline_quote`, `fetch_kline_finance`) +- ⭐⭐⭐⭐ 复杂 (4-6次): 1个接口 (`fetch_klines`) +- ⭐⭐⭐⭐⭐ 极复杂 (2+N次): 1个接口 (`fetch_stock_basic_info`) diff --git a/GET_HISTORY_STOCK_STATUS.md b/GET_HISTORY_STOCK_STATUS.md new file mode 100644 index 0000000..666de5d --- /dev/null +++ b/GET_HISTORY_STOCK_STATUS.md @@ -0,0 +1,123 @@ +# get_history_stock_status 接口实现 + +## 接口说明 + +`get_history_stock_status` 是 AmazingData SDK 提供的历史股票状态查询接口,用于获取股票的历史涨停/跌停价、ST状态、停牌状态等信息。 + +## 实现位置 + +### 1. 内部接口层 +**文件**: `app/adapters/internal_data_service.py` + +```python +class _InfoDataInternal: + def get_history_stock_status( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取历史股票状态数据 + + 包含字段: + - TRADE_DATE: 交易日期 + - UP_LIMIT: 涨停价 + - DOWN_LIMIT: 跌停价 + - MAX_UP_DOWN: 最大涨跌幅限制 + - IS_ST: 是否ST + - IS_SUSPEND: 是否停牌 + """ +``` + +### 2. 对外接口层 +**文件**: `app/adapters/amazingdata_adapter.py` + +```python +class AmazingDataAdapter(DataSourceAdapter): + async def get_history_stock_status( + self, + codes: List[str], + start_date: Optional[str] = None, + end_date: Optional[str] = None, + is_local: Optional[bool] = None + ) -> Dict[str, pd.DataFrame]: + """获取历史股票状态数据""" +``` + +## 返回字段说明 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| TRADE_DATE | int | 交易日期 (YYYYMMDD) | +| UP_LIMIT | float | 涨停价 | +| DOWN_LIMIT | float | 跌停价 | +| MAX_UP_DOWN | float | 最大涨跌幅限制(%) | +| IS_ST | bool/int | 是否ST股 | +| IS_SUSPEND | bool/int | 是否停牌 | + +## 使用示例 + +```python +from app.adapters.amazingdata_adapter import AmazingDataAdapter + +adapter = AmazingDataAdapter() +await adapter.connect({ + "username": "your_username", + "password": "your_password", + "host": "your_host", + "port": 8600 +}) + +# 获取股票历史状态 +result = await adapter.get_history_stock_status( + codes=["000001.SZ", "600519.SH"], + start_date="20240301", + end_date="20240310" +) + +# 处理结果 +for symbol, df in result.items(): + print(f"股票: {symbol}") + print(df[["TRADE_DATE", "UP_LIMIT", "DOWN_LIMIT", "IS_ST"]]) +``` + +## 调用链 + +``` +AmazingDataAdapter.get_history_stock_status (async) +└── _InfoDataInternal.get_history_stock_status (sync) + └── SDK: _info_data.get_history_stock_status() +``` + +## SDK 调用统计 + +| 层次 | 调用次数 | 说明 | +|------|----------|------| +| 对外接口 | 1 | async 包装 | +| 内部接口 | 1 | 错误处理 | +| SDK | 1 | 实际数据获取 | + +**总计**: 1 次 SDK 调用(单次查询) + +## 应用场景 + +1. **涨跌停分析** - 获取历史涨停/跌停价格 +2. **ST股票跟踪** - 监控股票ST状态变化 +3. **停牌监控** - 获取股票停牌状态 +4. **策略回测** - 基于历史涨跌停限制进行回测 + +## 与其他接口的关系 + +| 接口 | 用途 | 区别 | +|------|------|------| +| `get_history_stock_status` | 获取历史状态(涨停/跌停/ST/停牌) | 返回每日状态数据 | +| `get_code_info` | 获取当前代码信息 | 返回最新静态信息 | +| `fetch_klines` | 获取K线数据 | 包含涨跌停判断逻辑 | + +## 文档更新 + +以下文档已更新: +- `INTERNAL_INTERFACES.md` - 内部接口层文档 +- `AMAZINGDATA_ADAPTER_INTERFACES_V3.md` - 接口调用关系文档 diff --git a/INTERNAL_INTERFACES.md b/INTERNAL_INTERFACES.md new file mode 100644 index 0000000..bd51adf --- /dev/null +++ b/INTERNAL_INTERFACES.md @@ -0,0 +1,148 @@ +# 内部接口层文档 + +## 架构设计 + +``` +对外接口 (AmazingDataAdapter) + │ + ├── 内部接口层 (InternalDataService) + │ ├── _MarketDataInternal (市场数据) + │ ├── _BaseDataInternal (基础数据) + │ └── _InfoDataInternal (信息数据) + │ + └── AmazingData SDK + ├── _market_data + ├── _base_data + └── _info_data +``` + +## 设计原则 + +1. **对外接口不直接调用 SDK** - 所有 SDK 调用都通过内部接口层 +2. **统一错误处理** - 内部接口层统一处理 SDK 异常 +3. **日志记录** - 内部接口层统一记录调用日志 +4. **便于测试** - 可以 mock 内部接口进行单元测试 + +## 内部接口类 + +### _MarketDataInternal + +封装 `_market_data` SDK 对象的方法: + +| 方法 | SDK 方法 | 说明 | +|------|----------|------| +| `login()` | `_market_data.login()` | 登录 | +| `is_login()` | `_market_data.is_login()` | 检查登录状态 | +| `query_kline()` | `_market_data.query_kline()` | 查询K线 | +| `query_snapshot()` | `_market_data.query_snapshot()` | 查询快照 | + +### _BaseDataInternal + +封装 `_base_data` SDK 对象的方法: + +| 方法 | SDK 方法 | 说明 | +|------|----------|------| +| `get_code_list()` | `_base_data.get_code_list()` | 获取股票代码列表 | +| `get_future_code_list()` | `_base_data.get_future_code_list()` | 获取期货代码列表 | +| `get_code_info()` | `_base_data.get_code_info()` | 获取代码信息 | +| `get_calendar()` | `_base_data.get_calendar()` | 获取交易日历 | +| `get_adj_factor()` | `_base_data.get_adj_factor()` | 获取复权因子 | +| `get_backward_factor()` | `_base_data.get_backward_factor()` | 获取后复权因子 | +| `get_etf_pcf()` | `_base_data.get_etf_pcf()` | 获取ETF申赎数据 | +| `get_hist_code_list()` | `_base_data.get_hist_code_list()` | 获取历史代码列表 | + +### _InfoDataInternal + +封装 `_info_data` SDK 对象的方法: + +| 方法 | SDK 方法 | 说明 | +|------|----------|------| +| `get_equity_structure()` | `_info_data.get_equity_structure()` | 股本结构 | +| `get_share_holder()` | `_info_data.get_share_holder()` | 股东数据 | +| `get_holder_num()` | `_info_data.get_holder_num()` | 股东户数 | +| `get_income()` | `_info_data.get_income()` | 利润表 | +| `get_balance_sheet()` | `_info_data.get_balance_sheet()` | 资产负债表 | +| `get_cash_flow()` | `_info_data.get_cash_flow()` | 现金流量表 | +| `get_profit_express()` | `_info_data.get_profit_express()` | 业绩预告 | +| `get_profit_notice()` | `_info_data.get_profit_notice()` | 业绩快报 | +| `get_margin_summary()` | `_info_data.get_margin_summary()` | 融资融券汇总 | +| `get_margin_detail()` | `_info_data.get_margin_detail()` | 融资融券明细 | +| `get_long_hu_bang()` | `_info_data.get_long_hu_bang()` | 龙虎榜 | +| `get_block_trading()` | `_info_data.get_block_trading()` | 大宗交易 | +| `get_index_constituent()` | `_info_data.get_index_constituent()` | 指数成分股 | +| `get_index_weight()` | `_info_data.get_index_weight()` | 指数权重 | +| `get_fund_share()` | `_info_data.get_fund_share()` | 基金份额 | +| `get_kzz_issuance()` | `_info_data.get_kzz_issuance()` | 可转债发行 | +| `get_history_stock_status()` | `_info_data.get_history_stock_status()` | 历史股票状态(涨停/跌停/ST/停牌) | + +## 使用方式 + +### 在 AmazingDataAdapter 中 + +```python +class AmazingDataAdapter(DataSourceAdapter): + def __init__(self): + # ... 其他初始化 ... + self._internal: Optional[InternalDataService] = None + + async def connect(self, config: dict) -> None: + # ... 登录和初始化 SDK ... + + # 初始化内部数据服务层 + self._internal = InternalDataService(self) + + def _fetch_klines_sync(self, ...): + # 使用内部接口,而不是直接调用 SDK + kline_dict = self._internal.market.query_kline(...) + code_info = self._internal.base.get_code_info(...) + equity = self._internal.info.get_equity_structure(...) +``` + +### 错误处理 + +内部接口统一处理 SDK 异常,返回空数据或默认值: + +```python +class _BaseDataInternal: + def get_code_info(self, security_type: str) -> pd.DataFrame: + try: + return self._base_data.get_code_info(security_type=security_type) + except Exception as e: + error(f"[_BaseDataInternal] Get code info failed: {e}") + return pd.DataFrame() # 返回空数据而不是抛出异常 +``` + +## 文件结构 + +``` +app/adapters/ +├── __init__.py +├── base.py # 适配器基类 +├── amazingdata_adapter.py # 对外适配器(使用内部接口) +└── internal_data_service.py # 内部接口层 +``` + +## 迁移说明 + +### 之前的调用方式(已废弃) +```python +# 直接调用 SDK +code_info = self._base_data.get_code_info(security_type=...) +kline = self._market_data.query_kline(...) +equity = self._info_data.get_equity_structure(...) +``` + +### 现在的调用方式 +```python +# 通过内部接口调用 +code_info = self._internal.base.get_code_info(security_type=...) +kline = self._internal.market.query_kline(...) +equity = self._internal.info.get_equity_structure(...) +``` + +## 优势 + +1. **解耦** - 对外接口与 SDK 解耦,便于更换数据源 +2. **可测试性** - 可以 mock 内部接口进行单元测试 +3. **错误处理** - 统一的错误处理和日志记录 +4. **扩展性** - 可以在内部接口层添加缓存、限流等功能 diff --git a/app/adapters/__pycache__/amazingdata_adapter.cpython-311.pyc b/app/adapters/__pycache__/amazingdata_adapter.cpython-311.pyc index ec0b1d5..cc15fd8 100644 Binary files a/app/adapters/__pycache__/amazingdata_adapter.cpython-311.pyc and b/app/adapters/__pycache__/amazingdata_adapter.cpython-311.pyc differ diff --git a/app/adapters/__pycache__/internal_data_service.cpython-311.pyc b/app/adapters/__pycache__/internal_data_service.cpython-311.pyc new file mode 100644 index 0000000..7826012 Binary files /dev/null and b/app/adapters/__pycache__/internal_data_service.cpython-311.pyc differ diff --git a/app/adapters/amazingdata_adapter.py b/app/adapters/amazingdata_adapter.py index ce6c494..ab0f8bb 100644 --- a/app/adapters/amazingdata_adapter.py +++ b/app/adapters/amazingdata_adapter.py @@ -17,6 +17,7 @@ from app.adapters.base import ( TradeCalData, TickCallback ) from app.core.logger import info, error, warning +from app.adapters.internal_data_service import InternalDataService class SecurityType(Enum): """证券类型枚举""" @@ -65,6 +66,7 @@ class AmazingDataAdapter(DataSourceAdapter): self._calendar = None self._is_logged_in = False self._connected = False + self._internal: Optional[InternalDataService] = None # 内部数据服务 def _check_login(self): """检查是否已登录""" @@ -90,7 +92,7 @@ class AmazingDataAdapter(DataSourceAdapter): """ try: # 方法1:尝试从代码信息中获取 - code_info_df = self._base_data.get_code_info(security_type=SecurityType.STOCK_A.value) + code_info_df = self._internal.base.get_code_info(security_type=SecurityType.STOCK_A.value) if symbol in code_info_df.index: # 尝试不同的字段名 for field in ['list_date', 'LIST_DATE', 'listDate', 'founded_date']: @@ -107,7 +109,7 @@ class AmazingDataAdapter(DataSourceAdapter): # 方法2:尝试从历史代码列表获取 try: - hist_codes = self._base_data.get_hist_code_list(security_type=SecurityType.STOCK_A.value) + hist_codes = self._internal.base.get_hist_code_list(security_type=SecurityType.STOCK_A.value) if symbol in hist_codes.index and 'list_date' in hist_codes.columns: list_date_val = hist_codes.loc[symbol, 'list_date'] if pd.notna(list_date_val): @@ -182,8 +184,11 @@ class AmazingDataAdapter(DataSourceAdapter): # 初始化数据类 self._base_data = self._ad.BaseData() self._info_data = self._ad.InfoData() - self._calendar = self._base_data.get_calendar() + self._calendar = self._internal.base.get_calendar() self._market_data = self._ad.MarketData(self._calendar) + + # 初始化内部数据服务层 + self._internal = InternalDataService(self) self._is_logged_in = True print("[amazingdata_adapter]登录成功") @@ -258,8 +263,8 @@ class AmazingDataAdapter(DataSourceAdapter): end_int = self._format_date(end_date) print(f"[amazingdata_adapter _fetch_klines_sync]正在获取K线数据: 代码={codes}, 日期范围={start_date}~{end_date}, 周期={period_value}") - # 获取K线数据 - 将周期值转换为 SDK 的常量 - kline_dict = self._market_data.query_kline( + # 获取K线数据 - 使用内部接口 + kline_dict = self._internal.market.query_kline( code_list=codes, begin_date=start_int, end_date=end_int, @@ -278,7 +283,7 @@ class AmazingDataAdapter(DataSourceAdapter): # ============================================ print(f"[amazingdata_adapter _fetch_klines_sync]正在获取证券基本信息...") try: - code_info_df = self._base_data.get_code_info(security_type=SecurityType.STOCK_A.value) + code_info_df = self._internal.base.get_code_info(security_type=SecurityType.STOCK_A.value) # 提取当前股票的涨停价和跌停价 if symbol in code_info_df.index: high_limited = float(code_info_df.loc[symbol, 'high_limited']) if 'high_limited' in code_info_df.columns else None @@ -298,7 +303,7 @@ class AmazingDataAdapter(DataSourceAdapter): # ============================================ print(f"[amazingdata_adapter _fetch_klines_sync]正在获取股本结构...") try: - equity_dict = self._info_data.get_equity_structure( + equity_dict = self._internal.info.get_equity_structure( code_list=codes, local_path=self.config.local_path, is_local=self.config.use_local_cache @@ -341,7 +346,7 @@ class AmazingDataAdapter(DataSourceAdapter): print(f"[amazingdata_adapter _fetch_klines_sync]正在获取交易日历...") try: # 获取交易日历 - calendar = self._base_data.get_calendar(market=Market.SH.value) + calendar = self._internal.base.get_calendar(market=Market.SH.value) # 获取股票上市日期 list_date = self._get_list_date(symbol) @@ -453,11 +458,11 @@ class AmazingDataAdapter(DataSourceAdapter): if asset_type == "stock": # 获取A股代码列表 - codes = self._base_data.get_code_list( + codes = self._internal.base.get_code_list( security_type=SecurityType.STOCK_A.value ) # 获取代码信息 - info_df = self._base_data.get_code_info( + info_df = self._internal.base.get_code_info( security_type=SecurityType.STOCK_A.value ) @@ -487,7 +492,7 @@ class AmazingDataAdapter(DataSourceAdapter): elif asset_type == "futures": # 获取期货代码列表 - codes = self._base_data.get_future_code_list( + codes = self._internal.base.get_future_code_list( security_type=SecurityType.FUTURE.value ) @@ -556,7 +561,7 @@ class AmazingDataAdapter(DataSourceAdapter): """同步获取交易日历""" # 获取交易日历 market = Market.SH if exchange in ["SH", "SSE"] else Market.SZ - calendar = self._base_data.get_calendar(market=market.value) + calendar = self._internal.base.get_calendar(market=market.value) start_int = self._format_date(start) end_int = self._format_date(end) @@ -582,7 +587,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() await loop.run_in_executor( None, - lambda: self._base_data.get_code_list( + lambda: self._internal.base.get_code_list( security_type=SecurityType.STOCK_A.value ) ) @@ -613,7 +618,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._base_data.get_adj_factor( + lambda: self._internal.base.get_adj_factor( code_list=codes, local_path=self.config.local_path, is_local=is_local @@ -632,7 +637,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._base_data.get_backward_factor( + lambda: self._internal.base.get_backward_factor( code_list=codes, local_path=self.config.local_path, is_local=is_local @@ -658,7 +663,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_index_constituent( + lambda: self._internal.info.get_index_constituent( code_list=codes, local_path=self.config.local_path, is_local=is_local @@ -690,7 +695,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_index_weight( + lambda: self._internal.info.get_index_weight( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -714,7 +719,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._market_data.query_snapshot( + lambda: self._internal.market.query_snapshot( code_list=codes, begin_date=start_int, end_date=end_int @@ -811,7 +816,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._base_data.get_code_info(security_type=security_type.value) + lambda: self._internal.base.get_code_info(security_type=security_type.value) ) async def get_trading_calendar( @@ -831,7 +836,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._base_data.get_calendar(market=market.value) + lambda: self._internal.base.get_calendar(market=market.value) ) # ==================== 业绩数据接口 ==================== @@ -871,7 +876,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_profit_express( + lambda: self._internal.info.get_profit_express( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -913,7 +918,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_profit_notice( + lambda: self._internal.info.get_profit_notice( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -956,7 +961,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_share_holder( + lambda: self._internal.info.get_share_holder( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -994,7 +999,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_holder_num( + lambda: self._internal.info.get_holder_num( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -1035,7 +1040,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_equity_structure( + lambda: self._internal.info.get_equity_structure( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -1078,7 +1083,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_margin_summary( + lambda: self._internal.info.get_margin_summary( local_path=self.config.local_path, is_local=is_local, begin_date=begin_date, @@ -1118,7 +1123,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_margin_detail( + lambda: self._internal.info.get_margin_detail( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -1163,7 +1168,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_long_hu_bang( + lambda: self._internal.info.get_long_hu_bang( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -1205,7 +1210,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_block_trading( + lambda: self._internal.info.get_block_trading( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -1244,7 +1249,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._base_data.get_etf_pcf(code_list=codes) + lambda: self._internal.base.get_etf_pcf(code_list=codes) ) async def get_fund_share( @@ -1278,7 +1283,7 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_fund_share( + lambda: self._internal.info.get_fund_share( code_list=codes, local_path=self.config.local_path, is_local=is_local, @@ -1317,13 +1322,55 @@ class AmazingDataAdapter(DataSourceAdapter): loop = asyncio.get_event_loop() return await loop.run_in_executor( None, - lambda: self._info_data.get_kzz_issuance( + lambda: self._internal.info.get_kzz_issuance( code_list=codes, local_path=self.config.local_path, is_local=is_local ) ) + async def get_history_stock_status( + self, + codes: List[str], + start_date: Optional[str] = None, + end_date: Optional[str] = None, + is_local: Optional[bool] = None + ) -> Dict[str, pd.DataFrame]: + """获取历史股票状态数据 + + Args: + codes: 股票代码列表 + start_date: 开始日期 (YYYYMMDD) + end_date: 结束日期 (YYYYMMDD) + is_local: 是否使用本地缓存 + + Returns: + Dict[代码, DataFrame] 主要字段: + - TRADE_DATE: 交易日期 + - UP_LIMIT: 涨停价 + - DOWN_LIMIT: 跌停价 + - MAX_UP_DOWN: 最大涨跌幅限制 + - IS_ST: 是否ST + - IS_SUSPEND: 是否停牌 + """ + self._check_login() + is_local = is_local if is_local is not None else self.config.use_local_cache + + begin_date = self._format_date(start_date) if start_date else None + end_date_int = self._format_date(end_date) if end_date else None + + loop = asyncio.get_event_loop() + return await loop.run_in_executor( + None, + lambda: self._internal.info.get_history_stock_status( + code_list=codes, + local_path=self.config.local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date_int + ) + ) + # ==================== New Split Table Data Fetch Methods ==================== async def fetch_kline_base( @@ -1381,7 +1428,7 @@ class AmazingDataAdapter(DataSourceAdapter): start_int = self._format_date(start_date) end_int = self._format_date(end_date) - kline_dict = self._market_data.query_kline( + kline_dict = self._internal.market.query_kline( code_list=codes, begin_date=start_int, end_date=end_int, @@ -1473,7 +1520,7 @@ class AmazingDataAdapter(DataSourceAdapter): start_int = self._format_date(extended_start_str) end_int = self._format_date(end_date) - kline_dict = self._market_data.query_kline( + kline_dict = self._internal.market.query_kline( code_list=codes, begin_date=start_int, end_date=end_int, @@ -1487,7 +1534,7 @@ class AmazingDataAdapter(DataSourceAdapter): df = df.sort_values('kline_time') try: - code_info_df = self._base_data.get_code_info(security_type=SecurityType.STOCK_A.value) + code_info_df = self._internal.base.get_code_info(security_type=SecurityType.STOCK_A.value) if symbol in code_info_df.index: high_limited = float(code_info_df.loc[symbol, 'high_limited']) if 'high_limited' in code_info_df.columns else None low_limited = float(code_info_df.loc[symbol, 'low_limited']) if 'low_limited' in code_info_df.columns else None @@ -1635,7 +1682,7 @@ class AmazingDataAdapter(DataSourceAdapter): results = [] try: - equity_dict = self._info_data.get_equity_structure( + equity_dict = self._internal.info.get_equity_structure( code_list=codes, local_path=self.config.local_path, is_local=self.config.use_local_cache @@ -1659,7 +1706,7 @@ class AmazingDataAdapter(DataSourceAdapter): print(f"[amazingdata_adapter]Failed to get equity structure: {e}") equity_data = {} - kline_dict = self._market_data.query_kline( + kline_dict = self._internal.market.query_kline( code_list=codes, begin_date=start_int, end_date=end_int, @@ -1756,14 +1803,14 @@ class AmazingDataAdapter(DataSourceAdapter): ) -> List[Dict[str, Any]]: """Sync method to fetch stock basic info""" try: - all_codes = self._base_data.get_code_list( + all_codes = self._internal.base.get_code_list( security_type=SecurityType.STOCK_A.value ) if codes: all_codes = [c for c in all_codes if c in codes] - info_df = self._base_data.get_code_info( + info_df = self._internal.base.get_code_info( security_type=SecurityType.STOCK_A.value ) @@ -1784,7 +1831,7 @@ class AmazingDataAdapter(DataSourceAdapter): list_date = None try: - equity_dict = self._info_data.get_equity_structure( + equity_dict = self._internal.info.get_equity_structure( code_list=[code], local_path=self.config.local_path, is_local=self.config.use_local_cache diff --git a/app/adapters/internal_data_service.py b/app/adapters/internal_data_service.py new file mode 100644 index 0000000..4596666 --- /dev/null +++ b/app/adapters/internal_data_service.py @@ -0,0 +1,534 @@ +"""内部数据服务层 + +将 AmazingData SDK 的调用封装为内部接口 +对外接口不直接调用 SDK,而是通过内部接口调用 +""" +import pandas as pd +from datetime import datetime +from typing import List, Optional, Dict, Any + +from app.core.logger import info, error + + +class _MarketDataInternal: + """市场数据内部接口 - 封装 _market_data""" + + def __init__(self, market_data): + self._market_data = market_data + + def login(self, username: str, password: str, ip: str, port: str) -> bool: + """登录""" + try: + return self._market_data.login( + username=username, + password=password, + ip=ip, + port=port + ) + except Exception as e: + error(f"[_MarketDataInternal] Login failed: {e}") + raise + + def is_login(self) -> bool: + """检查登录状态""" + try: + return self._market_data.is_login() + except Exception: + return False + + def query_kline( + self, + code_list: List[str], + begin_date: int, + end_date: int, + period: int + ) -> Dict[str, pd.DataFrame]: + """查询K线数据""" + try: + return self._market_data.query_kline( + code_list=code_list, + begin_date=begin_date, + end_date=end_date, + period=period + ) + except Exception as e: + error(f"[_MarketDataInternal] Query kline failed: {e}") + return {} + + def query_snapshot( + self, + code_list: List[str], + begin_date: int, + end_date: int + ) -> Dict[str, pd.DataFrame]: + """查询快照数据""" + try: + return self._market_data.query_snapshot( + code_list=code_list, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_MarketDataInternal] Query snapshot failed: {e}") + return {} + + +class _BaseDataInternal: + """基础数据内部接口 - 封装 _base_data""" + + def __init__(self, base_data): + self._base_data = base_data + + def get_code_list(self, security_type: str) -> List[str]: + """获取代码列表""" + try: + return self._base_data.get_code_list(security_type=security_type) + except Exception as e: + error(f"[_BaseDataInternal] Get code list failed: {e}") + return [] + + def get_future_code_list(self, security_type: str) -> List[str]: + """获取期货代码列表""" + try: + return self._base_data.get_future_code_list(security_type=security_type) + except Exception as e: + error(f"[_BaseDataInternal] Get future code list failed: {e}") + return [] + + def get_code_info(self, security_type: str) -> pd.DataFrame: + """获取代码信息""" + try: + return self._base_data.get_code_info(security_type=security_type) + except Exception as e: + error(f"[_BaseDataInternal] Get code info failed: {e}") + return pd.DataFrame() + + def get_calendar(self, market: str) -> List[int]: + """获取交易日历""" + try: + return self._base_data.get_calendar(market=market) + except Exception as e: + error(f"[_BaseDataInternal] Get calendar failed: {e}") + return [] + + def get_adj_factor( + self, + code_list: List[str], + local_path: str, + is_local: bool + ) -> pd.DataFrame: + """获取复权因子""" + try: + return self._base_data.get_adj_factor( + code_list=code_list, + local_path=local_path, + is_local=is_local + ) + except Exception as e: + error(f"[_BaseDataInternal] Get adj factor failed: {e}") + return pd.DataFrame() + + def get_backward_factor( + self, + code_list: List[str], + local_path: str, + is_local: bool + ) -> pd.DataFrame: + """获取后复权因子""" + try: + return self._base_data.get_backward_factor( + code_list=code_list, + local_path=local_path, + is_local=is_local + ) + except Exception as e: + error(f"[_BaseDataInternal] Get backward factor failed: {e}") + return pd.DataFrame() + + def get_etf_pcf(self, code_list: List[str]) -> tuple: + """获取ETF申赎数据""" + try: + return self._base_data.get_etf_pcf(code_list=code_list) + except Exception as e: + error(f"[_BaseDataInternal] Get ETF PCF failed: {e}") + return ({}, {}) + + def get_hist_code_list(self, security_type: str) -> pd.DataFrame: + """获取历史代码列表""" + try: + return self._base_data.get_hist_code_list(security_type=security_type) + except Exception as e: + error(f"[_BaseDataInternal] Get hist code list failed: {e}") + return pd.DataFrame() + + +class _InfoDataInternal: + """信息数据内部接口 - 封装 _info_data""" + + def __init__(self, info_data): + self._info_data = info_data + + def get_equity_structure( + self, + code_list: List[str], + local_path: str, + is_local: bool + ) -> Dict[str, pd.DataFrame]: + """获取股本结构""" + try: + return self._info_data.get_equity_structure( + code_list=code_list, + local_path=local_path, + is_local=is_local + ) + except Exception as e: + error(f"[_InfoDataInternal] Get equity structure failed: {e}") + return {} + + def get_share_holder( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取股东数据""" + try: + return self._info_data.get_share_holder( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get share holder failed: {e}") + return {} + + def get_holder_num( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取股东户数""" + try: + return self._info_data.get_holder_num( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get holder num failed: {e}") + return {} + + def get_income( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取利润表""" + try: + return self._info_data.get_income( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get income failed: {e}") + return {} + + def get_balance_sheet( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取资产负债表""" + try: + return self._info_data.get_balance_sheet( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get balance sheet failed: {e}") + return {} + + def get_cash_flow( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取现金流量表""" + try: + return self._info_data.get_cash_flow( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get cash flow failed: {e}") + return {} + + def get_profit_express( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取业绩预告""" + try: + return self._info_data.get_profit_express( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get profit express failed: {e}") + return {} + + def get_profit_notice( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取业绩快报""" + try: + return self._info_data.get_profit_notice( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get profit notice failed: {e}") + return {} + + def get_margin_summary( + self, + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> pd.DataFrame: + """获取融资融券汇总""" + try: + return self._info_data.get_margin_summary( + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get margin summary failed: {e}") + return pd.DataFrame() + + def get_margin_detail( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取融资融券明细""" + try: + return self._info_data.get_margin_detail( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get margin detail failed: {e}") + return {} + + def get_long_hu_bang( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> pd.DataFrame: + """获取龙虎榜数据""" + try: + return self._info_data.get_long_hu_bang( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get long hu bang failed: {e}") + return pd.DataFrame() + + def get_block_trading( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> pd.DataFrame: + """获取大宗交易数据""" + try: + return self._info_data.get_block_trading( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get block trading failed: {e}") + return pd.DataFrame() + + def get_index_constituent( + self, + code_list: List[str], + local_path: str, + is_local: bool + ) -> Dict[str, pd.DataFrame]: + """获取指数成分股""" + try: + return self._info_data.get_index_constituent( + code_list=code_list, + local_path=local_path, + is_local=is_local + ) + except Exception as e: + error(f"[_InfoDataInternal] Get index constituent failed: {e}") + return {} + + def get_index_weight( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取指数权重""" + try: + return self._info_data.get_index_weight( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get index weight failed: {e}") + return {} + + def get_fund_share( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取基金份额""" + try: + return self._info_data.get_fund_share( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get fund share failed: {e}") + return {} + + def get_kzz_issuance( + self, + code_list: List[str], + local_path: str, + is_local: bool + ) -> Dict[str, pd.DataFrame]: + """获取可转债发行数据""" + try: + return self._info_data.get_kzz_issuance( + code_list=code_list, + local_path=local_path, + is_local=is_local + ) + except Exception as e: + error(f"[_InfoDataInternal] Get kzz issuance failed: {e}") + return {} + + def get_history_stock_status( + self, + code_list: List[str], + local_path: str, + is_local: bool, + begin_date: Optional[int] = None, + end_date: Optional[int] = None + ) -> Dict[str, pd.DataFrame]: + """获取历史股票状态数据 + + 包含字段: + - TRADE_DATE: 交易日期 + - UP_LIMIT: 涨停价 + - DOWN_LIMIT: 跌停价 + - MAX_UP_DOWN: 最大涨跌幅限制 + - IS_ST: 是否ST + - IS_SUSPEND: 是否停牌 + """ + try: + return self._info_data.get_history_stock_status( + code_list=code_list, + local_path=local_path, + is_local=is_local, + begin_date=begin_date, + end_date=end_date + ) + except Exception as e: + error(f"[_InfoDataInternal] Get history stock status failed: {e}") + return {} + + +class InternalDataService: + """内部数据服务统一入口""" + + def __init__(self, ad): + """ + Args: + ad: AmazingData SDK instance with _market_data, _base_data, _info_data + """ + self.market = _MarketDataInternal(ad._market_data) + self.base = _BaseDataInternal(ad._base_data) + self.info = _InfoDataInternal(ad._info_data) diff --git a/scripts/replace_sdk_calls.py b/scripts/replace_sdk_calls.py new file mode 100644 index 0000000..daa2e1a --- /dev/null +++ b/scripts/replace_sdk_calls.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""自动替换 SDK 调用为内部接口调用""" + +replacements = [ + # _base_data 调用 + ("self._base_data.get_code_info(", "self._internal.base.get_code_info("), + ("self._base_data.get_code_list(", "self._internal.base.get_code_list("), + ("self._base_data.get_future_code_list(", "self._internal.base.get_future_code_list("), + ("self._base_data.get_calendar(", "self._internal.base.get_calendar("), + ("self._base_data.get_adj_factor(", "self._internal.base.get_adj_factor("), + ("self._base_data.get_backward_factor(", "self._internal.base.get_backward_factor("), + ("self._base_data.get_etf_pcf(", "self._internal.base.get_etf_pcf("), + ("self._base_data.get_hist_code_list(", "self._internal.base.get_hist_code_list("), + + # _market_data 调用 + ("self._market_data.query_kline(", "self._internal.market.query_kline("), + ("self._market_data.query_snapshot(", "self._internal.market.query_snapshot("), + + # _info_data 调用 + ("self._info_data.get_equity_structure(", "self._internal.info.get_equity_structure("), + ("self._info_data.get_share_holder(", "self._internal.info.get_share_holder("), + ("self._info_data.get_holder_num(", "self._internal.info.get_holder_num("), + ("self._info_data.get_income(", "self._internal.info.get_income("), + ("self._info_data.get_balance_sheet(", "self._internal.info.get_balance_sheet("), + ("self._info_data.get_cash_flow(", "self._internal.info.get_cash_flow("), + ("self._info_data.get_profit_express(", "self._internal.info.get_profit_express("), + ("self._info_data.get_profit_notice(", "self._internal.info.get_profit_notice("), + ("self._info_data.get_margin_summary(", "self._internal.info.get_margin_summary("), + ("self._info_data.get_margin_detail(", "self._internal.info.get_margin_detail("), + ("self._info_data.get_long_hu_bang(", "self._internal.info.get_long_hu_bang("), + ("self._info_data.get_block_trading(", "self._internal.info.get_block_trading("), + ("self._info_data.get_index_constituent(", "self._internal.info.get_index_constituent("), + ("self._info_data.get_index_weight(", "self._internal.info.get_index_weight("), + ("self._info_data.get_fund_share(", "self._internal.info.get_fund_share("), + ("self._info_data.get_kzz_issuance(", "self._internal.info.get_kzz_issuance("), +] + +def main(): + with open('app/adapters/amazingdata_adapter.py', 'r', encoding='utf-8') as f: + content = f.read() + + count = 0 + for old, new in replacements: + if old in content: + occurrences = content.count(old) + content = content.replace(old, new) + count += occurrences + print(f"Replaced {occurrences} occurrence(s): {old} -> {new}") + + with open('app/adapters/amazingdata_adapter.py', 'w', encoding='utf-8') as f: + f.write(content) + + print(f"\nTotal replacements: {count}") + +if __name__ == "__main__": + main() diff --git a/todo_20260311.md b/todo_20260311.md index e6f4638..7a5b038 100644 --- a/todo_20260311.md +++ b/todo_20260311.md @@ -19,3 +19,14 @@ 2. 获取k线 stock/klines/{symbol} -- 缺少股本等信息 3. 获取交易日历 stock/trading-dates +# 2026年3月15日 接口调整 + +## 已实现 + +1. _fetch_calendar_sync 调用接口 get_calendar + +## 待调整 + +1. close接口需要logout进行退出 +2. +