审核模型: kimi-k2.7-code 审核时间: 2026-08-08 07:45 审核范围: quanxiel 全部 8 个 Python 文件(8 个模块)
关联:本次为 #1(2026-07-31 审核)的补充复审,覆盖当时超时/缺失的 strategy.py、backtest.py,并全量重审。
factors.py
data_loader.py
end_date
ann_date
backtest.py
importer.py
config.py
field(default_factory=...)
该文件整体结构清晰,抽象了信号生成、权重分配与因子合成,接口设计较为规范。但在多因子合成、分位数信号、日期边界以及股票池过滤等关键环节存在真实且影响回测正确性的问题,部分问题可能引入未来函数或导致信号缺失,需要优先修复。
该回测引擎结构清晰、模块职责分离明确,但在回测正确性和数据质量上存在关键缺陷:缺失价格的持仓估值、风控参数完全未生效、期末清算重复记录净值,以及同 bar 执行带来的前视偏差风险。当前代码若直接用于绩效评估,可能导致权益曲线失真和风险被低估,需要先修复上述问题。
该模块结构清晰,把常用绩效指标拆分为独立方法,便于扩展和单独调用;但核心输入列(return / nav)的语义处理存在明显歧义,且年化、回撤、基准对齐等关键计算在异常数据或边界条件下不够健壮,可能在真实资金曲线上给出错误结果。由于这是纯绩效统计模块,未发现未来函数、信号生成或复权处理相关代码。
return
nav
该模块在因子注册、抽象基类和 IC 计算框架上有清晰的设计,但因子标准化、分层回测和换手率分析存在明显的前视偏差或逻辑错误,直接用于实盘回测会导致信号失真。建议在继续扩展因子库之前先修正核心的截面处理与时序对齐问题。
该数据加载器接口清晰、字段白名单 + SQL 参数化能够有效防止注入,整体结构便于 Notebook 快速演示。但代码在“回测正确性”上存在硬伤:财务数据使用前视了报告期;所有数据库异常都静默回退到随机模拟数据;模拟数据又让不同因子完全相同。这些问题在不连接真实数据库时极难被发现,会严重污染研究结论。
该文件是一份数据层配置,整体上将敏感信息交给环境变量/.env 管理,并做了启动期凭证非空校验,符合基本安全习惯。但作为一个会被多处 import 的 config.py,它在模块导入时直接抛出硬错误,且对日期、批次等关键参数缺少格式/范围校验,目前不涉及量化策略逻辑,因此 look-ahead bias、因子计算、回测执行等风险暂不适用。
.env
完整逐条问题清单(🔴🟡🟢 分级)见评论。
config.py 作为纯配置类,结构清晰、注释完整,且通过 field(default_factory=...) 正确避免了可变默认值的陷阱。但该文件存在“文档与实现不一致”以及环境变量类型转换、关键参数校验缺失等稳健性问题,这些问题会在下游数据获取、回测或输出阶段引发连锁报错。由于文件本身只含静态参数,暂未看到直接的回测未来函数或因子计算错误。
🔴严重
[🔴严重] AlphaConfig.db_port 字段定义(约第 24–26 行)- int(os.getenv("DB_PORT", "12345")) 缺少异常保护 当 DB_PORT 被设为空字符串、带空格或其他非数字值时,int() 会直接抛出 ValueError,导致配置实例化失败、程序启动崩溃。 修复建议:在 __post_init__ 中或读取时加 try/except,非法时回退到默认端口并打印明确警告。
AlphaConfig.db_port
int(os.getenv("DB_PORT", "12345"))
DB_PORT
int()
ValueError
__post_init__
try/except
🟡中等
[🟡中等] 文件头部 docstring(约第 1–13 行)- 说明支持 .env 文件,但代码未加载 .env 注释提示“可创建 .env 文件(参考 .env.example)”,但 config.py 中并未调用 load_dotenv() 或类似逻辑,仅读取系统环境变量;按说明创建 .env 后数据库密码等参数仍不会生效。 修复建议:在模块顶部或应用入口处增加 from dotenv import load_dotenv; load_dotenv(),并确保 python-dotenv 在依赖列表中。
.env.example
load_dotenv()
from dotenv import load_dotenv; load_dotenv()
python-dotenv
[🟡中等] AlphaConfig.start_date / end_date 字段(约第 40–41 行)- 日期为字符串且无任何格式与顺序校验 若后续模块传入非法格式或 start_date > end_date,会在回测/数据获取阶段才暴露错误。 修复建议:在 __post_init__ 中用 datetime.strptime 校验格式,并断言 start_date <= end_date。
AlphaConfig.start_date
start_date > end_date
datetime.strptime
start_date <= end_date
[🟡中等] AlphaConfig.output_dir 字段(约第 58 行)- 输出目录仅作字符串配置,未校验或创建 默认 ./output 目录若不存在,后续保存 trade log 或结果时会抛出 FileNotFoundError。 修复建议:在配置初始化或输出模块中执行 os.makedirs(self.output_dir, exist_ok=True)。
AlphaConfig.output_dir
./output
FileNotFoundError
os.makedirs(self.output_dir, exist_ok=True)
[🟡中等] 交易成本与仓位约束参数(约第 44–51 行)- 缺少非负性与合理性校验 commission_rate、slippage、stamp_tax、max_position_pct 等若被误配为负数或大于 1,会直接污染回测结果。 修复建议:在 __post_init__ 中统一校验 0 <= rate <= 1 等约束,并给出明确报错。
commission_rate
slippage
stamp_tax
max_position_pct
0 <= rate <= 1
🟢轻微
[🟢轻微] 导入 typing.Optional(第 16 行)- 已导入但代码中未使用 属于未使用导入,影响代码整洁度。 修复建议:删除 Optional 或保留供后续扩展。
typing.Optional
Optional
[🟢轻微] 默认 end_date="2025-12-31"(约第 41 行)- 固定远期截止日期存在数据覆盖风险 若运行时刻行情数据尚未覆盖到 2025 年底,或该日期已超过当前日期且未做截断,可能导致空数据或未来数据问题。 修复建议:在回测入口根据实际数据可用性裁剪区间,或默认取最新交易日。
end_date="2025-12-31"
factor_windows
field(default_factory=lambda: [5, 10, 20, 60])
[🔴] Strategy.compute_composite_factor — NaN 污染导致合成因子失效 代码将每个因子标准化后直接 composite += w * z,由于 aligned 经过 reindex 后存在大量 NaN,任一因子在某个 (date, stock) 上缺失时,该位置的合成因子会变为 NaN,即使其他因子有有效值也会丢失。建议对每个因子标准化后 fillna(0) 再参与加权求和,或显式要求因子交集并在缺失时给出合理处理。
Strategy.compute_composite_factor
composite += w * z
aligned
reindex
(date, stock)
fillna(0)
[🔴] Strategy.run_step — 日期缺失时使用 iloc[-1] 存在未来函数风险 当 date 不在 composite.index 时,代码回退到 composite.iloc[-1:],若当前日期早于最新因子日期,则会使用未来因子。建议改为 composite.loc[:date].iloc[-1:] 或 composite.asof(date),确保只取当前日期及之前的最新截面。
Strategy.run_step
iloc[-1]
date
composite.index
composite.iloc[-1:]
composite.loc[:date].iloc[-1:]
composite.asof(date)
[🔴] Strategy — filter_universe 股票池参数被定义但从未使用 filter_universe 在 dataclass 中声明,但 compute_composite_factor 与权重分配均未用它过滤列,传入的股票池约束会完全失效。建议在合成因子前用 filter_universe 限制 all_stocks,并在权重分配阶段再次校验。
Strategy
filter_universe
compute_composite_factor
all_stocks
[🟡] QuantileSignal.generate — duplicates='drop' 与固定分位标签不匹配 pd.qcut(..., duplicates='drop') 会在重复值较多时减少实际分档数,但代码仍固定判断 long_quantile-1 / short_quantile-1,可能出现某一侧(尤其是 top 档)永远没有信号。建议根据实际返回的最大/最小标签动态判断,或在初始化时校验分档可行性。
QuantileSignal.generate
duplicates='drop'
pd.qcut(..., duplicates='drop')
long_quantile-1
short_quantile-1
[🟡] QuantileSignal.generate — 未保证截面样本数不少于分档数 min_stocks 默认 10 仅保证最小股票数,未与 n_quantiles 比较;当 len(row) < n_quantiles 时会触发 ValueError 并被静默跳过,导致该日无信号。建议增加 len(row) >= self.n_quantiles 的前置校验或动态调整分位数。
min_stocks
n_quantiles
len(row) < n_quantiles
len(row) >= self.n_quantiles
[🟡] EqualWeightAllocator / FactorWeightAllocator — 忽略空头信号 SignalGenerator 文档明确定义 -1=做空,但两个权重分配器只处理 signal > 0 的多头,空头信号被完全丢弃。若策略为多空,需支持负权重;若为纯多头,应在信号生成器或文档中明确说明。
EqualWeightAllocator
FactorWeightAllocator
SignalGenerator
-1=做空
signal > 0
[🟡] EqualWeightAllocator.allocate — 截仓未按信号强度排序 long_stocks[:self.max_positions] 按列顺序截取,未按因子值或信号强度排序,可能保留弱信号、剔除强信号。建议按 latest_signal 或因子值降序后再取前 N。
EqualWeightAllocator.allocate
long_stocks[:self.max_positions]
latest_signal
[🟡] 权重分配器 — 完全未使用 prices、cash、positions EqualWeightAllocator 与 FactorWeightAllocator 均未结合股价、可用资金和当前持仓计算,目标权重与实际可买股数、整数股、换手成本、滑点等脱节。建议在 allocator 中加入基于价格和现金的目标市值计算,或明确说明此处仅输出名义权重、由下游模块处理成交。
prices
cash
positions
[🟡] Strategy.compute_composite_factor — 小样本行标准化产生 NaN aligned.std(axis=1) + 1e-12 无法修复“有效数据过少导致 std 为 NaN”的情况,该日期所有合成因子会变为 NaN。建议设置 std(..., min_periods=...) 或在小样本时跳过该截面。
aligned.std(axis=1) + 1e-12
std(..., min_periods=...)
[🟡] 信号生成器 — 未校验输入日期索引唯一性 若 factor_df 存在重复日期,factor_df.loc[date] 会返回 DataFrame,row.dropna() 行为异常,可能导致 pd.qcut 输入错误。建议进入计算前校验 index.is_unique,必要时 ~index.duplicated() 去重并排序。
factor_df
factor_df.loc[date]
row.dropna()
pd.qcut
index.is_unique
~index.duplicated()
[🟢] FactorWeightAllocator.allocate — min_weight 截断后重新归一化可能扭曲权重 当 min_weight * len(selected) > 1 或原始权重均为极小值时,clip 后再归一化会压缩为近似等权,无法反映因子差异。建议仅在低于阈值时做下限处理,并校验分母有效性。
FactorWeightAllocator.allocate
min_weight
min_weight * len(selected) > 1
[🟢] FactorWeightAllocator.allocate — 每次 fallback 都新建 EqualWeightAllocator 实例 return EqualWeightAllocator(self.max_positions).allocate(...) 会重复创建对象,建议预创建实例或使用类内静态方法。
return EqualWeightAllocator(self.max_positions).allocate(...)
[🟢] Strategy.compute_composite_factor — 未校验 factor_weights 之和 当用户传入非归一化权重时,合成因子的量级会任意变化,进而影响 Z-Score/分位数信号的触发。建议在使用前对权重做归一化或校验。
factor_weights
[🟢] Strategy.run_step — 每次调用都重新计算全区间 composite 在逐日回测中会重复进行标准化与矩阵对齐,性能开销较大。建议将 composite 预计算并缓存,run_step 仅切片使用。
[🟢] QuantileSignal.__init__ — 未校验分位参数合法性 long_quantile、short_quantile 可能超出 [1, n_quantiles] 范围,导致信号永远为空。建议在初始化时校验。
QuantileSignal.__init__
long_quantile
short_quantile
[1, n_quantiles]
WeightAllocator
🔴 Broker.init / Broker.execute — 从配置读取了 max_position_pct、max_turnover、min_holding_period,但在 execute、_buy、_sell 中完全没有使用,风控约束形同虚设。 修复建议:在生成订单前校验/截断目标权重,并在买卖执行中加入个股仓位上限、单日换手上限和最短持有期检查。
max_turnover
min_holding_period
execute
_buy
_sell
🔴 BacktestEngine.run — 循环的最后一日已经调用 record_daily 记录净值,循环结束后又调用 _liquidate 并再次 record_daily(final_date),导致净值曲线出现重复索引,后续计算收益率、夏普等会异常。 修复建议:将期末清算合并到循环最后一天,或移除重复的最终 record_daily,确保每个交易日只有一条净值记录。
record_daily
_liquidate
record_daily(final_date)
🔴 Position.market_value / Portfolio.update_market_prices — 当持仓股票某日在 current_prices 中缺失时,update_market_prices 不会更新其 current_price,而 market_value 会继续使用旧价格(隐式前向填充),退市/停牌股票会被持续按过期价格估值,导致权益虚高。 修复建议:在 record_daily 前对所有持仓用上一日有效价格显式前向填充,或对长期缺失价格的股票按退市/停牌规则处理。
current_prices
update_market_prices
current_price
market_value
🟡 BacktestEngine.run + Broker.execute — 策略信号生成与订单成交均使用 price_data.loc[date] 的同一日价格(默认收盘价),属于同 bar 执行,策略可用当日收盘价决定当日仓位,存在前视偏差。 修复建议:使用上一日收盘价生成信号,并以次日开盘价或带滑点的次日收盘价执行成交。
price_data.loc[date]
🟡 Broker.execute — target_weights 未做归一化和有效性校验;权重总和大于 1、为负或为空时,会导致现金不足、买入被截断、实际仓位偏离目标。 修复建议:在 execute 开头校验 target_weights,必要时归一化、过滤负值并输出警告。
target_weights
🟡 BacktestEngine.run — all_dates = sorted(price_data.index) 未去重,若输入数据存在重复交易日,会导致单日多次调仓、多次记录净值。 修复建议:使用 price_data.index.unique().sort_values(),或在发现重复日期时报错/去重。
all_dates = sorted(price_data.index)
price_data.index.unique().sort_values()
🟡 BacktestEngine.run — factor_data 与 price_data 的日期、股票池未做任何对齐检查,缺失或错位的因子可能被策略错误使用。 修复建议:回测前将各因子 DataFrame 按 price_data 的 index/columns 进行 reindex 并对齐,检查缺失率。
factor_data
price_data
index/columns
🟡 Broker.execute / _buy — 现金不足时仅对单笔买入订单截断数量,未考虑多只股票顺序和后续订单对权重的影响,排在前面的股票可能占满现金,导致最终组合偏离目标。 修复建议:调仓前根据目标权重和预估交易成本统一计算各股票可成交数量,或按目标权重优先级排序并预留后续订单现金。
🟢 BacktestEngine.run — rebalance_freq 只显式判断 'M'/'W',其他字符串(如 'Q')会静默按日调仓处理。 修复建议:增加对 'D' 的显式判断,并对未识别的频率抛出 ValueError。
rebalance_freq
'M'
'W'
'Q'
'D'
🟢 BacktestEngine.run — 期末清算时若 final_date 不在 price_data.index 中,final_prices 为空,持仓无法平仓但又会重复记录一次净值,结果不一致。 修复建议:仅在 final_date 存在有效价格时执行最终清算,否则使用上一个有效交易日的价格。
final_date
price_data.index
final_prices
🟢 Portfolio.get_position / Broker.execute — get_position 会在未实际成交时创建 quantity=0 的 Position 对象,长期运行会累积大量空持仓。 修复建议:在 diff_qty == 0 时不调用 get_position,或定期清理 quantity == 0 的持仓。
get_position
quantity=0
Position
diff_qty == 0
quantity == 0
🟢 BacktestEngine.run — progress_callback 和 strategy.run_step 的调用没有异常处理,一个合约/策略异常会导致整个回测中断。 修复建议:增加 try/except 包裹策略调用与回调,记录日志并可选择跳过异常日期。
progress_callback
strategy.run_step
dataclass
TradeRecord
Portfolio
BacktestEngine
[🔴] PerformanceEvaluator.__init__(daily_return 生成逻辑) 当输入列名为 return 时,代码直接使用 self.equity["return"].diff()。这隐含假设该列是“累计收益率”或从 0 开始的价格序列;若用户传入的 return 已经是日收益率,则所有指标都会基于“收益率的差分”计算,结果完全错误。 → 修复建议:明确约定输入语义,或分别支持 daily_return / cum_return / nav,避免对同名列做歧义转换。
PerformanceEvaluator.__init__
daily_return
self.equity["return"].diff()
cum_return
[🔴] PerformanceEvaluator.total_return(nav 分支) 直接返回 nav.iloc[-1] - 1.0,默认初始 NAV 为 1.0;若传入的是绝对资金曲线(如起始 1,000,000),累计收益会被严重算错。 → 修复建议:统一使用 nav.iloc[-1] / nav.iloc[0] - 1.0,不依赖初始值。
PerformanceEvaluator.total_return
nav.iloc[-1] - 1.0
nav.iloc[-1] / nav.iloc[0] - 1.0
[🟡] PerformanceEvaluator.annual_return(负收益边界) 当 total_return < -1 且 years 为分数时,(1 + total) ** (1 / years) 会产生 nan 并伴随 RuntimeWarning,未做保护。 → 修复建议:在计算前判断 1 + total <= 0,返回 np.nan 或改用对数年化。
PerformanceEvaluator.annual_return
total_return < -1
years
(1 + total) ** (1 / years)
nan
1 + total <= 0
np.nan
[🟡] PerformanceEvaluator._year_frac(年化分母) 使用 n / periods_per_year 代替实际日历时长,未处理缺失交易日、非日频数据等情况,会导致年化收益/波动率被错误缩放。 → 修复建议:基于索引的实际日期跨度计算年化因子,例如 (last - first).days / 365.25,或在输入前补齐交易日。
PerformanceEvaluator._year_frac
n / periods_per_year
(last - first).days / 365.25
[🟡] PerformanceEvaluator.sortino_ratio(下行偏差定义) 仅取 returns < 0 的子样本来计算标准差,不符合 Sortino 比率通常使用的目标收益率/MAR(至少应考虑无风险利率),且仅使用亏损样本能低估/偏离真实下行偏差。 → 修复建议:以 returns - target 为基准,保留全部观测,计算 sqrt(mean(min(0, returns - target)^2))。
PerformanceEvaluator.sortino_ratio
returns < 0
returns - target
sqrt(mean(min(0, returns - target)^2))
[🟡] PerformanceEvaluator.alpha / beta / information_ratio(样本不足与对齐) _align_benchmark 使用 inner join + dropna,策略与基准日期不重合时会静默丢弃数据;当对齐后样本量小于 2 时,np.cov 会返回 NaN 并可能触发警告,后续仍参与计算。 → 修复建议:增加 len(aligned) < 2 校验并返回 np.nan,同时可考虑使用 reindex 对齐并显式处理缺失。
PerformanceEvaluator.alpha
beta
information_ratio
_align_benchmark
inner
dropna
np.cov
NaN
len(aligned) < 2
[🟡] PerformanceEvaluator.profit_loss_ratio(空子集除零) 若序列中没有盈利日或亏损日,avg_win 或 avg_loss 会为 NaN 或 0,导致返回 NaN 或异常大值。 → 修复建议:在子集为空时返回 np.nan,并对 avg_loss == 0 单独处理。
PerformanceEvaluator.profit_loss_ratio
avg_win
avg_loss
avg_loss == 0
[🟡] PerformanceEvaluator.__init__ 及多个方法(索引未校验) 未检查 equity_curve.index 是否唯一、是否按时间排序、是否为日期类型。重复索引会导致 pct_change / diff / cumprod 产生错误行;非日期索引会让 _year_frac 等单位换算无意义。 → 修复建议:初始化时校验 index.is_unique、index.is_monotonic_increasing 及 is_datetime64。
equity_curve.index
pct_change
diff
cumprod
_year_frac
index.is_monotonic_increasing
is_datetime64
[🟢] ReportGenerator.to_markdown(整数指标格式化) 对“最长回撤天数”等整数指标使用 {value:.4f},输出形如 15.0000,可读性不佳。 → 修复建议:根据数值类型选择 d 或 .4f 格式化。
ReportGenerator.to_markdown
{value:.4f}
15.0000
d
.4f
[🟢] ReportGenerator.to_markdown(交易对象未防御) 直接访问 t.date、t.stock 等属性,未校验 trades 元素结构,传入不兼容对象会抛出 AttributeError。 → 修复建议:使用 hasattr 检查或使用 dataclass/NamedTuple 约束交易对象结构。
t.date
t.stock
trades
AttributeError
hasattr
[🟢] PerformanceEvaluator.full_report(periods_per_year 被忽略) full_report 未暴露 periods_per_year 参数;若数据为周/月频,必须逐个调用方法,无法一次性生成正确频率的报告。 → 修复建议:为 full_report 增加 periods_per_year 参数并透传给各指标方法。
PerformanceEvaluator.full_report
periods_per_year
full_report
inner join
+ 1e-12
🔴 严重 TechnicalFactor.compute(约第118–126行)- 全样本标准化导致前视偏差 mom_z / vol_z 使用整个序列的 .mean() / .std() 做标准化,意味着在 t 日使用了未来数据计算均值和标准差;对多资产面板而言还混入了未来截面信息。 修复建议:按截面(或至少按扩展窗口/滚动窗口)逐期标准化,例如 factor.groupby(level=0).apply(lambda s: (s - s.mean()) / s.std())。
TechnicalFactor.compute
mom_z / vol_z
.mean()
.std()
factor.groupby(level=0).apply(lambda s: (s - s.mean()) / s.std())
🔴 严重 FundamentalFactor.compute(约第140–161行)- 同样存在全样本标准化前视偏差 ROE、PE、PB、营收增速均在全样本上做 z-score,且未按行业/日期做中性化处理,面板下会用到未来和跨期信息。 修复建议:按财报披露截止日或交易日期做截面标准化,并处理缺失值(如仅对非缺失值做标准化后再合并)。
FundamentalFactor.compute
🔴 严重 FactorAnalyzer.quantile_returns(约第232–239行)- 全局分位数而非截面分位数,且累计收益计算错误 pd.qcut 直接对所有样本做全局分位,未按日期截面分组;cum_return = avg_return.cumsum() 是在“组号 1→5”上累加,失去了时间维度,不是组合累计收益。 修复建议:按 level=0 分组后对每个截面 qcut,再计算每组时序平均收益和累计收益。
FactorAnalyzer.quantile_returns
cum_return = avg_return.cumsum()
level=0
qcut
🔴 严重 FactorAnalyzer.turnover(约第241–255行)- 换手率逻辑未实现且计算对象错误 MultiIndex 分支下仅写了 pass 未返回结果;非 MultiIndex 分支用 self.factor.autocorr(lag=1) 对整个面板做自相关,混淆了不同资产和日期,不能代表“相邻期分位数变化比例”。 修复建议:按日期截面计算持仓权重或分位组,再计算相邻期权的换手比例序列。
FactorAnalyzer.turnover
pass
self.factor.autocorr(lag=1)
🟡 中等 FactorAnalyzer.ic_decay(约第220–230行)- 池化计算 IC 衰减,未按期聚合 直接用 stats.spearmanr 对所有截面的因子值和未来收益做整体相关,得到的是池化相关系数,而不是“每期 IC 的均值”,会混入时序趋势。 修复建议:按 level=0 逐期计算 Spearman/Pearson IC,再对各期 IC 取平均。
FactorAnalyzer.ic_decay
stats.spearmanr
🟡 中等 FactorAnalyzer.compute_ic(约第179–205行)- 未显式对齐因子与收益,缺失日期会抛 KeyError fwd.get_group(g.name) 在 forward_returns 缺少某个日期时会直接抛错;g.corr 对内部股票做内连接,也可能在截面不完整时静默忽略。 修复建议:先 self.factor.align(self.forward, join='inner'),或在 apply 中用 try/except 跳过缺失截面。
FactorAnalyzer.compute_ic
fwd.get_group(g.name)
forward_returns
g.corr
self.factor.align(self.forward, join='inner')
apply
🟡 中等 BaseFactor.neutralize(约第69–96行)- 对空数据、共线行业和异常市值缺乏保护 market_cap <= 0 被 clip(lower=1) 当成市值 1 处理,会扭曲回归;group 含 NaN 时 get_dummies 会将其所有行业 dummy 置 0;若 common_idx 为空或 X 列完全共线,lstsq 结果不稳定。 修复建议:过滤市值<=0 和分组为 NaN 的样本,并在回归前检查 X 的秩或改用 np.linalg.lstsq 的残差逻辑加异常处理。
BaseFactor.neutralize
market_cap <= 0
clip(lower=1)
group
get_dummies
common_idx
lstsq
np.linalg.lstsq
🟡 中等 TechnicalFactor.compute / FundamentalFactor.compute - 未防御标准差为零 当因子序列恒定(如所有 PE 相同)或仅含 NaN 时,series.std() 为 0/NaN,导致 inf 或全 NaN,可能进一步触发 pd.qcut 报错。 修复建议:标准化前判断 std > 0,否则返回 NaN 或跳过该因子。
series.std()
inf
std > 0
🟡 中等 FactorAnalyzer.quantile_returns / turnover(约第235、244行)- pd.qcut 对重复边界会抛异常 当因子存在大量相同值时,qcut 默认 duplicates='raise' 会抛出 ValueError。 修复建议:使用 pd.qcut(..., duplicates='drop') 或改用 pd.cut。
turnover
duplicates='raise'
pd.cut
🟢 轻微 TechnicalFactor.compute(约第123–125行)- 动量/波动率窗口不一致导致合成结果含大量 NaN mom 与 vol 的 NaN 长度不同,0.5 * mom_z + 0.5 * vol_z 在任一者为 NaN 时整体为 NaN,损失了更多可用信号日。 修复建议:统一窗口长度或采用 fillna / 加权时只使用两者均非 NaN 的截面。
mom
vol
0.5 * mom_z + 0.5 * vol_z
fillna
🟢 轻微 BaseFactor.neutralize(约第83行)- 市值非正数未剔除而是硬编码为 1 np.log(market_cap.clip(lower=1)) 把 0/负市值变成 log(1)=0,相当于给这些样本赋予了错误市值。 修复建议:先 market_cap = market_cap[market_cap > 0] 再取 log,保证样本质量。
np.log(market_cap.clip(lower=1))
market_cap = market_cap[market_cap > 0]
🟢 轻微 FactorAnalyzer.turnover(约第252行)- 依赖 self.ic_series 决定是否计算自相关系数 换手率计算与 IC 序列无必然联系;若用户未先调用 compute_ic,此处会返回空 Series,行为不稳定。 修复建议:移除对 ic_series 的状态依赖,直接基于因子计算换手率。
self.ic_series
compute_ic
ic_series
FactorRegistry
BaseFactor
compute
按严重程度排列:
🔴 _load_fina_field 函数 — 以财报 end_date 作为数据生效日期并直接 ffill(),相当于在报告期末当天就知道该期财务结果,而实际财报通常在报告期结束后数周才披露,构成典型的未来函数/前视偏差。 修复建议:使用 ann_date(公告日)或报告期后的首个交易日作为生效起点,再向前填充。
ffill()
🔴 load_prices / load_factor / load_benchmark 函数 — 使用裸 except Exception 捕获所有异常后静默回退到模拟数据,SQL 语法错误、网络超时、表不存在等情况都会让调用方在不知情的情况下拿到随机数据。 修复建议:增加 strict 模式(默认开启),数据库异常时直接抛出;仅在网络不可用时明确回退,并在返回数据中标注 “SIMULATED”。
except Exception
strict
🔴 _simulate_factor 函数 — 每次调用都重置 np.random.seed(self.seed) 且未按因子名区分随机状态,导致不同因子的模拟矩阵完全相同;同时与 _simulate_prices 共享同一 seed,可能人为制造出因子与收益的相关性。 修复建议:为每个因子生成独立随机状态,例如 rng = np.random.default_rng(hash(name) ^ self.seed)。
np.random.seed(self.seed)
_simulate_prices
rng = np.random.default_rng(hash(name) ^ self.seed)
🟡 load_factor 函数 — 对于不在 DAILY_BASIC_FIELDS 或 FINA_FIELDS 中的 name 也直接返回模拟数据,不会报错,容易把拼写错误当成有效因子。 修复建议:在入口处校验 name,未知因子抛出 ValueError。
DAILY_BASIC_FIELDS
FINA_FIELDS
name
🟡 _load_prices_db 函数 — dropna(how='all', axis=1) 会静默剔除无数据的股票列,且不强制对齐到完整股票池/交易日,可能引入幸存者偏差或导致下游截面运算维度不一致。 修复建议:保留请求的股票列(缺失填 NaN),并打印/记录被剔除的股票。
dropna(how='all', axis=1)
🟡 _load_fina_field 函数 — WHERE end_date BETWEEN %s AND %s 会漏掉报告期在 start_date 之前、但在回测区间内已公告的最近一期财报,导致区间前段数据缺失。 修复建议:按公告日期对齐,并保留回测起点前最近一期可用报告。
WHERE end_date BETWEEN %s AND %s
start_date
🟡 _get_conn / DataLoader 实例 — 将 psycopg2 连接作为实例属性长期持有,psycopg2 连接非线程安全,且类未提供 close 方法,多线程或频繁实例化时可能并发异常或连接泄漏。 修复建议:每次查询使用上下文管理器新建/释放连接,或引入连接池,并暴露 close() 接口。
psycopg2
close
close()
🟡 get_trade_dates 函数 — 数据库可用时返回的是 pd.to_datetime(...) 产生的 Series,与类型标注 pd.DatetimeIndex 不一致;数据库不可用时 pd.bdate_range 只排除周末,不是真实交易日。 修复建议:统一返回 pd.DatetimeIndex(...);无库时明确说明是“工作日近似”。
pd.to_datetime(...)
Series
pd.DatetimeIndex
pd.bdate_range
pd.DatetimeIndex(...)
🟢 load_benchmark 函数 — 日期为空或数据库不可用时用股票池价格的算术均值代替真实指数,不能反映市值加权、分红、成分调整等,会误导绩效评估。 修复建议:回退序列命名或日志中标注 “SIMULATED”,且尽量采用随机游走生成独立基准。
🟢 _get_conn 函数 — 运行时修改全局 sys.path 以导入 config,会污染解释器环境,多个实例化可能重复插入路径。 修复建议:在模块顶层统一读取配置或从环境变量加载,避免运行时修改 sys.path。
sys.path
config
🟢 init / _stock_sql_placeholders — 未校验 stocks 是否为空,空列表会导致 SQL IN () 语法错误。 修复建议:在初始化或查询前检查 if not self.stocks 并抛出 ValueError。
stocks
IN ()
if not self.stocks
daily.close * adj_factor
这是一个结构清晰、功能较完整的数据导入模块,批量化 UPSERT、按日期全市场拉取日线、断点续传等设计都值得肯定。但作为量化数据管道,存在几个方法论层面的严重问题:当前上市列表导致退市/暂停股的关键数据缺失(幸存者偏差)、财务报表保留最新公告覆盖历史公告(未来函数风险)、以及财务接口参数语义误用可能导致最新报告期缺失。此外还有一些数据类型、辅助函数和性能方面的中等/轻微问题。
1. import_adj_factor_batch / import_financial_statements 使用当前上市股票列表,造成幸存者偏差
import_adj_factor_batch
import_financial_statements
stock_list
get_stock_codes_from_db()
list_status = 'L'
import_daily_by_date
import_daily_basic_by_date
D
P
daily
ts_code
2. 财务报表去重/更新机制存在未来函数风险(look-ahead bias)
batch_insert
income
balancesheet
cashflow
fina_indicator
ts_code, end_date[, report_type]
f_ann_date
ON CONFLICT DO UPDATE
ann_date <= trade_date
3. import_financial_statements 误用 Tushare 财务接口的 start_date / end_date 参数
START_DATE
END_DATE
income_vip
balancesheet_vip
cashflow_vip
fina_indicator_vip
period
4. import_daily_by_date 的 skip_existing 存在日期类型比较隐患
skip_existing
trade_dates
trade_cal.cal_date
datetime.date
existing_dates
daily.trade_date
timestamp
date == datetime
NOT EXISTS
datetime.date()
5. 多个接口缺少显式数值转换,可能产生脏数据或插入失败
import_index_daily
import_adj_factor
pd.to_numeric(errors='coerce')
'-'
''
'None'
_normalize_*_df
6. batch_insert 每次调用都查询 information_schema.columns,性能开销大
information_schema.columns
_get_table_columns
7. check_table_summary 中日期信息元组嵌套错误
check_table_summary
tables
(("trade_cal", "cal_date"),)
tbl, col = date_info
except
("trade_cal", "cal_date")
8. import_stock_basic 缺失暂停股 P,且退市股拉取失败不阻断
import_stock_basic
get_all_stock_codes
L/D/P
L
fetch_with_retry
9. fetch_with_retry 在全部失败或返回空时静默返回空 DataFrame
10. import_daily_basic 辅助函数可能同时传入互斥参数
import_daily_basic
trade_date
daily_basic_vip
11. init_database 对 DDL 文件的切分方式不够健壮
init_database
--
;
schema.sql
;\n
12. import_financial_statements 无跳过已存在逻辑且无重试
ts_code + end_date
13. 多处函数假设输入日期格式为 YYYY-MM-DD
YYYY-MM-DD
import_trade_cal
start_date.replace("-", "")
YYYYMMDD
14. 模块级 logging.basicConfig 可能覆盖应用日志配置
logging.basicConfig
if __name__ == "__main__"
15. get_sqlalchemy_engine 定义了引擎但当前模块未使用
get_sqlalchemy_engine
get_pg_connection
PASSWORD_ENCODED
DB_CONFIG["password"]
16. import_daily_by_date 的 skip_existing 只检查某日是否有记录,不检查完整性
COUNT(DISTINCT ts_code)
resume_daily_by_date
get_missing_daily_dates
check_daily_progress
[🔴严重] 模块顶层(约第 54–66 行)- 关键凭证缺失时在 import 阶段直接 raise ValueError 这会让任何仅想引用配置常量、运行与数据库/Tushare 无关的单元测试、lint 或 notebook 的场景都无法导入模块;同时把“配置读取”与“使用凭证”强耦合。 修复建议:将强校验改为惰性校验(如提供 validate_config() 函数),仅在真正建立连接或调用 API 前检查,避免 import 即失败。
raise ValueError
validate_config()
[🟡中等] 全局变量 START_DATE / END_DATE(约第 69–70 行)- 缺少日期格式与区间校验 目前仅按字符串读取,未校验是否为合法日期、是否 START_DATE <= END_DATE;默认 END_DATE 为固定日历日,可能在运行时成为过期日期或历史运行时的未来日期,向下游数据获取/回测传递错误区间。 修复建议:用 datetime.date.fromisoformat 解析并校验区间,结束日期默认建议取 date.today() 或由调用方显式覆盖。
START_DATE <= END_DATE
datetime.date.fromisoformat
date.today()
[🟡中等] .env 加载逻辑(约第 20–28 行)- 回退到 Path.cwd() / ".env" 可能误加载错误配置 当脚本从非项目目录启动时,可能加载到其他项目或临时目录下的 .env,导致使用了错误的数据库地址、Token 或参数。 修复建议:移除对当前工作目录的隐式回退,或仅允许显式指定 .env 路径,并在日志中明确输出加载的文件。
Path.cwd() / ".env"
[🟢轻微] .env 加载分支(约第 24、32 行)- 使用 print 输出加载状态 在服务端进程、定时任务、测试或非 UTF-8 终端中,print 会污染 stdout/日志,且无法按日志级别关闭。 修复建议:改用 logging 模块按级别输出,或仅在 verbose/debug 模式下打印。
print
logging
[🟢轻微] TUSHARE_TOKEN 定义与校验(约第 51、62 行)- 重复读取环境变量 第 51 行已读取 TUSHARE_TOKEN,第 62 行又重新读取到 _TUSHARE,存在维护时遗漏的风险。 修复建议:复用已定义的 TUSHARE_TOKEN 变量进行非空校验。
TUSHARE_TOKEN
_TUSHARE
[🟢轻微] BATCH_SIZE(约第 68 行)- 未校验正整数 若环境变量设置为 0、负数或非数字字符串,下游批量插入可能出现除零或异常。 修复建议:赋值后校验 BATCH_SIZE > 0,并对非数字值给出明确错误提示。
BATCH_SIZE
0
BATCH_SIZE > 0
quote_plus
Path(__file__).resolve()
No dependencies set.
The note is not visible to the blocked user.
代码审核报告(2026-08-08,kimi-k2.7-code 全量复审)
审核模型: kimi-k2.7-code
审核时间: 2026-08-08 07:45
审核范围: quanxiel 全部 8 个 Python 文件(8 个模块)
🔴 跨文件最重要的共性问题
factors.py:全样本 z-score 标准化(t 日用了未来数据)、全局分位数而非截面分位数data_loader.py:财务数据用报告期end_date直接生效+ffill,未等公告日(ann_date)backtest.py:同 bar 执行——当日收盘价既生成信号又成交importer.py复权因子/财务报表只按当前上市列表(L)导入,退市/暂停股数据缺失importer.py用最新公告覆盖历史公告,丢点-in-time 版本backtest.py风控参数(max_position_pct/max_turnover/min_holding_period)读取了但完全没生效;期末清算重复记录净值导致指标异常data_loader.py所有 DB 异常静默回退随机模拟数据,且不同因子模拟数据完全相同📋 各文件审核结论
alpha/config.py
审核: alpha/config.py
config.py作为纯配置类,结构清晰、注释完整,且通过field(default_factory=...)正确避免了可变默认值的陷阱。但该文件存在“文档与实现不一致”以及环境变量类型转换、关键参数校验缺失等稳健性问题,这些问题会在下游数据获取、回测或输出阶段引发连锁报错。由于文件本身只含静态参数,暂未看到直接的回测未来函数或因子计算错误。alpha/strategy.py
审核: alpha/strategy.py
该文件整体结构清晰,抽象了信号生成、权重分配与因子合成,接口设计较为规范。但在多因子合成、分位数信号、日期边界以及股票池过滤等关键环节存在真实且影响回测正确性的问题,部分问题可能引入未来函数或导致信号缺失,需要优先修复。
alpha/backtest.py
审核: alpha/backtest.py
该回测引擎结构清晰、模块职责分离明确,但在回测正确性和数据质量上存在关键缺陷:缺失价格的持仓估值、风控参数完全未生效、期末清算重复记录净值,以及同 bar 执行带来的前视偏差风险。当前代码若直接用于绩效评估,可能导致权益曲线失真和风险被低估,需要先修复上述问题。
alpha/evaluation.py
审核: alpha/evaluation.py
该模块结构清晰,把常用绩效指标拆分为独立方法,便于扩展和单独调用;但核心输入列(
return/nav)的语义处理存在明显歧义,且年化、回撤、基准对齐等关键计算在异常数据或边界条件下不够健壮,可能在真实资金曲线上给出错误结果。由于这是纯绩效统计模块,未发现未来函数、信号生成或复权处理相关代码。alpha/factors.py
审核: alpha/factors.py
该模块在因子注册、抽象基类和 IC 计算框架上有清晰的设计,但因子标准化、分层回测和换手率分析存在明显的前视偏差或逻辑错误,直接用于实盘回测会导致信号失真。建议在继续扩展因子库之前先修正核心的截面处理与时序对齐问题。
alpha/data_loader.py
审核: alpha/data_loader.py
该数据加载器接口清晰、字段白名单 + SQL 参数化能够有效防止注入,整体结构便于 Notebook 快速演示。但代码在“回测正确性”上存在硬伤:财务数据使用前视了报告期;所有数据库异常都静默回退到随机模拟数据;模拟数据又让不同因子完全相同。这些问题在不连接真实数据库时极难被发现,会严重污染研究结论。
quantitative_data/importer.py
审核: quantitative_data/importer.py
这是一个结构清晰、功能较完整的数据导入模块,批量化 UPSERT、按日期全市场拉取日线、断点续传等设计都值得肯定。但作为量化数据管道,存在几个方法论层面的严重问题:当前上市列表导致退市/暂停股的关键数据缺失(幸存者偏差)、财务报表保留最新公告覆盖历史公告(未来函数风险)、以及财务接口参数语义误用可能导致最新报告期缺失。此外还有一些数据类型、辅助函数和性能方面的中等/轻微问题。
quantitative_data/config.py
审核: quantitative_data/config.py
该文件是一份数据层配置,整体上将敏感信息交给环境变量/
.env管理,并做了启动期凭证非空校验,符合基本安全习惯。但作为一个会被多处 import 的config.py,它在模块导入时直接抛出硬错误,且对日期、批次等关键参数缺少格式/范围校验,目前不涉及量化策略逻辑,因此 look-ahead bias、因子计算、回测执行等风险暂不适用。✅ 亮点
完整逐条问题清单(🔴🟡🟢 分级)见评论。
完整问题清单(Part 1/2)
alpha/config.py
审查结论
config.py作为纯配置类,结构清晰、注释完整,且通过field(default_factory=...)正确避免了可变默认值的陷阱。但该文件存在“文档与实现不一致”以及环境变量类型转换、关键参数校验缺失等稳健性问题,这些问题会在下游数据获取、回测或输出阶段引发连锁报错。由于文件本身只含静态参数,暂未看到直接的回测未来函数或因子计算错误。问题清单
🔴严重
[🔴严重]
AlphaConfig.db_port字段定义(约第 24–26 行)-int(os.getenv("DB_PORT", "12345"))缺少异常保护当
DB_PORT被设为空字符串、带空格或其他非数字值时,int()会直接抛出ValueError,导致配置实例化失败、程序启动崩溃。修复建议:在
__post_init__中或读取时加try/except,非法时回退到默认端口并打印明确警告。🟡中等
[🟡中等] 文件头部 docstring(约第 1–13 行)- 说明支持
.env文件,但代码未加载.env注释提示“可创建
.env文件(参考.env.example)”,但config.py中并未调用load_dotenv()或类似逻辑,仅读取系统环境变量;按说明创建.env后数据库密码等参数仍不会生效。修复建议:在模块顶部或应用入口处增加
from dotenv import load_dotenv; load_dotenv(),并确保python-dotenv在依赖列表中。[🟡中等]
AlphaConfig.start_date/end_date字段(约第 40–41 行)- 日期为字符串且无任何格式与顺序校验若后续模块传入非法格式或
start_date > end_date,会在回测/数据获取阶段才暴露错误。修复建议:在
__post_init__中用datetime.strptime校验格式,并断言start_date <= end_date。[🟡中等]
AlphaConfig.output_dir字段(约第 58 行)- 输出目录仅作字符串配置,未校验或创建默认
./output目录若不存在,后续保存 trade log 或结果时会抛出FileNotFoundError。修复建议:在配置初始化或输出模块中执行
os.makedirs(self.output_dir, exist_ok=True)。[🟡中等] 交易成本与仓位约束参数(约第 44–51 行)- 缺少非负性与合理性校验
commission_rate、slippage、stamp_tax、max_position_pct等若被误配为负数或大于 1,会直接污染回测结果。修复建议:在
__post_init__中统一校验0 <= rate <= 1等约束,并给出明确报错。🟢轻微
[🟢轻微] 导入
typing.Optional(第 16 行)- 已导入但代码中未使用属于未使用导入,影响代码整洁度。
修复建议:删除
Optional或保留供后续扩展。[🟢轻微] 默认
end_date="2025-12-31"(约第 41 行)- 固定远期截止日期存在数据覆盖风险若运行时刻行情数据尚未覆盖到 2025 年底,或该日期已超过当前日期且未做截断,可能导致空数据或未来数据问题。
修复建议:在回测入口根据实际数据可用性裁剪区间,或默认取最新交易日。
亮点
factor_windows使用field(default_factory=lambda: [5, 10, 20, 60])声明,符合 dataclass 最佳实践。alpha/strategy.py
审查结论
该文件整体结构清晰,抽象了信号生成、权重分配与因子合成,接口设计较为规范。但在多因子合成、分位数信号、日期边界以及股票池过滤等关键环节存在真实且影响回测正确性的问题,部分问题可能引入未来函数或导致信号缺失,需要优先修复。
问题清单
🔴 严重
[🔴]
Strategy.compute_composite_factor— NaN 污染导致合成因子失效代码将每个因子标准化后直接
composite += w * z,由于aligned经过reindex后存在大量 NaN,任一因子在某个(date, stock)上缺失时,该位置的合成因子会变为 NaN,即使其他因子有有效值也会丢失。建议对每个因子标准化后fillna(0)再参与加权求和,或显式要求因子交集并在缺失时给出合理处理。[🔴]
Strategy.run_step— 日期缺失时使用iloc[-1]存在未来函数风险当
date不在composite.index时,代码回退到composite.iloc[-1:],若当前日期早于最新因子日期,则会使用未来因子。建议改为composite.loc[:date].iloc[-1:]或composite.asof(date),确保只取当前日期及之前的最新截面。[🔴]
Strategy—filter_universe股票池参数被定义但从未使用filter_universe在 dataclass 中声明,但compute_composite_factor与权重分配均未用它过滤列,传入的股票池约束会完全失效。建议在合成因子前用filter_universe限制all_stocks,并在权重分配阶段再次校验。🟡 中等
[🟡]
QuantileSignal.generate—duplicates='drop'与固定分位标签不匹配pd.qcut(..., duplicates='drop')会在重复值较多时减少实际分档数,但代码仍固定判断long_quantile-1/short_quantile-1,可能出现某一侧(尤其是 top 档)永远没有信号。建议根据实际返回的最大/最小标签动态判断,或在初始化时校验分档可行性。[🟡]
QuantileSignal.generate— 未保证截面样本数不少于分档数min_stocks默认 10 仅保证最小股票数,未与n_quantiles比较;当len(row) < n_quantiles时会触发ValueError并被静默跳过,导致该日无信号。建议增加len(row) >= self.n_quantiles的前置校验或动态调整分位数。[🟡]
EqualWeightAllocator/FactorWeightAllocator— 忽略空头信号SignalGenerator文档明确定义-1=做空,但两个权重分配器只处理signal > 0的多头,空头信号被完全丢弃。若策略为多空,需支持负权重;若为纯多头,应在信号生成器或文档中明确说明。[🟡]
EqualWeightAllocator.allocate— 截仓未按信号强度排序long_stocks[:self.max_positions]按列顺序截取,未按因子值或信号强度排序,可能保留弱信号、剔除强信号。建议按latest_signal或因子值降序后再取前 N。[🟡] 权重分配器 — 完全未使用
prices、cash、positionsEqualWeightAllocator与FactorWeightAllocator均未结合股价、可用资金和当前持仓计算,目标权重与实际可买股数、整数股、换手成本、滑点等脱节。建议在 allocator 中加入基于价格和现金的目标市值计算,或明确说明此处仅输出名义权重、由下游模块处理成交。[🟡]
Strategy.compute_composite_factor— 小样本行标准化产生 NaNaligned.std(axis=1) + 1e-12无法修复“有效数据过少导致 std 为 NaN”的情况,该日期所有合成因子会变为 NaN。建议设置std(..., min_periods=...)或在小样本时跳过该截面。[🟡] 信号生成器 — 未校验输入日期索引唯一性
若
factor_df存在重复日期,factor_df.loc[date]会返回 DataFrame,row.dropna()行为异常,可能导致pd.qcut输入错误。建议进入计算前校验index.is_unique,必要时~index.duplicated()去重并排序。🟢 轻微
[🟢]
FactorWeightAllocator.allocate—min_weight截断后重新归一化可能扭曲权重当
min_weight * len(selected) > 1或原始权重均为极小值时,clip 后再归一化会压缩为近似等权,无法反映因子差异。建议仅在低于阈值时做下限处理,并校验分母有效性。[🟢]
FactorWeightAllocator.allocate— 每次 fallback 都新建EqualWeightAllocator实例return EqualWeightAllocator(self.max_positions).allocate(...)会重复创建对象,建议预创建实例或使用类内静态方法。[🟢]
Strategy.compute_composite_factor— 未校验factor_weights之和当用户传入非归一化权重时,合成因子的量级会任意变化,进而影响 Z-Score/分位数信号的触发。建议在使用前对权重做归一化或校验。
[🟢]
Strategy.run_step— 每次调用都重新计算全区间 composite在逐日回测中会重复进行标准化与矩阵对齐,性能开销较大。建议将 composite 预计算并缓存,run_step 仅切片使用。
[🟢]
QuantileSignal.__init__— 未校验分位参数合法性long_quantile、short_quantile可能超出[1, n_quantiles]范围,导致信号永远为空。建议在初始化时校验。亮点
SignalGenerator、WeightAllocator抽象基类解耦信号生成与权重分配,便于扩展不同策略。compute_composite_factor在合并前对每个因子做截面 Z-Score,有助于消除量纲差异,符合多因子常用做法。QuantileSignal.generate对pd.qcut的ValueError做了 try/except,防止极端数据导致整个回测中断。alpha/backtest.py
审查结论
该回测引擎结构清晰、模块职责分离明确,但在回测正确性和数据质量上存在关键缺陷:缺失价格的持仓估值、风控参数完全未生效、期末清算重复记录净值,以及同 bar 执行带来的前视偏差风险。当前代码若直接用于绩效评估,可能导致权益曲线失真和风险被低估,需要先修复上述问题。
问题清单
🔴 Broker.init / Broker.execute — 从配置读取了
max_position_pct、max_turnover、min_holding_period,但在execute、_buy、_sell中完全没有使用,风控约束形同虚设。修复建议:在生成订单前校验/截断目标权重,并在买卖执行中加入个股仓位上限、单日换手上限和最短持有期检查。
🔴 BacktestEngine.run — 循环的最后一日已经调用
record_daily记录净值,循环结束后又调用_liquidate并再次record_daily(final_date),导致净值曲线出现重复索引,后续计算收益率、夏普等会异常。修复建议:将期末清算合并到循环最后一天,或移除重复的最终
record_daily,确保每个交易日只有一条净值记录。🔴 Position.market_value / Portfolio.update_market_prices — 当持仓股票某日在
current_prices中缺失时,update_market_prices不会更新其current_price,而market_value会继续使用旧价格(隐式前向填充),退市/停牌股票会被持续按过期价格估值,导致权益虚高。修复建议:在
record_daily前对所有持仓用上一日有效价格显式前向填充,或对长期缺失价格的股票按退市/停牌规则处理。🟡 BacktestEngine.run + Broker.execute — 策略信号生成与订单成交均使用
price_data.loc[date]的同一日价格(默认收盘价),属于同 bar 执行,策略可用当日收盘价决定当日仓位,存在前视偏差。修复建议:使用上一日收盘价生成信号,并以次日开盘价或带滑点的次日收盘价执行成交。
🟡 Broker.execute —
target_weights未做归一化和有效性校验;权重总和大于 1、为负或为空时,会导致现金不足、买入被截断、实际仓位偏离目标。修复建议:在
execute开头校验target_weights,必要时归一化、过滤负值并输出警告。🟡 BacktestEngine.run —
all_dates = sorted(price_data.index)未去重,若输入数据存在重复交易日,会导致单日多次调仓、多次记录净值。修复建议:使用
price_data.index.unique().sort_values(),或在发现重复日期时报错/去重。🟡 BacktestEngine.run —
factor_data与price_data的日期、股票池未做任何对齐检查,缺失或错位的因子可能被策略错误使用。修复建议:回测前将各因子 DataFrame 按
price_data的index/columns进行reindex并对齐,检查缺失率。🟡 Broker.execute / _buy — 现金不足时仅对单笔买入订单截断数量,未考虑多只股票顺序和后续订单对权重的影响,排在前面的股票可能占满现金,导致最终组合偏离目标。
修复建议:调仓前根据目标权重和预估交易成本统一计算各股票可成交数量,或按目标权重优先级排序并预留后续订单现金。
🟢 BacktestEngine.run —
rebalance_freq只显式判断'M'/'W',其他字符串(如'Q')会静默按日调仓处理。修复建议:增加对
'D'的显式判断,并对未识别的频率抛出ValueError。🟢 BacktestEngine.run — 期末清算时若
final_date不在price_data.index中,final_prices为空,持仓无法平仓但又会重复记录一次净值,结果不一致。修复建议:仅在
final_date存在有效价格时执行最终清算,否则使用上一个有效交易日的价格。🟢 Portfolio.get_position / Broker.execute —
get_position会在未实际成交时创建quantity=0的Position对象,长期运行会累积大量空持仓。修复建议:在
diff_qty == 0时不调用get_position,或定期清理quantity == 0的持仓。🟢 BacktestEngine.run —
progress_callback和strategy.run_step的调用没有异常处理,一个合约/策略异常会导致整个回测中断。修复建议:增加
try/except包裹策略调用与回调,记录日志并可选择跳过异常日期。亮点
dataclass定义TradeRecord与Position,交易记录和持仓结构清晰;佣金、印花税、滑点在买卖两侧分别实现,符合 A 股交易成本习惯。Portfolio与BacktestEngine职责分离,并提供了progress_callback接口,便于扩展和集成。alpha/evaluation.py
审查结论
该模块结构清晰,把常用绩效指标拆分为独立方法,便于扩展和单独调用;但核心输入列(
return/nav)的语义处理存在明显歧义,且年化、回撤、基准对齐等关键计算在异常数据或边界条件下不够健壮,可能在真实资金曲线上给出错误结果。由于这是纯绩效统计模块,未发现未来函数、信号生成或复权处理相关代码。问题清单
🔴 严重
[🔴]
PerformanceEvaluator.__init__(daily_return生成逻辑)当输入列名为
return时,代码直接使用self.equity["return"].diff()。这隐含假设该列是“累计收益率”或从 0 开始的价格序列;若用户传入的return已经是日收益率,则所有指标都会基于“收益率的差分”计算,结果完全错误。→ 修复建议:明确约定输入语义,或分别支持
daily_return/cum_return/nav,避免对同名列做歧义转换。[🔴]
PerformanceEvaluator.total_return(nav分支)直接返回
nav.iloc[-1] - 1.0,默认初始 NAV 为 1.0;若传入的是绝对资金曲线(如起始 1,000,000),累计收益会被严重算错。→ 修复建议:统一使用
nav.iloc[-1] / nav.iloc[0] - 1.0,不依赖初始值。🟡 中等
[🟡]
PerformanceEvaluator.annual_return(负收益边界)当
total_return < -1且years为分数时,(1 + total) ** (1 / years)会产生nan并伴随 RuntimeWarning,未做保护。→ 修复建议:在计算前判断
1 + total <= 0,返回np.nan或改用对数年化。[🟡]
PerformanceEvaluator._year_frac(年化分母)使用
n / periods_per_year代替实际日历时长,未处理缺失交易日、非日频数据等情况,会导致年化收益/波动率被错误缩放。→ 修复建议:基于索引的实际日期跨度计算年化因子,例如
(last - first).days / 365.25,或在输入前补齐交易日。[🟡]
PerformanceEvaluator.sortino_ratio(下行偏差定义)仅取
returns < 0的子样本来计算标准差,不符合 Sortino 比率通常使用的目标收益率/MAR(至少应考虑无风险利率),且仅使用亏损样本能低估/偏离真实下行偏差。→ 修复建议:以
returns - target为基准,保留全部观测,计算sqrt(mean(min(0, returns - target)^2))。[🟡]
PerformanceEvaluator.alpha/beta/information_ratio(样本不足与对齐)_align_benchmark使用innerjoin +dropna,策略与基准日期不重合时会静默丢弃数据;当对齐后样本量小于 2 时,np.cov会返回NaN并可能触发警告,后续仍参与计算。→ 修复建议:增加
len(aligned) < 2校验并返回np.nan,同时可考虑使用reindex对齐并显式处理缺失。[🟡]
PerformanceEvaluator.profit_loss_ratio(空子集除零)若序列中没有盈利日或亏损日,
avg_win或avg_loss会为NaN或 0,导致返回NaN或异常大值。→ 修复建议:在子集为空时返回
np.nan,并对avg_loss == 0单独处理。[🟡]
PerformanceEvaluator.__init__及多个方法(索引未校验)未检查
equity_curve.index是否唯一、是否按时间排序、是否为日期类型。重复索引会导致pct_change/diff/cumprod产生错误行;非日期索引会让_year_frac等单位换算无意义。→ 修复建议:初始化时校验
index.is_unique、index.is_monotonic_increasing及is_datetime64。🟢 轻微
[🟢]
ReportGenerator.to_markdown(整数指标格式化)对“最长回撤天数”等整数指标使用
{value:.4f},输出形如15.0000,可读性不佳。→ 修复建议:根据数值类型选择
d或.4f格式化。[🟢]
ReportGenerator.to_markdown(交易对象未防御)直接访问
t.date、t.stock等属性,未校验trades元素结构,传入不兼容对象会抛出AttributeError。→ 修复建议:使用
hasattr检查或使用 dataclass/NamedTuple 约束交易对象结构。[🟢]
PerformanceEvaluator.full_report(periods_per_year被忽略)full_report未暴露periods_per_year参数;若数据为周/月频,必须逐个调用方法,无法一次性生成正确频率的报告。→ 修复建议:为
full_report增加periods_per_year参数并透传给各指标方法。亮点
inner join+dropna对齐策略与基准,避免了因交易日不一致导致回归/相对指标出现偏差。+ 1e-12防止除零崩溃,提升了代码在常数收益序列上的稳定性。完整问题清单(Part 2/2)
alpha/factors.py
审查结论
该模块在因子注册、抽象基类和 IC 计算框架上有清晰的设计,但因子标准化、分层回测和换手率分析存在明显的前视偏差或逻辑错误,直接用于实盘回测会导致信号失真。建议在继续扩展因子库之前先修正核心的截面处理与时序对齐问题。
问题清单
🔴 严重
🔴 严重
TechnicalFactor.compute(约第118–126行)- 全样本标准化导致前视偏差mom_z / vol_z使用整个序列的.mean()/.std()做标准化,意味着在 t 日使用了未来数据计算均值和标准差;对多资产面板而言还混入了未来截面信息。修复建议:按截面(或至少按扩展窗口/滚动窗口)逐期标准化,例如
factor.groupby(level=0).apply(lambda s: (s - s.mean()) / s.std())。🔴 严重
FundamentalFactor.compute(约第140–161行)- 同样存在全样本标准化前视偏差ROE、PE、PB、营收增速均在全样本上做 z-score,且未按行业/日期做中性化处理,面板下会用到未来和跨期信息。
修复建议:按财报披露截止日或交易日期做截面标准化,并处理缺失值(如仅对非缺失值做标准化后再合并)。
🔴 严重
FactorAnalyzer.quantile_returns(约第232–239行)- 全局分位数而非截面分位数,且累计收益计算错误pd.qcut直接对所有样本做全局分位,未按日期截面分组;cum_return = avg_return.cumsum()是在“组号 1→5”上累加,失去了时间维度,不是组合累计收益。修复建议:按
level=0分组后对每个截面qcut,再计算每组时序平均收益和累计收益。🔴 严重
FactorAnalyzer.turnover(约第241–255行)- 换手率逻辑未实现且计算对象错误MultiIndex 分支下仅写了
pass未返回结果;非 MultiIndex 分支用self.factor.autocorr(lag=1)对整个面板做自相关,混淆了不同资产和日期,不能代表“相邻期分位数变化比例”。修复建议:按日期截面计算持仓权重或分位组,再计算相邻期权的换手比例序列。
🟡 中等
🟡 中等
FactorAnalyzer.ic_decay(约第220–230行)- 池化计算 IC 衰减,未按期聚合直接用
stats.spearmanr对所有截面的因子值和未来收益做整体相关,得到的是池化相关系数,而不是“每期 IC 的均值”,会混入时序趋势。修复建议:按
level=0逐期计算 Spearman/Pearson IC,再对各期 IC 取平均。🟡 中等
FactorAnalyzer.compute_ic(约第179–205行)- 未显式对齐因子与收益,缺失日期会抛 KeyErrorfwd.get_group(g.name)在forward_returns缺少某个日期时会直接抛错;g.corr对内部股票做内连接,也可能在截面不完整时静默忽略。修复建议:先
self.factor.align(self.forward, join='inner'),或在apply中用try/except跳过缺失截面。🟡 中等
BaseFactor.neutralize(约第69–96行)- 对空数据、共线行业和异常市值缺乏保护market_cap <= 0被clip(lower=1)当成市值 1 处理,会扭曲回归;group含 NaN 时get_dummies会将其所有行业 dummy 置 0;若common_idx为空或 X 列完全共线,lstsq结果不稳定。修复建议:过滤市值<=0 和分组为 NaN 的样本,并在回归前检查 X 的秩或改用
np.linalg.lstsq的残差逻辑加异常处理。🟡 中等
TechnicalFactor.compute/FundamentalFactor.compute- 未防御标准差为零当因子序列恒定(如所有 PE 相同)或仅含 NaN 时,
series.std()为 0/NaN,导致inf或全 NaN,可能进一步触发pd.qcut报错。修复建议:标准化前判断
std > 0,否则返回 NaN 或跳过该因子。🟡 中等
FactorAnalyzer.quantile_returns/turnover(约第235、244行)-pd.qcut对重复边界会抛异常当因子存在大量相同值时,
qcut默认duplicates='raise'会抛出ValueError。修复建议:使用
pd.qcut(..., duplicates='drop')或改用pd.cut。🟢 轻微
🟢 轻微
TechnicalFactor.compute(约第123–125行)- 动量/波动率窗口不一致导致合成结果含大量 NaNmom与vol的 NaN 长度不同,0.5 * mom_z + 0.5 * vol_z在任一者为 NaN 时整体为 NaN,损失了更多可用信号日。修复建议:统一窗口长度或采用
fillna/ 加权时只使用两者均非 NaN 的截面。🟢 轻微
BaseFactor.neutralize(约第83行)- 市值非正数未剔除而是硬编码为 1np.log(market_cap.clip(lower=1))把 0/负市值变成 log(1)=0,相当于给这些样本赋予了错误市值。修复建议:先
market_cap = market_cap[market_cap > 0]再取 log,保证样本质量。🟢 轻微
FactorAnalyzer.turnover(约第252行)- 依赖self.ic_series决定是否计算自相关系数换手率计算与 IC 序列无必然联系;若用户未先调用
compute_ic,此处会返回空 Series,行为不稳定。修复建议:移除对
ic_series的状态依赖,直接基于因子计算换手率。亮点
FactorRegistry与BaseFactor便于后续扩展新因子,并统一了compute接口。compute_ic对 MultiIndex 按日期分组计算 Spearman/Pearson,符合截面因子研究范式。alpha/data_loader.py
审查结论
该数据加载器接口清晰、字段白名单 + SQL 参数化能够有效防止注入,整体结构便于 Notebook 快速演示。但代码在“回测正确性”上存在硬伤:财务数据使用前视了报告期;所有数据库异常都静默回退到随机模拟数据;模拟数据又让不同因子完全相同。这些问题在不连接真实数据库时极难被发现,会严重污染研究结论。
问题清单
按严重程度排列:
🔴 _load_fina_field 函数 — 以财报
end_date作为数据生效日期并直接ffill(),相当于在报告期末当天就知道该期财务结果,而实际财报通常在报告期结束后数周才披露,构成典型的未来函数/前视偏差。修复建议:使用
ann_date(公告日)或报告期后的首个交易日作为生效起点,再向前填充。🔴 load_prices / load_factor / load_benchmark 函数 — 使用裸
except Exception捕获所有异常后静默回退到模拟数据,SQL 语法错误、网络超时、表不存在等情况都会让调用方在不知情的情况下拿到随机数据。修复建议:增加
strict模式(默认开启),数据库异常时直接抛出;仅在网络不可用时明确回退,并在返回数据中标注 “SIMULATED”。🔴 _simulate_factor 函数 — 每次调用都重置
np.random.seed(self.seed)且未按因子名区分随机状态,导致不同因子的模拟矩阵完全相同;同时与_simulate_prices共享同一 seed,可能人为制造出因子与收益的相关性。修复建议:为每个因子生成独立随机状态,例如
rng = np.random.default_rng(hash(name) ^ self.seed)。🟡 load_factor 函数 — 对于不在
DAILY_BASIC_FIELDS或FINA_FIELDS中的name也直接返回模拟数据,不会报错,容易把拼写错误当成有效因子。修复建议:在入口处校验
name,未知因子抛出ValueError。🟡 _load_prices_db 函数 —
dropna(how='all', axis=1)会静默剔除无数据的股票列,且不强制对齐到完整股票池/交易日,可能引入幸存者偏差或导致下游截面运算维度不一致。修复建议:保留请求的股票列(缺失填
NaN),并打印/记录被剔除的股票。🟡 _load_fina_field 函数 —
WHERE end_date BETWEEN %s AND %s会漏掉报告期在start_date之前、但在回测区间内已公告的最近一期财报,导致区间前段数据缺失。修复建议:按公告日期对齐,并保留回测起点前最近一期可用报告。
🟡 _get_conn / DataLoader 实例 — 将
psycopg2连接作为实例属性长期持有,psycopg2连接非线程安全,且类未提供close方法,多线程或频繁实例化时可能并发异常或连接泄漏。修复建议:每次查询使用上下文管理器新建/释放连接,或引入连接池,并暴露
close()接口。🟡 get_trade_dates 函数 — 数据库可用时返回的是
pd.to_datetime(...)产生的Series,与类型标注pd.DatetimeIndex不一致;数据库不可用时pd.bdate_range只排除周末,不是真实交易日。修复建议:统一返回
pd.DatetimeIndex(...);无库时明确说明是“工作日近似”。🟢 load_benchmark 函数 — 日期为空或数据库不可用时用股票池价格的算术均值代替真实指数,不能反映市值加权、分红、成分调整等,会误导绩效评估。
修复建议:回退序列命名或日志中标注 “SIMULATED”,且尽量采用随机游走生成独立基准。
🟢 _get_conn 函数 — 运行时修改全局
sys.path以导入config,会污染解释器环境,多个实例化可能重复插入路径。修复建议:在模块顶层统一读取配置或从环境变量加载,避免运行时修改
sys.path。🟢 init / _stock_sql_placeholders — 未校验
stocks是否为空,空列表会导致 SQLIN ()语法错误。修复建议:在初始化或查询前检查
if not self.stocks并抛出ValueError。亮点
daily.close * adj_factor按同交易日 join,符合常见的后复权口径。quantitative_data/importer.py
审查结论
这是一个结构清晰、功能较完整的数据导入模块,批量化 UPSERT、按日期全市场拉取日线、断点续传等设计都值得肯定。但作为量化数据管道,存在几个方法论层面的严重问题:当前上市列表导致退市/暂停股的关键数据缺失(幸存者偏差)、财务报表保留最新公告覆盖历史公告(未来函数风险)、以及财务接口参数语义误用可能导致最新报告期缺失。此外还有一些数据类型、辅助函数和性能方面的中等/轻微问题。
问题清单
🔴 严重
1.
import_adj_factor_batch/import_financial_statements使用当前上市股票列表,造成幸存者偏差stock_list默认来自get_stock_codes_from_db(),而该方法只取list_status = 'L'。import_daily_by_date/import_daily_basic_by_date是按交易日拉取全市场数据,会包含历史上已退市、暂停的股票。D、P),或直接从已导入的daily表中提取出现过的所有ts_code进行导入。2. 财务报表去重/更新机制存在未来函数风险(look-ahead bias)
batch_insert中对income/balancesheet/cashflow/fina_indicator按ts_code, end_date[, report_type]去重,并保留ann_date/f_ann_date最大的一条。ON CONFLICT DO UPDATE会用最新公告的数据覆盖旧公告,旧版本 announcement 行被丢失。ann_date(或f_ann_date)纳入唯一键,保存每一版公告;或维护“点-in-time 表”与“最新快照表”两张表,查询时按ann_date <= trade_date过滤。3.
import_financial_statements误用 Tushare 财务接口的start_date/end_date参数START_DATE/END_DATE直接传给income_vip/balancesheet_vip/cashflow_vip/fina_indicator_vip。start_date/end_date通常表示公告日期区间,而非报告期;若END_DATE设为回测期末,会遗漏期末之后才公告的最近报告期数据。period参数按报告期拉取,或将end_date显著延后并本地按end_date列过滤。🟡 中等
4.
import_daily_by_date的skip_existing存在日期类型比较隐患trade_dates来自trade_cal.cal_date(datetime.date),而existing_dates来自daily.trade_date。daily.trade_date在库中是timestamp类型,date == datetime在 Python 集合比较中永远不等,导致已导入日期不会被跳过,重复拉取并 UPSERT。NOT EXISTS过滤缺失日期,或在 Python 侧统一转换为datetime.date()再比较。5. 多个接口缺少显式数值转换,可能产生脏数据或插入失败
import_daily_basic_by_date、import_financial_statements、import_index_daily、import_adj_factor仅做日期/列名处理,未对数值列执行pd.to_numeric(errors='coerce')。'-'、''、'None'等异常字符串导致隐式转换失败或写入脏数据。_normalize_*_df函数,显式转换已知数值列;或在batch_insert前按数据库列类型做统一转换。6.
batch_insert每次调用都查询information_schema.columns,性能开销大_get_table_columns在每个batch_insert调用时都查一次元数据。import_daily_by_date中每个交易日调用一次,4000 个交易日会产生 4000 次元数据查询。7.
check_table_summary中日期信息元组嵌套错误tables中日期项写成(("trade_cal", "cal_date"),),导致tbl, col = date_info解包失败(只有一个元素)。except分支,日期统计功能失效。("trade_cal", "cal_date")。8.
import_stock_basic缺失暂停股P,且退市股拉取失败不阻断get_all_stock_codes已考虑L/D/P三类,但import_stock_basic只导入L并尝试D,未处理P。D的拉取放在try/except中且仅记录 warning,失败不会重试,退市股基本信息可能静默缺失。L/D/P三类;对D、P也使用fetch_with_retry重试。9.
fetch_with_retry在全部失败或返回空时静默返回空 DataFrame10.
import_daily_basic辅助函数可能同时传入互斥参数trade_date时,start_date/end_date仍被一起传给daily_basic_vip,Tushare 可能不支持同时指定单日和区间。trade_date。11.
init_database对 DDL 文件的切分方式不够健壮--开头行,再按;切分。若schema.sql中包含函数/触发器体、或字符串字面量里带;,会错误拆分语句。psycopg2直接执行整个文件,或按;\n切分时保留引号/块上下文。12.
import_financial_statements无跳过已存在逻辑且无重试ts_code + end_date的 skip_existing 检查,并对每个接口使用fetch_with_retry。🟢 轻微
13. 多处函数假设输入日期格式为
YYYY-MM-DDimport_trade_cal、import_daily_by_date直接使用start_date.replace("-", "")。YYYYMMDD,会得到错误参数。14. 模块级
logging.basicConfig可能覆盖应用日志配置if __name__ == "__main__"下,或提供配置开关。15.
get_sqlalchemy_engine定义了引擎但当前模块未使用get_pg_connection使用的密码来源不一致(PASSWORD_ENCODEDvsDB_CONFIG["password"]),容易造成维护混乱。16.
import_daily_by_date的skip_existing只检查某日是否有记录,不检查完整性COUNT(DISTINCT ts_code)阈值校验,或记录每日期望股票数。亮点
import_daily_by_date)是比按股票循环更合理的设计,能显著减少 API 调用次数,并避免按股票循环容易引入的幸存者偏差。batch_insert的批量 UPSERT + 批内去重逻辑实现得较为完整,特别是按公告日期保留最新记录、冲突键类型归一化、失败后逐行回退等细节考虑周到。resume_daily_by_date、get_missing_daily_dates、check_daily_progress),对长期运行的数据维护非常实用。quantitative_data/config.py
审查结论
该文件是一份数据层配置,整体上将敏感信息交给环境变量/
.env管理,并做了启动期凭证非空校验,符合基本安全习惯。但作为一个会被多处 import 的config.py,它在模块导入时直接抛出硬错误,且对日期、批次等关键参数缺少格式/范围校验,目前不涉及量化策略逻辑,因此 look-ahead bias、因子计算、回测执行等风险暂不适用。问题清单
🔴 严重
[🔴严重] 模块顶层(约第 54–66 行)- 关键凭证缺失时在 import 阶段直接
raise ValueError这会让任何仅想引用配置常量、运行与数据库/Tushare 无关的单元测试、lint 或 notebook 的场景都无法导入模块;同时把“配置读取”与“使用凭证”强耦合。
修复建议:将强校验改为惰性校验(如提供
validate_config()函数),仅在真正建立连接或调用 API 前检查,避免 import 即失败。🟡 中等
[🟡中等] 全局变量
START_DATE/END_DATE(约第 69–70 行)- 缺少日期格式与区间校验目前仅按字符串读取,未校验是否为合法日期、是否
START_DATE <= END_DATE;默认END_DATE为固定日历日,可能在运行时成为过期日期或历史运行时的未来日期,向下游数据获取/回测传递错误区间。修复建议:用
datetime.date.fromisoformat解析并校验区间,结束日期默认建议取date.today()或由调用方显式覆盖。[🟡中等]
.env加载逻辑(约第 20–28 行)- 回退到Path.cwd() / ".env"可能误加载错误配置当脚本从非项目目录启动时,可能加载到其他项目或临时目录下的
.env,导致使用了错误的数据库地址、Token 或参数。修复建议:移除对当前工作目录的隐式回退,或仅允许显式指定
.env路径,并在日志中明确输出加载的文件。🟢 轻微
[🟢轻微]
.env加载分支(约第 24、32 行)- 使用print输出加载状态在服务端进程、定时任务、测试或非 UTF-8 终端中,
print会污染 stdout/日志,且无法按日志级别关闭。修复建议:改用
logging模块按级别输出,或仅在 verbose/debug 模式下打印。[🟢轻微]
TUSHARE_TOKEN定义与校验(约第 51、62 行)- 重复读取环境变量第 51 行已读取
TUSHARE_TOKEN,第 62 行又重新读取到_TUSHARE,存在维护时遗漏的风险。修复建议:复用已定义的
TUSHARE_TOKEN变量进行非空校验。[🟢轻微]
BATCH_SIZE(约第 68 行)- 未校验正整数若环境变量设置为
0、负数或非数字字符串,下游批量插入可能出现除零或异常。修复建议:赋值后校验
BATCH_SIZE > 0,并对非数字值给出明确错误提示。亮点
.env管理,未在代码中硬编码数据库密码或 Tushare Token,符合安全最佳实践。quote_plus编码,为生成 SQLAlchemy 连接串时避免特殊字符解析错误做了准备。Path(__file__).resolve()定位.env,相比直接使用相对路径更能抵御工作目录变化带来的影响。