quantcli 0.2.1__tar.gz → 0.2.5__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 (102) hide show
  1. {quantcli-0.2.1/quantcli.egg-info → quantcli-0.2.5}/PKG-INFO +1 -1
  2. {quantcli-0.2.1 → quantcli-0.2.5}/pyproject.toml +1 -1
  3. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/cli.py +105 -15
  4. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/data.py +3 -3
  5. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/mysql.py +11 -4
  6. {quantcli-0.2.1 → quantcli-0.2.5/quantcli.egg-info}/PKG-INFO +1 -1
  7. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_data_manager.py +9 -7
  8. {quantcli-0.2.1 → quantcli-0.2.5}/LICENSE +0 -0
  9. {quantcli-0.2.1 → quantcli-0.2.5}/README.md +0 -0
  10. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/__init__.py +0 -0
  11. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/backtest.py +0 -0
  12. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/factor.py +0 -0
  13. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/__init__.py +0 -0
  14. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/akshare.py +0 -0
  15. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/baostock.py +0 -0
  16. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/base.py +0 -0
  17. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/cache.py +0 -0
  18. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/fundamentals/__init__.py +0 -0
  19. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/fundamentals/provider.py +0 -0
  20. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/mixed.py +0 -0
  21. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/__init__.py +0 -0
  22. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/akshare.py +0 -0
  23. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/base.py +0 -0
  24. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/gm.py +0 -0
  25. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/gm_fundamental.py +0 -0
  26. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/__init__.py +0 -0
  27. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_001.yaml +0 -0
  28. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_002.yaml +0 -0
  29. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_003.yaml +0 -0
  30. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_004.yaml +0 -0
  31. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_005.yaml +0 -0
  32. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_006.yaml +0 -0
  33. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_007.yaml +0 -0
  34. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_008.yaml +0 -0
  35. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_009.yaml +0 -0
  36. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_010.yaml +0 -0
  37. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_011.yaml +0 -0
  38. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_012.yaml +0 -0
  39. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_013.yaml +0 -0
  40. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_014.yaml +0 -0
  41. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_015.yaml +0 -0
  42. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_016.yaml +0 -0
  43. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_017.yaml +0 -0
  44. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_018.yaml +0 -0
  45. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_019.yaml +0 -0
  46. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_020.yaml +0 -0
  47. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_021.yaml +0 -0
  48. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_022.yaml +0 -0
  49. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_023.yaml +0 -0
  50. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_024.yaml +0 -0
  51. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_025.yaml +0 -0
  52. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_026.yaml +0 -0
  53. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_027.yaml +0 -0
  54. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_028.yaml +0 -0
  55. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_029.yaml +0 -0
  56. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_030.yaml +0 -0
  57. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_031.yaml +0 -0
  58. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_032.yaml +0 -0
  59. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_033.yaml +0 -0
  60. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_034.yaml +0 -0
  61. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_035.yaml +0 -0
  62. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_036.yaml +0 -0
  63. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_037.yaml +0 -0
  64. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_038.yaml +0 -0
  65. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_039.yaml +0 -0
  66. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_040.yaml +0 -0
  67. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/base.py +0 -0
  68. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/compute.py +0 -0
  69. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/loader.py +0 -0
  70. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/pipeline.py +0 -0
  71. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/ranking.py +0 -0
  72. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/ranking_executor.py +0 -0
  73. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/screening.py +0 -0
  74. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/screening_executor.py +0 -0
  75. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/models/bar.py +0 -0
  76. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/parser/__init__.py +0 -0
  77. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/parser/constants.py +0 -0
  78. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/parser/formula.py +0 -0
  79. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/__init__.py +0 -0
  80. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/env.py +0 -0
  81. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/logger.py +0 -0
  82. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/path.py +0 -0
  83. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/symbol_utils.py +0 -0
  84. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/time.py +0 -0
  85. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/validate.py +0 -0
  86. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/SOURCES.txt +0 -0
  87. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/dependency_links.txt +0 -0
  88. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/entry_points.txt +0 -0
  89. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/requires.txt +0 -0
  90. {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/top_level.txt +0 -0
  91. {quantcli-0.2.1 → quantcli-0.2.5}/setup.cfg +0 -0
  92. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_akshare_integration.py +0 -0
  93. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_builtin_factors.py +0 -0
  94. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_cli.py +0 -0
  95. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_datasources.py +0 -0
  96. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_factors.py +0 -0
  97. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_gm_executors.py +0 -0
  98. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_mixed_datasource.py +0 -0
  99. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_multi_factor.py +0 -0
  100. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_pipeline_integration.py +0 -0
  101. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_symbol_utils.py +0 -0
  102. {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.2.1
3
+ Version: 0.2.5
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantcli"
7
- version = "0.2.1"
7
+ version = "0.2.5"
8
8
  description = "面向AI的多因子量化选股策略挖掘工具,AI Agent 友好 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -107,10 +107,13 @@ def data(ctx):
107
107
  @click.option(
108
108
  "--source",
109
109
  type=click.Choice(["akshare", "baostock", "mixed", "mysql"]),
110
- default="mixed",
110
+ default="mysql",
111
111
  help="数据源 (akshare/baostock/mixed/mysql)",
112
112
  )
113
113
  @click.option("--use-cache/--no-cache", default=True, help="是否使用缓存")
114
+ @click.option(
115
+ "--cache-raw/--no-cache-raw", default=False, help="是否缓存原始数据到parquet"
116
+ )
114
117
  @click.option(
115
118
  "--output",
116
119
  "-o",
@@ -119,7 +122,7 @@ def data(ctx):
119
122
  help="输出文件路径 (如: data.csv)",
120
123
  )
121
124
  @click.pass_context
122
- def data_fetch(ctx, symbol, start, end, source, use_cache, output):
125
+ def data_fetch(ctx, symbol, start, end, source, use_cache, cache_raw, output):
123
126
  """获取股票日线数据
124
127
 
125
128
  股票代码:
@@ -131,6 +134,7 @@ def data_fetch(ctx, symbol, start, end, source, use_cache, output):
131
134
  quantcli data fetch 000001 --start 2025-01-01 --end 2025-12-31
132
135
  quantcli data fetch 600519 --start 2025-01-01 --source mysql
133
136
  quantcli data fetch 000001 --start 2025-01-01 -o data.csv
137
+ quantcli data fetch 000001 --start 2025-01-01 --cache-raw
134
138
  """
135
139
  if end is None:
136
140
  end = today()
@@ -139,7 +143,7 @@ def data_fetch(ctx, symbol, start, end, source, use_cache, output):
139
143
  f"Fetching {symbol} data from {format_date(start)} to {format_date(end)}..."
140
144
  )
141
145
 
142
- config = DataConfig(source=source)
146
+ config = DataConfig(source=source, cache_raw=cache_raw)
143
147
  dm = DataManager(config)
144
148
 
145
149
  try:
@@ -169,7 +173,93 @@ def data_fetch(ctx, symbol, start, end, source, use_cache, output):
169
173
  click.echo(f"Saved to {output}")
170
174
 
171
175
  except Exception as e:
172
- click.echo(f"Error: {e}", err=True)
176
+ error_msg = str(e)
177
+ if "Access denied" in error_msg or "Connection refused" in error_msg:
178
+ click.echo(f"MySQL 连接失败: 请检查数据库配置", err=True)
179
+ click.echo(
180
+ "提示: 使用 --source akshare 或 --source baostock 可以切换到其他数据源",
181
+ err=True,
182
+ )
183
+ else:
184
+ click.echo(f"Error: {e}", err=True)
185
+ sys.exit(1)
186
+
187
+
188
+ @data.command("fetch-fundamental")
189
+ @click.argument("symbols", type=str, nargs=-1)
190
+ @click.option(
191
+ "--date",
192
+ type=date_type,
193
+ default=None,
194
+ help="截止日期 (YYYY-MM-DD,默认今天)",
195
+ )
196
+ @click.option(
197
+ "--source",
198
+ type=click.Choice(["mysql", "baostock"]),
199
+ default="mysql",
200
+ help="数据源 (mysql/baostock)",
201
+ )
202
+ @click.option(
203
+ "--output",
204
+ "-o",
205
+ type=click.Path(),
206
+ default=None,
207
+ help="输出文件路径 (如: data.csv)",
208
+ )
209
+ @click.pass_context
210
+ def data_fetch_fundamental(ctx, symbols, date, source, output):
211
+ """获取股票基本面数据
212
+
213
+ 股票代码:
214
+ 000001 - 平安银行
215
+ 600519 - 贵州茅台
216
+
217
+ 示例:
218
+ quantcli data fetch-fundamental 000001 600519
219
+ quantcli data fetch-fundamental 600519 --date 2024-12-31
220
+ quantcli data fetch-fundamental 000001 --source baostock -o fundamentals.csv
221
+ """
222
+ if not symbols:
223
+ click.echo("Error: At least one symbol is required")
224
+ sys.exit(1)
225
+
226
+ if date is None:
227
+ date = today()
228
+
229
+ click.echo(
230
+ f"Fetching fundamental data for {len(symbols)} symbols at {format_date(date)}..."
231
+ )
232
+
233
+ config = DataConfig(source=source)
234
+ dm = DataManager(config)
235
+
236
+ try:
237
+ df = dm.datasource.get_fundamental(list(symbols), date)
238
+
239
+ if df.empty:
240
+ click.echo(f"No fundamental data found for symbols")
241
+ return
242
+
243
+ click.echo(f"Retrieved {len(df)} rows")
244
+
245
+ click.echo(df.to_string())
246
+
247
+ if output:
248
+ if output.endswith(".csv"):
249
+ df.to_csv(output, index=False)
250
+ elif output.endswith(".parquet"):
251
+ df.to_parquet(output, index=False)
252
+ else:
253
+ df.to_csv(output, index=False)
254
+ click.echo(f"Saved to {output}")
255
+
256
+ except Exception as e:
257
+ error_msg = str(e)
258
+ if "Access denied" in error_msg or "Connection refused" in error_msg:
259
+ click.echo(f"MySQL 连接失败: 请检查数据库配置", err=True)
260
+ click.echo("提示: 使用 --source baostock 可以切换到其他数据源", err=True)
261
+ else:
262
+ click.echo(f"Error: {e}", err=True)
173
263
  sys.exit(1)
174
264
 
175
265
 
@@ -191,7 +281,7 @@ def data_cache(ctx):
191
281
  @click.pass_context
192
282
  def data_cache_ls(ctx, pattern, sort):
193
283
  """列出缓存文件"""
194
- config = DataConfig()
284
+ config = DataConfig(source="mysql")
195
285
  dm = DataManager(config)
196
286
  sizes = dm.get_cache_size()
197
287
 
@@ -229,7 +319,7 @@ def data_cache_ls(ctx, pattern, sort):
229
319
  @click.pass_context
230
320
  def data_cache_clean(ctx, older_than):
231
321
  """清理缓存文件"""
232
- config = DataConfig()
322
+ config = DataConfig(source="mysql")
233
323
  dm = DataManager(config)
234
324
 
235
325
  count = dm.clear_cache(older_than=older_than)
@@ -240,7 +330,7 @@ def data_cache_clean(ctx, older_than):
240
330
  @click.pass_context
241
331
  def data_health(ctx):
242
332
  """检查数据源健康状态"""
243
- config = DataConfig()
333
+ config = DataConfig(source="mysql")
244
334
  dm = DataManager(config)
245
335
  health = dm.health_check()
246
336
 
@@ -305,7 +395,7 @@ def factor_run(ctx, name, expr, symbol, start, end, output):
305
395
  click.echo(f"Computing factor '{name}'...")
306
396
 
307
397
  # 创建引擎
308
- config = DataConfig()
398
+ config = DataConfig(source="mysql")
309
399
  dm = DataManager(config)
310
400
  engine = FactorEngine(dm)
311
401
 
@@ -366,7 +456,7 @@ def factor_eval(ctx, name, symbol, start, end, method):
366
456
 
367
457
  click.echo(f"Evaluating factor '{name}'...")
368
458
 
369
- config = DataConfig()
459
+ config = DataConfig(source="mysql")
370
460
  dm = DataManager(config)
371
461
  engine = FactorEngine(dm)
372
462
 
@@ -731,7 +821,7 @@ def filter_run(
731
821
  if daily_conditions:
732
822
  # 只对候选获取日线数据
733
823
  click.echo(f"Fetching daily data for {len(candidates)} candidates...")
734
- dm = DataManager(DataConfig(source="mixed"))
824
+ dm = DataManager(DataConfig(source="mysql"))
735
825
 
736
826
  price_data = {}
737
827
  for symbol in candidates:
@@ -829,7 +919,7 @@ def filter_run(
829
919
  # 阶段2: 日线
830
920
  click.echo(f"\n=== Stage 2: Daily Screening ===")
831
921
  click.echo(f"Fetching daily data for {len(candidates)} candidates...")
832
- dm = DataManager(DataConfig(source="mixed"))
922
+ dm = DataManager(DataConfig(source="mysql"))
833
923
 
834
924
  price_data = {}
835
925
  for symbol in candidates:
@@ -885,7 +975,7 @@ def filter_run(
885
975
  return
886
976
 
887
977
  click.echo(f"Fetching price data for {len(candidates)} candidates...")
888
- dm = DataManager(DataConfig(source="mixed"))
978
+ dm = DataManager(DataConfig(source="mysql"))
889
979
 
890
980
  price_data = {}
891
981
  for symbol in candidates:
@@ -1303,7 +1393,7 @@ def analyze_ic(ctx, expr, name, symbol, start, end, period, window, method):
1303
1393
  click.echo(f"Forward period: {period} days, Window: {window}")
1304
1394
 
1305
1395
  # 获取数据
1306
- config = DataConfig()
1396
+ config = DataConfig(source="mysql")
1307
1397
  dm = DataManager(config)
1308
1398
  df = dm.get_daily(symbol, start, end)
1309
1399
 
@@ -1422,7 +1512,7 @@ def analyze_batch(ctx, dir, symbol, start, end, period, window, top, output):
1422
1512
  click.echo(f"Analyzing with period={period}d, window={window}...\n")
1423
1513
 
1424
1514
  # 获取数据
1425
- config = DataConfig()
1515
+ config = DataConfig(source="mysql")
1426
1516
  dm = DataManager(config)
1427
1517
  df = dm.get_daily(symbol, start, end)
1428
1518
 
@@ -1541,7 +1631,7 @@ def config():
1541
1631
  @click.pass_context
1542
1632
  def config_show(ctx):
1543
1633
  """显示当前配置"""
1544
- config = DataConfig()
1634
+ config = DataConfig(source="mysql")
1545
1635
  click.echo("QuantCLI Configuration:")
1546
1636
  click.echo(f" data.source: {config.source}")
1547
1637
  click.echo(f" data.cache_dir: {config.cache_dir}")
@@ -30,6 +30,7 @@ logger = get_logger(__name__)
30
30
  class DataConfig:
31
31
  """数据管理配置"""
32
32
 
33
+ cache_raw: bool = False
33
34
  source: str = "akshare"
34
35
  cache_dir: str = "./data"
35
36
  parallel: int = 4
@@ -141,8 +142,7 @@ class DataManager:
141
142
  logger.warning(f"No data for {symbol}")
142
143
  return df
143
144
 
144
- # 缓存数据
145
- if use_cache:
145
+ if use_cache and self.config.cache_raw:
146
146
  self._save_to_cache(cache_file, df, f"stock_daily_{symbol}")
147
147
 
148
148
  return df
@@ -257,7 +257,7 @@ class DataManager:
257
257
 
258
258
  df = self.datasource.get_index_daily(symbol, start_date, end_date)
259
259
 
260
- if use_cache and not df.empty:
260
+ if use_cache and self.config.cache_raw and not df.empty:
261
261
  self._save_to_cache(cache_file, df, f"index_daily_{symbol}")
262
262
 
263
263
  return df
@@ -88,9 +88,14 @@ class MySQLDataSource(DataSource):
88
88
  self._prefix = config_dict["table_prefix"]
89
89
  self._autocommit = autocommit
90
90
  self._conn = None
91
+ self._tables_initialized = False
91
92
 
92
- # 初始化数据库表
93
+ def _ensure_tables(self):
94
+ """确保数据库表已初始化(懒加载)"""
95
+ if self._tables_initialized:
96
+ return
93
97
  self._init_tables()
98
+ self._tables_initialized = True
94
99
 
95
100
  def _get_connection(self):
96
101
  """获取数据库连接"""
@@ -208,9 +213,8 @@ class MySQLDataSource(DataSource):
208
213
  """批量转换为 MySQL 新格式"""
209
214
  return [to_mysql(s) for s in symbols]
210
215
 
211
- def get_daily(
212
- self, symbol: str, start_date, end_date, fields: Optional[List[str]] = None
213
- ) -> pd.DataFrame:
216
+ def get_daily(self, symbol: str, start_date, end_date) -> pd.DataFrame:
217
+ self._ensure_tables()
214
218
  """获取日线数据
215
219
 
216
220
  Args:
@@ -559,6 +563,7 @@ class MySQLDataSource(DataSource):
559
563
  # ==================== 股票列表和日历 ====================
560
564
 
561
565
  def get_stock_list(self, market: str = "all") -> pd.DataFrame:
566
+ self._ensure_tables()
562
567
  """获取股票列表"""
563
568
  conn = self._get_connection()
564
569
 
@@ -595,6 +600,7 @@ class MySQLDataSource(DataSource):
595
600
  )
596
601
 
597
602
  def get_trading_calendar(self, exchange: str = "SSE") -> List[date]:
603
+ self._ensure_tables()
598
604
  """获取交易日历"""
599
605
  conn = self._get_connection()
600
606
 
@@ -619,6 +625,7 @@ class MySQLDataSource(DataSource):
619
625
  def get_fundamental(
620
626
  self, symbols: List[str], date, indicators: Optional[List[str]] = None
621
627
  ) -> pd.DataFrame:
628
+ self._ensure_tables()
622
629
  """获取基本面数据
623
630
 
624
631
  Args:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.2.1
3
+ Version: 0.2.5
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
@@ -52,7 +52,7 @@ class TestGetDailyCacheLogic:
52
52
  self, temp_cache_dir, mock_datasource
53
53
  ):
54
54
  """缓存存在且完整 → 直接返回缓存"""
55
- config = DataConfig(cache_dir=temp_cache_dir, source="mock")
55
+ config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
56
56
  dm = DataManager(config)
57
57
  dm._datasource = mock_datasource
58
58
 
@@ -95,7 +95,7 @@ class TestGetDailyCacheLogic:
95
95
 
96
96
  def test_cache_incomplete_fetches_full_data(self, temp_cache_dir, mock_datasource):
97
97
  """缓存不完整 → 全量拉取"""
98
- config = DataConfig(cache_dir=temp_cache_dir, source="mock")
98
+ config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
99
99
  dm = DataManager(config)
100
100
  dm._datasource = mock_datasource
101
101
 
@@ -138,7 +138,7 @@ class TestGetDailyCacheLogic:
138
138
 
139
139
  def test_no_cache_fetches_full_data(self, temp_cache_dir, mock_datasource):
140
140
  """缓存不存在 → 全量拉取"""
141
- config = DataConfig(cache_dir=temp_cache_dir, source="mock")
141
+ config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
142
142
  dm = DataManager(config)
143
143
  dm._datasource = mock_datasource
144
144
 
@@ -165,7 +165,7 @@ class TestGetDailyCacheLogic:
165
165
 
166
166
  def test_no_calendar_fetches_full_data(self, temp_cache_dir, mock_datasource):
167
167
  """交易日历不可用 → 直接拉取,不使用缓存"""
168
- config = DataConfig(cache_dir=temp_cache_dir, source="mock")
168
+ config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
169
169
  dm = DataManager(config)
170
170
  dm._datasource = mock_datasource
171
171
 
@@ -200,7 +200,7 @@ class TestGetDailyCacheLogic:
200
200
  self, temp_cache_dir, mock_datasource
201
201
  ):
202
202
  """交易日历不可用 + 无缓存 → 全量拉取"""
203
- config = DataConfig(cache_dir=temp_cache_dir, source="mock")
203
+ config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
204
204
  dm = DataManager(config)
205
205
  dm._datasource = mock_datasource
206
206
 
@@ -222,7 +222,9 @@ class TestGetDailyBatch:
222
222
 
223
223
  def test_batch_uses_parallel_when_enabled(self, temp_cache_dir, mock_datasource):
224
224
  """批量获取启用并行"""
225
- config = DataConfig(cache_dir=temp_cache_dir, source="mock", parallel=2)
225
+ config = DataConfig(
226
+ cache_dir=temp_cache_dir, source="mock", parallel=2, cache_raw=True
227
+ )
226
228
  dm = DataManager(config)
227
229
  dm._datasource = mock_datasource
228
230
 
@@ -240,7 +242,7 @@ class TestCacheCleanup:
240
242
 
241
243
  def test_clear_cache_removes_files(self, temp_cache_dir, mock_datasource):
242
244
  """清理缓存删除文件"""
243
- config = DataConfig(cache_dir=temp_cache_dir, source="mock")
245
+ config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
244
246
  dm = DataManager(config)
245
247
  dm._datasource = mock_datasource
246
248
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes