importimportlib_SUBMODULES={"AlphaConfig":".config","FactorRegistry":".factors",# ...}def__getattr__(name:str):ifnamein_SUBMODULES:module=importlib.import_module(_SUBMODULES[name],__package__)returngetattr(module,name)raiseAttributeError(f"module {__name__!r} has no attribute {name!r}")
defget_stock_codes_from_db(conn=None,status_list=("L","D","P"))->List[str]:...cursor.execute("SELECT ts_code FROM stock_basic WHERE list_status = ANY(%s) ORDER BY ts_code",(list(status_list),),)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Gitea 代码审核报告 (quanxiel)
审核模型: kimi-k2.7-code
审核时间: 2026-07-31 23:54
📄 alpha/config.py
对
alpha/config.py的审核如下:🔴 严重
1. 数据库密码明文硬编码(第 19 行)
同时建议
db_host、db_user等也支持环境变量覆盖。2.
end_date为未来日期(第 24 行)end_date <= today。🟡 建议
3. 日期字段使用
str而非日期类型(第 22–24 行)datetime.date或在 dataclass 初始化后校验格式。4.
output_dir使用相对路径(第 36 行)pathlib.Path,并提供创建目录逻辑。5. 交易成本语义不明确(第 27–29 行)
6.
max_turnover缺少计算口径说明(第 33 行)max_daily_turnover_pct,并在回测文档中明确公式。🟢 优化
7. 内部 IP 默认写入配置(第 15 行)
localhost或空字符串强制配置。8. 缺少配置校验方法
__post_init__进行基础校验。9. 因子窗口命名可更精确(第 31 行)
factor_lookback_windows,避免与“滚动窗口函数”混淆。总结
strmax_turnover口径不明📄 alpha/__init__.py
对
alpha/__init__.py的审核结果如下:🔴 严重
无
该文件作为包的公共 API 入口,结构清晰,未发现正确性、安全性或量化回测逻辑上的严重问题。
🟡 建议
1. 行 7-15 — 循环导入风险
所有子模块在包导入时即被全部加载。若
.strategy、.backtest、.factors等子模块之间存在相互引用,容易引发ImportError/ 循环导入。建议:确保各子模块间为单向依赖,或通过类型注解字符串(
from __future__ import annotations)延迟类型解析。2. 行 4 — 包名一致性需确认
docstring 中使用的包名为
quanxiel,请确认与项目实际根包名一致。🟢 优化
1. 行 7-15 — 延迟导入(可选)
如果
.backtest、.evaluation等子模块较重(例如加载大量历史数据、依赖 pandas/numpy heavy 初始化),全量 eager import 会拖慢import alpha的启动时间。建议(可选):
2. 模块元数据补充
可在文件顶部或
__all__上方增加:便于版本管理与依赖约束。
审核小结
__all__与导入项一致总体评价:该
__init__.py是一份合格的包入口文件,主要风险在于子模块间可能存在的循环依赖,建议在后续子模块审核中重点关注。📄 alpha/factors.py
以下是对
alpha/factors.py的专业代码审核,按严重程度分级列出。🔴 严重
1. 技术面/基本面因子使用全局标准化,存在未来函数风险
位置:
TechnicalFactor.compute(第 121–125 行)、FundamentalFactor.compute(第 151–154 行)mom.mean()/mom.std()与series.mean()/series.std()是在全时间序列+全截面上计算。若data为面板数据(MultiIndex [date, code]),则当前因子值用到了未来日期和其他股票的信息,构成 look-ahead bias。修改建议:因子标准化应使用截面标准化(按日期分组):
2.
quantile_returns未按日期分组分箱,分层结果无意义位置:
FactorAnalyzer.quantile_returns(第 227–238 行)pd.qcut(df["factor"], n_quantiles)直接对整个因子序列分箱。若输入为面板数据,会混合不同日期的股票,导致"分组"跨期混合,分层收益不可解释。修改建议:按日期 groupby 后分箱:
3.
turnover为未完成的占位代码,返回自相关系数并非换手率位置:
FactorAnalyzer.turnover(第 240–257 行)函数体内有
pass # 此处需按股票计算 --- 简化处理,实际返回的是因子自相关系数,不是量化意义上的组合换手率(相邻期持仓变化比例)。该函数若被调用会产生误导性结果。修改建议:实现为真实换手率,例如 Top-K 组合的日度换手率:
4.
ic_decay混淆全样本相关性与截面 IC 衰减位置:
FactorAnalyzer.ic_decay(第 214–225 行)当前实现用
stats.spearmanr在整个因子/收益对上计算一个相关系数,这混合了时间序列与截面信息。IC 衰减的标准定义是:每个截面的 Rank IC 在不同前瞻期上的均值衰减。修改建议:逐期计算 IC 后取均值:
🟡 建议
5. 因子标准化存在除零风险
位置:第 121–125 行、第 151–154 行
series.std()可能为 0(例如全部相同值或仅 1 个样本),会导致inf/NaN。修改建议:分母加
eps:6.
neutralize未处理共线性、缺失值与截距项位置:
BaseFactor.neutralize(第 75–100 行)X.dropna().index可能因市值或行业缺失而过度剔除样本。np.linalg.lstsq矩阵不满秩。修改建议:
7.
pd.qcut遇到重复边界可能报错位置:
FactorAnalyzer.quantile_returns(第 232 行)因子值常存在大量相同值(如停牌、一字板),
pd.qcut可能抛出Bin edges must be unique。修改建议:使用
rank(method="first")先转秩再分箱,或设置duplicates="drop"(但后者会改变分组数)。8.
quantile_returns使用 cumsum 模拟累计收益不准确位置:第 237 行
result["avg_return"].cumsum()是算术累计,未考虑复利。若 avg_return 是单期收益率,应使用几何累计:9.
FactorRegistry.register静默覆盖同名因子位置:第 30–32 行
重复注册不会报错,可能覆盖已有因子,导致运行时行为不一致。
修改建议:
10.
compute_ic单截面分支空数据行为异常位置:第 191–198 行
当
self.factor为空时,返回index=[0]的 Series,索引语义错误。修改建议:
🟢 优化
11.
FactorRegistry._factors类级可变字典存在继承污染位置:第 25 行
子类会共享同一个
_factors。若未来有按策略/市场的多个注册表需求,会产生冲突。修改建议:
12. 标准化逻辑重复
位置:
TechnicalFactor与FundamentalFactor两处都实现了类似的 z-score 计算。建议提取公共工具函数
_zscore(series, inverse=False)。13. 高基数行业虚拟变量内存开销大
位置:
BaseFactor.neutralize(第 87–88 行)pd.get_dummies在行业数量多时会产生稠密矩阵。可考虑:pd.Categorical编码后用scipy.sparse拟合;statsmodels的OLS直接传入分类变量。14.
FactorAnalyzer输入格式假设不统一位置:
__init__与ic_decay__init__接收pd.Series,而ic_decay接收Dict[int, pd.Series],且各方法对单截面/面板数据的分支处理分散,易出错。修改建议:在
__init__中统一要求MultiIndex [date, code],若不是则转换为该格式或显式报错。安全性
本文件为纯计算逻辑模块,未发现 API key 硬编码、文件路径操作、SQL/命令注入等安全风险。
总结
该模块结构清晰,注册表与基类设计合理,但在量化正确性方面存在多个关键问题:全局标准化导致未来函数、分层未按日期分组、
turnover未完成、ic_decay计算方法错误。建议优先修复 🔴 严重级别问题后再投入回测使用。📄 alpha/strategy.py
📄 alpha/backtest.py
📄 alpha/evaluation.py
📄 quantitative_data/config.py
代码审核报告:
quantitative_data/config.py总体评价
这是一个结构清晰的配置模块,环境变量管理思路正确。但存在量化投资特有的未来函数风险、默认值安全隐患以及生产环境健壮性问题。
🔴 严重问题
1. 默认
END_DATE为未来日期,存在未来函数风险行号:61
2. 敏感/关键配置使用空字符串默认值,失败点后置
行号:42、56
raise ValueError或pydantic校验,在启动期即失败。3.
load_dotenv(override=True)可能覆盖系统环境变量行号:32
.env反而可能是开发环境残留,override 会导致生产配置被本地配置覆盖。override=False,或根据ENVIRONMENT变量决定是否覆盖。🟡 建议问题
4.
_LOADED标志定义后未使用行号:20
True,但没有任何模块读取该变量,属于无效代码。.env是否被加载。5.
int()转换缺少错误处理行号:47、59
ValueError,但错误信息不直观,难以定位是配置问题。int转换函数,给出包含变量名的错误提示。6.
.env路径查找逻辑冗余且存在误加载风险行号:25-30
Path(__file__).resolve()已经是绝对路径,注释中的顾虑不成立。Path.cwd()可能在 Notebook 中加载错误目录下的.env,造成配置串扰。cwd回退逻辑;若需多环境支持,通过ENVIRONMENT变量显式指定文件。7. 硬编码内网 IP、端口及数据库名
行号:44-49
localhost/5432,或不提供默认值强制外部传入。8. 使用
print输出配置加载状态行号:33、35、37、39
print输出,不利于日志分级、持久化和生产环境排查。logging模块。9. 日期格式与范围未校验
行号:60-61
START_DATE和END_DATE仅作为字符串,无法保证格式正确、起始不晚于结束。datetime.date.fromisoformat校验,并断言start <= end。10.
.env文件有被误提交到版本控制的风险.gitignore中加入.env,并提供.env.example模板。🟢 优化建议
END_DATE用date.today(),结合业务需求允许显式覆盖。START_DATE固定为 2010-01-01 时,需确保下游代码处理退市/已消失标的,避免仅保留存活股票导致幸存者偏差。重构建议
核心修改点总结:消除未来函数风险、启动期校验必要凭证、避免
.env覆盖系统环境变量、用日志替代print、提供统一的数据库 URL 构建入口。📄 quantitative_data/importer.py
📄 quantitative_data/schema.sql
Gitea 代码审核报告 Part2
📄 alpha/strategy.py
调用失败: The read operation timed out
📄 alpha/backtest.py
📄 alpha/evaluation.py
总体评价
alpha/evaluation.py结构清晰、指标覆盖较全,适合作为回测后绩效统计模块。但存在 量化指标计算语义歧义 与 边界/输入校验不足 的问题,尤其return列的处理和 NAV 起点假设可能在真实数据中导致结果完全错误。建议在接入生产环境前修复 🔴 问题,并补充单元测试。🔴 严重
'return'列被强制diff(),默认当作累计收益;若用户传入的是日收益率,则所有指标都会错算(变成一阶差分)。daily_return列;若只有return,建议增加return_type参数或统一要求传入nav/daily_return。total_return在nav列上假设初始净值恒为1.0:nav.iloc[-1] - 1.0。若 NAV 从1000或任意非 1 起点开始,累计收益会完全错误。nav.iloc[-1] / nav.iloc[0] - 1.0,并断言/处理nav.iloc[0] <= 0的情况。🟡 建议
fillna(0)强填,会引入一个“零收益”观测,低估波动、影响年化起点。NaN,或在计算指标时从第二个有效值开始;文档中明确说明。annual_return基于len(self.equity) / 252,未考虑实际日历跨度、缺失交易日或非日频数据;当total_return < -1时几何年化可能出现异常值/复数。self.equity.index计算实际年化天数;对total_return <= -1返回NaN或做保护。annual_volatility在样本数< 2时std()返回NaN,未做边界处理。NaN并打印警告。max_drawdown未处理running_max <= 0,若传入未归一化的净值或价格为 0,会导致除零或错误回撤。nav > 0;或处理异常值。max_drawdown_duration统计的是“连续低于历史高点的天数”,不是从峰值到再创新高的完整回撤周期,语义可能与业务预期不一致。sharpe_ratio用1e-12硬编码兜底,波动接近 0 时会返回巨大数值;且未处理全零/单样本。excess.std() == 0或样本不足时返回NaN/0并警告,不要依赖 magic epsilon。sortino_ratio实现的是“负收益的标准差”,不是标准的 target downside deviation(超过目标收益率部分计为 0)。target = self.rf / periods_per_year,计算np.sqrt(np.mean(np.minimum(returns - target, 0) ** 2)) * sqrt(252)。profit_loss_ratio在无盈利或无亏损时会因空序列平均产生NaN/Inf。avg_win/avg_loss是否为空,返回NaN并提示。alpha/beta/information_ratio均依赖_align_benchmark(),但未校验基准类型、索引是否重叠、样本量是否 ≥2;benchmark_returns若传入 DataFrame 还会导致列名赋值失败。_align_benchmark()📄 quantitative_data/importer.py
总体评估
该模块结构清晰、职责单一,能满足基础数据落地需求。但在**数据正确性(NaT/NaN、DDL 错误隐藏)、量化回测有效性(幸存者偏差)、安全性(SQL 标识符未转义)**三类问题上存在较严重隐患,建议优先修复。其余多为可维护性与性能优化项。
🔴 严重问题
1. 幸存者偏差:仅导入当前上市股票
get_stock_codes_from_db()约第993–1010行;full_import()约第1050行附近WHERE list_status = 'L'只取上市股票,退市/暂停股票的历史日线、财务数据不会被导入。若后续用于回测,策略只会在“当前仍存活”的股票上测试,显著高估收益。L/D/P全部状态,或至少把D(退市)纳入日线与财务数据导入范围;full_import()增加include_delisted: bool = True参数,并在文档中明确说明。get_stock_codes_from_db()改为:2.
batch_insert中pd.NaT/NaN直接入库batch_insert()约第109–110行df.itertuples(index=False)会保留pd.NaT与float('nan')。psycopg2无法识别NaT,且NaN写入数值列会产生 PostgreSQLNaN,导致后续计算/比较异常。None:3.
batch_insert的conflict_columns未做 SQL 标识符转义113、124、137行conflict_str = ", ".join(conflict_columns)后直接sql.SQL(conflict_str)拼入 SQL。虽然当前由内部常量传入,但属于可被外部参数影响的函数接口,存在 SQL 注入/语法错误风险。4.
init_database()静默吞掉 DDL 错误915–930行(cursor.execute(stmt)的except Exception只记logger.debug)batch_insert时才报错,调试成本高;重跑时难以判断 schema 是否完整。logger.error并重新抛出,或至少收集错误后统一抛出:🟡 建议改进
5. 类型注解错误:
str = Noneimport_trade_cal(start_date: str = None, ...)、import_daily_batch(...)、import_adj_factor(...)等None默认值与str类型注解冲突,mypy 会报错。Optional[str] = None。6.
import_daily_by_year()计数逻辑错误且未聚合失败列表445–470行total_imported += 1按“年”计数,而非实际记录数;每年fail_list被丢弃,无法整体重试。7.
import_financial_statements()异常只记debug,失败不可见775–780行logger.warning或logger.error,并记录(ts_code, table_name)。8. 财务数据变量名具有误导性
import_financial_statements()约第735–745行period_start/period_end,但 Tushare 的income/balancesheet/cashflow/fina_indicator接口中start_date/end_date实际代表公告日期范围,不是报告期。ann_start/ann_end,并在 docstring 中说明,避免下游按报告期理解。9.
import_daily_basic_by_date()没有重试机制550–595行pro.daily_basic(trade_date=td),遇到 Tushare 偶发超时/限流即单日记丢失。fetch_with_retry():10. 数据库密码未做 URL 编码
get_sqlalchemy_engine()约第57–60行PASSWORD_ENCODED直接拼入连接串,若含@、/、#等字符会解析失败。11.
schema.sql按分号切分过于脆弱init_database()约第905–915行s.strip().startswith("--"))。sqlparse.split(ddl_sql);psql执行 schema 文件,而不是在 Python 里做简单 split。12.
import_daily_for_stock()强插列可能引发 schema 不匹配335–340行turnover_rate、ma5等列强制设为None;若目标表无这些列,batch_insert会直接报错。information_schema.columns读取目标表实际列,或仅在 schema 保证包含这些列时保留该逻辑。13.
safe_float()已定义但未使用91–98行NaN转成None,但实际未应用,导致第 2 条风险。🟢 优化项
14. 模块导入时即配置全局日志
23–31行logging.basicConfig()。建议封装成setup_logging(),仅在if __name__ == "__main__":中调用。15. 使用
with上下文管理连接与游标get_pg_connection()、conn.cursor()建议用上下文管理器,避免异常时连接泄漏。例如:16. 单线程串行抓取,全量导入极慢
import_daily_batch()、import_adj_factor_batch()、import_financial_statements()ThreadPoolExecutor/asyncio+ 令牌桶限流器并发抓取;写入数据库仍建议批量事务,避免单条提交。17. 按年导入日线效率低
import_daily_by_year()约第430–470行daily接口单次最多返回约 6000 条,A 股 15 年日线通常不到 4000 条,完全可以一次取完整区间,按年调用浪费 API 额度。18.
itertuples可替换为更快的转换batch_insert()约第110行itertuples更快,且配合df.where(pd.notna(df), None)后元素类型更可控。19. 去除冗余/未使用代码
timedelta已导入未使用;get_sqlalchemy_engine()当前未在文件内使用;init_database()内局部import os as _os可改为模块顶部import os或pathlib.Path。量化投资特有风险汇总
get_stock_codes_from_db/full_importlist_status='L',历史回测漏掉退市股income/balancesheet/cashflow/fina_indicatorend_date作为冲突键,未以ann_date/f_ann_date作为实际可用时点;下游若直接按报告期末使用数据,将引入未来信息stock_basiclist_status,未保留历史变更,行业中性/分层回测可能使用未来状态daily+adj_factor分别导入建议在后端查询层增加
ann_date <= 当前交易日的点-in-time 视图,并在回测框架中显式处理前视偏差与幸存者偏差。