Natural Language → Executable Quant Strategy Code
NL2QuantStrategy 是一个基于结构化 DSL 的量化策略翻译流水线,用于将自然语言量化策略描述转换为可执行的 Python / pandas 信号代码。
把中文选股问句(比如“收盘价的5日均线大于阈值0.01时买入”)翻译成可以直接运行的 pandas 信号代码。翻译过程不是直接让 LLM 生成代码,而是先抽成一份结构化 DSL 契约,再确定性渲染——以此换取可复现、低幻觉、可审计。
这类问句的句式风格,参考了同花顺问财这类中文自然语言选股平台的问题域。仓库内不含、也未改写任何第三方平台的真实问句、作者或翻译对偶——所有问句与数值都是独立创作的合成数据(data_origin=synthetic),不是真实策略,也不是市场事实。
输入:中文策略问句(自然语言)
↓
输出:def signal(df: pd.DataFrame) -> pd.Series 形式的 pandas 信号代码
120 条合成问句走完“切分子句 → 抽成 DSL 契约 → 渲染成 Python”的同一条流水线,每一条的结果都是确定、可复现的。整个项目由四个小模块组成:parser(问句→契约)、codegen(契约→代码)、schema(契约校验)、future_leak(前视检测)。
为什么不用直接让 LLM 翻译? 这是本项目的核心问题。
LLM 直接生成策略代码的痛点是不可控:
Natural Language
|
v
Python Code ← 每次调用都可能不同,可能编造不存在的函数
|
v
unpredictable
本仓库的做法是在中间插入一层结构化表示:
Natural Language
|
v
Structured DSL ← 可校验、可审计的中间契约
|
v
Validated Code Generation
|
v
Executable Signal
| 维度 | 结构化翻译(本仓库) | 直接让 LLM 翻译 |
|---|---|---|
| 可复现性 | 同一问句永远产出同一份代码,规则固定、逐字节可复现 | 同一问句多次调用,可能每次代码都不一样 |
| 幻觉 | 低:不认识的函数或运算符直接报 NotImplementedError,绝不瞎编 |
可能编造不存在的函数、字段名或逻辑 |
| 行为一致 | 代码永远操作同一套数据形态(close/open/high/low/vol/amount…),换数据源不用改翻译逻辑 | 输出风格飘忽,下游处理困难 |
| 可审计 | 每一步都有中间产物(子句、AST、DSL 契约)可以检查 | 只有黑盒输出,难以追溯 |
结构化翻译牺牲了一点灵活度,换来的是可靠:遇到没见过的写法宁可跳过(fail-closed),也不输出一个猜出来的结果。
flowchart TD
Q["中文策略问句"] --> S["parser.split_clauses<br/>按 且/并且/同时/以及 切分子句"]
S --> P["parser.parse_clause<br/>识别价格字段 / DSL 函数 / 比较运算符 / 窗口 / 阈值"]
P --> C["expected_ast + expected_filters<br/>DSL 契约(schema.SyntheticTranslationRecord)"]
C --> R["codegen.render_python<br/>def signal(df) -> pd.Series 源码"]
C --> F["future_leak.detect_future_leak_query<br/>fail-closed 黑名单命中列表"]
R --> OUT["可执行 pandas 信号"]
F --> OUT2["检测报告"]
四个模块各自独立、可单独替换:
parser.py:教学骨架解析器,只演示“切分子句 → 识别函数 → 比较运算符 → 窗口/阈值”最小链路;codegen.py:遇到未知函数与未知比较运算符一律NotImplementedError,宁可失败不猜;future_leak.py:20 条正则黑名单,对问句或子句做保守扫描,可独立使用;schema.py:不满足data_origin=synthetic等不变量直接抛ValueError。
- 结构化 DSL 中间表示:问句先解析成
expected_ast+expected_filters契约,再做代码渲染; - fail-closed 渲染:未知函数、未知运算符一律拒绝(
NotImplementedError),不输出猜出来的结果; - 前视检测:20 条正则黑名单识别未来函数/未来财务/同日成交等违规模式,故意构造的违规样本只作检测器正例;
- 数据契约校验:每条记录强制
data_origin=synthetic、字段一致性校验; - 确定性可复现:合成数据由固定种子
20260816生成,逐字节可复现。
拿一条合成问句走完全流程:
问句:若示例股票A的收盘价的5日均线大于阈值0.01,则下一交易日开盘买入并持有5日。
第 1 步:切分子句
from strategy_translation.parser import split_clauses
split_clauses("若示例股票A的收盘价的5日均线大于阈值0.01,则下一交易日开盘买入并持有5日。")
# ['若示例股票A的收盘价的5日均线大于阈值0.01,则下一交易日开盘买入并持有5日。']整句是一个子句(“则”不是切分符,只有“且/并且/同时/以及”会切分;执行短语由数据契约里的 EXECUTION_PHRASES 处理,不进入解析器)。
第 2 步:解析成 DSL 契约
from strategy_translation.parser import parse_clause
parse_clause("若示例股票A的收盘价的5日均线大于阈值0.01,则下一交易日开盘买入并持有5日。")
# AtomicClause(function='ma', args=(5,), price_field='close', comparison_operator='gt', threshold=0.01)问句被抽成:5 日均线(ma)作用于收盘价(close),比较符是大于(gt),阈值 0.01。
第 3 步:渲染成 pandas 代码
PYTHONPATH=src python3 -m strategy_translation.codegen \
--query "若示例股票A的收盘价的5日均线大于阈值0.01,则下一交易日开盘买入并持有5日。"输出:
# 问句:若示例股票A的收盘价的5日均线大于阈值0.01,则下一交易日开盘买入并持有5日。
import pandas as pd
from typing import Any
def signal(df: pd.DataFrame) -> pd.Series:
mask = df["close"].rolling(5).mean() > 0.01
return mask第 4 步(可选):前视检测
如果问句里混进了未来信息,检测器会命中黑名单:
from strategy_translation.future_leak import detect_future_leak_query
detect_future_leak_query("若示例股票A的收盘价大于阈值0.01且依据未来财务数据,则下一交易日开盘买入。")
# ['future_financial']这类故意构造的违规样本只用于展示检测器,标记为不可准入,不会进入任何可复现的收益结论。
需要 Python 3.11+。运行时依赖为空(dependencies=[]),测试依赖只有 pytest>=8([test] 可选组)。
pip install -e .
# 之后可直接用 strategy-translation-generate / strategy-translation-codegen 两个命令所有命令也可以用 PYTHONPATH=src 前缀运行(未 pip install -e . 时的形态):
# 1. 生成 120 条合成数据(固定种子 20260816,逐字节可复现)
PYTHONPATH=src python3 -m strategy_translation.generate --output-dir data
# 2. 单条翻译:问句 → pandas 信号代码
PYTHONPATH=src python3 -m strategy_translation.codegen \
--query "收盘价的5日均线大于等于阈值0.01"
# 3. 批量翻译:每条可渲染的记录写一个 .py 文件
# (需要先执行第 1 步生成 data/synthetic_translation_queries.jsonl)
PYTHONPATH=src python3 -m strategy_translation.codegen \
--input data/synthetic_translation_queries.jsonl --output-dir examples
# 4. 跑全量测试
PYTHONPATH=src python3 -m pytest -qdata/ 下的逐行数据默认不随仓库分发,由 generate 命令用固定种子 20260816 确定性生成,逐字节可复现:
| 文件 | 内容 |
|---|---|
synthetic_translation_queries.jsonl |
120 行:合成中文问句、粗粒度 expected_ast、expected_filters、lookahead_label |
synthetic_translation_coverage.csv |
同一份数据的扁平 CSV,嵌套结构紧凑 JSON 序列化,120 行 + 表头 |
每条记录的元数据契约见 schema.SyntheticTranslationRecord,硬约束:
data_origin=synthetic、rights_status=self_created、release_status=local_preview;strategy_family∈ 11 类家族;lookahead_label∈ {none, future_financial, ambiguous_price_basis, same_day_fill};function与expected_ast.function必须一致;expected_ast必须含args与price。
合成样本里故意保留 future_financial / ambiguous_price_basis / same_day_fill 三类违规样本,用作 future_leak 黑名单的正例——它们不能进入任何可复现的收益结论。
- fail-closed:遇到没见过的写法宁可跳过,也不输出一个猜出来的结果。批量模式下 120 条中约 64 条可渲染,剩下 56 条因为
function=wma/or/and/not/rank/percentile/...或operator=extreme触发跳过——跳过不是 bug,是 fail-closed 边界本身; - 可复现优先:固定种子、确定性渲染、逐字节可复现;
- 可审计:每一步都有中间产物(子句、AST、DSL 契约)可以检查;
- 研究纪律:T 日可知信息 → T+1 成交;故意构造的违规样本只作检测器负例,标记为不可准入。
当前支持:
- 移动平均类:
ma/ema/wma(wma未实现渲染,触发 fail-closed); - 价格与量字段:
close/open/high/low/vol/amount/change/pct_chg; - 参考与窗口函数:
ref/sum/std/llv/hhv/count/cross等; - 比较运算符:
gt/gte/lt/lte/eq。
解析器是有意保持的教学骨架:不处理否定、嵌套、指代消解。更细的字段说明、模块设计动机与 fail-closed 边界见 docs/methodology.md。
以下为规划方向(尚未实现):
- 扩展 DSL 函数库与运算符覆盖,缩小 fail-closed 跳过面;
- 接入 LLM 作为“问句 → 契约”阶段的辅助解析,保留契约校验与渲染的确定性;
- 增加回测与评估模块,形成“翻译 → 验证”闭环;
- 支持更多语言与句式(否定、嵌套、指代消解)。
本仓库的代码与合成数据采用 MIT 许可证,全文见 LICENSE。 This repository is intended for research and educational purposes only and does not constitute investment advice. 仅作为学习参考,不构成任何投资建议。 版权行:Copyright (c) 2026 NL2QuantStrategy contributors