Skip to Content
数据基石后端模块模块二:量化分析设计

ZLData 量化分析模块 — 详细设计文档

属性内容
文档编号ZLData-QA-DD-001
配套文档ZLData_PRD_v3.1.md §5.2
日期2026-07-21
状态Draft — 待评审
作者无尘

目录

  1. 模块概述
  2. 技术指标计算
  3. 量化因子体系
  4. 交易信号生成
  5. 数据流与入库
  6. API 设计
  7. 性能优化
  8. 前端可视化

1. 模块概述

1.1 定位

量化分析模块是 ZLData 的策略引擎层。它消费模块一(数据采集)产出的标准化行情和财务数据,输出技术指标、量化因子和交易信号。这层是”从数据到决策”的核心转换环节。

消费上游产出下游外部依赖
trade_stock_daily (行情)技术指标表pandas-ta / ta-lib
trade_stock_financial (财务)多因子评分pandas / numpy
trade_macro_indicator (宏观)交易信号scipy / sklearn (v2)

1.2 设计原则

  1. 确定性优先:指标和因子的计算全部采用确定性的数学公式,不使用 AI/LLM(LLM 在 v2.0 智能体层介入信号解读)
  2. 批量计算:全市场 ~5000 只股票的计算必须在 10 分钟内完成
  3. 增量更新:每日只计算新增交易日的数据,不重算全量历史
  4. 参数可配置:技术指标的周期、因子的权重全部通过配置文件管理,不硬编码

2. 技术指标计算

2.1 指标清单

类别指标常用周期pandas-ta 函数说明
趋势MA (简单移动均线)5, 10, 20, 60, 120sma最基础的均线
趋势EMA (指数移动均线)12, 26emaMACD 的基础组件
趋势MACD(12, 26, 9)macdDIF/DEA/柱
动量RSI (相对强弱指标)6, 14, 24rsi0-100,超买超卖
动量KDJ (随机指标)(9, 3, 3)kdjK/D/J 三条线
动量WR (威廉指标)10, 6willr短期超买超卖
波动BOLL (布林带)(20, 2)bbands上轨/中轨/下轨
波动ATR (平均真实波幅)14atr波动率度量
成交量OBV (能量潮)obv量价配合
成交量VWAP (成交量加权均价)自行计算日内基准价
均线关系MA 多头/空头排列自行计算MA5>MA10>MA20… 判断
背离MACD 背离自行计算价格新高 MACD 未新高

2.2 核心指标详解

MACD

EMA12 = EMA(close, 12) EMA26 = EMA(close, 26) DIF = EMA12 - EMA26 DEA = EMA(DIF, 9) MACD柱 = 2 × (DIF - DEA)

信号含义

  • 金叉(DIF 上穿 DEA)→ 看涨信号
  • 死叉(DIF 下穿 DEA)→ 看跌信号
  • 柱状线由负转正 → 多头增强
  • DIF/DEA 与价格背离 → 趋势可能反转

RSI(相对强弱指标)

RS = 周期内平均涨幅 / 周期内平均跌幅 RSI = 100 - 100 / (1 + RS)

阈值

  • RSI > 70:超买(可能回调)
  • RSI < 30:超卖(可能反弹)
  • RSI 50 附近:无明显方向

布林带

中轨 = MA(close, 20) 上轨 = 中轨 + 2 × std(close, 20) 下轨 = 中轨 - 2 × std(close, 20)

信号含义

  • 价格触及上轨 → 超买/强势(在趋势中上轨不是卖出信号)
  • 价格触及下轨 → 超卖/弱势
  • 带宽收窄(“缩口”)→ 即将变盘
  • 带宽扩张 → 趋势启动

2.3 批量计算策略

import pandas_ta as ta import pandas as pd def compute_all_indicators(df: pd.DataFrame) -> pd.DataFrame: """ 对单只股票的日K线 DataFrame 计算全部技术指标。 df 必须包含: date, open, high, low, close, volume 返回添加了全部指标列的 DataFrame """ df = df.copy() # 趋势类 df['ma5'] = ta.sma(df['close'], length=5) df['ma10'] = ta.sma(df['close'], length=10) df['ma20'] = ta.sma(df['close'], length=20) df['ma60'] = ta.sma(df['close'], length=60) df['ma120'] = ta.sma(df['close'], length=120) # MACD macd = ta.macd(df['close'], fast=12, slow=26, signal=9) df = pd.concat([df, macd], axis=1) # RSI df['rsi6'] = ta.rsi(df['close'], length=6) df['rsi14'] = ta.rsi(df['close'], length=14) df['rsi24'] = ta.rsi(df['close'], length=24) # KDJ kdj = ta.kdj(df['high'], df['low'], df['close'], length=9, signal=3) df = pd.concat([df, kdj], axis=1) # 布林带 bbands = ta.bbands(df['close'], length=20, std=2) df = pd.concat([df, bbands], axis=1) # ATR df['atr14'] = ta.atr(df['high'], df['low'], df['close'], length=14) # OBV df['obv'] = ta.obv(df['close'], df['volume']) # 均线多头排列 df['ma_bullish'] = ( (df['ma5'] > df['ma10']) & (df['ma10'] > df['ma20']) & (df['ma20'] > df['ma60']) ).astype(int) return df

2.4 增量更新

def update_indicators_incremental(stock_code: str, since_date: str): """ 只更新 since_date 之后的技术指标。 为实现计算精确,需要回取 N 个历史交易日的数据(N = max_lookback)。 """ MAX_LOOKBACK = 120 # MA120 需要 120 个交易日 # 1. 从 DB 取 since_date - MAX_LOOKBACK 以来的全部行情 df = fetch_daily_kline(stock_code, from_date=offset_date(since_date, -MAX_LOOKBACK)) # 2. 全量计算指标(只写入 since_date 之后的记录) df = compute_all_indicators(df) df_to_write = df[df['trade_date'] >= since_date] # 3. 幂等写入指标表 upsert_indicators(stock_code, df_to_write)

2.5 指标表设计

CREATE TABLE trade_stock_indicator_daily ( id BIGINT IDENTITY(1,1) PRIMARY KEY, stock_code VARCHAR(20) NOT NULL, trade_date DATE NOT NULL, -- 趋势类 ma5 DECIMAL(12,4), ma10 DECIMAL(12,4), ma20 DECIMAL(12,4), ma60 DECIMAL(12,4), ma120 DECIMAL(12,4), -- MACD 系列 macd_dif DECIMAL(12,6), macd_dea DECIMAL(12,6), macd_hist DECIMAL(12,6), -- RSI 系列 rsi6 DECIMAL(8,4), rsi14 DECIMAL(8,4), rsi24 DECIMAL(8,4), -- KDJ 系列 kdj_k DECIMAL(8,4), kdj_d DECIMAL(8,4), kdj_j DECIMAL(8,4), -- 布林带 boll_upper DECIMAL(12,4), boll_middle DECIMAL(12,4), boll_lower DECIMAL(12,4), -- 波动/成交量 atr14 DECIMAL(12,4), obv DECIMAL(20,2), ma_bullish TINYINT DEFAULT 0, -- 元数据 created_at DATETIME2 DEFAULT GETDATE(), updated_at DATETIME2 DEFAULT GETDATE(), CONSTRAINT uq_stock_indicator_date UNIQUE (stock_code, trade_date) ); CREATE INDEX idx_indicator_date ON trade_stock_indicator_daily(trade_date); CREATE INDEX idx_indicator_stock ON trade_stock_indicator_daily(stock_code);

3. 量化因子体系

3.1 因子总览

类别因子权重计算周期数据来源
价值PE (市盈率)0.15EPS + 收盘价
价值PB (市净率)0.10每股净资产 + 收盘价
动量20日动量0.15收盘价
动量60日动量0.10收盘价
质量ROE0.10净利润 / 净资产
质量毛利率0.05(营收 - 营业成本) / 营收
波动20日波动率0.10日收益率标准差
波动Beta (β)0.1060日对沪深300回归
情绪换手率异常0.05换手率 vs 20日均值
成长净利润同比增长0.10上年同期比较

3.2 因子数值计算

动量因子

def momentum_20d(close_series: pd.Series) -> float: """20日动量:今日收盘 / 20日前收盘 - 1""" if len(close_series) < 21: return None return close_series.iloc[-1] / close_series.iloc[-21] - 1 def momentum_60d(close_series: pd.Series) -> float: """60日动量""" if len(close_series) < 61: return None return close_series.iloc[-1] / close_series.iloc[-61] - 1

波动率因子

import numpy as np def volatility_20d(close_series: pd.Series) -> float: """20日年化波动率(基于日收益率标准差)""" returns = close_series.pct_change().dropna().tail(20) if len(returns) < 5: return None daily_vol = returns.std() annual_vol = daily_vol * np.sqrt(252) # 年化 return annual_vol

Beta 因子

def calc_beta(stock_returns: pd.Series, benchmark_returns: pd.Series, window=60) -> float: """计算个股相对基准(沪深300)的 Beta""" aligned = pd.concat([stock_returns, benchmark_returns], axis=1).dropna().tail(window) if len(aligned) < 30: return None covariance = aligned.cov().iloc[0, 1] variance = benchmark_returns.var() if variance == 0: return None return covariance / variance

换手率异常因子

def turnover_anomaly(turnover_series: pd.Series) -> float: """当前换手率相对 20 日均值的偏离度""" if len(turnover_series) < 21: return None current = turnover_series.iloc[-1] avg_20 = turnover_series.iloc[-21:-1].mean() if avg_20 == 0: return None return current / avg_20 - 1 # >0 表示异常活跃,<0 表示异常冷淡

3.3 因子标准化与综合评分

各因子数值量纲不同(PE 是倍数、ROE 是百分比、动量是小数),需要先标准化再合成综合评分。

from scipy import stats def normalize_factors(factor_df: pd.DataFrame) -> pd.DataFrame: """ 对全市场同一交易日的因子值做横截面标准化(Z-score)。 缺失值跳过,不填充。 """ result = factor_df.copy() factor_cols = [ 'pe', 'pb', 'momentum_20d', 'momentum_60d', 'roe', 'gross_margin', 'volatility_20d', 'beta', 'turnover_anomaly', 'netprofit_yoy' ] for col in factor_cols: if col in result.columns: result[f'{col}_zscore'] = stats.zscore(result[col], nan_policy='omit') return result def compute_composite_score(row, weights: dict) -> float | None: """ 根据标准化因子值和权重计算综合评分。 缺失因子跳过,权重归一化到剩余因子。 注意:部分因子方向需要反转: - PE/PB 越低越好 → zscore 取反 - 波动率越低越好 → zscore 取反 """ direction = { 'pe_zscore': -1, # PE 低 = 好 'pb_zscore': -1, # PB 低 = 好 'volatility_20d_zscore': -1, # 波动低 = 好 'beta_zscore': -1, # Beta 低 = 防御性强 # 其他因子方向为正(越高越好) } available = {} available_weights = 0 for factor, weight in weights.items(): z_col = f'{factor}_zscore' if z_col in row.index and not pd.isna(row[z_col]): dir_mult = direction.get(z_col, 1) available[factor] = row[z_col] * dir_mult * weight available_weights += weight if available_weights == 0: return None return sum(available.values()) / available_weights

3.4 因子权重配置

# config/factors.yaml factors: weights: pe: 0.15 pb: 0.10 momentum_20d: 0.15 momentum_60d: 0.10 roe: 0.10 gross_margin: 0.05 volatility_20d: 0.10 beta: 0.10 turnover_anomaly: 0.05 netprofit_yoy: 0.10 # 因子缺失容忍度:至少需要多少权重覆盖才生成评分 min_weight_coverage: 0.5 # 50% # 方向定义:哪些因子值越低越好 inverse_direction: - pe - pb - volatility_20d - beta

4. 交易信号生成

4.1 信号类型

信号条件置信度
STRONG_BUY综合评分 > 1.5σ + MACD 金叉0.80-0.95
BUY综合评分 > 0.5σ + 多头排列0.60-0.80
HOLD综合评分在 -0.5σ ~ 0.5σ0.40-0.60
SELL综合评分 < -0.5σ + MACD 死叉0.60-0.80
STRONG_SELL综合评分 < -1.5σ + RSI > 70 超买0.80-0.95

4.2 信号生成逻辑

def generate_signal(row) -> dict: """ 基于单日的所有指标和因子值,生成交易信号。 返回:{ signal_type, confidence, reasons[] } """ reasons = [] score = 0 # 1. 综合因子评分贡献 if row.get('composite_score') is not None: z = row['composite_score'] if z > 1.5: score += 3 reasons.append(f'多因子综合评分显著偏高 (z={z:.2f})') elif z > 0.5: score += 1 reasons.append(f'多因子综合评分略高 (z={z:.2f})') elif z < -1.5: score -= 3 reasons.append(f'多因子综合评分显著偏低 (z={z:.2f})') elif z < -0.5: score -= 1 reasons.append(f'多因子综合评分略低 (z={z:.2f})') # 2. MACD 信号 if row.get('macd_hist') is not None: if row['macd_hist'] > 0 and row.get('macd_hist_prev', 0) <= 0: score += 2 reasons.append('MACD 金叉(柱转正)') elif row['macd_hist'] < 0 and row.get('macd_hist_prev', 0) >= 0: score -= 2 reasons.append('MACD 死叉(柱转负)') # 3. 均线排列 if row.get('ma_bullish'): score += 1 reasons.append('均线多头排列') # 4. RSI 极端值 if row.get('rsi14') is not None: if row['rsi14'] > 80: score -= 1 reasons.append(f'RSI 严重超买 ({row["rsi14"]:.0f})') elif row['rsi14'] > 70: score -= 0.5 elif row['rsi14'] < 20: score += 1 reasons.append(f'RSI 严重超卖 ({row["rsi14"]:.0f})') # 5. 布林带位置 if row.get('close') and row.get('boll_lower'): if row['close'] <= row['boll_lower']: score += 0.5 reasons.append('价格触及布林下轨') elif row['close'] >= row['boll_upper']: score -= 0.5 # 6. 综合判定 if score >= 4: signal = 'STRONG_BUY' confidence = min(0.95, 0.60 + score * 0.05) elif score >= 1: signal = 'BUY' confidence = min(0.80, 0.50 + score * 0.05) elif score >= -1: signal = 'HOLD' confidence = 0.50 elif score >= -3: signal = 'SELL' confidence = min(0.80, 0.50 + abs(score) * 0.05) else: signal = 'STRONG_SELL' confidence = min(0.95, 0.60 + abs(score) * 0.05) return { 'signal_type': signal, 'confidence': round(confidence, 2), 'score': score, 'reasons': reasons, }

4.3 信号表设计

CREATE TABLE trade_signal_daily ( id BIGINT IDENTITY(1,1) PRIMARY KEY, stock_code VARCHAR(20) NOT NULL, trade_date DATE NOT NULL, signal_type VARCHAR(20) NOT NULL, -- STRONG_BUY/BUY/HOLD/SELL/STRONG_SELL confidence DECIMAL(4,3), composite_score DECIMAL(8,4), -- 综合因子评分(Z-score) signal_score INT, -- 算分结果 reasons NVARCHAR(MAX), -- JSON: [{"code":"macd_golden","text":"..."}] raw_indicators NVARCHAR(MAX), -- JSON: 触发信号的原始指标值 created_at DATETIME2 DEFAULT GETDATE(), CONSTRAINT uq_signal_date UNIQUE (stock_code, trade_date) ); CREATE INDEX idx_signal_date ON trade_signal_daily(trade_date); CREATE INDEX idx_signal_type ON trade_signal_daily(trade_date, signal_type);

5. 数据流与入库

5.1 每日处理流程

5.2 并行计算架构

from concurrent.futures import ThreadPoolExecutor, as_completed def compute_daily_indicators_and_signals(): """每日收盘后运行的量化分析任务""" # 1. 获取全市场股票列表 stocks = get_all_a_stocks() # 2. 批量加载行情数据(一次 SQL 查所有股票最近 120 日) df_all = fetch_recent_kline_all(back_days=120) # 3. 按股票分组并行计算 results = [] with ThreadPoolExecutor(max_workers=8) as executor: futures = { executor.submit(compute_one_stock, code, group): code for code, group in df_all.groupby('stock_code') } for future in as_completed(futures): try: result = future.result() results.append(result) except Exception as e: stock = futures[future] logger.error("indicator_compute_failed", stock_code=stock, error=str(e)) # 4. 批量写入 indicators_df = pd.concat([r['indicators'] for r in results if r]) signals_df = pd.concat([r['signals'] for r in results if r]) batch_upsert_indicators(indicators_df) batch_upsert_signals(signals_df) # 5. 输出 TOP 信号 top_buy = signals_df.nlargest(20, 'composite_score') top_sell = signals_df.nsmallest(20, 'composite_score') logger.info("daily_indicators_complete", total_stocks=len(stocks), indicators_written=len(indicators_df), signals_generated=len(signals_df), top_buy_signals=len(top_buy), top_sell_signals=len(top_sell) ) def compute_one_stock(code: str, df: pd.DataFrame) -> dict: """单只股票:指标 + 因子 + 信号""" df = df.sort_values('trade_date') # 计算技术指标 df = compute_all_indicators(df) # 只取今日的数据 today = df.iloc[-1:] if today.empty: return None # 计算今日因子值 + 信号 signal = generate_signal(today.iloc[0]) return { 'indicators': today[['stock_code', 'trade_date', 'ma5', 'ma10', 'ma20', 'ma60', 'ma120', 'macd_dif', 'macd_dea', 'macd_hist', 'rsi6', 'rsi14', 'rsi24', 'kdj_k', 'kdj_d', 'kdj_j', 'boll_upper', 'boll_middle', 'boll_lower', 'atr14', 'obv', 'ma_bullish']], 'signals': pd.DataFrame([{ 'stock_code': code, 'trade_date': today.iloc[0]['trade_date'], **signal, }]), }

6. API 设计

6.1 技术指标 API

GET /api/v1/indicators/{stock_code}
参数类型说明
stock_codepath600519.SH
fromquery起始日期 YYYY-MM-DD
toquery结束日期
fieldsquery逗号分隔的字段名,默认全部

响应

{ "stock_code": "600519.SH", "data": [ { "trade_date": "2026-07-18", "ma5": 1680.5, "ma10": 1672.3, "rsi14": 62.5, "macd_hist": 3.21, "ma_bullish": 1 } ] }

6.2 信号查询 API

GET /api/v1/signals/daily
参数类型默认值说明
trade_datequery今日查询日期
signal_typequery全部筛选信号类型
min_confidencequery0.6最低置信度
limitquery50返回条数

响应

{ "trade_date": "2026-07-18", "total": 1523, "signals": [ { "stock_code": "600519.SH", "stock_name": "贵州茅台", "signal_type": "BUY", "confidence": 0.72, "composite_score": 0.85, "reasons": ["MACD 金叉", "多因子综合评分略高"] } ] }

6.3 因子排名 API

GET /api/v1/factors/ranking
参数类型默认值说明
trade_datequery今日排名日期
factorquerycomposite_score排名因子
top_nquery30返回前 N 名
bottom_nquery10返回后 N 名

7. 性能优化

7.1 pandas-ta 加速

# 使用 pandas-ta 的 strategy 模式批量计算 df.ta.strategy( ta.CommonStrategy, # 内置策略:SMA + EMA + MACD + RSI + BBANDS + ATR + OBV length=[5, 10, 20, 60, 120] )

7.2 查询优化

  • 因子标准化使用 pandas.DataFrame.rank(pct=True) 替代 scipy.stats.zscore(大数据集下 rank 快 3-5 倍)
  • 信号写入使用 executemany 批量 INSERT
  • 热门股票(沪深300成分股)的指标缓存 24 小时到 Redis

7.3 性能基准

操作数据量目标耗时
全市场技术指标计算~5000只 × 120日< 5 分钟
全市场因子标准化~5000只 × 1日< 30 秒
信号生成~5000只< 10 秒
批量写入~5000行指标 + ~5000行信号< 20 秒
全流程总计< 8 分钟

8. 前端可视化

8.1 页面布局

┌─────────────────────────────────────────────────────┐ │ 📊 量化分析 [2026-07-18] [沪深300 ▼] │ ├─────────────────────────────────────────────────────┤ │ │ │ ┌─ TOP 买入信号 ──────────────────────────────┐ │ │ │ #1 贵州茅台 BUY 置信度 0.72 Z-score 0.85 │ │ │ │ #2 宁德时代 STRONG_BUY 0.88 1.62 │ │ │ │ #3 ... │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ ┌─ K线图 + 技术指标叠加 ───────────────────────┐ │ │ │ 📈 [K线] [成交量] │ │ │ │ 叠加: MA5/MA10/MA20 │ │ │ │ 副图: MACD / RSI / KDJ (可切换) │ │ │ │ 标注: 信号点 (买卖标记) │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ ┌─ 因子雷达图 ──┐ ┌─ 信号分布 ────────────────┐ │ │ │ (六维雷达) │ │ 📊 柱状图 │ │ │ │ │ │ BUY: 320 SELL: 180 │ │ │ └───────────────┘ │ HOLD: 4230 │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────┘

8.2 K线图交互

  • 时间范围:7天 / 30天 / 90天 / 自定义
  • 指标叠加:勾选即可在 K 线上叠加 MA/BOLL,副图切换 MACD/RSI/KDJ/VOL
  • 信号标记:K 线上以 ▲(买)▼(卖)标注信号发生点
  • 悬停提示:鼠标悬停显示当日全部指标数值

8.3 技术选型

组件推荐说明
K线图lightweight-charts (TradingView)专业金融图表,性能好,免费
雷达图Recharts (RadarChart)与现有技术栈一致
柱状图Recharts (BarChart)与现有技术栈一致
表格TanStack Table虚拟滚动支持大数据量
反馈与评论

来源路径:docs:/docs/backend/quantitative-analysis-design

请勿提交账户、密钥、未脱敏交易数据、真实持仓明细或其他敏感信息。

打开反馈服务
Last updated on