quantcli 0.1.14__tar.gz → 0.1.16__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.16}/PKG-INFO +1 -1
  2. {quantcli-0.1.14 → quantcli-0.1.16}/pyproject.toml +1 -1
  3. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/cli.py +3 -1
  4. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/core/data.py +71 -48
  5. {quantcli-0.1.14 → quantcli-0.1.16/quantcli.egg-info}/PKG-INFO +1 -1
  6. {quantcli-0.1.14 → quantcli-0.1.16}/LICENSE +0 -0
  7. {quantcli-0.1.14 → quantcli-0.1.16}/README.md +0 -0
  8. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/core/__init__.py +0 -0
  9. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/core/backtest.py +0 -0
  10. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/core/factor.py +0 -0
  11. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/__init__.py +0 -0
  12. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/akshare.py +0 -0
  13. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/baostock.py +0 -0
  14. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/base.py +0 -0
  15. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/cache.py +0 -0
  16. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/fundamentals/__init__.py +0 -0
  17. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/fundamentals/provider.py +0 -0
  18. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/mixed.py +0 -0
  19. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/mysql.py +0 -0
  20. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/sync/__init__.py +0 -0
  21. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/sync/akshare.py +0 -0
  22. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/sync/base.py +0 -0
  23. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/sync/gm.py +0 -0
  24. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/datasources/sync/gm_fundamental.py +0 -0
  25. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/__init__.py +0 -0
  26. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_001.yaml +0 -0
  27. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_002.yaml +0 -0
  28. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_003.yaml +0 -0
  29. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_004.yaml +0 -0
  30. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_005.yaml +0 -0
  31. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_006.yaml +0 -0
  32. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_007.yaml +0 -0
  33. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_008.yaml +0 -0
  34. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_009.yaml +0 -0
  35. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_010.yaml +0 -0
  36. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_011.yaml +0 -0
  37. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_012.yaml +0 -0
  38. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_013.yaml +0 -0
  39. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_014.yaml +0 -0
  40. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_015.yaml +0 -0
  41. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_016.yaml +0 -0
  42. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_017.yaml +0 -0
  43. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_018.yaml +0 -0
  44. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_019.yaml +0 -0
  45. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_020.yaml +0 -0
  46. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_021.yaml +0 -0
  47. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_022.yaml +0 -0
  48. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_023.yaml +0 -0
  49. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_024.yaml +0 -0
  50. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_025.yaml +0 -0
  51. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_026.yaml +0 -0
  52. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_027.yaml +0 -0
  53. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_028.yaml +0 -0
  54. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_029.yaml +0 -0
  55. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_030.yaml +0 -0
  56. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_031.yaml +0 -0
  57. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_032.yaml +0 -0
  58. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_033.yaml +0 -0
  59. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_034.yaml +0 -0
  60. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_035.yaml +0 -0
  61. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_036.yaml +0 -0
  62. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_037.yaml +0 -0
  63. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_038.yaml +0 -0
  64. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_039.yaml +0 -0
  65. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/alpha101/alpha_040.yaml +0 -0
  66. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/base.py +0 -0
  67. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/compute.py +0 -0
  68. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/loader.py +0 -0
  69. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/pipeline.py +0 -0
  70. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/ranking.py +0 -0
  71. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/ranking_executor.py +0 -0
  72. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/screening.py +0 -0
  73. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/factors/screening_executor.py +0 -0
  74. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/models/bar.py +0 -0
  75. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/parser/__init__.py +0 -0
  76. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/parser/constants.py +0 -0
  77. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/parser/formula.py +0 -0
  78. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/__init__.py +0 -0
  79. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/env.py +0 -0
  80. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/logger.py +0 -0
  81. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/path.py +0 -0
  82. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/symbol_utils.py +0 -0
  83. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/time.py +0 -0
  84. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli/utils/validate.py +0 -0
  85. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli.egg-info/SOURCES.txt +0 -0
  86. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli.egg-info/dependency_links.txt +0 -0
  87. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli.egg-info/entry_points.txt +0 -0
  88. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli.egg-info/requires.txt +0 -0
  89. {quantcli-0.1.14 → quantcli-0.1.16}/quantcli.egg-info/top_level.txt +0 -0
  90. {quantcli-0.1.14 → quantcli-0.1.16}/setup.cfg +0 -0
  91. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_akshare_integration.py +0 -0
  92. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_builtin_factors.py +0 -0
  93. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_cli.py +0 -0
  94. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_datasources.py +0 -0
  95. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_factors.py +0 -0
  96. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_gm_executors.py +0 -0
  97. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_mixed_datasource.py +0 -0
  98. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_multi_factor.py +0 -0
  99. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_pipeline_integration.py +0 -0
  100. {quantcli-0.1.14 → quantcli-0.1.16}/tests/test_symbol_utils.py +0 -0
  101. {quantcli-0.1.14 → quantcli-0.1.16}/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.16
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.16"
8
8
  description = "面向AI的多因子量化选股策略挖掘工具,AI Agent 友好 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -78,10 +78,12 @@ def quantcli(ctx, verbose):
78
78
 
79
79
  if verbose:
80
80
  import logging
81
+ import importlib.metadata
81
82
 
82
83
  logging.basicConfig(level=logging.DEBUG)
83
84
 
84
- logger.info(f"QuantCLI v0.1.10 started")
85
+ version = importlib.metadata.version("quantcli")
86
+ logger.info(f"QuantCLI v{version} started")
85
87
 
86
88
 
87
89
  # =============================================================================
@@ -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,53 @@ 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:
315
+ """清理缓存"""
316
+ count = 0
317
+
318
+ def clear_cache(
319
+ self, older_than: Optional[int] = None, pattern: Optional[str] = None
320
+ ) -> int:
323
321
  """清理缓存"""
324
322
  count = 0
325
- raw_dir = self.cache_dir / "raw"
326
323
 
327
324
  if older_than:
328
325
  from datetime import timedelta
326
+
329
327
  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
328
+ for cache_file in self.cache_dir.rglob("*.parquet"):
329
+ try:
330
+ if cache_file.is_file():
331
+ mtime = parse_date(cache_file.stat().st_mtime)
332
+ if mtime < cutoff:
333
+ cache_file.unlink()
334
+ count += 1
335
+ except OSError:
336
+ continue
335
337
  elif pattern:
336
- for cache_file in raw_dir.rglob(pattern):
337
- if cache_file.is_file():
338
- cache_file.unlink()
339
- count += 1
338
+ for cache_file in self.cache_dir.rglob(pattern):
339
+ try:
340
+ if cache_file.is_file():
341
+ cache_file.unlink()
342
+ count += 1
343
+ except OSError:
344
+ continue
340
345
  else:
341
- for cache_file in raw_dir.rglob("*.parquet"):
342
- cache_file.unlink()
343
- count += 1
344
- ensure_dir(self.cache_dir / "features")
346
+ for cache_file in self.cache_dir.rglob("*.parquet"):
347
+ try:
348
+ if cache_file.is_file():
349
+ cache_file.unlink()
350
+ count += 1
351
+ except OSError:
352
+ continue
345
353
 
346
354
  logger.info(f"Cleared {count} cache files")
347
355
  return count
@@ -354,7 +362,13 @@ class DataManager:
354
362
  relative = str(path.relative_to(self.cache_dir))
355
363
  sizes[relative] = format_size(path.stat().st_size)
356
364
 
357
- sizes["_total"] = format_size(sum(path.stat().st_size for path in self.cache_dir.rglob("*.parquet") if path.is_file()))
365
+ sizes["_total"] = format_size(
366
+ sum(
367
+ path.stat().st_size
368
+ for path in self.cache_dir.rglob("*.parquet")
369
+ if path.is_file()
370
+ )
371
+ )
358
372
  return sizes
359
373
 
360
374
  # =============================================================================
@@ -369,7 +383,7 @@ class DataManager:
369
383
  filter_st: Optional[bool] = None,
370
384
  filter_suspended: Optional[bool] = None,
371
385
  filter_new: Optional[int] = None,
372
- inplace: bool = False
386
+ inplace: bool = False,
373
387
  ) -> pd.DataFrame:
374
388
  """数据清洗"""
375
389
  if not inplace:
@@ -381,7 +395,7 @@ class DataManager:
381
395
  outlier_count={},
382
396
  st_filtered=0,
383
397
  suspended_filtered=0,
384
- new_stock_filtered=0
398
+ new_stock_filtered=0,
385
399
  )
386
400
 
387
401
  # 空值填充
@@ -404,14 +418,20 @@ class DataManager:
404
418
  report.st_filtered = original_len - len(df)
405
419
 
406
420
  # 过滤停牌
407
- filter_suspended_flag = filter_suspended if filter_suspended is not None else self.config.filter_suspended
421
+ filter_suspended_flag = (
422
+ filter_suspended
423
+ if filter_suspended is not None
424
+ else self.config.filter_suspended
425
+ )
408
426
  if filter_suspended_flag:
409
427
  original_len = len(df)
410
428
  df = self._filter_suspended(df)
411
429
  report.suspended_filtered = original_len - len(df)
412
430
 
413
431
  # 过滤新股
414
- filter_new_days = filter_new if filter_new is not None else self.config.filter_new
432
+ filter_new_days = (
433
+ filter_new if filter_new is not None else self.config.filter_new
434
+ )
415
435
  if filter_new_days and filter_new_days > 0:
416
436
  original_len = len(df)
417
437
  df = self._filter_new_stocks(df, filter_new_days)
@@ -506,7 +526,10 @@ class DataManager:
506
526
  return {
507
527
  **status,
508
528
  "cache": self.get_cache_size(),
509
- "config": {"source": self.config.source, "cache_dir": str(self.cache_dir)}
529
+ "config": {
530
+ "source": self.config.source,
531
+ "cache_dir": str(self.cache_dir),
532
+ },
510
533
  }
511
534
  except Exception as e:
512
535
  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.16
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