这是什么?
当你输入一句话,背后发生了什么
从一条命令出发,认识 QuantaAlpha 的整个世界
你遇到了什么问题?
量化投资 的核心挑战只有一句话:在噪声中找到真正的信号。 股票市场每天产生海量数据,大部分都是随机涨跌的"噪声"——真正能预测未来走势的规律,往往藏得很深。
传统方式
研究员手工阅读研报、设计公式、反复调参。一个有价值的因子可能需要数周甚至数月。
QuantaAlpha 的方式
你只需告诉系统一个方向,剩下的——提假设、写代码、跑回测、筛选结果——全部交给 AI 自动完成。
一个由 LLM 驱动的 Alpha 因子 挖掘系统。你输入一个探索方向,它自动产出一批经过 回测 验证的因子,存入因子库等待下游模型使用。
什么是 Alpha 因子?
想象一位天气预报员。他不能控制明天的天气,但他能从气压、湿度、云图中找到"预兆"——这些预兆就是他的"信号"。
Alpha 因子 就是量化世界的"预兆"。它是一个数学公式,输入是历史价格、成交量等数据,输出是一个数字信号——这个信号越高,预示股票未来可能表现越好。
(昨日收盘 - 5日前收盘) / 5日前收盘
这个公式计算了最近5天的涨幅。背后的假设是:近期上涨的股票,短期内可能继续上涨。
这就是一个最简单的
动量
因子。
比如:"高成交量配合价格上涨,信号更可靠"
把文字描述变成可计算的数学公式
IC 值衡量因子预测能力:越高越好
一句命令,触发了什么?
当你运行 python launcher.py mine --direction "价量动量因子",
系统在幕后自动完成了 5 个环节:
代码翻译:launcher.py 入口
你不需要看懂每一行代码,但看懂"入口"很重要。下面是 launcher.py 文件开头的用法注释——它告诉你这个工具能做什么:
"""
Usage:
python launcher.py mine --direction "price-volume factor mining"
python launcher.py mine --direction "momentum reversal" --config configs/experiment.yaml
python launcher.py backtest --factor-source alpha158_20
"""
--direction 后面跟你想探索的研究方向。方向用自然语言写,比如"价量动量因子"。
--direction 是你和系统对话的界面。你不需要写公式、不需要改代码,只需要告诉系统"想往哪个方向找"——剩下的交给
LLM
来理解和展开。
组件对话:谁在幕后工作?
来看一次真实的挖掘过程中,各个组件之间是怎么"说话"的:
理解检测
试试看你吸收了多少——3 道题,每题只选一个最佳答案。
Q1:你输入 --direction "价量动量因子" 后,系统第一步会做什么?
Q2:Alpha 因子的作用是什么?
Q3:如果你想让 QuantaAlpha 挖掘和财报相关的因子,你应该怎么做?
五位主角——谁在做什么?
认识 QuantaAlpha 的五个核心组件,理解每个人的职责,这样你就能告诉 AI "把这个逻辑放在 X 里"。
想象一家量化研究所
QuantaAlpha 不是一段"从上到下跑一遍"的脚本——它更像一家分工明确的量化研究所。 每位成员各司其职:有人负责提灵感,有人写代码,有人做实验,有人管质量,还有一位导师统筹全局。 五个组件,五种角色,缺一不可。
认识这五位"主角"之后,你就能准确地告诉 AI: "这段逻辑应该放在 FactorParser 里",而不是含糊地说"放在某个地方"。
提出研究假设
读取历史记录,调用 LLM,产出有具体内容的研究方向。就像研究所里那位灵感涌现的科学家,专门负责写"灵感笔记本"。
把假设变成可运行的代码
把自然语言假设翻译成 Qlib 因子表达式,写出 factor.py。像把建筑师图纸翻译成施工图的工程师。
运行代码,拿到真实回测结果
执行 factor.py,合并数据,调用 Qlib 做历史回测,返回 IC、年化收益等指标。
决定下一轮探索哪个方向
维护轨迹池,选出最优父代,触发变异或交叉策略。就像围棋教练,看完每盘棋决定下一步怎么练。
确保生成的因子不是垃圾
在因子进入回测前做5重质量检查:冗余度、长度、自由参数、特征数量、语义一致性。
架构图——谁向谁汇报?
点击任意组件,查看它的详细职责说明。注意最顶层的 settings.py——它是整个研究所的"人事档案",决定每个岗位由谁来担任。
HypothesisGen 产出假设 → FactorParser 写代码 → FactorRegulator 质检 → FactorRunner 回测 → EvolutionController 决定下一轮方向。这个循环会自动运行数百次,每次都在轨迹池里留下记录。
settings.py 的秘密——依赖注入
这是 QuantaAlpha 最聪明的设计之一:五个组件的具体实现类,全部通过字符串路径写在 settings.py 里。
主循环从不硬编码"用哪个类"——它只读配置,再用 Python 的 importlib 动态加载。
想换一个更好的 HypothesisGen?只改一行配置,不动任何业务逻辑。这就是依赖注入的魔法。
QLIB_FACTOR_,可以用环境变量覆盖任何配置项quantaalpha/factors/runner.py — 执行 Docker 环境中的因子回测,结果写入 mlflow
quantaalpha/factors/qlib_coder/ — 使用 CoSTEER 迭代写出可执行代码
quantaalpha/factors/proposal.py — 核心方法 gen(trace) 接收Trace,返回假设对象
每个字段存的是字符串,不是类本身。主循环用
importlib.import_module() 在运行时动态加载。
这意味着你可以写自己的 HypothesisGen,只要把类路径填进去,整个系统就会用你的版本——不需要改任何其他代码。
组件对话——一个假设是怎么变成代码的
让我们跟着一个真实假设,看它如何在五个组件之间流转,最终变成一条有 IC 值的回测结果。 点击"下一条"逐步播放,或点击"全部展示"一次看完。
QlibFactorRunner 在 Docker 容器里运行,每个因子的 factor.py、config.yaml、价量数据都放在独立文件夹,结果通过 mlflow 持久化——这就是那段注释说的 "Everything in a folder"。
测验——把职责配对到正确的组件
把上方的组件名称拖拽到下方对应的职责描述里。全对了说明你已经认识这五位主角了!
认识了这五位主角,下一个模块我们将跟随一轮完整的因子挖掘旅程——从"提出一个假设"到"拿到回测结果",每一步都有真实代码。
一轮挖掘的旅程
5步循环深度解析——跟着一个假设,从想法到因子值,再到反馈入库
想象一条汽车工厂流水线
汽车工厂里,一辆车从底盘到出厂,要经过焊接、喷漆、装配、检测、验收5道工位。每个工位只做自己的事,产出交给下一位,整条线循环不止。
AlphaAgentLoop 正是这样一条"因子工厂"流水线。每跑一圈,就完成1次完整的探索——
提假设 → 造表达式 → 写代码跑计算 → 回测 → 反馈归档,环环相扣,缺一不可
代码可能失败、因子可能被质量门控过滤,最终存活的才进入因子库
每轮的假设、结果、反馈都追加到 Trace,下一轮 LLM 提假设时会读取它,形成真正的记忆闭环
我们将跟踪"一个假设从想法到因子值"的完整路径,理解每一步做了什么、交出了什么、为什么这样设计。
Step 1 & 2:从想法到因子表达式
流水线的前两个工位负责把一个模糊的"研究直觉"变成一段精确的数学表达式。
调用 Trace(最多6条历史记录)作为上下文,由 HypothesisGen 向 LLM 提出新假设。
输出一个 AlphaAgentHypothesis 对象,包含 hypothesis(假设陈述)、observation(市场观察)、justification(逻辑依据)、specification(实现规格)四个字段。
AlphaAgentHypothesis2FactorExpression.convert() 把假设拆解为 FactorTask 列表。每个 FactorTask 包含: factor_name、factor_description、factor_formulation(数学公式)、factor_expression(可执行表达式)。 质量门控 FactorRegulator 在此触发——不合规的表达式直接拦截,不进入后续步骤。
Trace 是一个列表,每条记录是 (hypothesis, experiment, feedback) 三元组。LLM 每次提假设前都会读取这份日志——它知道上几轮探索了什么、结果怎样,从而自然地避开已走过的死路,向未探索的方向前进。
@measure_time
@stop_event_check
def factor_propose(self, prev_out: dict[str, Any]):
"""Propose hypothesis as the basis for factor construction."""
with logger.tag("r"):
idea = self.hypothesis_generator.gen(self.trace)
logger.log_object(idea, tag="hypothesis generation")
self._last_hypothesis = idea
return idea
@measure_time — 自动计时,记录这一步耗时多少秒@stop_event_check — 收到停止信号时可以优雅中断,不强制杀进程hypothesis_generator.gen(self.trace) — 把 Trace(历史记忆)传给 LLM,让它提出新假设log_object(idea, tag="hypothesis generation") — 把假设写入结构化日志,便于事后审查self._last_hypothesis = idea — 暂存本轮假设,后续步骤通过 prev_out 字典传递Step 3:代码执行的秘密
这是5步中技术细节最密集的一步。factor_calculate() 会为每个 FactorTask 生成一个独立的 factor.py,在隔离的 workspace 文件夹里运行它。
HDF5 格式的日线价量数据。factor.py 通过符号链接读取,不复制原始文件,节省磁盘空间。
代码失败时,CoSTEER 分析错误信息,自动修改代码重试,最多若干次,不让整个流程崩溃。
每个因子输出独立的 result.h5,存储因子值——MultiIndex(datetime × instrument)格式的二维表,是下一步回测的输入。
def execute(self, data_type: str = "Debug") -> Tuple[str, pd.DataFrame]:
"""
execute the implementation and get the factor value
by the following steps:
1. make the directory in workspace path
2. write the code to the file in the workspace path
3. link all the source data to the workspace path folder
if call_factor_py is True:
4. execute the code
else:
4. generate a script from template to import the factor.py
5. read the factor value from the output file
"""
LLM 生成的代码不可能每次都完美。CoSTEER 的存在让"失败→分析→修复"变成自动化循环。就像一位会自我纠错的实习生——不完美,但会学习。
Step 4 & 5:回测与反馈闭环
前3步把假设变成了因子值,后2步负责"审判"这些因子——量化它们的预测能力,并把结论写回记忆,指导下一轮探索。
def feedback(self, prev_out: dict[str, Any]):
feedback = self.summarizer.generate_feedback(
prev_out["factor_backtest"],
prev_out["factor_propose"],
self.trace
)
with logger.tag("ef"):
logger.log_object(feedback, tag="feedback")
self.trace.hist.append((
prev_out["factor_propose"],
prev_out["factor_backtest"],
feedback
))
generate_feedback(...) — LLM 同时看到:回测指标 + 本轮假设 + 历史 Trace,生成自然语言反馈prev_out["factor_backtest"] — 本轮回测对象(含 IC、ARR、MDD 等指标)prev_out["factor_propose"] — 本轮提出的假设对象self.trace.hist.append(...) — 把(假设, 实验, 反馈)三元组追加到 Trace 历史factor_propose() 调用 gen(self.trace) 时,会读到这条新记录通过质量门控的因子会被保存到 all_factors_library.json(FactorLibrary),累积成为后续模型训练的知识资产。
测验:追踪一个假设
三道场景题,检验你对5步循环的理解。
AlphaAgent 刚运行完第3轮,IC 一直在 0.02 左右,没什么进步。你想让下一轮换个角度探索。
最有效的做法是?
factor.py 运行失败了(比如 LLM 生成了含语法错误的代码)。
系统会怎么做?
Step 3 执行完毕,workspace 目录下出现了 result.h5。
result.h5 里存的是什么?
进化的秘密
从 Original 到 Mutation 到 Crossover——理解 EvolutionController 如何把随机探索变成有记忆的定向搜索
为什么不用随机搜索?
想象你在培育冠军赛马。你不会每次都从马圈里随机挑一匹野马配种——你会找到跑得最快的那两匹,让它们的后代继承双方的优良基因。几代之后,马群整体的速度就会远超随机配种的结果。
量化因子挖掘面临同样的问题:因子的可能空间无穷大,如果每次都从头随机生成,大部分尝试都是在浪费时间。进化算法的核心思想是:记住什么管用,然后在这个基础上继续改进。
每次生成全新因子,没有记忆,没有方向。就像每次都从野生马里随机挑——偶尔运气好,但效率极低。
每一轮都记录哪些因子表现最好,下一轮在这些"优秀父代"的基础上探索变体。搜索空间逐渐收窄到有价值的区域。
每个因子的表现都被存入轨迹池,就像赛马的血统档案。EvolutionController 每轮都会查阅这份档案,决定下一步怎么走。
Evolutionary Algorithm——一个能自我改进的系统。它不需要人类专家告诉它"往哪个方向改",而是通过积累的实验历史,自动发现有效的探索路径。QuantaAlpha 把这个思想用在了量化因子挖掘上。
三个阶段:Original → Mutation → Crossover
每一轮进化,EvolutionController 会根据历史进展,选择三种策略之一。点击"下一步"追踪一次完整的进化轮次数据流。
LLM 从零开始提出全新的因子假设。没有任何父代约束,代表"种群初始化"阶段。通常在前几轮使用,建立初始基因库。
取当前最优因子,让 LLM 在它的基础上微调:延长/缩短时间窗口、替换运算符、改变价量变量组合。像给冠军马的后代做定向训练。
选两个表现都很好的因子,让 LLM 把它们的子表达式组合在一起。就像让两匹冠军马配种,期望后代继承双方优点。
Controller
Gen
AgentLoop
Pool
Result
代码解读:进化循环的核心逻辑
这两段代码来自 quantaalpha/pipeline/factor_mining.py,是整个进化系统的骨架。左边是真实代码,右边是对应的中文解读。
def run_evolution_loop(cfg, trace):
controller = EvolutionController(cfg)
for round_idx in range(cfg.evolution.max_rounds):
tasks = controller.schedule_tasks(trace)
results = _run_tasks_parallel(tasks, cfg)
for task, result in zip(tasks, results):
trace.update_with_result(task, result)
def run_evolution_loop(cfg, trace): — 定义主进化函数,接收配置(cfg)和共享记忆(Trace)controller = EvolutionController(cfg) — 创建"指挥官",它根据历史决定每轮跑什么任务for round_idx in range(cfg.evolution.max_rounds): — 循环指定轮数(默认 3 轮),每轮是一次完整的进化迭代tasks = controller.schedule_tasks(trace) — 问指挥官:这一轮应该跑哪些实验?返回任务列表results = _run_tasks_parallel(tasks, cfg) — 同时并行运行所有任务,充分利用计算资源trace.update_with_result(task, result) — 把每个任务的结果写回共享记忆,供下一轮决策使用下面是 schedule_tasks 的核心逻辑——它决定"这一轮用哪种策略":
def schedule_tasks(self, trace):
phase = self._determine_phase(trace)
if phase == "original":
return self._create_original_tasks()
elif phase == "mutation":
parents = trace.get_top_factors(n=1)
return self._create_mutation_tasks(parents)
else: # crossover
parents = trace.get_top_factors(n=2)
return self._create_crossover_tasks(parents)
phase = self._determine_phase(trace) — 根据 Trace 历史(跑了几轮、通过了多少因子)判断当前应处于哪个阶段if phase == "original": — Original 阶段 → 不需要父代,直接从零生成新假设任务return self._create_original_tasks() — 返回全新任务列表,LLM 用白板思维探索parents = trace.get_top_factors(n=1) — Mutation 阶段 → 取 IC 最高的 1 个父代因子return self._create_mutation_tasks(parents) — 把这个最优因子交给 LLM,让它生成变异版本parents = trace.get_top_factors(n=2) — Crossover 阶段 → 取 IC 最高的 2 个父代因子return self._create_crossover_tasks(parents) — 把两个父代交给 LLM,让它合并各自的子表达式注意 get_top_factors(n=1) 和 get_top_factors(n=2) 的区别——这一个数字的差异,决定了是"单亲变异"还是"双亲杂交"。Mutation 只需要一个榜样,Crossover 需要两个互补的来源。
进化委员会开会——Mutation 阶段现场
看看当系统进入 Mutation 阶段时,这几个组件之间是怎么协作的。点击"下一条"逐步播放,或点击"全部展示"一次看完。
注意消息 8:即使在 Mutation 阶段生成的因子,也要经过 FactorRegulator 的质检。重复的因子(AST 相似度过高)会被直接拒绝,确保每次变异都是真正有价值的探索。
进化为什么有效?——以及你学到了什么
随机搜索每次都是在整个因子空间里抓彩票,而进化搜索利用 TrajectoryPool 积累的知识,把探索方向不断收窄到"已知有价值的邻域"。每一轮,搜索空间都在收缩,而不是保持不变。这就是为什么经过几轮进化后,新发现的因子质量会系统性地高于随机生成的结果——不是运气,是超参数驱动的定向搜索。
测验:进化机制你掌握了吗?
三道场景题,检验你对进化循环的理解。
如果第一轮跑了 3 个原始因子,最佳 IC 是 0.05,第二轮会发生什么?
Crossover 阶段需要几个父代因子?
你发现系统运行了很多轮但效果没有提升。你会怎么调整进化参数?
Original → Mutation → Crossover 三阶段进化循环;EvolutionController 如何用 TrajectoryPool 驱动父代选择;以及为什么定向进化比随机搜索在因子挖掘中更高效。下一个模块将深入探索因子质量管控——FactorRegulator 的五道质检门控。
质量守门员
The Quality Gatekeeper
每一个因子在进入回测之前,都必须通过 FactorRegulator 的五道安检。了解这些规则,你就能更好地引导 AI 生成高质量的候选因子。
为什么需要质量门控?
想象一座繁忙机场的安检通道。每一位旅客——不管是外国政要还是背包客——都必须通过同样的安检流程,没有例外,没有快速通道。 QuantaAlpha 里,每一个 LLM 提出的候选因子也是如此——在进入昂贵的回测之前,必须通过 FactorRegulator 的五道安检。
没有质量门控会怎样?
LLM 会反复生成"换汤不换药"的重复公式,浪费大量回测算力,同时让因子库充满冗余,最终污染所有下游分析。
有了质量门控
低质量因子在进入昂贵回测之前就被拦截,只有真正新颖、结构合理的候选因子才能"登机",保持因子库的高价值密度。
它位于
quantaalpha/factors/regulator/factor_regulator.py,
在每一个新因子提交给 Qlib 回测之前执行检查。通过全部五项检查才放行,一项不过即告拒绝。
五道安检——逐条解析
以下是 is_expression_acceptable()
方法中的五项判断条件。每一项都对应一类常见问题,缺少任何一项,因子库就会被某种"坏因子"悄悄污染。
cond1
与已有因子共享的最大公共AST子树不超过 8 个节点。 防止"换汤不换药"——即使公式写法不同,只要数学逻辑相同,就会被识别为重复。
cond2
公式中的"魔法数字"(自由参数)占比不超过 50%。
一个到处是神秘数字的公式如 0.382 * close + 0.618 * volume * 1.41 很可能是在过拟合历史数据。
cond3
独特变量(不同的运算符和函数)占所有符号的比例不超过 50%。 这保证公式有足够的"逻辑骨架"——不是一堆随机嵌套,而是有结构意义的表达式。
cond4
因子表达式字符串长度不超过 300 个字符。 过于复杂的嵌套表达式不仅难以解释,而且几乎肯定是在记忆历史噪声。简洁的因子往往更稳健——想想最经典的动量因子,不超过 30 个字符。
cond5
使用的原始基础特征(如 close、volume、high)不超过 6 个。 一个依赖太多不同数据维度的因子往往是"拼凑"出来的,而不是捕捉到了真正的市场逻辑。
代码是 return cond1 and cond2 and cond3 and cond4 and cond5——任何一项不过,因子直接被拒绝,不会给出部分通过的机会。这是一个"AND 门",不是"OR 门"。
深度解析:AST 去重是怎么工作的?
五项检查中,最技术也最精妙的是第一项——AST 去重。
普通的字符串比较很容易被骗:close/Ref(close,5) - 1 和
(close - Ref(close,5)) / Ref(close,5) 是同一个动量公式,但字符串完全不同。
AST 比较则能看穿表面差异,找到数学本质。
食谱比喻: 想象每个因子是一份菜谱。AST 去重不是对比菜名或配料表格式,而是逐步骤比对操作流程——如果两份菜谱共享了超过 8 个相同的操作步骤,系统就认定它们"本质上是同一道菜"。
示例:将 (close - Ref(close,5)) / Ref(close,5) 解析为 AST
蓝色框 Ref(close,5) 在树中出现了 两次(左子树的右叶 + 右子树),这是一个内部重复。
对外去重时,系统会比较整棵树与已有因子的最大公共子树大小。
def find_largest_common_subtree(root1, root2):
if root1 is None or root2 is None:
return 0
if root1.node_type != root2.node_type:
return 0
if root1.value != root2.value:
return 0
size = 1
for c1, c2 in zip(root1.children, root2.children):
size += find_largest_common_subtree(c1, c2)
return size
现有因子已经覆盖了这片数学领域。继续放行只会浪费算力,并增加因子库中的噪声。 这个阈值(默认 8)可以通过配置调整——阈值越大,限制越严格,因子库越干净但也越小。
安检现场:一次真实的检查过程
来看看当 HypothesisGen 提交两个候选因子时,FactorRegulator 是怎么处理的:
实际意义:为什么这对你很重要?
① 节省算力:拒绝一个候选因子的成本接近于零,而一次完整回测需要分钟到小时级的计算时间。早拒绝 = 大省算力。
② 保持因子库的信息密度:下游的IC去重和因子组合优化,需要一个"干净"的因子库作为基础。如果因子库里 80% 是重复,后续所有分析的有效性都会大打折扣。
作为使用这个系统的人,你可以通过调整以下配置参数来控制质量门槛的松紧。了解这些参数的含义,能帮你在"严格"和"宽松"之间找到合适的平衡:
当你发现系统一直在拒绝因子时,先看拒绝原因。如果大多数是 cond1(AST重叠),说明 LLM 在生成同质化的公式——你应该在提示词中引导它探索不同的算子组合、不同的时间窗口或不同的价量关系,而不是盲目降低阈值。
理解检测
3 道题,每题选一个最佳答案。
Q1:你发现系统一直拒绝 LLM 生成的因子,大多是因为 AST 重叠。你会怎么做?
Q2:为什么 FactorRegulator 要限制公式长度(符号长度 ≤ 300)?
Q3:FactorRegulator 的哪项检查最能防止"换汤不换药"的重复因子?
如何驾驭它
从指挥台到配置文件——学会运行、调参、排错,真正把 QuantaAlpha 用起来
你现在站在指挥台上
前五个模块,你一直在机器内部探索:看懂了主角、跟完了循环、理解了进化、见识了质量门控。 现在是时候走出机器间,走进指挥台。
configs/experiment.yaml
这个文件是你和 QuantaAlpha 对话的主界面。不需要改代码,只需要调整这里的参数,就能控制系统挖掘的广度、深度、质量标准。
一切从这一行开始:
python launcher.py mine \
--direction "量价动量" \
--config_path configs/experiment.yaml
launcher.py 是整个系统的入口,--direction 是你给
LLM
的研究方向(自然语言即可),--config_path 指向控制台文件。
改变 --direction 就像告诉指挥家"今晚演莫扎特",改变 experiment.yaml 就像决定乐团规模和排练轮数。
.env 环境变量(API Key 等),然后调用 cli.py 执行主流程
experiment.yaml,构建 EvolutionController 并启动 factor_mining.main()
控制面板——你最需要了解的参数
configs/experiment.yaml 有数十个参数,但真正需要调整的只有这几组。
下面是配置文件的真实结构,配上每个参数的中文解释。
planning:
num_directions: 2 # 论文使用 10
direction_template: "价量因子挖掘,专注于{direction}"
execution:
max_loops: 2 # 论文使用 5
evolution:
enabled: true
max_rounds: 3
factor:
factors_per_hypothesis: 1 # 论文使用 3
quality_gate:
redundancy_enabled: true
complexity_enabled: true
planning.num_directions — 每次运行要探索几个研究子方向。2 个方向 = 2 条并行探索线。数字越大,覆盖越广,但 API 消耗也越多。
direction_template — 发给 LLM 的方向模板,{direction} 会被你的 --direction 参数替换。可以用来限定 LLM 的探索语境。
execution.max_loops — 每个方向运行几轮完整的"假设→构建→测试→反馈"循环。每一轮都消耗 LLM 调用,也都会有新的因子产出(或被拒绝)。
evolution.enabled — 是否启用进化框架(Original→Mutation→Crossover 循环)。关闭后系统仅做随机探索,不会从历史结果中学习和改进。
evolution.max_rounds — 进化循环的总轮数(含 Original 轮)。第 0 轮是原始探索,后续轮是变异和交叉。
factor.factors_per_hypothesis — 每个假设生成几个因子。设为 3 时,每轮每个方向最多产出 3 个候选因子,探索密度更高。
quality_gate.redundancy_enabled — 是否运行 AST 去重检查。关闭后相似因子会进入因子库,可能导致因子库中有大量冗余。
quality_gate.complexity_enabled — 是否强制执行符号长度和基础特征数量的限制。关闭后可能产生极度复杂的因子,容易 过拟合。
你可能注意到代码里的默认值(2, 2, 1)比论文中的配置(10, 5, 3)小很多。 这是有意为之的——开发者用小值作为快速调试的默认配置。 完整复现论文结果需要使用论文中的 超参数, 详见论文 Appendix B。
实战调参指南:三种场景
根据你的目标选择合适的配置。没有"正确配置",只有"适合当前目标的配置"。
目标:确认系统能跑通,花最少的 API 配额。
适用时机:新环境首次安装测试、Backtest 数据路径刚配好、API Key 刚到位。 只要系统能跑通一个完整的 propose→build→test→feedback 循环,基础设施就正常了。
目标:探索一个新的研究方向主题,平衡效果与成本。
适用时机:尝试新的方向主题(比如"日内成交量结构")、常规周期性挖掘、IC 结果评估研究。
目标:复现论文中报告的结果,需要大量计算资源。
这是论文中使用的配置,需要大量 LLM API 调用和计算资源。 建议先用场景一确认系统正常运行后,再逐步扩大配置规模。
从命令到结果:完整数据流
你输入一条命令,背后触发了12个步骤。点击"下一步"跟随数据流,理解每个环节交出了什么、传给了谁。
整个流程的终点是一个 Parquet 文件,存储所有通过质量门控的因子。这个文件可以直接被量化模型读取,作为选股特征输入。 你挖到的因子就在这里——打开它,读懂它,才是理解 QuantaAlpha 输出的最后一步。
当出现问题时——排错指南
这些是最常见的四种故障模式,以及对应的排查思路。
可能原因:AST 去重阈值过严,或 LLM 生成的因子结构雷同。
排查: 降低 factor.duplication.threshold(默认值 5),或修改 direction 提示词让 LLM 更有创意。也可以暂时将 quality_gate.redundancy_enabled: false 观察是否能通过。
可能原因:数据质量问题,或 direction 太宽泛。
排查: 优先检查 Qlib 数据路径配置,确认 daily_pv.h5 数据不陈旧;尝试更具体的方向主题(如"开盘后30分钟的成交量模式"而非"成交量因子");查看 IC 是接近 0 还是接近 -0.05(负 IC 可能说明方向反了)。
可能原因:配置过大,或 LLM API 网络延迟高。
排查: 降低 num_directions 和 factors_per_hypothesis;设置 execution.parallel_execution: true(需要多核环境);用 ping 测试 API 端点延迟;检查 Backtest 的 timeout 参数是否设置合理。
可能原因:LLM 使用了不支持的操作符,或输出不符合解析器预期的格式。
排查: 查看 quantaalpha/factors/coder/factor_ast.py 中定义的支持操作符列表;检查 llm.json_mode_strict: true 是否已启用;查看 LLM 返回的原始输出日志(运行时加 --verbose 参数)。
从哲学到代码,从进化框架到质量控制,从命令行到配置文件——你已经有了完整的地图。 下一步:运行它,读因子库,理解它为什么选择了那些因子。
模块测验
Q1:你想要快速验证 QuantaAlpha 系统是否正常工作,同时最小化 API 成本。你会怎么设置配置?
Q2:你发现系统挖到的因子 IC 都在 0.01 以下,远低于论文的 0.15。哪个是最不可能的原因?
Q3:论文中的配置(num_directions=10, max_loops=5, factors_per_hypothesis=3)和代码默认值(2, 2, 1)差距很大。这说明什么?
课程完成!
你从一个完全陌生的代码库出发,现在已经能够理解、运行并定制 QuantaAlpha 的每一个核心部分。
量化信号的本质、为什么需要挖掘它、QuantaAlpha 如何用 LLM 自动化这个过程。
HypothesisGen、FactorParser、FactorRunner、EvolutionController、FactorRegulator——每位的职责和协作方式。
从假设提出到因子值计算,再到 Backtest 和反馈写入 Trace,理解了5步循环的每个细节。
Original→Mutation→Crossover 进化框架如何让系统从历史结果中自我改进,越跑越好。
FactorRegulator 的5重质量门控如何防止垃圾因子污染因子库,以及 过拟合 风险的控制逻辑。
如何运行、调参、选择场景配置、排查常见问题——你有了完整的操作指南。
接下来探索什么?
用场景一配置(快速原型)跑一次,然后打开 factors_df.parquet,用 pandas 读取它,看看系统挖出来的因子长什么样——这才是真正的理解。
依赖注入设计让你可以替换任何组件。在 settings.py 里修改 hypothesis_gen 字段,指向你自己写的类——这是扩展 QuantaAlpha 最干净的方式。
代码来自一篇量化研究论文。理解了代码之后,读论文会变得容易很多——你会认出每个公式对应哪个函数,每个实验设置对应哪个 超参数。
QuantaAlpha 的设计哲学是:让研究员专注于"方向",让机器负责"探索"。 你现在已经同时理解了这两端——你知道怎么给出好的方向,也知道机器背后发生了什么。 这就是驾驭它的完整能力。