quantcli 0.1.10__tar.gz → 0.1.12__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.10/quantcli.egg-info → quantcli-0.1.12}/PKG-INFO +1 -1
  2. {quantcli-0.1.10 → quantcli-0.1.12}/pyproject.toml +4 -1
  3. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/cli.py +43 -73
  4. quantcli-0.1.12/quantcli/factors/alpha101/alpha_001.yaml +6 -0
  5. quantcli-0.1.12/quantcli/factors/alpha101/alpha_002.yaml +6 -0
  6. quantcli-0.1.12/quantcli/factors/alpha101/alpha_003.yaml +6 -0
  7. quantcli-0.1.12/quantcli/factors/alpha101/alpha_004.yaml +6 -0
  8. quantcli-0.1.12/quantcli/factors/alpha101/alpha_005.yaml +6 -0
  9. quantcli-0.1.12/quantcli/factors/alpha101/alpha_006.yaml +6 -0
  10. quantcli-0.1.12/quantcli/factors/alpha101/alpha_007.yaml +6 -0
  11. quantcli-0.1.12/quantcli/factors/alpha101/alpha_008.yaml +6 -0
  12. quantcli-0.1.12/quantcli/factors/alpha101/alpha_009.yaml +6 -0
  13. quantcli-0.1.12/quantcli/factors/alpha101/alpha_010.yaml +6 -0
  14. quantcli-0.1.12/quantcli/factors/alpha101/alpha_011.yaml +6 -0
  15. quantcli-0.1.12/quantcli/factors/alpha101/alpha_012.yaml +6 -0
  16. quantcli-0.1.12/quantcli/factors/alpha101/alpha_013.yaml +6 -0
  17. quantcli-0.1.12/quantcli/factors/alpha101/alpha_014.yaml +6 -0
  18. quantcli-0.1.12/quantcli/factors/alpha101/alpha_015.yaml +6 -0
  19. quantcli-0.1.12/quantcli/factors/alpha101/alpha_016.yaml +6 -0
  20. quantcli-0.1.12/quantcli/factors/alpha101/alpha_017.yaml +6 -0
  21. quantcli-0.1.12/quantcli/factors/alpha101/alpha_018.yaml +6 -0
  22. quantcli-0.1.12/quantcli/factors/alpha101/alpha_019.yaml +6 -0
  23. quantcli-0.1.12/quantcli/factors/alpha101/alpha_020.yaml +6 -0
  24. quantcli-0.1.12/quantcli/factors/alpha101/alpha_021.yaml +6 -0
  25. quantcli-0.1.12/quantcli/factors/alpha101/alpha_022.yaml +6 -0
  26. quantcli-0.1.12/quantcli/factors/alpha101/alpha_023.yaml +6 -0
  27. quantcli-0.1.12/quantcli/factors/alpha101/alpha_024.yaml +6 -0
  28. quantcli-0.1.12/quantcli/factors/alpha101/alpha_025.yaml +6 -0
  29. quantcli-0.1.12/quantcli/factors/alpha101/alpha_026.yaml +6 -0
  30. quantcli-0.1.12/quantcli/factors/alpha101/alpha_027.yaml +6 -0
  31. quantcli-0.1.12/quantcli/factors/alpha101/alpha_028.yaml +6 -0
  32. quantcli-0.1.12/quantcli/factors/alpha101/alpha_029.yaml +6 -0
  33. quantcli-0.1.12/quantcli/factors/alpha101/alpha_030.yaml +6 -0
  34. quantcli-0.1.12/quantcli/factors/alpha101/alpha_031.yaml +6 -0
  35. quantcli-0.1.12/quantcli/factors/alpha101/alpha_032.yaml +6 -0
  36. quantcli-0.1.12/quantcli/factors/alpha101/alpha_033.yaml +6 -0
  37. quantcli-0.1.12/quantcli/factors/alpha101/alpha_034.yaml +6 -0
  38. quantcli-0.1.12/quantcli/factors/alpha101/alpha_035.yaml +6 -0
  39. quantcli-0.1.12/quantcli/factors/alpha101/alpha_036.yaml +6 -0
  40. quantcli-0.1.12/quantcli/factors/alpha101/alpha_037.yaml +6 -0
  41. quantcli-0.1.12/quantcli/factors/alpha101/alpha_038.yaml +6 -0
  42. quantcli-0.1.12/quantcli/factors/alpha101/alpha_039.yaml +6 -0
  43. quantcli-0.1.12/quantcli/factors/alpha101/alpha_040.yaml +6 -0
  44. {quantcli-0.1.10 → quantcli-0.1.12/quantcli.egg-info}/PKG-INFO +1 -1
  45. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli.egg-info/SOURCES.txt +40 -0
  46. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_builtin_factors.py +89 -68
  47. {quantcli-0.1.10 → quantcli-0.1.12}/LICENSE +0 -0
  48. {quantcli-0.1.10 → quantcli-0.1.12}/README.md +0 -0
  49. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/core/__init__.py +0 -0
  50. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/core/backtest.py +0 -0
  51. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/core/data.py +0 -0
  52. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/core/factor.py +0 -0
  53. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/__init__.py +0 -0
  54. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/akshare.py +0 -0
  55. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/baostock.py +0 -0
  56. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/base.py +0 -0
  57. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/cache.py +0 -0
  58. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/fundamentals/__init__.py +0 -0
  59. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/fundamentals/provider.py +0 -0
  60. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/mixed.py +0 -0
  61. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/mysql.py +0 -0
  62. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/sync/__init__.py +0 -0
  63. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/sync/akshare.py +0 -0
  64. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/sync/base.py +0 -0
  65. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/sync/gm.py +0 -0
  66. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/datasources/sync/gm_fundamental.py +0 -0
  67. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/__init__.py +0 -0
  68. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/base.py +0 -0
  69. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/compute.py +0 -0
  70. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/loader.py +0 -0
  71. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/pipeline.py +0 -0
  72. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/ranking.py +0 -0
  73. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/ranking_executor.py +0 -0
  74. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/screening.py +0 -0
  75. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/factors/screening_executor.py +0 -0
  76. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/models/bar.py +0 -0
  77. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/parser/__init__.py +0 -0
  78. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/parser/constants.py +0 -0
  79. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/parser/formula.py +0 -0
  80. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/__init__.py +0 -0
  81. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/env.py +0 -0
  82. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/logger.py +0 -0
  83. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/path.py +0 -0
  84. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/symbol_utils.py +0 -0
  85. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/time.py +0 -0
  86. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli/utils/validate.py +0 -0
  87. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli.egg-info/dependency_links.txt +0 -0
  88. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli.egg-info/entry_points.txt +0 -0
  89. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli.egg-info/requires.txt +0 -0
  90. {quantcli-0.1.10 → quantcli-0.1.12}/quantcli.egg-info/top_level.txt +0 -0
  91. {quantcli-0.1.10 → quantcli-0.1.12}/setup.cfg +0 -0
  92. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_akshare_integration.py +0 -0
  93. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_cli.py +0 -0
  94. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_datasources.py +0 -0
  95. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_factors.py +0 -0
  96. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_gm_executors.py +0 -0
  97. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_mixed_datasource.py +0 -0
  98. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_multi_factor.py +0 -0
  99. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_pipeline_integration.py +0 -0
  100. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_symbol_utils.py +0 -0
  101. {quantcli-0.1.10 → quantcli-0.1.12}/tests/test_time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.1.10
3
+ Version: 0.1.12
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.10"
7
+ version = "0.1.12"
8
8
  description = "面向AI的多因子量化选股策略挖掘工具,AI Agent 友好 CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -48,6 +48,9 @@ where = ["."]
48
48
  include = ["quantcli*"]
49
49
  exclude = ["tests*"]
50
50
 
51
+ [tool.setuptools.package-data]
52
+ quantcli = ["factors/alpha101/*.yaml", "factors/*.yaml"]
53
+
51
54
  [tool.pytest.ini_options]
52
55
  testpaths = ["tests"]
53
56
  python_files = ["test_*.py"]
@@ -277,68 +277,6 @@ def data_sync_fundamentals(ctx, years):
277
277
  sys.exit(1)
278
278
 
279
279
 
280
- # =============================================================================
281
- # Factors 命令 - 列出内置因子
282
- # =============================================================================
283
-
284
-
285
- @quantcli.group()
286
- def factors():
287
- """内置因子管理"""
288
- pass
289
-
290
-
291
- @factors.command("list")
292
- @click.option("--json", is_flag=True, help="Output as JSON")
293
- @click.pass_context
294
- def factors_list(ctx, json):
295
- """列出所有内置 Alpha101 因子"""
296
- from .utils import builtin_factors_dir
297
-
298
- builtin_dir = builtin_factors_dir() / "alpha101"
299
-
300
- if not builtin_dir.exists():
301
- click.echo("No built-in factors found")
302
- return
303
-
304
- # 加载所有因子
305
- factors = []
306
- for f in sorted(builtin_dir.glob("alpha_*.yaml")):
307
- import yaml
308
-
309
- with open(f, "r", encoding="utf-8") as fp:
310
- data = yaml.safe_load(fp)
311
- factors.append(
312
- {
313
- "name": data.get("name", f.stem),
314
- "type": data.get("type", "technical"),
315
- "direction": data.get("direction", "neutral"),
316
- "description": data.get("description", ""),
317
- "file": f"alpha101/{f.name}",
318
- }
319
- )
320
-
321
- if json:
322
- import json
323
-
324
- click.echo(
325
- json.dumps(
326
- {"status": "success", "count": len(factors), "factors": factors},
327
- ensure_ascii=False,
328
- indent=2,
329
- )
330
- )
331
- return
332
-
333
- click.echo(f"Built-in Alpha101 Factors ({len(factors)} total):\n")
334
- click.echo(f"{'File':<22} {'Type':<12} {'Direction':<10} Description")
335
- click.echo("-" * 60)
336
- for f in factors:
337
- click.echo(
338
- f"{f['file']:<22} {f['type']:<12} {f['direction']:<10} {f['description']}"
339
- )
340
-
341
-
342
280
  # =============================================================================
343
281
  # Factor 命令
344
282
  # =============================================================================
@@ -469,21 +407,53 @@ def factor_eval(ctx, name, symbol, start, end, method):
469
407
 
470
408
 
471
409
  @factor.command("list")
410
+ @click.option("--json", is_flag=True, help="Output as JSON")
472
411
  @click.pass_context
473
- def factor_list(ctx):
474
- """列出已注册的因子"""
475
- engine = FactorEngine()
476
- factors = engine.registry.list_all()
412
+ def factor_list(ctx, json):
413
+ """列出所有内置 Alpha101 因子"""
414
+ from .utils import builtin_factors_dir
477
415
 
478
- if not factors:
479
- click.echo("No factors registered")
416
+ builtin_dir = builtin_factors_dir() / "alpha101"
417
+
418
+ if not builtin_dir.exists():
419
+ click.echo("No built-in factors found")
480
420
  return
481
421
 
482
- click.echo("Registered factors:")
483
- for name in factors:
484
- factor = engine.registry.get(name)
485
- if factor:
486
- click.echo(f" - {name}: {factor.formula[:50]}...")
422
+ factors = []
423
+ for f in sorted(builtin_dir.glob("alpha_*.yaml")):
424
+ import yaml
425
+
426
+ with open(f, "r", encoding="utf-8") as fp:
427
+ data = yaml.safe_load(fp)
428
+ factors.append(
429
+ {
430
+ "name": data.get("name", f.stem),
431
+ "type": data.get("type", "technical"),
432
+ "direction": data.get("direction", "neutral"),
433
+ "description": data.get("description", ""),
434
+ "file": f"alpha101/{f.name}",
435
+ }
436
+ )
437
+
438
+ if json:
439
+ import json
440
+
441
+ click.echo(
442
+ json.dumps(
443
+ {"status": "success", "count": len(factors), "factors": factors},
444
+ ensure_ascii=False,
445
+ indent=2,
446
+ )
447
+ )
448
+ return
449
+
450
+ click.echo(f"Built-in Alpha101 Factors ({len(factors)} total):\n")
451
+ click.echo(f"{'File':<22} {'Type':<12} {'Direction':<10} Description")
452
+ click.echo("-" * 60)
453
+ for f in factors:
454
+ click.echo(
455
+ f"{f['file']:<22} {f['type']:<12} {f['direction']:<10} {f['description']}"
456
+ )
487
457
 
488
458
 
489
459
  @factor.command("run-file")
@@ -0,0 +1,6 @@
1
+ name: ALPHA_001 经典反转因子
2
+ type: technical
3
+ expr: "rank(ts_argmax(where(close == delay(close, 1), 0, close), 20)) - 0.5"
4
+ direction: positive
5
+ category: reversal
6
+ description: "20日创新高后转跌"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_002 阴线放量反转因子
2
+ type: technical
3
+ expr: "where(close < open, volume, -volume) / (ma(volume, 20) + 1)"
4
+ direction: positive
5
+ category: reversal
6
+ description: "下跌放量见底"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_003 低波动率因子
2
+ type: technical
3
+ expr: "ts_rank(ts_decayexp(close, 7), 10) - ts_rank(ts_decayexp(close, 20), 30)"
4
+ direction: positive
5
+ category: volatility
6
+ description: "低波动稳定"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_004 资金流向因子
2
+ type: technical
3
+ expr: "signed_power(where(close < open, volume, -volume), 2)"
4
+ direction: positive
5
+ category: flow
6
+ description: "资金净流入"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_005 均线偏离趋势因子
2
+ type: technical
3
+ expr: "ts_rank(close - ts_ave(close, 5), 5) - ts_rank(ts_decayexp(-close, 10), 20)"
4
+ direction: positive
5
+ category: trend
6
+ description: "趋势向上"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_006 收盘价位置因子
2
+ type: technical
3
+ expr: "where(close == ts_min(close, 5), close, ts_rank(close, 5))"
4
+ direction: positive
5
+ category: momentum
6
+ description: "收盘在高位"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_007 量价稳定性因子
2
+ type: technical
3
+ expr: "where(ts_rank(volume / ts_max(volume, 5), 5) < ts_rank(close / ts_max(close, 10), 10), -1, 1)"
4
+ direction: positive
5
+ category: volatility
6
+ description: "量价配合"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_008 资金净流入因子
2
+ type: technical
3
+ expr: "where(close < open, volume, -volume)"
4
+ direction: positive
5
+ category: flow
6
+ description: "资金流入"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_009 12月动量因子
2
+ type: technical
3
+ expr: "ts_sum(close, 240) / ts_sum(ts_ave(close, 20), 12) - 1"
4
+ direction: positive
5
+ category: momentum
6
+ description: "长期动量"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_010 均价偏离因子
2
+ type: technical
3
+ expr: "close - ts_ave(close, 20)"
4
+ direction: positive
5
+ category: reversal
6
+ description: "偏离均线修复"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_011 波动率收缩因子
2
+ type: technical
3
+ expr: "ts_rank(close, 10) / ts_rank(close + ts_std(close, 10), 50)"
4
+ direction: positive
5
+ category: volatility
6
+ description: "低波动率时买入"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_012 成交量反转因子
2
+ type: technical
3
+ expr: "where(ts_rank(volume, 10) < ts_rank(close + ts_std(close, 5), 100), -1, 1)"
4
+ direction: positive
5
+ category: reversal
6
+ description: "量价背离"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_013 动量加速因子
2
+ type: technical
3
+ expr: "ts_rank(close - delay(close, 5), 20) - ts_rank(close - delay(close, 10), 40)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "动量加速"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_014 价格位置因子
2
+ type: technical
3
+ expr: "ts_rank(close / ts_max(close, 20), 5)"
4
+ direction: positive
5
+ category: trend
6
+ description: "接近周期高点"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_015 成交量趋势因子
2
+ type: technical
3
+ expr: "ts_rank(volume / ts_ave(volume, 20), 10) - ts_rank(close / ts_ave(close, 20), 10)"
4
+ direction: positive
5
+ category: volume
6
+ description: "放量上涨"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_016 波动率动量因子
2
+ type: technical
3
+ expr: "ts_rank(ts_std(close, 5), 20) - ts_rank(ts_std(close, 20), 20)"
4
+ direction: negative
5
+ category: volatility
6
+ description: "低波动率动量"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_017 价格宽度因子
2
+ type: technical
3
+ expr: "ts_rank(close / ts_ave(close, 50), 10) * ts_rank(volume / ts_ave(volume, 50), 5)"
4
+ direction: positive
5
+ category: trend
6
+ description: "价格强度与成交量"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_018 短期反转因子
2
+ type: technical
3
+ expr: "ts_rank(delay(close, 5), 50) - ts_rank(close, 50)"
4
+ direction: positive
5
+ category: reversal
6
+ description: "短期反转"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_019 成交量动量因子
2
+ type: technical
3
+ expr: "ts_sum(volume * sign(close - delay(close, 1)), 10) / ts_sum(volume, 10)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "资金净流入"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_020 均线偏离因子
2
+ type: technical
3
+ expr: "(close - ma(close, 20)) / ma(close, 20) - (close - ma(close, 5)) / ma(close, 5)"
4
+ direction: positive
5
+ category: mean_reversion
6
+ description: "短期偏离收敛"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_021 成交量加权均价因子
2
+ type: technical
3
+ expr: "ts_rank(ts_ave(close * volume, 10) / ts_ave(volume, 10), 20)"
4
+ direction: positive
5
+ category: price
6
+ description: "成交量加权价格"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_022 量价相关性因子
2
+ type: technical
3
+ expr: "ts_rank(correlation(close, volume, 10), 20) - ts_rank(correlation(close, volume, 50), 20)"
4
+ direction: positive
5
+ category: volume
6
+ description: "短期量价关系"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_023 价格加速度因子
2
+ type: technical
3
+ expr: "ts_rank(close - delay(close, 10), 20) - ts_rank(close - delay(close, 20), 20)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "价格加速度"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_024 波动率突变因子
2
+ type: technical
3
+ expr: "ts_rank(ts_std(close, 5), 10) - ts_rank(ts_std(close, 20), 10)"
4
+ direction: negative
5
+ category: volatility
6
+ description: "波动率收缩"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_025 资金流向因子
2
+ type: technical
3
+ expr: "ts_sum(where(close > delay(close, 1), volume, -volume), 20) / ts_sum(volume, 20)"
4
+ direction: positive
5
+ category: flow
6
+ description: "20日资金流向"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_026 相对强弱因子
2
+ type: technical
3
+ expr: "ts_rank(close / ts_ave(close, 50), 20) - ts_rank(ts_std(close, 10), 20)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "相对强弱"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_027 量能潮因子
2
+ type: technical
3
+ expr: "ts_sum(where(close > delay(close, 1), volume, -volume), 10) / ts_sum(volume, 10)"
4
+ direction: positive
5
+ category: volume
6
+ description: "10日量能潮"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_028 均线金叉因子
2
+ type: technical
3
+ expr: "where(ma(close, 10) > ma(close, 20), 1, 0) * ts_rank(volume / ts_ave(volume, 20), 5)"
4
+ direction: positive
5
+ category: trend
6
+ description: "均线金叉"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_029 价格动量因子
2
+ type: technical
3
+ expr: "ts_rank((close - delay(close, 5)) / delay(close, 5), 20)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "5日价格动量"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_030 成交量比率因子
2
+ type: technical
3
+ expr: "ts_rank(volume / ts_max(volume, 20), 5)"
4
+ direction: positive
5
+ category: volume
6
+ description: "相对成交量"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_031 价格位置因子
2
+ type: technical
3
+ expr: "ts_rank(close / ts_min(close, 20), 20) - ts_rank(close / ts_max(close, 20), 20)"
4
+ direction: positive
5
+ category: price
6
+ description: "价格相对位置"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_032 趋势强度因子
2
+ type: technical
3
+ expr: "ts_rank(close - ma(close, 20), 20) / ts_rank(ts_std(close, 20), 20)"
4
+ direction: positive
5
+ category: trend
6
+ description: "趋势强度"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_033 成交量趋势因子
2
+ type: technical
3
+ expr: "ts_rank(volume - ma(volume, 20), 10)"
4
+ direction: positive
5
+ category: volume
6
+ description: "成交量趋势"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_034 价格波动率因子
2
+ type: technical
3
+ expr: "ts_rank((high - low) / ma(close, 10), 20)"
4
+ direction: negative
5
+ category: volatility
6
+ description: "低波动率"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_035 资金流入因子
2
+ type: technical
3
+ expr: "ts_rank(where(close > open, volume, 0), 10)"
4
+ direction: positive
5
+ category: flow
6
+ description: "资金流入"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_036 RSI超卖因子
2
+ type: technical
3
+ expr: "where(rsi(close, 14) < 30, 1, 0) * ts_rank(volume / ts_ave(volume, 20), 5)"
4
+ direction: positive
5
+ category: reversal
6
+ description: "RSI超卖"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_037 布林带突破因子
2
+ type: technical
3
+ expr: "where(close > ma(close, 20) + 2 * ts_std(close, 20), 1, 0)"
4
+ direction: positive
5
+ category: trend
6
+ description: "布林带上轨突破"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_038 MACD信号因子
2
+ type: technical
3
+ expr: "ts_rank(ema(close, 12) - ema(close, 26), 10)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "MACD值"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_039 量价齐升因子
2
+ type: technical
3
+ expr: "ts_rank(close / delay(close, 5), 10) * ts_rank(volume / delay(volume, 5), 10)"
4
+ direction: positive
5
+ category: momentum
6
+ description: "量价齐升"
@@ -0,0 +1,6 @@
1
+ name: ALPHA_040 强势整理因子
2
+ type: technical
3
+ expr: "where(close > ma(close, 10), 1, 0) * where(ts_std(close, 5) < ts_std(close, 20), 1, 0)"
4
+ direction: positive
5
+ category: trend
6
+ description: "强势整理"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantcli
3
- Version: 0.1.10
3
+ Version: 0.1.12
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
@@ -35,6 +35,46 @@ quantcli/factors/ranking.py
35
35
  quantcli/factors/ranking_executor.py
36
36
  quantcli/factors/screening.py
37
37
  quantcli/factors/screening_executor.py
38
+ quantcli/factors/alpha101/alpha_001.yaml
39
+ quantcli/factors/alpha101/alpha_002.yaml
40
+ quantcli/factors/alpha101/alpha_003.yaml
41
+ quantcli/factors/alpha101/alpha_004.yaml
42
+ quantcli/factors/alpha101/alpha_005.yaml
43
+ quantcli/factors/alpha101/alpha_006.yaml
44
+ quantcli/factors/alpha101/alpha_007.yaml
45
+ quantcli/factors/alpha101/alpha_008.yaml
46
+ quantcli/factors/alpha101/alpha_009.yaml
47
+ quantcli/factors/alpha101/alpha_010.yaml
48
+ quantcli/factors/alpha101/alpha_011.yaml
49
+ quantcli/factors/alpha101/alpha_012.yaml
50
+ quantcli/factors/alpha101/alpha_013.yaml
51
+ quantcli/factors/alpha101/alpha_014.yaml
52
+ quantcli/factors/alpha101/alpha_015.yaml
53
+ quantcli/factors/alpha101/alpha_016.yaml
54
+ quantcli/factors/alpha101/alpha_017.yaml
55
+ quantcli/factors/alpha101/alpha_018.yaml
56
+ quantcli/factors/alpha101/alpha_019.yaml
57
+ quantcli/factors/alpha101/alpha_020.yaml
58
+ quantcli/factors/alpha101/alpha_021.yaml
59
+ quantcli/factors/alpha101/alpha_022.yaml
60
+ quantcli/factors/alpha101/alpha_023.yaml
61
+ quantcli/factors/alpha101/alpha_024.yaml
62
+ quantcli/factors/alpha101/alpha_025.yaml
63
+ quantcli/factors/alpha101/alpha_026.yaml
64
+ quantcli/factors/alpha101/alpha_027.yaml
65
+ quantcli/factors/alpha101/alpha_028.yaml
66
+ quantcli/factors/alpha101/alpha_029.yaml
67
+ quantcli/factors/alpha101/alpha_030.yaml
68
+ quantcli/factors/alpha101/alpha_031.yaml
69
+ quantcli/factors/alpha101/alpha_032.yaml
70
+ quantcli/factors/alpha101/alpha_033.yaml
71
+ quantcli/factors/alpha101/alpha_034.yaml
72
+ quantcli/factors/alpha101/alpha_035.yaml
73
+ quantcli/factors/alpha101/alpha_036.yaml
74
+ quantcli/factors/alpha101/alpha_037.yaml
75
+ quantcli/factors/alpha101/alpha_038.yaml
76
+ quantcli/factors/alpha101/alpha_039.yaml
77
+ quantcli/factors/alpha101/alpha_040.yaml
38
78
  quantcli/models/bar.py
39
79
  quantcli/parser/__init__.py
40
80
  quantcli/parser/constants.py
@@ -18,7 +18,11 @@ import numpy as np
18
18
  from click.testing import CliRunner
19
19
 
20
20
  from quantcli import cli
21
- from quantcli.factors.loader import load_strategy, load_all_factors, load_factor_from_ref
21
+ from quantcli.factors.loader import (
22
+ load_strategy,
23
+ load_all_factors,
24
+ load_factor_from_ref,
25
+ )
22
26
  from quantcli.factors.pipeline import FactorPipeline
23
27
 
24
28
 
@@ -38,15 +42,17 @@ def sample_price_data():
38
42
  open_ = close * (1 + np.random.randn(100) * 0.01)
39
43
  volume = np.random.randint(1000000, 10000000, 100)
40
44
 
41
- return pd.DataFrame({
42
- "date": dates,
43
- "symbol": ["600519"] * 100,
44
- "open": open_,
45
- "high": close * 1.02,
46
- "low": close * 0.98,
47
- "close": close,
48
- "volume": volume,
49
- })
45
+ return pd.DataFrame(
46
+ {
47
+ "date": dates,
48
+ "symbol": ["600519"] * 100,
49
+ "open": open_,
50
+ "high": close * 1.02,
51
+ "low": close * 0.98,
52
+ "close": close,
53
+ "volume": volume,
54
+ }
55
+ )
50
56
 
51
57
 
52
58
  @pytest.fixture
@@ -60,25 +66,27 @@ def multi_symbol_price_data():
60
66
 
61
67
  for symbol in symbols:
62
68
  close = 100 + np.cumsum(np.random.randn(60) * 0.5)
63
- data[symbol] = pd.DataFrame({
64
- "date": dates,
65
- "symbol": [symbol] * 60,
66
- "open": close * (1 + np.random.randn(60) * 0.01),
67
- "high": close * 1.02,
68
- "low": close * 0.98,
69
- "close": close,
70
- "volume": np.random.randint(1000000, 10000000, 60),
71
- })
69
+ data[symbol] = pd.DataFrame(
70
+ {
71
+ "date": dates,
72
+ "symbol": [symbol] * 60,
73
+ "open": close * (1 + np.random.randn(60) * 0.01),
74
+ "high": close * 1.02,
75
+ "low": close * 0.98,
76
+ "close": close,
77
+ "volume": np.random.randint(1000000, 10000000, 60),
78
+ }
79
+ )
72
80
 
73
81
  return data
74
82
 
75
83
 
76
- class TestFactorsListCommand:
77
- """quantcli factors list 命令测试"""
84
+ class TestFactorBuiltinListCommand:
85
+ """quantcli factor list 命令测试"""
78
86
 
79
- def test_factors_list_basic(self, runner):
80
- """测试 factors list 基本功能"""
81
- result = runner.invoke(cli.factors_list, ["--json"])
87
+ def test_factor_list_builtin_basic(self, runner):
88
+ """测试 factor list 基本功能"""
89
+ result = runner.invoke(cli.factor_list, ["--json"])
82
90
 
83
91
  assert result.exit_code == 0
84
92
  output = json.loads(result.output)
@@ -87,20 +95,19 @@ class TestFactorsListCommand:
87
95
  assert output["count"] == 40
88
96
  assert len(output["factors"]) == 40
89
97
 
90
- def test_factors_list_shows_alpha101(self, runner):
91
- """测试 factors list 显示 Alpha101 因子"""
92
- result = runner.invoke(cli.factors_list, ["--json"])
98
+ def test_factor_list_builtin_shows_alpha101(self, runner):
99
+ """测试 factor list 显示 Alpha101 因子"""
100
+ result = runner.invoke(cli.factor_list, ["--json"])
93
101
 
94
102
  assert result.exit_code == 0
95
103
  output = json.loads(result.output)
96
104
 
97
- # 检查包含 alpha_001
98
105
  factor_files = [f["file"] for f in output["factors"]]
99
106
  assert "alpha101/alpha_001.yaml" in factor_files
100
107
 
101
- def test_factors_list_human_format(self, runner):
102
- """测试 factors list 人类友好格式"""
103
- result = runner.invoke(cli.factors_list, [])
108
+ def test_factor_list_builtin_human_format(self, runner):
109
+ """测试 factor list 人类友好格式"""
110
+ result = runner.invoke(cli.factor_list, [])
104
111
 
105
112
  assert result.exit_code == 0
106
113
  assert "Built-in Alpha101 Factors" in result.output
@@ -113,7 +120,9 @@ class TestBuiltinFactors:
113
120
 
114
121
  def test_load_single_builtin_factor(self):
115
122
  """测试加载单个内置因子"""
116
- factor = load_factor_from_ref("/Users/apple/quantcli/examples/strategies", "alpha101/alpha_001")
123
+ factor = load_factor_from_ref(
124
+ "/Users/apple/quantcli/examples/strategies", "alpha101/alpha_001"
125
+ )
117
126
 
118
127
  assert factor is not None
119
128
  assert "alpha_001" in factor.name.lower() or "反转" in factor.description
@@ -135,7 +144,9 @@ class TestBuiltinFactors:
135
144
 
136
145
  def test_builtin_factor_fields(self):
137
146
  """测试内置因子字段完整"""
138
- factor = load_factor_from_ref("/Users/apple/quantcli/examples/strategies", "alpha101/alpha_008")
147
+ factor = load_factor_from_ref(
148
+ "/Users/apple/quantcli/examples/strategies", "alpha101/alpha_008"
149
+ )
139
150
 
140
151
  assert factor is not None
141
152
  assert factor.name is not None
@@ -156,7 +167,9 @@ class TestBuiltinFactors:
156
167
  # 测试每个因子都能加载
157
168
  for yaml_file in yaml_files:
158
169
  alpha_name = yaml_file.stem # alpha_001
159
- factor = load_factor_from_ref("/Users/apple/quantcli/examples/strategies", f"alpha101/{alpha_name}")
170
+ factor = load_factor_from_ref(
171
+ "/Users/apple/quantcli/examples/strategies", f"alpha101/{alpha_name}"
172
+ )
160
173
  assert factor is not None, f"Failed to load {yaml_file.name}"
161
174
 
162
175
 
@@ -248,8 +261,8 @@ class TestAnalyzeCommands:
248
261
  window = 20
249
262
  ic_rolling = []
250
263
  for i in range(window, len(factor)):
251
- f = factor.iloc[i-window:i]
252
- r = forward_returns.iloc[i-window:i]
264
+ f = factor.iloc[i - window : i]
265
+ r = forward_returns.iloc[i - window : i]
253
266
  valid_mask = ~(f.isna() | r.isna())
254
267
  if valid_mask.sum() > 5:
255
268
  ic, _ = spearmanr(f[valid_mask], r[valid_mask])
@@ -268,7 +281,7 @@ class TestFilterRunCommand:
268
281
 
269
282
  def test_filter_config_loading(self, tmp_path):
270
283
  """测试 filter 配置加载"""
271
- with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
284
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
272
285
  f.write("""
273
286
  name: 测试策略
274
287
  version: 1.0.0
@@ -293,6 +306,7 @@ output:
293
306
  assert config.name == "测试策略"
294
307
  finally:
295
308
  import os
309
+
296
310
  os.unlink(config_path)
297
311
 
298
312
  def test_filter_with_weights(self, tmp_path, multi_symbol_price_data):
@@ -300,7 +314,7 @@ output:
300
314
  from quantcli.factors.compute import FactorComputer
301
315
  from quantcli.factors.ranking import ScoringEngine
302
316
 
303
- with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
317
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
304
318
  f.write("""
305
319
  name: 测试策略
306
320
  version: 1.0.0
@@ -332,7 +346,7 @@ output:
332
346
  factors,
333
347
  multi_symbol_price_data,
334
348
  {},
335
- list(multi_symbol_price_data.keys())
349
+ list(multi_symbol_price_data.keys()),
336
350
  )
337
351
 
338
352
  assert "close_price" in factor_df.columns
@@ -345,6 +359,7 @@ output:
345
359
  assert len(result) == len(multi_symbol_price_data)
346
360
  finally:
347
361
  import os
362
+
348
363
  os.unlink(config_path)
349
364
 
350
365
  def test_filter_with_conditions(self, tmp_path, multi_symbol_price_data):
@@ -352,7 +367,7 @@ output:
352
367
  from quantcli.factors.compute import FactorComputer
353
368
  from quantcli.factors.ranking import ScoringEngine
354
369
 
355
- with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
370
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
356
371
  f.write("""
357
372
  name: 测试条件筛选
358
373
  version: 1.0.0
@@ -387,7 +402,7 @@ output:
387
402
  factors,
388
403
  multi_symbol_price_data,
389
404
  {},
390
- list(multi_symbol_price_data.keys())
405
+ list(multi_symbol_price_data.keys()),
391
406
  )
392
407
 
393
408
  scorer = ScoringEngine(normalize="zscore")
@@ -397,6 +412,7 @@ output:
397
412
  assert "score" in result.columns
398
413
  finally:
399
414
  import os
415
+
400
416
  os.unlink(config_path)
401
417
 
402
418
  def test_filter_json_output_format(self, tmp_path, multi_symbol_price_data):
@@ -406,7 +422,7 @@ output:
406
422
  from quantcli.factors.compute import FactorComputer
407
423
  from quantcli.factors.ranking import ScoringEngine
408
424
 
409
- with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
425
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
410
426
  f.write("""
411
427
  name: 测试策略
412
428
  version: 1.0.0
@@ -435,7 +451,7 @@ output:
435
451
  factors,
436
452
  multi_symbol_price_data,
437
453
  {},
438
- list(multi_symbol_price_data.keys())
454
+ list(multi_symbol_price_data.keys()),
439
455
  )
440
456
 
441
457
  scorer = ScoringEngine(normalize="zscore")
@@ -445,7 +461,7 @@ output:
445
461
  output = {
446
462
  "status": "success",
447
463
  "count": len(result),
448
- "results": result.to_dict(orient="records")
464
+ "results": result.to_dict(orient="records"),
449
465
  }
450
466
 
451
467
  assert output["status"] == "success"
@@ -454,6 +470,7 @@ output:
454
470
  assert len(parsed) > 0
455
471
  finally:
456
472
  import os
473
+
457
474
  os.unlink(config_path)
458
475
 
459
476
 
@@ -712,17 +729,22 @@ class TestFormulaSyntax:
712
729
  computer = FactorComputer()
713
730
 
714
731
  factors = {
715
- "ma20": FactorDefinition(name="ma20", type="technical", expr="ma(close, 20)"),
716
- "ema12": FactorDefinition(name="ema12", type="technical", expr="ema(close, 12)"),
717
- "delay5": FactorDefinition(name="delay5", type="technical", expr="delay(close, 5)"),
718
- "std20": FactorDefinition(name="std20", type="technical", expr="rolling_std(close, 20)"),
732
+ "ma20": FactorDefinition(
733
+ name="ma20", type="technical", expr="ma(close, 20)"
734
+ ),
735
+ "ema12": FactorDefinition(
736
+ name="ema12", type="technical", expr="ema(close, 12)"
737
+ ),
738
+ "delay5": FactorDefinition(
739
+ name="delay5", type="technical", expr="delay(close, 5)"
740
+ ),
741
+ "std20": FactorDefinition(
742
+ name="std20", type="technical", expr="rolling_std(close, 20)"
743
+ ),
719
744
  }
720
745
 
721
746
  result = computer.compute_all_factors(
722
- factors,
723
- {"600519": sample_price_data},
724
- {},
725
- ["600519"]
747
+ factors, {"600519": sample_price_data}, {}, ["600519"]
726
748
  )
727
749
 
728
750
  assert "ma20" in result.columns
@@ -739,14 +761,13 @@ class TestFormulaSyntax:
739
761
 
740
762
  factors = {
741
763
  "rank": FactorDefinition(name="rank", type="technical", expr="rank(close)"),
742
- "zscore": FactorDefinition(name="zscore", type="technical", expr="zscore(close)"),
764
+ "zscore": FactorDefinition(
765
+ name="zscore", type="technical", expr="zscore(close)"
766
+ ),
743
767
  }
744
768
 
745
769
  result = computer.compute_all_factors(
746
- factors,
747
- {"600519": sample_price_data},
748
- {},
749
- ["600519"]
770
+ factors, {"600519": sample_price_data}, {}, ["600519"]
750
771
  )
751
772
 
752
773
  assert "rank" in result.columns
@@ -761,14 +782,13 @@ class TestFormulaSyntax:
761
782
 
762
783
  # correlation 计算 - 价格与成交量的相关性
763
784
  factors = {
764
- "corr": FactorDefinition(name="corr", type="technical", expr="correlation(close, volume, 10)"),
785
+ "corr": FactorDefinition(
786
+ name="corr", type="technical", expr="correlation(close, volume, 10)"
787
+ ),
765
788
  }
766
789
 
767
790
  result = computer.compute_all_factors(
768
- factors,
769
- {"600519": sample_price_data},
770
- {},
771
- ["600519"]
791
+ factors, {"600519": sample_price_data}, {}, ["600519"]
772
792
  )
773
793
 
774
794
  assert "corr" in result.columns
@@ -781,15 +801,16 @@ class TestFormulaSyntax:
781
801
  computer = FactorComputer()
782
802
 
783
803
  factors = {
784
- "is_up": FactorDefinition(name="is_up", type="technical", expr="where(close > open, 1, 0)"),
785
- "is_yinliang": FactorDefinition(name="is_yinliang", type="technical", expr="where(close < open, 1, 0)"),
804
+ "is_up": FactorDefinition(
805
+ name="is_up", type="technical", expr="where(close > open, 1, 0)"
806
+ ),
807
+ "is_yinliang": FactorDefinition(
808
+ name="is_yinliang", type="technical", expr="where(close < open, 1, 0)"
809
+ ),
786
810
  }
787
811
 
788
812
  result = computer.compute_all_factors(
789
- factors,
790
- {"600519": sample_price_data},
791
- {},
792
- ["600519"]
813
+ factors, {"600519": sample_price_data}, {}, ["600519"]
793
814
  )
794
815
 
795
816
  assert "is_up" in result.columns
File without changes
File without changes
File without changes
File without changes
File without changes