quantcli 0.1.14__tar.gz → 0.1.15__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 (101) hide show
  1. {quantcli-0.1.14/quantcli.egg-info → quantcli-0.1.15}/PKG-INFO +1 -1
  2. {quantcli-0.1.14 → quantcli-0.1.15}/pyproject.toml +1 -1
  3. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/core/data.py +80 -48
  4. {quantcli-0.1.14 → quantcli-0.1.15/quantcli.egg-info}/PKG-INFO +1 -1
  5. {quantcli-0.1.14 → quantcli-0.1.15}/LICENSE +0 -0
  6. {quantcli-0.1.14 → quantcli-0.1.15}/README.md +0 -0
  7. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/cli.py +0 -0
  8. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/core/__init__.py +0 -0
  9. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/core/backtest.py +0 -0
  10. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/core/factor.py +0 -0
  11. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/__init__.py +0 -0
  12. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/akshare.py +0 -0
  13. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/baostock.py +0 -0
  14. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/base.py +0 -0
  15. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/cache.py +0 -0
  16. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/fundamentals/__init__.py +0 -0
  17. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/fundamentals/provider.py +0 -0
  18. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/mixed.py +0 -0
  19. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/mysql.py +0 -0
  20. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/sync/__init__.py +0 -0
  21. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/sync/akshare.py +0 -0
  22. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/sync/base.py +0 -0
  23. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/sync/gm.py +0 -0
  24. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/datasources/sync/gm_fundamental.py +0 -0
  25. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/__init__.py +0 -0
  26. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_001.yaml +0 -0
  27. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_002.yaml +0 -0
  28. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_003.yaml +0 -0
  29. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_004.yaml +0 -0
  30. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_005.yaml +0 -0
  31. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_006.yaml +0 -0
  32. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_007.yaml +0 -0
  33. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_008.yaml +0 -0
  34. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_009.yaml +0 -0
  35. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_010.yaml +0 -0
  36. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_011.yaml +0 -0
  37. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_012.yaml +0 -0
  38. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_013.yaml +0 -0
  39. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_014.yaml +0 -0
  40. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_015.yaml +0 -0
  41. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_016.yaml +0 -0
  42. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_017.yaml +0 -0
  43. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_018.yaml +0 -0
  44. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_019.yaml +0 -0
  45. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_020.yaml +0 -0
  46. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_021.yaml +0 -0
  47. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_022.yaml +0 -0
  48. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_023.yaml +0 -0
  49. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_024.yaml +0 -0
  50. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_025.yaml +0 -0
  51. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_026.yaml +0 -0
  52. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_027.yaml +0 -0
  53. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_028.yaml +0 -0
  54. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_029.yaml +0 -0
  55. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_030.yaml +0 -0
  56. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_031.yaml +0 -0
  57. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_032.yaml +0 -0
  58. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_033.yaml +0 -0
  59. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_034.yaml +0 -0
  60. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_035.yaml +0 -0
  61. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_036.yaml +0 -0
  62. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_037.yaml +0 -0
  63. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_038.yaml +0 -0
  64. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_039.yaml +0 -0
  65. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/alpha101/alpha_040.yaml +0 -0
  66. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/base.py +0 -0
  67. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/compute.py +0 -0
  68. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/loader.py +0 -0
  69. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/pipeline.py +0 -0
  70. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/ranking.py +0 -0
  71. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/ranking_executor.py +0 -0
  72. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/screening.py +0 -0
  73. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/factors/screening_executor.py +0 -0
  74. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/models/bar.py +0 -0
  75. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/parser/__init__.py +0 -0
  76. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/parser/constants.py +0 -0
  77. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/parser/formula.py +0 -0
  78. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/__init__.py +0 -0
  79. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/env.py +0 -0
  80. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/logger.py +0 -0
  81. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/path.py +0 -0
  82. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/symbol_utils.py +0 -0
  83. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/time.py +0 -0
  84. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli/utils/validate.py +0 -0
  85. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli.egg-info/SOURCES.txt +0 -0
  86. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli.egg-info/dependency_links.txt +0 -0
  87. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli.egg-info/entry_points.txt +0 -0
  88. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli.egg-info/requires.txt +0 -0
  89. {quantcli-0.1.14 → quantcli-0.1.15}/quantcli.egg-info/top_level.txt +0 -0
  90. {quantcli-0.1.14 → quantcli-0.1.15}/setup.cfg +0 -0
  91. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_akshare_integration.py +0 -0
  92. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_builtin_factors.py +0 -0
  93. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_cli.py +0 -0
  94. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_datasources.py +0 -0
  95. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_factors.py +0 -0
  96. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_gm_executors.py +0 -0
  97. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_mixed_datasource.py +0 -0
  98. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_multi_factor.py +0 -0
  99. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_pipeline_integration.py +0 -0
  100. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_symbol_utils.py +0 -0
  101. {quantcli-0.1.14 → quantcli-0.1.15}/tests/test_time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.1.14
3
+ Version: 0.1.15
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.1.14"
7
+ version = "0.1.15"
8
8
  description = "面向AI的多因子量化选股策略挖掘工具,AI Agent 友好 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -21,10 +21,7 @@ from pathlib import Path
21
21
  import pandas as pd
22
22
 
23
23
  from ..datasources import create_datasource, DataSource, StockInfo
24
- from ..utils import (
25
- get_logger, project_root, ensure_dir, format_size,
26
- parse_date, today
27
- )
24
+ from ..utils import get_logger, project_root, ensure_dir, format_size, parse_date, today
28
25
 
29
26
  logger = get_logger(__name__)
30
27
 
@@ -32,6 +29,7 @@ logger = get_logger(__name__)
32
29
  @dataclass
33
30
  class DataConfig:
34
31
  """数据管理配置"""
32
+
35
33
  source: str = "akshare"
36
34
  cache_dir: str = "./data"
37
35
  parallel: int = 4
@@ -54,6 +52,7 @@ class DataConfig:
54
52
  @dataclass
55
53
  class DataQualityReport:
56
54
  """数据质量报告"""
55
+
57
56
  total_rows: int
58
57
  null_count: Dict[str, int]
59
58
  outlier_count: Dict[str, int]
@@ -113,12 +112,7 @@ class DataManager:
113
112
  # =============================================================================
114
113
 
115
114
  def get_daily(
116
- self,
117
- symbol: str,
118
- start_date,
119
- end_date,
120
- use_cache: bool = True,
121
- **kwargs
115
+ self, symbol: str, start_date, end_date, use_cache: bool = True, **kwargs
122
116
  ) -> pd.DataFrame:
123
117
  """获取单只股票日线数据"""
124
118
  cache_file = self.cache_dir / "raw" / "stock_daily" / f"{symbol}.parquet"
@@ -151,7 +145,7 @@ class DataManager:
151
145
  start_date,
152
146
  end_date,
153
147
  use_cache: bool = True,
154
- parallel: bool = False
148
+ parallel: bool = False,
155
149
  ) -> pd.DataFrame:
156
150
  """批量获取多只股票日线数据"""
157
151
  if not symbols:
@@ -161,6 +155,7 @@ class DataManager:
161
155
 
162
156
  if parallel and len(symbols) > 1:
163
157
  from concurrent.futures import ThreadPoolExecutor
158
+
164
159
  with ThreadPoolExecutor(max_workers=self.config.parallel) as executor:
165
160
  futures = [
166
161
  executor.submit(self.get_daily, s, start_date, end_date, use_cache)
@@ -192,10 +187,7 @@ class DataManager:
192
187
  return result
193
188
 
194
189
  def get_universe(
195
- self,
196
- universe: str = "all",
197
- market: str = "all",
198
- use_cache: bool = True
190
+ self, universe: str = "all", market: str = "all", use_cache: bool = True
199
191
  ) -> List[StockInfo]:
200
192
  """获取股票池"""
201
193
  cache_file = self.cache_dir / "cache" / f"stock_list_{market}.json"
@@ -221,9 +213,7 @@ class DataManager:
221
213
  return stocks
222
214
 
223
215
  def get_trading_calendar(
224
- self,
225
- exchange: str = "SSE",
226
- use_cache: bool = True
216
+ self, exchange: str = "SSE", use_cache: bool = True
227
217
  ) -> List:
228
218
  """获取交易日历"""
229
219
  cache_file = self.cache_dir / "cache" / f"trading_calendar_{exchange}.json"
@@ -244,11 +234,7 @@ class DataManager:
244
234
  return trading_days
245
235
 
246
236
  def get_index_daily(
247
- self,
248
- symbol: str,
249
- start_date,
250
- end_date,
251
- use_cache: bool = True
237
+ self, symbol: str, start_date, end_date, use_cache: bool = True
252
238
  ) -> pd.DataFrame:
253
239
  """获取指数日线数据"""
254
240
  cache_file = self.cache_dir / "raw" / "index_daily" / f"{symbol}.parquet"
@@ -268,7 +254,9 @@ class DataManager:
268
254
 
269
255
  return df
270
256
 
271
- def _filter_universe(self, stocks: List[StockInfo], universe: str) -> List[StockInfo]:
257
+ def _filter_universe(
258
+ self, stocks: List[StockInfo], universe: str
259
+ ) -> List[StockInfo]:
272
260
  """根据股票池名称过滤股票"""
273
261
  return stocks
274
262
 
@@ -303,8 +291,10 @@ class DataManager:
303
291
  try:
304
292
  existing = pd.read_parquet(cache_file)
305
293
  df_copy = pd.concat([existing, df_copy]).drop_duplicates(
306
- subset=["date"] if "symbol" not in df_copy.columns else ["date", "symbol"],
307
- keep="last"
294
+ subset=["date"]
295
+ if "symbol" not in df_copy.columns
296
+ else ["date", "symbol"],
297
+ keep="last",
308
298
  )
309
299
  except Exception:
310
300
  pass
@@ -313,35 +303,62 @@ class DataManager:
313
303
 
314
304
  self._cache_metadata[cache_key] = {
315
305
  "last_updated": datetime.now().isoformat(),
316
- "rows": len(df_copy)
306
+ "rows": len(df_copy),
317
307
  }
318
308
  self._save_metadata()
319
309
 
320
310
  logger.debug(f"Cached {len(df_copy)} rows to {cache_file}")
321
311
 
322
- def clear_cache(self, older_than: Optional[int] = None, pattern: Optional[str] = None) -> int:
312
+ def clear_cache(
313
+ self, older_than: Optional[int] = None, pattern: Optional[str] = None
314
+ ) -> int:
323
315
  """清理缓存"""
324
316
  count = 0
325
- raw_dir = self.cache_dir / "raw"
317
+
318
+ # 清理所有缓存子目录
319
+ cache_subdirs = ["raw", "prices", "stocklist", "calendar"]
326
320
 
327
321
  if older_than:
328
322
  from datetime import timedelta
323
+
329
324
  cutoff = today() - timedelta(days=older_than)
330
- for cache_file in raw_dir.rglob("*.parquet"):
331
- mtime = parse_date(cache_file.stat().st_mtime)
332
- if mtime < cutoff:
333
- cache_file.unlink()
334
- count += 1
325
+ for subdir in cache_subdirs:
326
+ dir_path = self.cache_dir / subdir
327
+ if dir_path.exists():
328
+ for cache_file in dir_path.rglob("*.parquet"):
329
+ try:
330
+ mtime = parse_date(cache_file.stat().st_mtime)
331
+ if mtime < cutoff:
332
+ cache_file.unlink()
333
+ count += 1
334
+ except OSError:
335
+ continue
335
336
  elif pattern:
336
- for cache_file in raw_dir.rglob(pattern):
337
- if cache_file.is_file():
338
- cache_file.unlink()
339
- count += 1
337
+ for subdir in cache_subdirs:
338
+ dir_path = self.cache_dir / subdir
339
+ if dir_path.exists():
340
+ for cache_file in dir_path.rglob(pattern):
341
+ if cache_file.is_file():
342
+ try:
343
+ cache_file.unlink()
344
+ count += 1
345
+ except OSError:
346
+ continue
340
347
  else:
341
- for cache_file in raw_dir.rglob("*.parquet"):
342
- cache_file.unlink()
343
- count += 1
344
- ensure_dir(self.cache_dir / "features")
348
+ # 清理所有缓存目录
349
+ for subdir in cache_subdirs:
350
+ dir_path = self.cache_dir / subdir
351
+ if dir_path.exists():
352
+ for cache_file in dir_path.rglob("*.parquet"):
353
+ try:
354
+ cache_file.unlink()
355
+ count += 1
356
+ except OSError:
357
+ continue
358
+
359
+ # 确保目录结构存在
360
+ for subdir in cache_subdirs:
361
+ ensure_dir(self.cache_dir / subdir)
345
362
 
346
363
  logger.info(f"Cleared {count} cache files")
347
364
  return count
@@ -354,7 +371,13 @@ class DataManager:
354
371
  relative = str(path.relative_to(self.cache_dir))
355
372
  sizes[relative] = format_size(path.stat().st_size)
356
373
 
357
- sizes["_total"] = format_size(sum(path.stat().st_size for path in self.cache_dir.rglob("*.parquet") if path.is_file()))
374
+ sizes["_total"] = format_size(
375
+ sum(
376
+ path.stat().st_size
377
+ for path in self.cache_dir.rglob("*.parquet")
378
+ if path.is_file()
379
+ )
380
+ )
358
381
  return sizes
359
382
 
360
383
  # =============================================================================
@@ -369,7 +392,7 @@ class DataManager:
369
392
  filter_st: Optional[bool] = None,
370
393
  filter_suspended: Optional[bool] = None,
371
394
  filter_new: Optional[int] = None,
372
- inplace: bool = False
395
+ inplace: bool = False,
373
396
  ) -> pd.DataFrame:
374
397
  """数据清洗"""
375
398
  if not inplace:
@@ -381,7 +404,7 @@ class DataManager:
381
404
  outlier_count={},
382
405
  st_filtered=0,
383
406
  suspended_filtered=0,
384
- new_stock_filtered=0
407
+ new_stock_filtered=0,
385
408
  )
386
409
 
387
410
  # 空值填充
@@ -404,14 +427,20 @@ class DataManager:
404
427
  report.st_filtered = original_len - len(df)
405
428
 
406
429
  # 过滤停牌
407
- filter_suspended_flag = filter_suspended if filter_suspended is not None else self.config.filter_suspended
430
+ filter_suspended_flag = (
431
+ filter_suspended
432
+ if filter_suspended is not None
433
+ else self.config.filter_suspended
434
+ )
408
435
  if filter_suspended_flag:
409
436
  original_len = len(df)
410
437
  df = self._filter_suspended(df)
411
438
  report.suspended_filtered = original_len - len(df)
412
439
 
413
440
  # 过滤新股
414
- filter_new_days = filter_new if filter_new is not None else self.config.filter_new
441
+ filter_new_days = (
442
+ filter_new if filter_new is not None else self.config.filter_new
443
+ )
415
444
  if filter_new_days and filter_new_days > 0:
416
445
  original_len = len(df)
417
446
  df = self._filter_new_stocks(df, filter_new_days)
@@ -506,7 +535,10 @@ class DataManager:
506
535
  return {
507
536
  **status,
508
537
  "cache": self.get_cache_size(),
509
- "config": {"source": self.config.source, "cache_dir": str(self.cache_dir)}
538
+ "config": {
539
+ "source": self.config.source,
540
+ "cache_dir": str(self.cache_dir),
541
+ },
510
542
  }
511
543
  except Exception as e:
512
544
  return {"status": "error", "error": str(e)}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.1.14
3
+ Version: 0.1.15
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes