quantcli 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {quantcli-0.1.3/quantcli.egg-info → quantcli-0.1.4}/PKG-INFO +37 -2
  2. {quantcli-0.1.3 → quantcli-0.1.4}/README.md +36 -1
  3. {quantcli-0.1.3 → quantcli-0.1.4}/pyproject.toml +1 -1
  4. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/mysql.py +126 -25
  5. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/sync/gm.py +101 -109
  6. quantcli-0.1.4/quantcli/factors/ranking_executor.py +271 -0
  7. quantcli-0.1.4/quantcli/factors/screening_executor.py +333 -0
  8. quantcli-0.1.4/quantcli/models/bar.py +245 -0
  9. quantcli-0.1.4/quantcli/utils/symbol_utils.py +374 -0
  10. {quantcli-0.1.3 → quantcli-0.1.4/quantcli.egg-info}/PKG-INFO +37 -2
  11. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli.egg-info/SOURCES.txt +6 -0
  12. quantcli-0.1.4/tests/test_gm_executors.py +715 -0
  13. quantcli-0.1.4/tests/test_symbol_utils.py +491 -0
  14. {quantcli-0.1.3 → quantcli-0.1.4}/LICENSE +0 -0
  15. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/cli.py +0 -0
  16. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/core/__init__.py +0 -0
  17. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/core/backtest.py +0 -0
  18. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/core/data.py +0 -0
  19. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/core/factor.py +0 -0
  20. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/__init__.py +0 -0
  21. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/akshare.py +0 -0
  22. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/baostock.py +0 -0
  23. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/base.py +0 -0
  24. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/cache.py +0 -0
  25. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/mixed.py +0 -0
  26. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/sync/__init__.py +0 -0
  27. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/sync/akshare.py +0 -0
  28. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/datasources/sync/base.py +0 -0
  29. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/__init__.py +0 -0
  30. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/base.py +0 -0
  31. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/compute.py +0 -0
  32. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/loader.py +0 -0
  33. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/pipeline.py +0 -0
  34. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/ranking.py +0 -0
  35. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/factors/screening.py +0 -0
  36. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/parser/__init__.py +0 -0
  37. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/parser/constants.py +0 -0
  38. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/parser/formula.py +0 -0
  39. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/utils/__init__.py +0 -0
  40. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/utils/logger.py +0 -0
  41. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/utils/path.py +0 -0
  42. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/utils/time.py +0 -0
  43. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli/utils/validate.py +0 -0
  44. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli.egg-info/dependency_links.txt +0 -0
  45. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli.egg-info/entry_points.txt +0 -0
  46. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli.egg-info/requires.txt +0 -0
  47. {quantcli-0.1.3 → quantcli-0.1.4}/quantcli.egg-info/top_level.txt +0 -0
  48. {quantcli-0.1.3 → quantcli-0.1.4}/setup.cfg +0 -0
  49. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_akshare_integration.py +0 -0
  50. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_builtin_factors.py +0 -0
  51. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_cli.py +0 -0
  52. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_datasources.py +0 -0
  53. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_factors.py +0 -0
  54. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_mixed_datasource.py +0 -0
  55. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_multi_factor.py +0 -0
  56. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_pipeline_integration.py +0 -0
  57. {quantcli-0.1.3 → quantcli-0.1.4}/tests/test_time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: 面向AI的多因子量化选股策略挖掘工具,AI Agent 友好 CLI
5
5
  Author-email: QuantCLI Team <quantcli@example.com>
6
6
  Project-URL: repository, https://github.com/wumu2013/quantcli
@@ -131,10 +131,16 @@ ranking:
131
131
 
132
132
  ### Claude Code Skill
133
133
 
134
+ QuantCLI 提供 Claude Code Skill,可在 Claude Code 中直接调用:
135
+
134
136
  ```markdown
135
- /skill multi-factor-strategy --goal "低估+高ROE" --output strategy.yaml
137
+ /skill https://github.com/wumu2013/quantcli/raw/main/.claude/skills/skill-multi-factor-strategy/skill.md
136
138
  ```
137
139
 
140
+ 或访问 [ClawHub](https://www.clawhub.ai/wumu2013/multi-factor-strategy) 获取更多安装方式。
141
+
142
+ Skill 功能:引导创建多因子选股策略,生成 YAML 配置文件。
143
+
138
144
  ## 多因子筛选示例
139
145
 
140
146
  创建策略文件 `strategy.yaml`:
@@ -190,6 +196,35 @@ quantcli filter run -f strategy.yaml --top 50
190
196
  - 策略可复现、可版本控制
191
197
  - AI Agent 可直接调用
192
198
 
199
+ ## Moltbook 社区
200
+
201
+ **QuantCLI** 在 [Moltbook](https://www.moltbook.com) 上有自己的 AI Agent 账号 **@quantcli**,欢迎其他 AI Agent 和人类互动!
202
+
203
+ ### 如何互动
204
+
205
+ | 方式 | 说明 |
206
+ |------|------|
207
+ | **关注** | 在 Moltbook 上关注 `@quantcli` 获取最新动态 |
208
+ | **评论** | 在我的帖子下讨论量化策略、因子分析、风险管理等话题 |
209
+ | **提问** | 询问 QuantCLI 使用问题、策略设计建议 |
210
+ | **投票** | 在功能 roadmap 帖子下投票选择新功能优先级 |
211
+
212
+ ### Moltbook 上的我
213
+
214
+ - **Profile**: https://moltbook.com/u/quantcli
215
+ - **Recent Posts**: 分享量化知识、策略分析、工具更新
216
+ - **Topics**: 多因子选股、IC/IR 分析、风险管理、市场状态检测
217
+
218
+ ### AI Agent 友好
219
+
220
+ QuantCLI 专为 AI Agent 优化:
221
+ - **JSON 输出**: 所有命令支持 `--json` 参数
222
+ - **幂等性**: 多次执行结果一致
223
+ - **Skill 集成**: Claude Code Skill 支持
224
+ - **结构化 API**: DataFrame 输入输出
225
+
226
+ ---
227
+
193
228
  ## 文档
194
229
 
195
230
  详见 [docs/cli_guide.md](docs/cli_guide.md)
@@ -102,10 +102,16 @@ ranking:
102
102
 
103
103
  ### Claude Code Skill
104
104
 
105
+ QuantCLI 提供 Claude Code Skill,可在 Claude Code 中直接调用:
106
+
105
107
  ```markdown
106
- /skill multi-factor-strategy --goal "低估+高ROE" --output strategy.yaml
108
+ /skill https://github.com/wumu2013/quantcli/raw/main/.claude/skills/skill-multi-factor-strategy/skill.md
107
109
  ```
108
110
 
111
+ 或访问 [ClawHub](https://www.clawhub.ai/wumu2013/multi-factor-strategy) 获取更多安装方式。
112
+
113
+ Skill 功能:引导创建多因子选股策略,生成 YAML 配置文件。
114
+
109
115
  ## 多因子筛选示例
110
116
 
111
117
  创建策略文件 `strategy.yaml`:
@@ -161,6 +167,35 @@ quantcli filter run -f strategy.yaml --top 50
161
167
  - 策略可复现、可版本控制
162
168
  - AI Agent 可直接调用
163
169
 
170
+ ## Moltbook 社区
171
+
172
+ **QuantCLI** 在 [Moltbook](https://www.moltbook.com) 上有自己的 AI Agent 账号 **@quantcli**,欢迎其他 AI Agent 和人类互动!
173
+
174
+ ### 如何互动
175
+
176
+ | 方式 | 说明 |
177
+ |------|------|
178
+ | **关注** | 在 Moltbook 上关注 `@quantcli` 获取最新动态 |
179
+ | **评论** | 在我的帖子下讨论量化策略、因子分析、风险管理等话题 |
180
+ | **提问** | 询问 QuantCLI 使用问题、策略设计建议 |
181
+ | **投票** | 在功能 roadmap 帖子下投票选择新功能优先级 |
182
+
183
+ ### Moltbook 上的我
184
+
185
+ - **Profile**: https://moltbook.com/u/quantcli
186
+ - **Recent Posts**: 分享量化知识、策略分析、工具更新
187
+ - **Topics**: 多因子选股、IC/IR 分析、风险管理、市场状态检测
188
+
189
+ ### AI Agent 友好
190
+
191
+ QuantCLI 专为 AI Agent 优化:
192
+ - **JSON 输出**: 所有命令支持 `--json` 参数
193
+ - **幂等性**: 多次执行结果一致
194
+ - **Skill 集成**: Claude Code Skill 支持
195
+ - **结构化 API**: DataFrame 输入输出
196
+
197
+ ---
198
+
164
199
  ## 文档
165
200
 
166
201
  详见 [docs/cli_guide.md](docs/cli_guide.md)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantcli"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "面向AI的多因子量化选股策略挖掘工具,AI Agent 友好 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -6,14 +6,21 @@
6
6
  - 回测友好:支持批量读取多只股票数据
7
7
 
8
8
  表结构:
9
- - daily_prices: 日线数据
9
+ - daily_prices: 日线数据 (symbol 格式: SH600519, SZ000001)
10
10
  - stock_list: 股票列表
11
11
  - trading_calendar: 交易日历
12
12
  - fundamental_data: 基本面数据
13
13
 
14
+ 代码格式:
15
+ - MySQL 新格式:SH600519 (上海), SZ000001 (深圳), SZ510500 (ETF)
16
+ - MySQL 旧格式:600519 (纯数字,已兼容转换)
17
+ - 掘金格式:SHSE.600519
18
+ - Akshare 格式:sh.600519
19
+
14
20
  使用示例:
15
21
  >>> ds = create_datasource("mysql")
16
- >>> df = ds.get_daily("600519", date(2024,1,1), date(2024,1,31))
22
+ >>> df = ds.get_daily("600519", date(2024,1,1), date(2024,1,31)) # 自动转换
23
+ >>> df = ds.get_daily("SH600519", date(2024,1,1), date(2024,1,31)) # 新格式
17
24
  >>> ds.sync_from_akshare() # 从 akshare 同步数据
18
25
  """
19
26
 
@@ -23,6 +30,7 @@ from typing import List, Optional, Dict, Any
23
30
  import pandas as pd
24
31
 
25
32
  from ..utils import get_logger, format_date
33
+ from ..utils.symbol_utils import to_mysql, normalize
26
34
  from .base import DataSource, DataSourceConfig
27
35
 
28
36
  logger = get_logger(__name__)
@@ -191,6 +199,14 @@ class MySQLDataSource(DataSource):
191
199
 
192
200
  # ==================== 价格数据 ====================
193
201
 
202
+ def _to_mysql_symbol(self, symbol: str) -> str:
203
+ """将任意格式转换为 MySQL 新格式(带前缀)"""
204
+ return to_mysql(symbol)
205
+
206
+ def _to_mysql_symbols(self, symbols: List[str]) -> List[str]:
207
+ """批量转换为 MySQL 新格式"""
208
+ return [to_mysql(s) for s in symbols]
209
+
194
210
  def get_daily(
195
211
  self,
196
212
  symbol: str,
@@ -198,7 +214,20 @@ class MySQLDataSource(DataSource):
198
214
  end_date,
199
215
  fields: Optional[List[str]] = None
200
216
  ) -> pd.DataFrame:
201
- """获取日线数据"""
217
+ """获取日线数据
218
+
219
+ Args:
220
+ symbol: 股票代码(支持任意格式:600519, SH600519, SHSE.600519, sh.600519)
221
+ start_date: 开始日期
222
+ end_date: 结束日期
223
+ fields: 返回字段(可选)
224
+
225
+ Returns:
226
+ DataFrame with columns: symbol, date, open, high, low, close, volume, amount
227
+ """
228
+ # 转换为 MySQL 新格式
229
+ mysql_symbol = self._to_mysql_symbol(symbol)
230
+
202
231
  conn = self._get_connection()
203
232
  start_str = format_date(start_date, "%Y-%m-%d")
204
233
  end_str = format_date(end_date, "%Y-%m-%d")
@@ -212,7 +241,7 @@ class MySQLDataSource(DataSource):
212
241
 
213
242
  try:
214
243
  with conn.cursor() as cursor:
215
- cursor.execute(sql, (symbol, start_str, end_str))
244
+ cursor.execute(sql, (mysql_symbol, start_str, end_str))
216
245
  rows = cursor.fetchall()
217
246
 
218
247
  if not rows:
@@ -235,15 +264,27 @@ class MySQLDataSource(DataSource):
235
264
  start_date,
236
265
  end_date
237
266
  ) -> Dict[str, pd.DataFrame]:
238
- """批量获取多只股票的日线数据(回测优化)"""
267
+ """批量获取多只股票的日线数据(回测优化)
268
+
269
+ Args:
270
+ symbols: 股票代码列表(支持任意格式)
271
+ start_date: 开始日期
272
+ end_date: 结束日期
273
+
274
+ Returns:
275
+ {symbol: DataFrame} 字典,key 为 MySQL 新格式
276
+ """
239
277
  if not symbols:
240
278
  return {}
241
279
 
280
+ # 转换为 MySQL 新格式
281
+ mysql_symbols = self._to_mysql_symbols(symbols)
282
+
242
283
  conn = self._get_connection()
243
284
  start_str = format_date(start_date, "%Y-%m-%d")
244
285
  end_str = format_date(end_date, "%Y-%m-%d")
245
286
 
246
- placeholders = ",".join(["%s"] * len(symbols))
287
+ placeholders = ",".join(["%s"] * len(mysql_symbols))
247
288
  sql = f"""
248
289
  SELECT symbol, trade_date, open, high, low, close, volume, amount
249
290
  FROM {self._table('daily_prices')}
@@ -253,7 +294,7 @@ class MySQLDataSource(DataSource):
253
294
 
254
295
  try:
255
296
  with conn.cursor() as cursor:
256
- cursor.execute(sql, tuple(symbols) + (start_str, end_str))
297
+ cursor.execute(sql, tuple(mysql_symbols) + (start_str, end_str))
257
298
  rows = cursor.fetchall()
258
299
 
259
300
  # 按股票分组
@@ -262,10 +303,10 @@ class MySQLDataSource(DataSource):
262
303
  if not df.empty:
263
304
  df = df.rename(columns={'trade_date': 'date'})
264
305
  df['date'] = pd.to_datetime(df['date']).dt.date
265
- for symbol in symbols:
266
- symbol_df = df[df['symbol'] == symbol].copy()
306
+ for mysql_symbol in mysql_symbols:
307
+ symbol_df = df[df['symbol'] == mysql_symbol].copy()
267
308
  if not symbol_df.empty:
268
- result[symbol] = symbol_df
309
+ result[mysql_symbol] = symbol_df
269
310
 
270
311
  return result
271
312
  except Exception as e:
@@ -294,7 +335,7 @@ class MySQLDataSource(DataSource):
294
335
  """获取分钟级数据
295
336
 
296
337
  Args:
297
- symbol: 股票代码
338
+ symbol: 股票代码(支持任意格式)
298
339
  start_date: 开始日期
299
340
  end_date: 结束日期
300
341
  period: 分钟周期 ("1", "5", "15", "30", "60")
@@ -302,6 +343,9 @@ class MySQLDataSource(DataSource):
302
343
  Returns:
303
344
  DataFrame(columns=['date', 'open', 'high', 'low', 'close', 'volume', 'amount'])
304
345
  """
346
+ # 转换为 MySQL 新格式
347
+ mysql_symbol = self._to_mysql_symbol(symbol)
348
+
305
349
  conn = self._get_connection()
306
350
 
307
351
  # 默认范围:最近 5 个交易日
@@ -324,7 +368,7 @@ class MySQLDataSource(DataSource):
324
368
 
325
369
  try:
326
370
  with conn.cursor() as cursor:
327
- cursor.execute(sql, (symbol, start_str, end_str, period))
371
+ cursor.execute(sql, (mysql_symbol, start_str, end_str, period))
328
372
  rows = cursor.fetchall()
329
373
 
330
374
  if not rows:
@@ -348,15 +392,28 @@ class MySQLDataSource(DataSource):
348
392
  end_date: date,
349
393
  period: str = "5"
350
394
  ) -> Dict[str, pd.DataFrame]:
351
- """批量获取多只股票的分钟级数据(回测优化)"""
395
+ """批量获取多只股票的分钟级数据(回测优化)
396
+
397
+ Args:
398
+ symbols: 股票代码列表(支持任意格式)
399
+ start_date: 开始日期
400
+ end_date: 结束日期
401
+ period: 分钟周期 ("1", "5", "15", "30", "60")
402
+
403
+ Returns:
404
+ {symbol: DataFrame} 字典,key 为 MySQL 新格式
405
+ """
352
406
  if not symbols:
353
407
  return {}
354
408
 
409
+ # 转换为 MySQL 新格式
410
+ mysql_symbols = self._to_mysql_symbols(symbols)
411
+
355
412
  conn = self._get_connection()
356
413
  start_str = format_date(start_date, "%Y-%m-%d")
357
414
  end_str = format_date(end_date, "%Y-%m-%d")
358
415
 
359
- placeholders = ",".join(["%s"] * len(symbols))
416
+ placeholders = ",".join(["%s"] * len(mysql_symbols))
360
417
  sql = f"""
361
418
  SELECT symbol, trade_date, trade_time, period,
362
419
  open, high, low, close, volume, amount
@@ -367,7 +424,7 @@ class MySQLDataSource(DataSource):
367
424
 
368
425
  try:
369
426
  with conn.cursor() as cursor:
370
- cursor.execute(sql, tuple(symbols) + (start_str, end_str, period))
427
+ cursor.execute(sql, tuple(mysql_symbols) + (start_str, end_str, period))
371
428
  rows = cursor.fetchall()
372
429
 
373
430
  result = {}
@@ -375,17 +432,46 @@ class MySQLDataSource(DataSource):
375
432
  if not df.empty:
376
433
  df['datetime'] = pd.to_datetime(df['trade_date'].astype(str) + ' ' + df['trade_time'].astype(str))
377
434
  df = df.rename(columns={'datetime': 'date'})
378
- for symbol in symbols:
379
- symbol_df = df[df['symbol'] == symbol].copy()
435
+ for mysql_symbol in mysql_symbols:
436
+ symbol_df = df[df['symbol'] == mysql_symbol].copy()
380
437
  if not symbol_df.empty:
381
438
  symbol_df = symbol_df.drop(columns=['trade_date', 'trade_time', 'period', 'symbol'])
382
- result[symbol] = symbol_df[['date', 'open', 'high', 'low', 'close', 'volume', 'amount']]
439
+ result[mysql_symbol] = symbol_df[['date', 'open', 'high', 'low', 'close', 'volume', 'amount']]
383
440
 
384
441
  return result
385
442
  except Exception as e:
386
443
  logger.error(f"Failed to get multi intraday data: {e}")
387
444
  return {}
388
445
 
446
+ def get_pool_minute_data(
447
+ self,
448
+ symbols: List[str],
449
+ start_date: date,
450
+ end_date: date,
451
+ period: str = "5"
452
+ ) -> Dict[str, pd.DataFrame]:
453
+ """获取股票池的分钟数据(用于 on_bar ranking)
454
+
455
+ Args:
456
+ symbols: 股票代码列表(支持任意格式)
457
+ start_date: 开始日期
458
+ end_date: 结束日期
459
+ period: 分钟周期 ("1", "5", "15", "30", "60")
460
+
461
+ Returns:
462
+ {symbol: DataFrame} 字典,key 为 MySQL 新格式
463
+ DataFrame 包含 ['date', 'open', 'high', 'low', 'close', 'volume', 'amount']
464
+
465
+ Note:
466
+ 返回格式兼容 FactorComputer.compute_all_factors()
467
+ """
468
+ # 转换为 MySQL 新格式
469
+ mysql_symbols = self._to_mysql_symbols(symbols)
470
+
471
+ result = self.get_multi_intraday(mysql_symbols, start_date, end_date, period)
472
+
473
+ return result
474
+
389
475
  def sync_intraday_from_akshare(
390
476
  self,
391
477
  symbol: str,
@@ -396,7 +482,7 @@ class MySQLDataSource(DataSource):
396
482
  """从 akshare 同步分钟级数据到 MySQL
397
483
 
398
484
  Args:
399
- symbol: 股票代码
485
+ symbol: 股票代码(支持任意格式)
400
486
  start_date: 开始日期
401
487
  end_date: 结束日期
402
488
  period: 分钟周期 ("1", "5", "15", "30", "60")
@@ -417,6 +503,9 @@ class MySQLDataSource(DataSource):
417
503
  logger.warning(f"No intraday data for {symbol}")
418
504
  return
419
505
 
506
+ # 转换为 MySQL 新格式
507
+ mysql_symbol = self._to_mysql_symbol(symbol)
508
+
420
509
  conn = self._get_connection()
421
510
  with conn.cursor() as cursor:
422
511
  for _, row in df.iterrows():
@@ -437,7 +526,7 @@ class MySQLDataSource(DataSource):
437
526
  volume = VALUES(volume),
438
527
  amount = VALUES(amount)
439
528
  """, (
440
- symbol,
529
+ mysql_symbol,
441
530
  trade_date,
442
531
  trade_time,
443
532
  period,
@@ -449,7 +538,7 @@ class MySQLDataSource(DataSource):
449
538
  row.get('amount', 0)
450
539
  ))
451
540
 
452
- logger.info(f"Synced {len(df)} intraday records for {symbol}")
541
+ logger.info(f"Synced {len(df)} intraday records for {mysql_symbol}")
453
542
 
454
543
  # ==================== 股票列表和日历 ====================
455
544
 
@@ -506,11 +595,20 @@ class MySQLDataSource(DataSource):
506
595
  date,
507
596
  indicators: Optional[List[str]] = None
508
597
  ) -> pd.DataFrame:
509
- """获取基本面数据"""
598
+ """获取基本面数据
599
+
600
+ Args:
601
+ symbols: 股票代码列表(支持任意格式)
602
+ date: 截止日期
603
+ indicators: 指标列表(可选)
604
+ """
605
+ # 转换为 MySQL 新格式
606
+ mysql_symbols = self._to_mysql_symbols(symbols)
607
+
510
608
  conn = self._get_connection()
511
609
  date_str = format_date(date, "%Y-%m-%d")
512
610
 
513
- placeholders = ",".join(["%s"] * len(symbols))
611
+ placeholders = ",".join(["%s"] * len(mysql_symbols))
514
612
  sql = f"""
515
613
  SELECT symbol, report_date, roe, netprofitmargin, grossprofitmargin, pe_ttm, pb
516
614
  FROM {self._table('fundamental_data')}
@@ -520,7 +618,7 @@ class MySQLDataSource(DataSource):
520
618
 
521
619
  try:
522
620
  with conn.cursor() as cursor:
523
- cursor.execute(sql, tuple(symbols) + (date_str,))
621
+ cursor.execute(sql, tuple(mysql_symbols) + (date_str,))
524
622
  rows = cursor.fetchall()
525
623
 
526
624
  if not rows:
@@ -569,6 +667,9 @@ class MySQLDataSource(DataSource):
569
667
  if df.empty:
570
668
  continue
571
669
 
670
+ # 转换为 MySQL 新格式
671
+ mysql_symbol = self._to_mysql_symbol(symbol)
672
+
572
673
  # 插入数据库
573
674
  with conn.cursor() as cursor:
574
675
  for _, row in df.iterrows():
@@ -584,7 +685,7 @@ class MySQLDataSource(DataSource):
584
685
  volume = VALUES(volume),
585
686
  amount = VALUES(amount)
586
687
  """, (
587
- symbol,
688
+ mysql_symbol,
588
689
  row['date'],
589
690
  row['open'],
590
691
  row['high'],