
Financial Data Analysis
- 267 installs
- 61 repo stars
- Updated March 16, 2026
- kirkluokun/awesome-a-stock-openclawskills
financial-data-analysis is a Claude Code skill that analyzes market and company financial datasets for developers who need to stress-test investment theses and scope dashboards or trading assistants before building.
About
financial-data-analysis is a finance skill from kirkluokun/awesome-a-stock-openclawskills that guides developers analyzing market and company financial datasets. It supports stress-testing investment theses, comparing fundamental metrics across companies, and scoping dashboards or trading assistant features before engineering commitment. Developers reach for financial-data-analysis during product discovery for fintech tools, stock research apps, or portfolio analytics when raw financial data must be interpreted into actionable comparisons and feature requirements. The skill bridges quantitative analysis and software scoping so teams validate data availability, key metrics, and analytical workflows early rather than building dashboards on untested assumptions.
- Fundamental and market data review
- Thesis validation before build
- Comparable company benchmarking
- Risk and signal framing
- Feeds finance product scoping
Financial Data Analysis by the numbers
- 267 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #349 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/kirkluokun/awesome-a-stock-openclawskills --skill financial-data-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 267 |
|---|---|
| repo stars | ★ 61 |
| Last updated | March 16, 2026 |
| Repository | kirkluokun/awesome-a-stock-openclawskills ↗ |
How do you analyze financial data for apps?
Analyze market and company financial datasets to stress-test investment theses, compare fundamentals, and scope dashboards or trading assistants before committing engineering effort.
Who is it for?
Developers scoping fintech dashboards, stock research tools, or trading assistants who need fundamental analysis before implementation.
Skip if: Developers seeking live trade execution, brokerage API integration, or regulated investment advice workflows.
When should I use this skill?
A developer asks to analyze market or company financial data to compare fundamentals or scope a trading or analytics product.
What you get
Fundamental comparisons, stress-tested investment theses, and scoped dashboard or trading assistant feature requirements.
- Fundamental comparison analysis
- Scoped fintech feature requirements
Files
📊 金融时间序列分析工具箱
覆盖股票 / 商品期货 / 加密货币 / ETF / 外汇 / 指数的综合数据分析方法工具箱。
数据获取策略
| 资产类型 | 数据源 | 方式 |
|---|---|---|
| A股行情/财务/指数 | Tushare MCP | tushare_daily, tushare_income 等 MCP tool |
| A股期货 | Tushare MCP | tushare_fut_daily, tushare_fut_holding 等 |
| 港股/美股 | Tushare MCP | tushare_hk_daily, tushare_us_daily |
| 宏观经济 | Tushare MCP | tushare_cn_gdp, tushare_shibor 等 |
| 国际商品期货(WTI/黄金等) | yfinance | scripts/data_fetcher.py |
| 加密货币 | yfinance | scripts/data_fetcher.py |
| 外汇 | Tushare MCP 或 yfinance | 视具体币种 |
| 全球指数 | Tushare MCP 或 yfinance | tushare_index_global / yfinance |
规则:调用 tushare MCP tool 前必须先 ToolSearch("+tushare <关键词>") 加载。tushare tool 完整索引见stock-tushare-pro-mcpskill 的reference/tool-index.md。
分析方法路由
根据用户意图,阅读对应 references/methods/ 文档后执行分析:
| 用户意图关键词 | 参考文档 | 可用脚本 |
|---|---|---|
| 平稳性、趋势检验、序列分解、结构断裂、Hurst | references/methods/01_time_series_fundamentals.md | scripts/analysis_toolkit.py |
| 价格预测、ARIMA、Prophet、VAR | references/methods/02_forecasting.md | scripts/analysis_toolkit.py |
| 相关性、协整、因果关系、领先滞后 | references/methods/03_cross_asset_relationships.md | scripts/analysis_toolkit.py |
| 波动率、GARCH、VaR、尾部风险 | references/methods/04_volatility_and_risk.md | scripts/analysis_toolkit.py |
| 组合优化、因子分析、风险平价、有效前沿 | references/methods/05_portfolio_and_factor.md | scripts/analysis_toolkit.py |
| 市场状态、regime、周期、小波 | references/methods/06_regime_and_structure.md | scripts/analysis_toolkit.py |
| 商品季节性、价差、期限结构、contango | references/methods/07_commodity_specific.md | scripts/analysis_toolkit.py |
| 网络分析、信息流、聚类、MST | references/methods/08_network_and_information.md | scripts/analysis_toolkit.py |
| 技术指标(MA/RSI/MACD/KDJ/布林带) | references/methods/01_time_series_fundamentals.md | scripts/indicators.py |
| 图表绘制、可视化 | references/visualization_cookbook.md | — |
| 报告格式 | references/output_templates.md | — |
执行流程
1. 识别用户意图 → 查上方路由表
2. 读取对应 references/methods/ 文档 → 选择合适方法
3. 获取数据:tushare MCP tool(优先)或 scripts/data_fetcher.py
4. 执行分析:scripts/analysis_toolkit.py 或 scripts/indicators.py
5. 生成图表:参照 references/visualization_cookbook.md
6. 输出报告:按 references/output_templates.md 格式约束
MUST
- 标注数据获取时间和来源(tushare / yfinance)
- 每份报告附免责声明
- 分析前检查序列平稳性(适用时)
- 异常值标注和处理
MUST NOT
- ❌ 给出确定性收益承诺
- ❌ 伪造或编造数据
- ❌ 忽略风险提示
- ❌ 数据缺失时猜测关键指标
输出存储规范
输出目录
默认根目录为 {output_dir}(由 input-variables 配置,默认 {workspace}/data/analysis/)。
{output_dir}/
├── reports/ # 分析报告 (.md)
├── charts/ # 图表文件 (.png)
├── datasets/ # 中间数据集 (.csv)
└── temp/ # 临时数据(可清理)文件命名
{类型}_{标的}_{日期}.{格式}示例:
report_CU_20260306.mdchart_AAPL_seasonal_20260306.pngdataset_corr_matrix_20260306.csv
输出规则
| 数据量 | 处理方式 |
|---|---|
| < 20 行 | 直接在对话中展示,不存文件 |
| >= 20 行 | 存入 datasets/,返回文件路径 + 摘要 |
| 图表 | 存入 charts/,在对话中内嵌展示 |
| 分析报告 | 存入 reports/,返回完整报告 |
| 临时/中间数据 | 存入 temp/,提醒用户可清理 |
与 tushare skill 协作:原始行情数据存储遵循 tushare skill 的output-storage.md规范({workspace}/data/tushare/),
本 skill 的 {output_dir} 只存分析结果,不存原始数据,避免重复。参数使用
所有可配置参数通过 input-variables 声明,AI 在执行时按如下优先级获取值:
1. 用户在对话中明确指定 -> 最高优先 2. input-variables 中的 default 值 -> 兜底
# 用户说 "把分析结果存到 ~/Desktop/analysis"
-> output_dir = ~/Desktop/analysis
# 用户说 "分析铜价"
-> output_dir = {workspace}/data/analysis (使用默认值)
-> default_period = 1y (使用默认值)01 — 时间序列基础 (Time Series Fundamentals)
在对任何金融序列进行分析之前,必须先理解其统计特性。本文档涵盖平稳性检验、序列分解、自相关分析、结构断裂检测和长记忆性判断。
---
方法一览
| 方法 | 函数 | 用途 |
|---|---|---|
| ADF 检验 | test_stationarity(s, methods=['adf']) | 单位根检验,判断序列是否平稳 |
| KPSS 检验 | test_stationarity(s, methods=['kpss']) | ADF 的互补检验(原假设相反) |
| Phillips-Perron | test_stationarity(s, methods=['pp']) | 对异方差更稳健的单位根检验 |
| STL 分解 | decompose_series(s, model='stl') | 趋势 + 季节性 + 残差 |
| 经典分解 | decompose_series(s, model='additive') | 加法/乘法分解 |
| ACF / PACF | acf_pacf(s, nlags=40) | 自相关分析,确定模型阶数 |
| 变点检测 | detect_changepoints(s, method='pelt') | 检测趋势突变时间点 |
| Hurst 指数 | hurst_exponent(s) | 趋势性(>0.5)/随机(=0.5)/均值回复(<0.5) |
所有函数均在 scripts/analysis_toolkit.py 中。
---
平稳性检验
为什么重要?
大多数统计分析方法(ARIMA、VAR、协整等)要求序列平稳或差分后平稳。对非平稳序列直接建模会产生伪回归。
推荐流程
from analysis_toolkit import test_stationarity
# 1. 检验原始价格序列
results = test_stationarity(price_series, methods=['adf', 'kpss'])
print(results['adf']['interpretation'])
print(results['kpss']['interpretation'])
# 2. 如果不平稳,检验一阶差分(日收益率)
returns = price_series.pct_change().dropna()
results_diff = test_stationarity(returns, methods=['adf', 'kpss'])
# 3. ADF 和 KPSS 结论比较
# ADF说平稳 + KPSS说平稳 → 确认平稳
# ADF说不平稳 + KPSS说不平稳 → 确认不平稳
# 冲突 → 需进一步检验(差分阶数可能不够或存在结构断裂)解读经验
- 金融价格序列几乎总是非平稳的(ADF p > 0.05)
- 收益率序列通常平稳(ADF p < 0.05)
- 商品价格可能存在单位根,也可能是带趋势的平稳过程
---
序列分解
将价格/成交量分解为三个成分:
- 趋势 (Trend):长期方向
- 季节性 (Seasonal):固定周期的重复模式
- 残差 (Residual):去除趋势和季节性后的波动
from analysis_toolkit import decompose_series
# STL 分解(推荐,更灵活)
result = decompose_series(price_series, period=252, model='stl')
# 获取各成分
trend = result.trend
seasonal = result.seasonal
residual = result.resid
# 可视化
import matplotlib.pyplot as plt
fig, axes = plt.subplots(4, 1, figsize=(14, 10), sharex=True)
price_series.plot(ax=axes[0], title='原始序列')
trend.plot(ax=axes[1], title='趋势')
seasonal.plot(ax=axes[2], title='季节性')
residual.plot(ax=axes[3], title='残差')
plt.tight_layout()period 选择指南
| 数据频率 | 推荐 period | 场景 |
|---|---|---|
| 日线 | 252 | 年度季节性(交易日) |
| 日线 | 21 | 月度周期 |
| 周线 | 52 | 年度季节性 |
| 月线 | 12 | 年度季节性 |
---
Hurst 指数
判断序列的长记忆特性,对策略选择有直接指导意义:
from analysis_toolkit import hurst_exponent
H = hurst_exponent(price_series)
if H > 0.6:
print(f"H = {H:.3f} → 趋势性强,适合趋势跟踪策略")
elif H < 0.4:
print(f"H = {H:.3f} → 均值回复,适合均值回归策略")
else:
print(f"H = {H:.3f} → 接近随机游走")---
变点检测
找到价格趋势发生质变的时间点:
from analysis_toolkit import detect_changepoints
breakpoints = detect_changepoints(price_series, method='pelt')
# 将索引位置映射到日期
dates = price_series.dropna().index
bp_dates = [dates[bp] for bp in breakpoints if bp < len(dates)]
print("结构断裂时间点:", bp_dates)三种方法对比
| 方法 | 特点 | 适用场景 |
|---|---|---|
| PELT | 自动确定断点数,速度快 | 首选,大多数场景 |
| Binseg | 需指定断点数,二分搜索 | 已知大约有几个转折点 |
| BottomUp | 自底向上合并,保守 | 需要更稳定的结果 |
---
典型分析组合
"铜价有没有趋势?"
1. ADF 检验 → 是否单位根
2. Hurst 指数 → 是否趋势性/均值回复
3. STL 分解 → 提取趋势成分看方向"COVID 后市场结构是否变了?"
1. 变点检测 → 找到 2020年初的结构断裂
2. 分段比较均值/方差
3. 分段 Hurst 指数对比"确定 ARIMA 模型的阶数"
1. ADF 确认差分阶数 d
2. ACF/PACF 确认 p 和 q
3. 拟合后检查残差白噪声02 — 预测方法 (Forecasting)
本文档覆盖主流时间序列预测方法。注意:金融市场预测固有不确定性,预测结果仅供参考。
---
方法一览
| 方法 | 函数/工具 | 适用场景 | 数据要求 |
|---|---|---|---|
| ARIMA/SARIMA | fit_arima() | 单变量短期预测 | ≥100个观测 |
| VAR/VECM | fit_var() | 多变量互相预测 | ≥100个观测×N |
| Prophet | prophet 库直接调用 | 快速基线、假日效应 | ≥2年日线 |
| Holt-Winters | statsmodels.ExponentialSmoothing | 有季节性的序列 | ≥2个完整周期 |
---
ARIMA
from analysis_toolkit import fit_arima
# 自动选参 + 预测10天
result = fit_arima(price_series, forecast_steps=10)
print(f"最优阶数: {result['order']}, AIC: {result['aic']:.1f}")
print(result['forecast'])
# 手动指定阶数
result = fit_arima(price_series, order=(1,1,1), forecast_steps=5)
# SARIMA(含季节项)
result = fit_arima(price_series, order=(1,1,1),
seasonal_order=(1,1,1,252), forecast_steps=10)ARIMA 阶数选择指南
1. d (差分阶数):ADF 检验确定。价格序列通常 d=1 2. p (AR 阶数):看 PACF 在第几阶截断 3. q (MA 阶数):看 ACF 在第几阶截断 4. 或者用自动搜索(函数内置 AIC 网格搜索)
---
VAR (向量自回归)
多个序列互相预测:
from analysis_toolkit import fit_var
import pandas as pd
# 准备多资产收益率
returns = pd.DataFrame({
'原油': oil_returns,
'航空': airline_returns,
'美元': usd_returns,
})
result = fit_var(returns, max_lag=10, forecast_steps=5)
print(f"最优滞后阶数: {result['optimal_lag']}")
print("预测:\n", result['forecast'])VAR 适用场景
- "油价如何影响航空股?" → 油价和航空收益率的 VAR
- "美元和黄金的联动预测" → 美元指数和黄金收益率
- "铜铝联动分析" → 两个金属的收益率
---
Prophet
from prophet import Prophet
# 准备数据格式
df_prophet = price_series.reset_index()
df_prophet.columns = ['ds', 'y']
model = Prophet(
changepoint_prior_scale=0.05, # 趋势灵活度
seasonality_prior_scale=10,
yearly_seasonality=True,
weekly_seasonality=True,
)
model.fit(df_prophet)
# 预测
future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)
# 可视化
fig = model.plot(forecast)
fig2 = model.plot_components(forecast)Prophet 适用场景
- 快速基线预测
- 有明显季节性的商品(天然气、农产品)
- 需要考虑假日效应的市场
---
预测验证
任何预测方法都应进行验证:
from sklearn.metrics import mean_absolute_error, mean_squared_error
# 滚动窗口回测
train_size = int(len(series) * 0.8)
train, test = series[:train_size], series[train_size:]
result = fit_arima(train, forecast_steps=len(test))
forecast = result['forecast']
mae = mean_absolute_error(test, forecast[:len(test)])
rmse = mean_squared_error(test, forecast[:len(test)], squared=False)
print(f"MAE: {mae:.4f}, RMSE: {rmse:.4f}")---
⚠️ 预测方法的局限性
1. 金融市场不是纯时间序列问题 — 受政策、情绪、突发事件影响 2. 过拟合风险 — 参数过多或自动选参可能捕捉噪声 3. 结构变化 — 模型假设数据生成过程不变,但市场 regime 会切换 4. 预测≠交易信号 — 预测方向正确不代表能盈利(还有波动率、交易成本)
建议:将时间序列预测作为分析的一个参考维度,而非唯一决策依据。
03 — 跨资产关系分析 (Cross-Asset Relationships)
分析不同资产之间的统计关系。从简单的相关性到复杂的非线性因果。
---
方法一览
| 方法 | 函数 | 输出 | 复杂度 |
|---|---|---|---|
| 相关矩阵 | correlation_matrix(df) | N×N 矩阵 | ⭐ |
| 滚动相关 | rolling_correlation(a, b, window) | 时间序列 | ⭐ |
| 协整检验 | test_cointegration(a, b) | 是否协整 + 价差 | ⭐⭐ |
| Granger 因果 | granger_causality(a, b) | 方向性预测因果 | ⭐⭐ |
| 交叉相关 | cross_correlation(a, b) | 最优 lag | ⭐⭐ |
| 互信息 | mutual_information(a, b) | 非线性相关度量 | ⭐⭐⭐ |
---
相关性分析
from analysis_toolkit import correlation_matrix, rolling_correlation
# 静态相关矩阵
corr = correlation_matrix(returns_df, method='spearman')
# 可视化热力图
import seaborn as sns
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(corr, annot=True, cmap='RdBu_r', center=0, fmt='.2f', ax=ax)
ax.set_title('资产相关性矩阵')
# 滚动相关(动态追踪)
rolling_corr = rolling_correlation(gold_returns, usd_returns, window=60)
rolling_corr.plot(title='黄金 vs 美元 60日滚动相关')
plt.axhline(y=0, color='gray', linestyle='--')Pearson vs Spearman vs Kendall
| 方法 | 测量 | 适用 |
|---|---|---|
| Pearson | 线性关系 | 正态分布数据 |
| Spearman | 秩相关(单调关系) | 金融数据首选,对异常值稳健 |
| Kendall | 排序一致性 | 小样本、离散数据 |
---
协整分析
两个资产虽然分别非平稳,但它们的线性组合是平稳的 → 长期均衡关系。
from analysis_toolkit import test_cointegration
result = test_cointegration(gold_series, silver_series, method='engle-granger')
print(result['interpretation'])
print(f"对冲比率: {result['hedge_ratio']:.4f}")
# 价差序列
spread = result['spread']
z_score = (spread - spread.mean()) / spread.std()
# 配对交易信号
fig, axes = plt.subplots(2, 1, figsize=(14, 8))
spread.plot(ax=axes[0], title='价差序列')
z_score.plot(ax=axes[1], title='Z-Score')
axes[1].axhline(y=2, color='r', linestyle='--', label='卖出阈值')
axes[1].axhline(y=-2, color='g', linestyle='--', label='买入阈值')
axes[1].axhline(y=0, color='gray', linestyle='--')
axes[1].legend()配对交易逻辑
1. 找到协整的资产对 2. 计算价差序列和 Z-Score 3. Z > +2 → 做空价差(空A多B) 4. Z < -2 → 做多价差(多A空B) 5. Z 回归 0 → 平仓
---
Granger 因果
检验 A 的历史信息是否有助于预测 B(不是真正的因果,是"预测性因果")。
from analysis_toolkit import granger_causality
# 铜价是否"Granger 因果"于有色板块?
result = granger_causality(copper_returns, nonferrous_returns, max_lag=10)
for lag, info in result.items():
if info['is_causal']:
print(f"Lag {lag}: F={info['f_stat']:.2f}, p={info['p_value']:.4f} ✅ 显著")注意
- Granger 因果是线性的,非线性关系用 Transfer Entropy
- 需要平稳序列(先差分)
- 双向检验:A→B 和 B→A 都要做
---
交叉相关(领先-滞后)
找两个序列之间的最优时间差:
from analysis_toolkit import cross_correlation
result = cross_correlation(copper_returns, stock_returns, max_lag=20)
print(f"最优滞后: {result['optimal_lag']}天, 相关系数: {result['max_corr']:.3f}")
# 正 lag = A 领先 B,负 lag = B 领先 A---
互信息
捕捉非线性相关性(相关系数只能捕捉线性关系):
from analysis_toolkit import mutual_information
mi = mutual_information(series_a, series_b, n_bins=20)
print(f"互信息: {mi:.4f}")
# MI = 0 → 独立
# MI 越大 → 依赖性越强(包括非线性)---
典型分析组合
"黄金和美元的关系"
1. Spearman 相关矩阵 → 看整体方向(通常负相关)
2. 滚动相关(60天) → 负相关是否一直稳定
3. 协整检验 → 是否存在长期均衡
4. Granger 因果 → 谁领先谁"该不该做铜铝配对交易?"
1. 协整检验 → 是否长期均衡
2. 价差 Z-Score → 当前偏离程度
3. 滚动相关 → 相关性是否稳定
4. Hurst 指数(价差) → 价差是否均值回复"BTC 和纳指的联动"
1. 滚动相关(30天/90天) → 联动是否加强
2. Granger 因果 → 谁影响谁
3. 交叉相关 → 谁领先几天
4. 互信息 → 是否有非线性联动04 — 波动率与风险 (Volatility & Risk)
波动率是金融分析的核心维度。本文档覆盖波动率建模、风险度量和极端风险分析。
---
方法一览
| 方法 | 函数 | 用途 |
|---|---|---|
| GARCH(1,1) | fit_garch(r, model_type='garch') | 标准波动率建模 |
| EGARCH | fit_garch(r, model_type='egarch') | 捕捉杠杆效应 |
| GJR-GARCH | fit_garch(r, model_type='gjr-garch') | 不对称波动率 |
| 波动率锥 | volatility_cone(prices) | 当前波动率在历史中的位置 |
| VaR | calculate_var(r, method='historical') | 风险价值 |
| CVaR | calculate_var(r) 返回的 cvar 字段 | 尾部风险 |
| 最大回撤 | max_drawdown(cum_series) | 峰谷最大亏损 |
---
GARCH 波动率建模
from analysis_toolkit import fit_garch
# 注意:输入必须是收益率,不是价格!
returns = price_series.pct_change().dropna()
# 标准 GARCH
result = fit_garch(returns, model_type='garch', p=1, q=1)
print(f"AIC: {result['aic']:.1f}")
# EGARCH(捕捉下跌波动>上涨波动的不对称性)
result_e = fit_garch(returns, model_type='egarch')
# 条件波动率序列
cond_vol = result['conditional_volatility']
cond_vol.plot(title='GARCH 条件波动率')
# 未来5天波动率预测
print("未来5天预测方差:", result['forecast_variance'])模型选择
| 模型 | 特点 | 适用 |
|---|---|---|
| GARCH(1,1) | 对称,最经典 | 默认首选 |
| EGARCH | 允许波动率对下跌更敏感 | 股票市场(杠杆效应) |
| GJR-GARCH | 另一种不对称方式 | 和 EGARCH 对比选优 |
分布选择
dist='normal'— 默认dist='t'— 厚尾分布,更适合金融数据dist='skewt'— 偏态 + 厚尾
---
波动率锥
判断当前波动率处于历史什么水平:
from analysis_toolkit import volatility_cone
cone = volatility_cone(price_series)
print(cone)
# 5D 10D 21D 63D 126D 252D
# min 0.05 0.08 0.10 0.12 0.14 0.15
# 25% 0.12 0.14 0.15 0.16 0.17 0.18
# 50% 0.18 0.19 0.20 0.21 0.22 0.22
# 75% 0.28 0.27 0.26 0.25 0.25 0.24
# max 0.55 0.48 0.42 0.38 0.35 0.30
# current 0.15 0.16 0.18 0.20 0.21 0.22
# 可视化
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
cone.loc[['min', '25%', '50%', '75%', 'max']].T.plot(ax=ax, style='--', alpha=0.5)
cone.loc['current'].plot(ax=ax, style='ro-', linewidth=2, label='当前')
ax.set_title('波动率锥')
ax.set_ylabel('年化波动率')
ax.legend()---
VaR 与 CVaR
from analysis_toolkit import calculate_var
returns = price_series.pct_change().dropna()
# 历史模拟法
var_hist = calculate_var(returns, confidence=0.95, method='historical')
print(var_hist['interpretation'])
# 参数法(假设正态分布)
var_param = calculate_var(returns, confidence=0.99, method='parametric')
# Monte Carlo
var_mc = calculate_var(returns, confidence=0.95, method='montecarlo')
# 多日 VaR(10天持有期)
var_10d = calculate_var(returns, confidence=0.95, method='historical', n_days=10)三种方法对比
| 方法 | 优点 | 缺点 |
|---|---|---|
| 历史模拟 | 不需分布假设 | 依赖历史样本 |
| 参数法 | 计算快,公式明确 | 假设正态分布(低估尾部) |
| Monte Carlo | 灵活,可自定义分布 | 计算量大 |
---
最大回撤
from analysis_toolkit import max_drawdown
prices = (1 + returns).cumprod()
dd = max_drawdown(prices)
print(f"最大回撤: {dd['max_dd']:.2%}")
print(f"顶点: {dd['peak_date']}")
print(f"谷底: {dd['trough_date']}")
print(f"恢复: {dd['recovery_date']}")
# 画回撤曲线
dd['dd_series'].plot(title='回撤曲线', figsize=(12, 4))
plt.fill_between(dd['dd_series'].index, dd['dd_series'], alpha=0.3, color='red')---
典型分析组合
"NVDA 波动率处于什么水平?"
1. 波动率锥 → 当前 vs 历史分位数
2. GARCH 模型 → 条件波动率趋势
3. GARCH 预测 → 未来5天波动率走向"原油的下行风险有多大?"
1. VaR(95%/99%) → 单日最大可能亏损
2. CVaR → 极端情况下的平均亏损
3. GARCH → 波动率是否在扩大
4. 最大历史回撤 → 最坏情景05 — 组合与因子分析 (Portfolio & Factor Analysis)
从单资产分析到多资产组合维度。涵盖组合优化、因子分解和绩效评估。
---
方法一览
| 方法 | 函数 | 输出 |
|---|---|---|
| 均值-方差(Markowitz) | optimize_portfolio(df, method='markowitz') | 有效前沿 + 权重 |
| 最大夏普 | optimize_portfolio(df, method='max_sharpe') | 最优风险收益比 |
| 最小方差 | optimize_portfolio(df, method='min_variance') | 最低风险组合 |
| 风险平价 | optimize_portfolio(df, method='risk_parity') | 等风险贡献权重 |
| PCA 因子 | pca_factors(df) | 主成分 + 载荷 |
| 绩效指标 | performance_metrics(returns) | Sharpe/Sortino/Calmar |
---
组合优化
from analysis_toolkit import optimize_portfolio
# 准备多资产日收益率
returns_df = pd.DataFrame({
'AAPL': aapl_returns,
'GLD': gold_returns,
'TLT': bond_returns,
'BTC': btc_returns,
})
# 最大夏普比率组合
result = optimize_portfolio(returns_df, method='max_sharpe', risk_free=0.04)
print(f"最优权重: {result['weights']}")
print(f"预期年化: {result['expected_return']:.2%}")
print(f"年化波动: {result['volatility']:.2%}")
print(f"夏普比率: {result['sharpe']:.2f}")
# 风险平价组合
rp = optimize_portfolio(returns_df, method='risk_parity')
print(f"风险平价权重: {rp['weights']}")有效前沿可视化
import matplotlib.pyplot as plt
result = optimize_portfolio(returns_df, method='markowitz', n_portfolios=10000)
plt.figure(figsize=(10, 6))
plt.scatter(result['frontier_volatilities'], result['frontier_returns'],
c=result['frontier_sharpes'], cmap='viridis', alpha=0.5, s=5)
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('年化波动率')
plt.ylabel('年化收益率')
plt.title('有效前沿')方法选择
| 方法 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| Max Sharpe | 最优风险收益比 | 对估计误差敏感 | 预期收益可靠时 |
| Min Variance | 不需要收益率估计 | 可能过度集中 | 不确定收益方向时 |
| Risk Parity | 分散化好,稳健 | 不考虑预期收益 | 默认推荐 |
---
PCA 因子分析
从多只股票中提取公共驱动因子:
from analysis_toolkit import pca_factors
# 用一组股票的收益率
result = pca_factors(returns_df, n_components=3)
# 解释方差比
print(f"前3个因子解释了 {result['cumulative_ratio'][-1]:.1%} 的方差")
print(f"PC1: {result['explained_ratio'][0]:.1%}")
print(f"PC2: {result['explained_ratio'][1]:.1%}")
print(f"PC3: {result['explained_ratio'][2]:.1%}")
# 因子载荷(哪些股票对哪个因子暴露大)
print("因子载荷:")
print(result['loadings'])解读
- PC1 通常代表市场因子(所有股票同涨同跌)
- PC2 可能代表板块因子(行业分化)
- PC3 可能代表风格因子(大小盘、价值成长)
---
绩效指标
from analysis_toolkit import performance_metrics
metrics = performance_metrics(strategy_returns, risk_free=0.04/252,
benchmark=index_returns)
print(f"年化收益: {metrics['annual_return']:.2%}")
print(f"年化波动: {metrics['annual_volatility']:.2%}")
print(f"Sharpe: {metrics['sharpe_ratio']:.2f}")
print(f"Sortino: {metrics['sortino_ratio']:.2f}")
print(f"Calmar: {metrics['calmar_ratio']:.2f}")
print(f"最大回撤: {metrics['max_drawdown']:.2%}")
print(f"胜率: {metrics['win_rate']:.1%}")
print(f"偏度: {metrics['skewness']:.2f}")
print(f"峰度: {metrics['kurtosis']:.2f}")
if 'information_ratio' in metrics:
print(f"信息比率: {metrics['information_ratio']:.2f}")
print(f"跟踪误差: {metrics['tracking_error']:.2%}")指标解读快查
| 指标 | 好的标准 | 含义 |
|---|---|---|
| Sharpe > 1 | 优秀 | 每单位风险的超额收益 |
| Sortino > 1.5 | 优秀 | 只考虑下行风险 |
| Calmar > 1 | 良好 | 收益 / 最大回撤 |
| MDD < 20% | 可接受 | 历史最大亏损 |
| 偏度 > 0 | 好 | 正向非对称 |
| 峰度 < 3 | 好 | 尾部风险小 |
---
典型分析组合
"优化我的组合"
1. 计算各资产绩效指标 → 了解各自风险收益
2. 相关矩阵 → 确认分散化效果
3. 组合优化 → 生成有效前沿
4. 比较 Max Sharpe vs Risk Parity → 推荐方案"我的持仓暴露了什么风险?"
1. PCA 因子分析 → 提取主驱动因子
2. 因子载荷矩阵 → 每只股票的因子暴露
3. 协方差分解 → 系统性 vs 特异性风险占比06 — 状态识别与结构分析 (Regime & Structure)
识别市场所处的状态、发现隐藏周期、多尺度分解价格结构。
---
方法一览
| 方法 | 函数 | 用途 |
|---|---|---|
| HMM 状态识别 | detect_regimes(r, n_states=3) | 自动划分牛/熊/震荡 |
| 变点检测 | detect_changepoints(s) | 趋势突变时间点 |
| 谱分析 (FFT) | spectral_analysis(s) | 发现隐藏周期 |
| 小波分解 | wavelet_decompose(s) | 多尺度时频分析 |
| Hurst 指数 | hurst_exponent(s) | 趋势/随机/均值回复 |
---
HMM 市场状态识别
from analysis_toolkit import detect_regimes
returns = price_series.pct_change().dropna()
# 三状态模型
result = detect_regimes(returns, n_states=3)
print(f"当前市场状态: {result['current_state']}")
print(f"各状态均值: {result['state_means']}")
# 可视化
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)
# 价格 + 状态着色
colors = {0: 'red', 1: 'yellow', 2: 'green'}
state_series = result['state_series']
for state, color in colors.items():
mask = state_series == state
axes[0].fill_between(mask.index, price_series.loc[mask.index].min(),
price_series.loc[mask.index].max(),
where=mask, alpha=0.3, color=color,
label=result['labels'].get(state))
price_series.plot(ax=axes[0], color='black', linewidth=0.8)
axes[0].legend()
axes[0].set_title('价格 + 市场状态')
# 状态序列
state_series.plot(ax=axes[1], title='状态序列')状态数选择
| n_states | 含义 | 适用 |
|---|---|---|
| 2 | 牛市 / 熊市 | 趋势策略的开关 |
| 3 | 牛 / 震荡 / 熊 | 默认推荐 |
| 4 | 加入"高波动"状态 | 需要区分平稳震荡和剧烈震荡 |
---
谱分析 (FFT)
发现价格中的隐藏周期成分:
from analysis_toolkit import spectral_analysis
result = spectral_analysis(price_series)
print(result['interpretation'])
# 功率谱可视化
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(1/result['frequencies'][1:], result['power'][1:])
ax.set_xlabel('周期 (天)')
ax.set_ylabel('功率')
ax.set_title('功率谱密度')
ax.set_xlim(2, 300)
# 标注主导周期
for p in result['dominant_periods'][:3]:
ax.axvline(x=p, color='r', linestyle='--', alpha=0.5)
ax.text(p, ax.get_ylim()[1]*0.9, f'{p:.0f}天', ha='center')---
小波分解
同时在时域和频域上分析信号——比 FFT 更适合分析非平稳的金融序列:
from analysis_toolkit import wavelet_decompose
result = wavelet_decompose(price_series, wavelet='db4', level=5)
# 每层对应不同的时间尺度
for info in result['levels_info']:
print(f"{info['level']}: 周期≈{info.get('period', 'N/A')}, 能量={info['energy']:.1f}")
# 可视化各层
import pywt
coeffs = result['coeffs']
fig, axes = plt.subplots(len(coeffs), 1, figsize=(14, 3*len(coeffs)))
for i, (c, ax) in enumerate(zip(coeffs, axes)):
ax.plot(c)
ax.set_title(result['levels_info'][i]['level'])
plt.tight_layout()小波选择
| 小波 | 特点 |
|---|---|
| db4 | 默认推荐,平衡时频分辨率 |
| haar | 最简单,检测突变 |
| sym8 | 对称,减少相位失真 |
---
典型分析组合
"现在 A 股处于什么状态?"
1. HMM 3状态 → 当前是牛/熊/震荡
2. 转移概率矩阵 → 下一个状态最可能是什么
3. 各状态的历史持续时间统计"螺纹钢有没有 40 天周期?"
1. FFT 谱分析 → 主导周期是多少
2. 小波分解 → 该周期成分的能量变化
3. 与已知基本面周期对比(产能周期/补库周期)"这轮下跌是趋势反转还是震荡回调?"
1. HMM → 是否切换到了熊市状态
2. 变点检测 → 是否检测到结构断裂
3. Hurst 指数(近期) → 近期是趋势性还是均值回复07 — 商品特有分析 (Commodity-Specific)
商品市场有独特的分析维度。本文档覆盖季节性、价差、期限结构等商品特有的分析方法。
---
方法一览
| 方法 | 函数/方式 | 用途 |
|---|---|---|
| 季节性分析 | seasonal_analysis(s, freq='monthly') | 月度/周度涨跌规律 |
| 价差分析 | spread_analysis(a, b) | 跨品种/跨期价差 |
| 期限结构 | 手动构建(见下方) | Contango / Backwardation |
| 裂解价差 | 手动构建(见下方) | 炼化利润 |
| 压榨价差 | 手动构建(见下方) | 油脂加工利润 |
数据获取
| 资产 | 数据源 | 方式 |
|---|---|---|
| CBOT 大豆/玉米/小麦 | yfinance | ZS=F, ZC=F, ZW=F |
| NYMEX 原油/天然气 | yfinance | CL=F, NG=F |
| COMEX 黄金/白银/铜 | yfinance | GC=F, SI=F, HG=F |
| 国内期货(铜/螺纹钢) | tushare MCP | tushare_fut_daily |
| 国内期货持仓 | tushare MCP | tushare_fut_holding |
| 仓单数据 | tushare MCP | tushare_fut_wsr |
---
季节性分析
from analysis_toolkit import seasonal_analysis
# 月度季节性
result = seasonal_analysis(natgas_prices, freq='monthly')
print(f"历史上最强月份: {result['best_period']}")
print(f"历史上最弱月份: {result['worst_period']}")
print(result['stats'])
# 可视化
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
result['stats']['mean'].plot(kind='bar', ax=axes[0], title='月均收益率',
color=['g' if x > 0 else 'r' for x in result['stats']['mean']])
result['win_rate'].plot(kind='bar', ax=axes[1], title='月度上涨概率')
axes[1].axhline(y=0.5, color='gray', linestyle='--')经典商品季节性规律(供参考,非绝对)
| 商品 | 通常强势期 | 通常弱势期 | 驱动因素 |
|---|---|---|---|
| 天然气 | 10月-2月 | 4月-6月 | 冬季取暖需求 |
| 原油 | 2月-5月 | 9月-11月 | 驾驶季+炼厂检修 |
| 大豆 | 6月-7月 | 9月-10月 | 北半球种植→收获 |
| 黄金 | 1月, 8月-9月 | 3月-4月 | 印度婚季+避险 |
| 铜 | 1月-4月 | 7月-8月 | 开工季+淡季 |
⚠️ 季节性是历史统计规律,不是确定性信号。需结合当期基本面。
---
价差分析
from analysis_toolkit import spread_analysis
# 金银比
result = spread_analysis(gold_prices, silver_prices, names=['黄金', '白银'])
print(result['interpretation'])
# Z-Score 可视化
fig, axes = plt.subplots(2, 1, figsize=(14, 8))
result['spread'].plot(ax=axes[0], title='价差序列')
result['z_score'].plot(ax=axes[1], title='Z-Score')
axes[1].axhline(y=2, color='r', linestyle='--')
axes[1].axhline(y=-2, color='g', linestyle='--')
axes[1].axhline(y=0, color='gray', linestyle='--')
axes[1].fill_between(result['z_score'].index, -2, 2, alpha=0.1, color='gray')---
裂解价差 (Crack Spread)
原油 → 汽油 + 取暖油的炼化利润:
from data_fetcher import fetch
oil = fetch('CL=F', period='2y')['Close']
gasoline = fetch('RB=F', period='2y')['Close']
heating_oil = fetch('HO=F', period='2y')['Close']
# 3:2:1 裂解价差(3桶原油 → 2桶汽油 + 1桶取暖油)
# 注意单位:原油$/桶,成品油$/加仑,1桶=42加仑
crack_spread = (2 * gasoline * 42 + 1 * heating_oil * 42 - 3 * oil) / 3
crack_spread.plot(title='3:2:1 裂解价差', figsize=(12, 5))
plt.ylabel('$/桶')
plt.axhline(y=crack_spread.mean(), color='gray', linestyle='--', label=f'均值 ${crack_spread.mean():.1f}')
plt.legend()---
压榨价差 (Crush Spread)
大豆 → 豆粕 + 豆油的加工利润:
soybean = fetch('ZS=F', period='2y')['Close'] # 美分/蒲式耳
meal = fetch('ZM=F', period='2y')['Close'] # $/短吨
oil = fetch('ZL=F', period='2y')['Close'] # 美分/磅
# 大豆压榨毛利 (Board Crush)
# 1蒲式耳大豆 ≈ 产出 44磅豆粕 + 11磅豆油
crush_margin = (meal / 2000 * 44 + oil * 11 / 100 - soybean / 100)
crush_margin.plot(title='大豆压榨价差', figsize=(12, 5))
plt.ylabel('$/蒲式耳')---
期限结构 (Term Structure)
用 yfinance 获取不同月份的期货合约来判断 Contango(远月>近月)或 Backwardation(近月>远月):
from data_fetcher import fetch
# 原油不同月份合约(示例)
contracts = {
'近月': 'CL=F',
'次月': 'CLG25.NYM', # 具体合约代码需查询
}
# 国内期货用 tushare MCP
# ToolSearch("+tushare 期货")
# tushare_fut_basic → 获取合约列表
# tushare_fut_daily → 获取各合约价格
# tushare_fut_mapping → 主力合约映射Contango vs Backwardation
| 状态 | 特征 | 含义 |
|---|---|---|
| Contango | 远月 > 近月 | 供应充足,持有成本为正,做多展期亏损 |
| Backwardation | 近月 > 远月 | 供应紧张,便利收益高,做多展期盈利 |
---
典型分析组合
"天然气每年什么时候涨?"
1. 季节性分析(monthly) → 月度涨跌统计
2. 5年/10年叠加图 → 可视化历史模式
3. STL 分解 → 提取季节成分"炼化利润趋势如何?"
1. 裂解价差计算 → 当前利润水平
2. 价差 Z-Score → vs 历史均值
3. 趋势分析 → 利润是否在扩大/收缩
4. 季节性 → 夏季驾驶季利润是否符合规律"铜价和螺纹钢有套利机会吗?"
1. 协整检验 → 长期均衡关系
2. 价差分析 + Z-Score → 当前偏离程度
3. 季节性 → 价差本身有没有季节规律08 — 网络与信息论 (Network & Information Theory)
将资产间的关系可视化为网络结构,发现隐藏的聚类和信息传导路径。
---
方法一览
| 方法 | 函数 | 输出 |
|---|---|---|
| 相关性网络 | correlation_network(df, threshold) | 网络图 + 中心性 |
| 最小生成树 | build_mst(df) | 核心关系骨架 |
| 社区检测 | community_detection(df) | 自动聚类分组 |
| 互信息 | mutual_information(a, b) | 非线性相关度量 |
---
相关性网络
将资产两两相关关系构建为网络图:
from analysis_toolkit import correlation_network
result = correlation_network(returns_df, threshold=0.3)
# 基本信息
print(f"网络中心(Hub): {result['hub']}")
print(f"度中心性: {result['degree_centrality']}")
# 可视化
import networkx as nx
import matplotlib.pyplot as plt
G = result['graph']
fig, ax = plt.subplots(figsize=(12, 10))
pos = nx.spring_layout(G, k=2, seed=42)
centrality = result['degree_centrality']
node_sizes = [centrality.get(n, 0) * 3000 + 300 for n in G.nodes()]
edge_weights = [G[u][v]['weight'] for u, v in G.edges()]
edge_colors = ['green' if w > 0 else 'red' for w in edge_weights]
nx.draw(G, pos, ax=ax, with_labels=True, node_size=node_sizes,
node_color='lightblue', edge_color=edge_colors,
width=[abs(w)*3 for w in edge_weights],
font_size=9, font_weight='bold')
ax.set_title('资产相关性网络')threshold 选择
| threshold | 效果 |
|---|---|
| 0.3 | 显示弱相关以上,网络密 |
| 0.5 | 推荐默认,中等密度 |
| 0.7 | 只显示强相关,网络稀疏 |
---
最小生成树 (MST)
提取最核心的N-1条边,简化复杂网络:
from analysis_toolkit import build_mst
result = build_mst(returns_df)
# 可视化
G = result['mst_graph']
fig, ax = plt.subplots(figsize=(14, 10))
pos = nx.spring_layout(G, k=3, seed=42)
nx.draw(G, pos, ax=ax, with_labels=True,
node_color='lightcoral', node_size=800,
edge_color='gray', width=2, font_size=10)
# 标注边的距离
edge_labels = {(u, v): f"{d['weight']:.2f}" for u, v, d in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels, font_size=7)
ax.set_title('最小生成树 (MST)')MST 的解读
- 中心节点(度数高的节点)= 市场中的"核心驱动者"
- 叶子节点 = 相对独立的资产
- 边越短(距离小) = 两个资产越相似
- 同一分支上的资产 = 可能属于同一板块/主题
---
社区检测
自动发现资产的聚类分组:
from analysis_toolkit import community_detection
result = community_detection(returns_df)
print(f"发现 {result['n_communities']} 个社区")
print(f"模块度: {result['modularity']:.3f}")
for i, community in enumerate(result['communities']):
print(f" 社区 {i+1}: {community}")
# 想象一下输出:
# 社区 1: ['AAPL', 'MSFT', 'GOOG'] → 科技股
# 社区 2: ['GC=F', 'SI=F'] → 贵金属
# 社区 3: ['XOM', 'CL=F'] → 能源模块度 (Modularity) 解读
| 值 | 含义 |
|---|---|
| < 0.3 | 社区结构不明显 |
| 0.3 - 0.7 | 中等社区结构 |
| > 0.7 | 强社区结构 |
---
典型分析组合
"A 股市场的板块结构"
1. 取沪深300成分股(tushare) → 计算收益率矩阵
2. MST → 核心关系骨架
3. 社区检测 → 自动分组
4. 对比申万行业分类 → 验证聚类合理性"哪只股票是风险传播的 Hub?"
1. 相关性网络 → 度中心性排名
2. 介数中心性 → 信息传导的桥梁
3. MST → 在核心骨架中的位置"构建分散化组合"
1. 社区检测 → 发现聚类
2. 每个社区选代表 → 确保跨社区分散
3. 组合优化 → 在分散基础上优化权重输出模板 (Output Templates)
分析结果的标准化输出格式。
---
个股分析报告
# 📊 [公司名称]([代码])分析报告
> 📅 生成日期:YYYY-MM-DD | 数据来源:tushare / yfinance | 数据截至:最新交易日
## 1. 概览
| 指标 | 值 |
| -------- | ------- |
| 最新价 | ¥XX.XX |
| 今日涨跌 | +X.XX% |
| 总市值 | XXXX 亿 |
| PE(TTM) | XX.X |
| 所属行业 | XXX |
## 2. 技术面分析
### 趋势判断
- 当前趋势:【多头 / 空头 / 震荡】
- 支撑位:¥XX.XX
- 压力位:¥XX.XX
### 信号汇总
| 信号 | 方向 | 说明 |
| ----------- | ---- | ------------ |
| 🟢 MACD 金叉 | 看多 | DIF 上穿 DEA |
## 3. 基本面分析
### 估值 & 盈利
| 指标 | 当前 | 行业均值 | 判断 |
| ---- | ---- | -------- | -------------- |
| PE | XX | XX | 偏高/合理/偏低 |
| ROE | XX% | XX% | — |
## 4. 风险提示
- ⚠️ [具体风险]
## 5. 综合建议
- 📊 短期:[观点]
- 📈 中期:[观点]
---
⚠️ **免责声明**:本报告仅供参考,不构成投资建议。投资有风险,入市需谨慎。---
对比分析报告
# ⚔️ 对比分析:[A] vs [B]
## 核心指标
| 维度 | [A] | [B] | 优势方 |
| ---- | ----- | ----- | ------ |
| 市值 | XXX亿 | XXX亿 | A |
| PE | XX | XX | B |
| ROE | XX% | XX% | A |
## 走势对比

## 相关性分析
- Pearson 相关系数:X.XX
- 60日滚动相关变化趋势
- Granger 因果:A → B (lag=X天)
## 结论
...---
时间序列分析报告
# 📈 [资产名称] 时间序列分析
## 1. 数据概况
- 时间范围:YYYY-MM-DD ~ YYYY-MM-DD
- 观测数量:XXX 个交易日
- 最新价:¥XX.XX
## 2. 平稳性检验
| 检验 | 统计量 | p值 | 结论 |
| ---- | ------ | ---- | ----------- |
| ADF | X.XX | X.XX | 平稳/非平稳 |
| KPSS | X.XX | X.XX | 平稳/非平稳 |
## 3. 结构分析
- Hurst 指数:X.XX → 趋势性/均值回复/随机
- 变点检测:在 YYYY-MM-DD 发现结构断裂
- 主导周期:XX天, XX天(谱分析)
## 4. 波动率分析
- 当前波动率:XX%(年化)
- 历史分位数:XX%
- GARCH 预测:下周波动率约 XX%
## 5. 风险度量
| 指标 | 值 |
| -------- | ----- |
| 95% VaR | X.XX% |
| 95% CVaR | X.XX% |
| 最大回撤 | X.XX% |---
商品分析报告
# 🛢️ [商品名称] 分析报告
## 1. 行情概况
| 指标 | 值 |
| -------- | ------------------------ |
| 最新价 | $XX.XX |
| 近月合约 | XXX |
| 期限结构 | Contango / Backwardation |
## 2. 季节性分析
- 历史最强月份:X月(平均 +X.X%)
- 历史最弱月份:X月(平均 -X.X%)
- 当前月份处于季节性 [强/弱] 势期

## 3. 价差分析
- 裂解/压榨价差:$XX.XX
- 价差 Z-Score:X.XX → [偏高/正常/偏低]
## 4. 结构分析
- 趋势方向:[上行/下行/震荡]
- 波动率:XX%,处于历史 XX% 分位---
组合分析报告
# 💼 组合分析报告
## 1. 组合概况
| 资产 | 权重 |
| ---- | ---- |
| XXX | XX% |
## 2. 绩效指标
| 指标 | 值 |
| -------- | ---- |
| 年化收益 | XX% |
| 年化波动 | XX% |
| Sharpe | X.XX |
| 最大回撤 | XX% |
## 3. 优化建议
| 方案 | 权重调整 | 预期效果 |
| ----------- | -------- | ------------- |
| Max Sharpe | ... | Sharpe → X.XX |
| Risk Parity | ... | MDD → XX% |
## 4. 风险分解
- 系统性风险占比:XX%
- 特异性风险占比:XX%
- PCA 前3因子解释:XX%---
免责声明(所有报告必须附加)
---
⚠️ **免责声明**:本报告由 AI 系统基于历史数据生成,仅供参考和学习使用,不构成任何投资建议。
金融市场存在不确定性,历史表现不代表未来结果。投资有风险,入市需谨慎。
数据来源:Tushare Pro / yfinance,数据准确性依赖于源数据质量。可视化菜谱 (Visualization Cookbook)
常见金融图表的代码模板。所有模板共享中文字体设置。
---
全局设置
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns
import numpy as np
# 中文字体
plt.rcParams['font.sans-serif'] = ['PingFang SC', 'Heiti TC', 'SimHei', 'WenQuanYi Micro Hei']
plt.rcParams['axes.unicode_minus'] = False
# 配色方案
COLORS = {
'bull': '#FF4444',
'bear': '#44AA44',
'neutral': '#AAAAAA',
'primary': '#2196F3',
'secondary': '#FF9800',
'accent': '#9C27B0',
}---
K 线图(mplfinance)
import mplfinance as mpf
mc = mpf.make_marketcolors(up='r', down='g', edge='inherit', wick='inherit', volume='in')
style = mpf.make_mpf_style(marketcolors=mc, gridstyle='--', gridcolor='#e6e6e6')
add_plots = [
mpf.make_addplot(df['MA5'], color='#FF6B6B', width=0.8),
mpf.make_addplot(df['MA20'], color='#4ECDC4', width=0.8),
mpf.make_addplot(df['MA60'], color='#45B7D1', width=0.8),
]
fig, axes = mpf.plot(df[['Open','High','Low','Close','Volume']], type='candle',
style=style, volume=True, addplot=add_plots,
title=f'\n{symbol} K线图', figsize=(14, 8), returnfig=True)
fig.savefig('kline.png', dpi=150, bbox_inches='tight')---
热力图(相关矩阵)
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='RdBu_r', center=0,
fmt='.2f', square=True, linewidths=0.5, ax=ax,
vmin=-1, vmax=1)
ax.set_title('资产相关性矩阵', fontsize=14)---
技术指标四合一面板
fig, axes = plt.subplots(4, 1, figsize=(14, 12), height_ratios=[3,1,1,1], sharex=True)
# 价格 + 布林带
axes[0].plot(df.index, df['Close'], 'k-', lw=1, label='收盘价')
axes[0].fill_between(df.index, df['BOLL_UPPER'], df['BOLL_LOWER'], alpha=0.1, color='blue')
axes[0].legend(loc='upper left', fontsize=8)
# MACD
colors = ['#FF4444' if v >= 0 else '#44AA44' for v in df['MACD_HIST']]
axes[1].bar(df.index, df['MACD_HIST'], color=colors, width=0.8, alpha=0.6)
axes[1].plot(df.index, df['MACD_DIF'], 'b-', lw=0.8)
axes[1].plot(df.index, df['MACD_DEA'], 'r--', lw=0.8)
# RSI
axes[2].plot(df.index, df['RSI14'], color='purple', lw=1)
axes[2].axhline(y=70, color='red', linestyle='--', lw=0.5)
axes[2].axhline(y=30, color='green', linestyle='--', lw=0.5)
axes[2].set_ylim(0, 100)
# KDJ
axes[3].plot(df.index, df['KDJ_K'], 'b-', lw=0.8, label='K')
axes[3].plot(df.index, df['KDJ_D'], color='orange', lw=0.8, label='D')
axes[3].plot(df.index, df['KDJ_J'], color='purple', lw=0.8, label='J')
axes[3].legend(fontsize=8)
plt.tight_layout()---
多股归一化对比
fig, ax = plt.subplots(figsize=(12, 6))
for col in df_returns.columns:
cumret = (1 + df_returns[col]).cumprod() - 1
ax.plot(cumret.index, cumret * 100, lw=1.5, label=col)
ax.axhline(y=0, color='gray', lw=0.5, linestyle='--')
ax.set_ylabel('累计收益率 (%)')
ax.set_title('资产收益率对比')
ax.legend()
ax.grid(True, alpha=0.3)---
波动率锥
fig, ax = plt.subplots(figsize=(10, 6))
cone_df = volatility_cone_data # DataFrame from volatility_cone()
windows = cone_df.columns
for pct in ['min', '25%', '50%', '75%', 'max']:
ax.plot(range(len(windows)), cone_df.loc[pct], '--', alpha=0.5, label=pct)
ax.plot(range(len(windows)), cone_df.loc['current'], 'ro-', lw=2, ms=8, label='当前')
ax.set_xticks(range(len(windows)))
ax.set_xticklabels(windows)
ax.set_ylabel('年化波动率')
ax.set_title('波动率锥')
ax.legend()---
有效前沿散点图
fig, ax = plt.subplots(figsize=(10, 6))
scatter = ax.scatter(vols, rets, c=sharpes, cmap='viridis', alpha=0.5, s=5)
plt.colorbar(scatter, label='Sharpe Ratio')
ax.scatter(opt_vol, opt_ret, marker='*', s=300, c='red', label='最优组合')
ax.set_xlabel('年化波动率')
ax.set_ylabel('年化收益率')
ax.set_title('有效前沿')
ax.legend()---
网络图
import networkx as nx
fig, ax = plt.subplots(figsize=(12, 10))
pos = nx.spring_layout(G, k=2, seed=42)
sizes = [centrality[n] * 3000 + 300 for n in G.nodes()]
nx.draw(G, pos, ax=ax, with_labels=True, node_size=sizes,
node_color='lightblue', font_size=9, font_weight='bold',
edge_color='gray', width=1.5)
ax.set_title('资产网络结构')---
季节性柱状图
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
colors = ['#44AA44' if x > 0 else '#FF4444' for x in monthly_means]
axes[0].bar(range(12), monthly_means, color=colors)
axes[0].set_xticks(range(12))
axes[0].set_xticklabels(['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'])
axes[0].set_title('月均收益率')
axes[0].axhline(y=0, color='gray', lw=0.5)
axes[1].bar(range(12), win_rates, color='#2196F3')
axes[1].axhline(y=0.5, color='gray', linestyle='--')
axes[1].set_title('月度上涨概率')---
保存规范
- 分辨率:
dpi=150(报告用)或dpi=300(出版级) - 格式:
.png(通用)或.svg(矢量) - 裁切:始终使用
bbox_inches='tight' - 命名:
{symbol}_{chart_type}_{date}.png
# === 核心依赖 ===
pandas>=2.0
numpy>=1.24
matplotlib>=3.7
seaborn>=0.12
# === 数据源 ===
yfinance>=0.2.28 # 美股/港股/商品/加密/外汇
mplfinance>=0.12 # 专业K线图
# === 统计分析 ===
statsmodels>=0.14 # 时间序列、检验、VAR、VECM
arch>=6.0 # GARCH 家族、单位根检验
scipy>=1.11 # 统计检验、优化器
# === 高阶分析 ===
scikit-learn>=1.3 # PCA、聚类、t-SNE、UMAP
hmmlearn>=0.3 # 隐马尔可夫模型
ruptures>=1.1 # 变点检测 (PELT, Binseg, Bai-Perron)
networkx>=3.1 # 网络分析、MST、社区检测
PyWavelets>=1.4 # 小波分析
# === 预测 ===
prophet>=1.1 # Facebook Prophet 时间序列预测
# === A股数据(可选) ===
# tushare>=1.4 # 取消注释并配置 TUSHARE_TOKEN
# === 加密货币(可选) ===
# ccxt>=4.0 # 交易所数据接口
#!/usr/bin/env python3
"""
高阶金融分析工具集
涵盖 8 大方法域的核心函数,AI 按需调用
用法:
from analysis_toolkit import (
test_stationarity, decompose_series, detect_changepoints, hurst_exponent,
test_cointegration, granger_causality, rolling_correlation, cross_correlation,
fit_garch, calculate_var, max_drawdown, volatility_cone,
optimize_portfolio, pca_factors, performance_metrics,
detect_regimes, spectral_analysis, wavelet_decompose,
seasonal_analysis, spread_analysis,
correlation_network, build_mst,
)
"""
import warnings
import pandas as pd
import numpy as np
from scipy import stats
warnings.filterwarnings('ignore')
# ================================================================
# 01. 时间序列基础
# ================================================================
def test_stationarity(series, methods=('adf', 'kpss')):
"""
序列平稳性检验
Args:
series: pd.Series,价格或收益率序列
methods: 检验方法列表 ('adf', 'kpss', 'pp')
Returns:
dict: {method: {statistic, p_value, critical_values, is_stationary, interpretation}}
"""
from statsmodels.tsa.stattools import adfuller, kpss
results = {}
if 'adf' in methods:
stat, pval, _, _, crit, _ = adfuller(series.dropna(), autolag='AIC')
results['adf'] = {
'statistic': stat, 'p_value': pval,
'critical_values': crit,
'is_stationary': pval < 0.05,
'interpretation': f'ADF={stat:.4f}, p={pval:.4f} → {"平稳" if pval < 0.05 else "非平稳"}'
}
if 'kpss' in methods:
stat, pval, _, crit = kpss(series.dropna(), regression='c', nlags='auto')
results['kpss'] = {
'statistic': stat, 'p_value': pval,
'critical_values': crit,
'is_stationary': pval > 0.05, # KPSS 原假设是平稳
'interpretation': f'KPSS={stat:.4f}, p={pval:.4f} → {"平稳" if pval > 0.05 else "非平稳"}'
}
if 'pp' in methods:
from arch.unitroot import PhillipsPerron
pp = PhillipsPerron(series.dropna())
results['pp'] = {
'statistic': pp.stat, 'p_value': pp.pvalue,
'is_stationary': pp.pvalue < 0.05,
'interpretation': f'PP={pp.stat:.4f}, p={pp.pvalue:.4f} → {"平稳" if pp.pvalue < 0.05 else "非平稳"}'
}
return results
def decompose_series(series, period=None, model='additive'):
"""
时间序列分解:趋势 + 季节性 + 残差
Args:
series: pd.Series(需有 DatetimeIndex)
period: 季节周期(None 则自动检测)
model: 'additive' 或 'multiplicative' 或 'stl'
Returns:
decomposition 对象,含 .trend, .seasonal, .resid
"""
from statsmodels.tsa.seasonal import seasonal_decompose, STL
s = series.dropna()
if period is None:
period = min(len(s) // 3, 252) # 默认年周期或1/3序列长度
if model == 'stl':
return STL(s, period=period).fit()
else:
return seasonal_decompose(s, model=model, period=period)
def hurst_exponent(series, max_lag=100):
"""
Hurst 指数 (R/S分析)
H > 0.5: 趋势性 | H = 0.5: 随机游走 | H < 0.5: 均值回复
Returns:
float: Hurst 指数
"""
s = np.array(series.dropna())
lags = range(2, min(max_lag, len(s) // 2))
rs_values = []
for lag in lags:
subseries = [s[i:i + lag] for i in range(0, len(s) - lag, lag)]
rs_list = []
for sub in subseries:
if len(sub) < 2:
continue
mean = sub.mean()
devs = sub - mean
cumdev = np.cumsum(devs)
R = cumdev.max() - cumdev.min()
S = sub.std(ddof=1)
if S > 0:
rs_list.append(R / S)
if rs_list:
rs_values.append((lag, np.mean(rs_list)))
if len(rs_values) < 2:
return 0.5
log_lags = np.log([v[0] for v in rs_values])
log_rs = np.log([v[1] for v in rs_values])
H, _ = np.polyfit(log_lags, log_rs, 1)
return H
def detect_changepoints(series, method='pelt', penalty='bic', n_bkps=None):
"""
变点检测:找到序列中统计特性发生突变的时间点
Args:
series: pd.Series
method: 'pelt' / 'binseg' / 'bottomup'
penalty: PELT 惩罚项 ('bic', 'mbic', 'l2')
n_bkps: Binseg/BottomUp 时指定断点数
Returns:
list: 断点索引位置
"""
import ruptures as rpt
signal = series.dropna().values
algo_map = {
'pelt': rpt.Pelt(model='rbf').fit(signal),
'binseg': rpt.Binseg(model='rbf').fit(signal),
'bottomup': rpt.BottomUp(model='rbf').fit(signal),
}
algo = algo_map[method]
if method == 'pelt':
return algo.predict(pen=np.log(len(signal)) * signal.var())
else:
return algo.predict(n_bkps=n_bkps or 5)
def acf_pacf(series, nlags=40):
"""
计算 ACF 和 PACF
Returns:
dict: {acf: array, pacf: array, nlags: int}
"""
from statsmodels.tsa.stattools import acf, pacf
s = series.dropna()
return {
'acf': acf(s, nlags=nlags),
'pacf': pacf(s, nlags=nlags, method='ywm'),
'nlags': nlags
}
# ================================================================
# 02. 预测
# ================================================================
def fit_arima(series, order=None, seasonal_order=None, forecast_steps=10):
"""
ARIMA/SARIMA 拟合与预测
Args:
series: pd.Series
order: (p,d,q),None 则自动选择
seasonal_order: (P,D,Q,s)
forecast_steps: 预测步数
Returns:
dict: {model, fitted, forecast, aic, bic, summary}
"""
from statsmodels.tsa.arima.model import ARIMA
s = series.dropna()
if order is None:
# 简单自动选参:用 AIC 挑选
best_aic = np.inf
best_order = (1, 1, 1)
for p in range(4):
for d in range(2):
for q in range(4):
try:
m = ARIMA(s, order=(p, d, q)).fit()
if m.aic < best_aic:
best_aic, best_order = m.aic, (p, d, q)
except Exception:
continue
order = best_order
model = ARIMA(s, order=order, seasonal_order=seasonal_order).fit()
forecast = model.forecast(steps=forecast_steps)
return {
'model': model,
'order': order,
'fitted': model.fittedvalues,
'forecast': forecast,
'aic': model.aic,
'bic': model.bic,
}
def fit_var(df_returns, max_lag=10, forecast_steps=10):
"""
VAR 向量自回归模型
Args:
df_returns: DataFrame,每列为一个资产的收益率
max_lag: 最大滞后阶数
forecast_steps: 预测步数
Returns:
dict: {model, optimal_lag, forecast, granger_results}
"""
from statsmodels.tsa.api import VAR
data = df_returns.dropna()
model = VAR(data)
lag_order = model.select_order(maxlags=max_lag)
optimal_lag = lag_order.aic
fitted = model.fit(optimal_lag)
forecast = fitted.forecast(data.values[-optimal_lag:], steps=forecast_steps)
forecast_df = pd.DataFrame(forecast, columns=data.columns)
return {
'model': fitted,
'optimal_lag': optimal_lag,
'forecast': forecast_df,
'lag_selection': lag_order.summary(),
}
# ================================================================
# 03. 跨资产关系
# ================================================================
def correlation_matrix(df, method='pearson'):
"""
相关性矩阵
Args:
df: DataFrame,每列一个资产的收益率或价格
method: 'pearson' / 'spearman' / 'kendall'
"""
return df.corr(method=method)
def rolling_correlation(series_a, series_b, window=60):
"""
滚动相关系数
Returns:
pd.Series: 滚动相关系数时间序列
"""
return series_a.rolling(window).corr(series_b)
def test_cointegration(series_a, series_b, method='engle-granger'):
"""
协整检验
Args:
method: 'engle-granger' 或 'johansen'
Returns:
dict: {is_cointegrated, statistic, p_value, spread, hedge_ratio}
"""
if method == 'engle-granger':
from statsmodels.tsa.stattools import coint
from statsmodels.regression.linear_model import OLS
from statsmodels.tools import add_constant
# 先对齐索引,避免长度不一致
aligned = pd.concat([series_a, series_b], axis=1).dropna()
sa = aligned.iloc[:, 0]
sb = aligned.iloc[:, 1]
stat, pval, crit = coint(sa, sb)
# OLS 求对冲比
X = add_constant(sb.values)
model = OLS(sa.values, X).fit()
hedge_ratio = model.params[1]
spread = sa - hedge_ratio * sb
return {
'is_cointegrated': pval < 0.05,
'statistic': stat,
'p_value': pval,
'critical_values': crit,
'hedge_ratio': hedge_ratio,
'spread': spread,
'interpretation': f'协整检验: stat={stat:.4f}, p={pval:.4f} → {"协整" if pval < 0.05 else "不协整"}'
}
elif method == 'johansen':
from statsmodels.tsa.vector_ar.vecm import coint_johansen
data = pd.concat([series_a, series_b], axis=1).dropna()
result = coint_johansen(data, det_order=0, k_ar_diff=1)
return {
'trace_stat': result.trace_stat,
'trace_crit': result.trace_stat_crit_vals,
'eigen_stat': result.max_eig_stat,
'eigen_crit': result.max_eig_stat_crit_vals,
'evec': result.evec,
}
def granger_causality(series_a, series_b, max_lag=10):
"""
Granger 因果检验
Returns:
dict: {lag: {f_stat, p_value, is_causal}}
"""
from statsmodels.tsa.stattools import grangercausalitytests
data = pd.concat([series_a, series_b], axis=1).dropna()
results = grangercausalitytests(data, maxlag=max_lag, verbose=False)
output = {}
for lag, res in results.items():
f_stat = res[0]['ssr_ftest'][0]
p_val = res[0]['ssr_ftest'][1]
output[lag] = {
'f_stat': f_stat,
'p_value': p_val,
'is_causal': p_val < 0.05,
}
return output
def cross_correlation(series_a, series_b, max_lag=20):
"""
交叉相关分析:找最优领先-滞后关系
Returns:
dict: {lags: array, correlations: array, optimal_lag: int, max_corr: float}
"""
a = (series_a - series_a.mean()) / series_a.std()
b = (series_b - series_b.mean()) / series_b.std()
a, b = a.dropna(), b.dropna()
idx = a.index.intersection(b.index)
a, b = a.loc[idx].values, b.loc[idx].values
lags = range(-max_lag, max_lag + 1)
corrs = []
for lag in lags:
if lag >= 0:
corrs.append(np.corrcoef(a[lag:], b[:len(a) - lag])[0, 1] if lag < len(a) else 0)
else:
corrs.append(np.corrcoef(a[:len(a) + lag], b[-lag:])[0, 1] if -lag < len(a) else 0)
corrs = np.array(corrs)
opt_idx = np.argmax(np.abs(corrs))
return {
'lags': np.array(list(lags)),
'correlations': corrs,
'optimal_lag': list(lags)[opt_idx],
'max_corr': corrs[opt_idx],
}
def mutual_information(series_a, series_b, n_bins=20):
"""
互信息:非线性相关性度量
Returns:
float: 互信息值
"""
from sklearn.metrics import mutual_info_score
a = pd.cut(series_a.dropna(), bins=n_bins, labels=False)
b = pd.cut(series_b.dropna(), bins=n_bins, labels=False)
idx = a.dropna().index.intersection(b.dropna().index)
return mutual_info_score(a.loc[idx], b.loc[idx])
# ================================================================
# 04. 波动率与风险
# ================================================================
def fit_garch(series, model_type='garch', p=1, q=1, dist='normal'):
"""
GARCH 家族波动率建模
Args:
series: 收益率序列(不是价格!)
model_type: 'garch' / 'egarch' / 'gjr-garch'
dist: 'normal' / 't' / 'skewt'
Returns:
dict: {model, conditional_volatility, forecast, params, summary}
"""
from arch import arch_model
vol_map = {'garch': 'Garch', 'egarch': 'EGARCH', 'gjr-garch': 'GARCH'}
o = 1 if model_type == 'gjr-garch' else 0
# 收益率 ×100 提升数值稳定性,输出时 /100 和 /10000 还原
am = arch_model(series.dropna() * 100, vol=vol_map.get(model_type, 'Garch'),
p=p, o=o, q=q, dist=dist)
res = am.fit(disp='off')
forecast = res.forecast(horizon=5)
return {
'model': res,
'conditional_volatility': res.conditional_volatility / 100,
'forecast_variance': forecast.variance.iloc[-1] / 10000,
'params': res.params,
'aic': res.aic,
'bic': res.bic,
}
def calculate_var(returns, confidence=0.95, method='historical', n_days=1):
"""
VaR 风险价值
Args:
returns: 收益率序列
confidence: 置信水平
method: 'historical' / 'parametric' / 'montecarlo'
n_days: 持有天数
Returns:
dict: {var, cvar, method, confidence}
"""
r = returns.dropna()
alpha = 1 - confidence
valid_methods = ('historical', 'parametric', 'montecarlo')
if method not in valid_methods:
raise ValueError(f"method 必须为 {valid_methods},得到: '{method}'")
if method == 'historical':
var = np.percentile(r, alpha * 100)
cvar = r[r <= var].mean()
elif method == 'parametric':
mu, sigma = r.mean(), r.std()
z = stats.norm.ppf(alpha)
var = mu + z * sigma
cvar = mu - sigma * stats.norm.pdf(z) / alpha
else: # montecarlo
mu, sigma = r.mean(), r.std()
rng = np.random.default_rng(seed=42) # 固定种子保证可复现
simulated = rng.normal(mu, sigma, 100000)
var = np.percentile(simulated, alpha * 100)
cvar = simulated[simulated <= var].mean()
# 多日调整
var *= np.sqrt(n_days)
cvar *= np.sqrt(n_days)
return {
'var': var,
'cvar': cvar,
'method': method,
'confidence': confidence,
'n_days': n_days,
'interpretation': f'{confidence:.0%} VaR = {var:.4f} ({var*100:.2f}%), CVaR = {cvar:.4f}'
}
def max_drawdown(series):
"""
最大回撤
Returns:
dict: {max_dd, peak_date, trough_date, recovery_date, dd_series}
"""
cummax = series.cummax()
drawdown = (series - cummax) / cummax
max_dd = drawdown.min()
trough_idx = drawdown.idxmin()
peak_idx = series.loc[:trough_idx].idxmax()
recovery = series.loc[trough_idx:][series.loc[trough_idx:] >= series.loc[peak_idx]]
recovery_idx = recovery.index[0] if len(recovery) > 0 else None
return {
'max_dd': max_dd,
'peak_date': peak_idx,
'trough_date': trough_idx,
'recovery_date': recovery_idx,
'dd_series': drawdown,
}
def volatility_cone(series, windows=(5, 10, 21, 63, 126, 252)):
"""
波动率锥:各窗口的波动率分位数分布
Returns:
DataFrame: columns=windows, index=[min, 25%, 50%, 75%, max, current]
"""
returns = series.pct_change().dropna()
result = {}
for w in windows:
rolling_vol = returns.rolling(w).std() * np.sqrt(252)
rv = rolling_vol.dropna()
if len(rv) == 0:
result[f'{w}D'] = {
'min': np.nan, '25%': np.nan,
'50%': np.nan, '75%': np.nan,
'max': np.nan, 'current': np.nan,
}
else:
result[f'{w}D'] = {
'min': rv.min(), '25%': rv.quantile(0.25),
'50%': rv.median(), '75%': rv.quantile(0.75),
'max': rv.max(), 'current': rv.iloc[-1],
}
return pd.DataFrame(result)
# ================================================================
# 05. 组合与因子
# ================================================================
def optimize_portfolio(returns_df, method='markowitz', risk_free=0.03, n_portfolios=5000):
"""
组合优化
Args:
returns_df: DataFrame,每列为一个资产的日收益率
method: 'markowitz' / 'risk_parity' / 'max_sharpe' / 'min_variance'
risk_free: 无风险利率(年化)
Returns:
dict: {weights, expected_return, volatility, sharpe, efficient_frontier}
"""
mu = returns_df.mean() * 252
cov = returns_df.cov() * 252
n = len(returns_df.columns)
if method in ('markowitz', 'max_sharpe', 'min_variance'):
# Monte Carlo 模拟有效前沿
results = np.zeros((3, n_portfolios))
weights_record = []
for i in range(n_portfolios):
w = np.random.random(n)
w /= w.sum()
ret = np.dot(w, mu)
vol = np.sqrt(np.dot(w, np.dot(cov, w)))
sharpe = (ret - risk_free) / vol
results[0, i] = ret
results[1, i] = vol
results[2, i] = sharpe
weights_record.append(w)
if method == 'max_sharpe':
idx = results[2].argmax()
else:
idx = results[1].argmin()
best_weights = weights_record[idx]
return {
'weights': dict(zip(returns_df.columns, best_weights)),
'expected_return': results[0, idx],
'volatility': results[1, idx],
'sharpe': results[2, idx],
'frontier_returns': results[0],
'frontier_volatilities': results[1],
'frontier_sharpes': results[2],
}
elif method == 'risk_parity':
from scipy.optimize import minimize
def risk_contrib(w):
vol = np.sqrt(w @ cov @ w)
mrc = cov @ w / vol
rc = w * mrc
target = vol / n
return np.sum((rc - target) ** 2)
w0 = np.ones(n) / n
bounds = [(0.01, 1.0)] * n
constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
result = minimize(risk_contrib, w0, method='SLSQP', bounds=bounds, constraints=constraints)
w = result.x
ret = np.dot(w, mu)
vol = np.sqrt(np.dot(w, np.dot(cov, w)))
return {
'weights': dict(zip(returns_df.columns, w)),
'expected_return': ret,
'volatility': vol,
'sharpe': (ret - risk_free) / vol,
}
def pca_factors(returns_df, n_components=3):
"""
PCA 因子提取
Returns:
dict: {components, explained_variance, explained_ratio, loadings}
"""
from sklearn.decomposition import PCA
pca = PCA(n_components=n_components)
factors = pca.fit_transform(returns_df.dropna())
return {
'factors': pd.DataFrame(factors, index=returns_df.dropna().index,
columns=[f'PC{i+1}' for i in range(n_components)]),
'explained_variance': pca.explained_variance_,
'explained_ratio': pca.explained_variance_ratio_,
'cumulative_ratio': np.cumsum(pca.explained_variance_ratio_),
'loadings': pd.DataFrame(pca.components_.T, index=returns_df.columns,
columns=[f'PC{i+1}' for i in range(n_components)]),
}
def performance_metrics(returns, risk_free=0.03/252, benchmark=None):
"""
绩效指标计算
Returns:
dict: {annual_return, annual_vol, sharpe, sortino, calmar, max_dd, ...}
"""
r = returns.dropna()
ann_ret = r.mean() * 252
ann_vol = r.std() * np.sqrt(252)
sharpe = (ann_ret - risk_free * 252) / ann_vol if ann_vol > 0 else 0
downside = r[r < 0].std() * np.sqrt(252)
sortino = (ann_ret - risk_free * 252) / downside if downside > 0 else 0
cum_ret = (1 + r).cumprod()
dd = max_drawdown(cum_ret)
calmar = ann_ret / abs(dd['max_dd']) if dd['max_dd'] != 0 else 0
result = {
'annual_return': ann_ret,
'annual_volatility': ann_vol,
'sharpe_ratio': sharpe,
'sortino_ratio': sortino,
'calmar_ratio': calmar,
'max_drawdown': dd['max_dd'],
'skewness': r.skew(),
'kurtosis': r.kurtosis(),
'win_rate': (r > 0).mean(),
}
if benchmark is not None:
b = benchmark.dropna()
idx = r.index.intersection(b.index)
excess = r.loc[idx] - b.loc[idx]
tracking_error = excess.std() * np.sqrt(252)
info_ratio = excess.mean() * 252 / tracking_error if tracking_error > 0 else 0
result['tracking_error'] = tracking_error
result['information_ratio'] = info_ratio
return result
# ================================================================
# 06. 状态识别与结构分析
# ================================================================
def detect_regimes(series, n_states=3):
"""
HMM 隐马尔可夫模型 — 市场状态识别
Args:
series: 收益率序列
n_states: 状态数(通常 2=牛熊 或 3=牛/熊/震荡)
Returns:
dict: {states, state_means, state_vars, transition_matrix, state_series}
"""
from hmmlearn.hmm import GaussianHMM
r = series.dropna().values.reshape(-1, 1)
model = GaussianHMM(n_components=n_states, covariance_type='full',
n_iter=200, random_state=42)
model.fit(r)
states = model.predict(r)
# 按均值排序状态(低→高 = 熊→牛)
means = model.means_.flatten()
order = np.argsort(means)
state_map = {old: new for new, old in enumerate(order)}
states_sorted = np.array([state_map[s] for s in states])
labels = {0: '熊市', 1: '震荡', 2: '牛市'} if n_states == 3 else {0: '熊市', 1: '牛市'}
return {
'states': states_sorted,
'state_series': pd.Series(states_sorted, index=series.dropna().index),
'state_means': means[order],
'state_vars': model.covars_.flatten()[order],
'transition_matrix': model.transmat_,
'labels': labels,
'current_state': labels.get(states_sorted[-1], f'State {states_sorted[-1]}'),
}
def spectral_analysis(series, sampling_rate=1):
"""
谱分析 (FFT) — 发现隐藏周期
Returns:
dict: {frequencies, power, dominant_periods}
"""
s = series.dropna().values
s = s - s.mean() # 去均值
n = len(s)
fft_vals = np.fft.rfft(s)
power = np.abs(fft_vals) ** 2
freqs = np.fft.rfftfreq(n, d=1.0/sampling_rate)
# 找主导周期(排除 DC 分量)
power_no_dc = power[1:]
freqs_no_dc = freqs[1:]
top_indices = np.argsort(power_no_dc)[-5:][::-1]
dominant_periods = [1.0/freqs_no_dc[i] for i in top_indices if freqs_no_dc[i] > 0]
return {
'frequencies': freqs,
'power': power,
'dominant_periods': dominant_periods,
'interpretation': f'主导周期: {", ".join([f"{p:.0f}天" for p in dominant_periods[:3]])}'
}
def wavelet_decompose(series, wavelet='db4', level=4):
"""
小波分解 — 多尺度分析
Returns:
dict: {coeffs, levels_info}
"""
import pywt
s = series.dropna().values
coeffs = pywt.wavedec(s, wavelet, level=level)
levels_info = []
for i, c in enumerate(coeffs):
if i == 0:
levels_info.append({'level': 'Approximation', 'length': len(c), 'energy': np.sum(c**2)})
else:
period = 2 ** i
levels_info.append({'level': f'Detail {i}', 'period': f'~{period}天', 'length': len(c), 'energy': np.sum(c**2)})
return {'coeffs': coeffs, 'levels_info': levels_info}
# ================================================================
# 07. 商品特有分析
# ================================================================
def seasonal_analysis(series, freq='monthly'):
"""
季节性分析
Args:
series: 价格序列(需至少2年数据)
freq: 'monthly' / 'weekly' / 'daily'
Returns:
dict: {seasonal_pattern, best_month, worst_month, monthly_stats}
"""
valid_freqs = ('monthly', 'weekly', 'daily')
if freq not in valid_freqs:
raise ValueError(f"freq 必须为 {valid_freqs},得到: '{freq}'")
r = series.pct_change().dropna()
if freq == 'monthly':
grouped = r.groupby(r.index.month)
stats_df = grouped.agg(['mean', 'std', 'median', 'count'])
stats_df.columns = ['mean', 'std', 'median', 'count']
month_names = {1:'1月', 2:'2月', 3:'3月', 4:'4月', 5:'5月', 6:'6月',
7:'7月', 8:'8月', 9:'9月', 10:'10月', 11:'11月', 12:'12月'}
stats_df.index = [month_names.get(m, str(m)) for m in stats_df.index]
elif freq == 'weekly':
grouped = r.groupby(r.index.dayofweek)
stats_df = grouped.agg(['mean', 'std', 'median', 'count'])
stats_df.columns = ['mean', 'std', 'median', 'count']
day_names = {0:'周一', 1:'周二', 2:'周三', 3:'周四', 4:'周五'}
stats_df.index = [day_names.get(d, str(d)) for d in stats_df.index]
elif freq == 'daily':
grouped = r.groupby(r.index.day)
stats_df = grouped.agg(['mean', 'std', 'median', 'count'])
stats_df.columns = ['mean', 'std', 'median', 'count']
stats_df.index = [f'{d}日' for d in stats_df.index]
best = stats_df['mean'].idxmax()
worst = stats_df['mean'].idxmin()
return {
'stats': stats_df,
'best_period': best,
'worst_period': worst,
'win_rate': grouped.apply(lambda x: (x > 0).mean()),
}
def spread_analysis(series_a, series_b, names=None):
"""
价差分析
Returns:
dict: {spread, z_score, mean, std, current_z, signal}
"""
spread = series_a - series_b
spread_clean = spread.dropna()
mu = spread_clean.mean()
sigma = spread_clean.std()
z = (spread_clean - mu) / sigma
current_z = z.iloc[-1]
if current_z > 2:
signal = '价差偏高,可能回归(空A多B)'
elif current_z < -2:
signal = '价差偏低,可能回归(多A空B)'
else:
signal = '价差在正常范围内'
label_a = names[0] if names else 'A'
label_b = names[1] if names else 'B'
return {
'spread': spread_clean,
'z_score': z,
'mean': mu,
'std': sigma,
'current_z': current_z,
'signal': signal,
'interpretation': f'{label_a} - {label_b} 价差: Z={current_z:.2f} → {signal}'
}
# ================================================================
# 08. 网络与信息论
# ================================================================
def correlation_network(returns_df, threshold=0.5):
"""
构建相关性网络
Returns:
dict: {graph, adjacency_matrix, node_centrality}
"""
import networkx as nx
corr = returns_df.corr()
G = nx.Graph()
for col in corr.columns:
G.add_node(col)
for i, col_a in enumerate(corr.columns):
for j, col_b in enumerate(corr.columns):
if i < j and abs(corr.iloc[i, j]) >= threshold:
G.add_edge(col_a, col_b, weight=corr.iloc[i, j])
centrality = nx.degree_centrality(G)
betweenness = nx.betweenness_centrality(G)
return {
'graph': G,
'adjacency': corr,
'degree_centrality': centrality,
'betweenness_centrality': betweenness,
'hub': max(centrality, key=centrality.get) if centrality else None,
}
def build_mst(returns_df):
"""
最小生成树 (MST) — 提取最核心的资产关系
Returns:
dict: {mst_graph, distance_matrix, edges}
"""
import networkx as nx
corr = returns_df.corr()
# 相关距离: d = sqrt(2(1-rho))
dist = np.sqrt(2 * (1 - corr))
G_full = nx.Graph()
for i, a in enumerate(corr.columns):
for j, b in enumerate(corr.columns):
if i < j:
G_full.add_edge(a, b, weight=dist.iloc[i, j])
mst = nx.minimum_spanning_tree(G_full)
return {
'mst_graph': mst,
'distance_matrix': dist,
'edges': list(mst.edges(data=True)),
'n_nodes': mst.number_of_nodes(),
'n_edges': mst.number_of_edges(),
}
def community_detection(returns_df):
"""
社区检测 — 自动发现资产聚类
Returns:
dict: {communities, n_communities, modularity}
"""
import networkx as nx
from networkx.algorithms.community import greedy_modularity_communities
corr = returns_df.corr()
G = nx.Graph()
for i, a in enumerate(corr.columns):
for j, b in enumerate(corr.columns):
if i < j and corr.iloc[i, j] > 0:
G.add_edge(a, b, weight=corr.iloc[i, j])
communities = list(greedy_modularity_communities(G))
modularity = nx.community.modularity(G, communities)
return {
'communities': [list(c) for c in communities],
'n_communities': len(communities),
'modularity': modularity,
}
#!/usr/bin/env python3
"""
yfinance 数据获取器 — 补充 Tushare MCP 未覆盖的资产
主要用于:国际商品期货(CL=F, GC=F...)、加密货币(BTC-USD)、国际指数、部分外汇
A 股 / 港股 / 美股 / 国内期货 / 宏观数据 → 请直接使用 tushare MCP tool
用法:
from data_fetcher import fetch, fetch_multi, identify_asset_type
python data_fetcher.py AAPL --period 1y
python data_fetcher.py --check
"""
import sys
import argparse
import pandas as pd
import numpy as np
# ===== 商品/指数中文名映射 =====
COMMODITY_TICKERS = {
'原油': 'CL=F', 'crude': 'CL=F', 'wti': 'CL=F',
'brent': 'BZ=F', '布伦特': 'BZ=F',
'天然气': 'NG=F', 'natgas': 'NG=F',
'汽油': 'RB=F', '取暖油': 'HO=F',
'黄金': 'GC=F', 'gold': 'GC=F',
'白银': 'SI=F', 'silver': 'SI=F',
'铂金': 'PL=F', '钯金': 'PA=F',
'铜': 'HG=F', 'copper': 'HG=F',
'大豆': 'ZS=F', '豆粕': 'ZM=F', '豆油': 'ZL=F',
'玉米': 'ZC=F', '小麦': 'ZW=F',
'棉花': 'CT=F', '糖': 'SB=F', '咖啡': 'KC=F', '可可': 'CC=F',
'活牛': 'LE=F', '瘦肉猪': 'HE=F',
}
INDEX_TICKERS = {
'标普500': '^GSPC', 'sp500': '^GSPC',
'纳斯达克': '^IXIC', 'nasdaq': '^IXIC',
'道琼斯': '^DJI', 'dow': '^DJI',
'恐慌指数': '^VIX', 'vix': '^VIX',
'美元指数': 'DX-Y.NYB', 'dxy': 'DX-Y.NYB',
'日经': '^N225', 'nikkei': '^N225',
}
def identify_asset_type(symbol: str) -> dict:
"""识别资产类型,返回 {ticker, type, market}"""
s = symbol.lower().strip()
su = symbol.upper().strip()
if s in COMMODITY_TICKERS:
return {'ticker': COMMODITY_TICKERS[s], 'type': 'commodity', 'market': 'global', 'name_cn': symbol}
if s in INDEX_TICKERS:
return {'ticker': INDEX_TICKERS[s], 'type': 'index', 'market': 'global', 'name_cn': symbol}
if su.endswith('=F'):
return {'ticker': su, 'type': 'commodity', 'market': 'global', 'name_cn': ''}
if '-USD' in su or '-USDT' in su:
return {'ticker': su, 'type': 'crypto', 'market': 'global', 'name_cn': ''}
if su.endswith('=X'):
return {'ticker': su, 'type': 'forex', 'market': 'global', 'name_cn': ''}
if symbol.startswith('^'):
return {'ticker': symbol, 'type': 'index', 'market': 'global', 'name_cn': ''}
return {'ticker': su, 'type': 'stock', 'market': 'US', 'name_cn': ''}
def fetch(symbol: str, period: str = '1y', interval: str = '1d',
start: str = None, end: str = None) -> pd.DataFrame:
"""
通过 yfinance 获取数据,返回标准化 DataFrame (Open, High, Low, Close, Volume)
Args:
symbol: ticker 或中文名(自动映射)
period: 1d/5d/1mo/3mo/6mo/1y/2y/5y/10y/ytd/max
interval: 1m/5m/15m/30m/1h/1d/1wk/1mo
start/end: YYYY-MM-DD 格式,指定时 period 无效
"""
import yfinance as yf
asset = identify_asset_type(symbol)
ticker = asset['ticker']
stock = yf.Ticker(ticker)
hist = stock.history(start=start, end=end, interval=interval) if start else stock.history(period=period, interval=interval)
if hist.empty:
raise ValueError(f"未获取到 {ticker} 的数据")
# 部分外汇/指数可能没有 Volume 列
cols = ['Open', 'High', 'Low', 'Close']
if 'Volume' in hist.columns:
cols.append('Volume')
df = hist[cols].copy()
if 'Volume' not in df.columns:
df['Volume'] = 0
df.index.name = 'Date'
return df
def fetch_multi(symbols: list, period: str = '1y', column: str = 'Close',
how: str = 'inner') -> pd.DataFrame:
"""获取多资产数据合并为 DataFrame,列名为资产代码
Args:
how: 合并方式,'inner'(交集,无NaN) / 'outer'(并集,可能有NaN)
"""
dfs = {}
for s in symbols:
try:
df = fetch(s, period=period)
asset = identify_asset_type(s)
label = asset.get('name_cn') or asset['ticker']
dfs[label] = df[column]
except Exception as e:
print(f"⚠️ {s}: {e}")
if not dfs:
return pd.DataFrame()
result = pd.DataFrame(dfs)
if how == 'inner':
result = result.dropna()
return result
def get_info(symbol: str) -> dict:
"""获取资产基本信息(仅 yfinance 支持的)"""
import yfinance as yf
return yf.Ticker(identify_asset_type(symbol)['ticker']).info
def check_env():
"""检查环境依赖"""
pkgs = {
'pandas': 'pandas', 'numpy': 'numpy', 'matplotlib': 'matplotlib',
'seaborn': 'seaborn', 'yfinance': 'yfinance', 'mplfinance': 'mplfinance',
'statsmodels': 'statsmodels', 'arch': 'arch', 'scipy': 'scipy',
'sklearn': 'scikit-learn', 'hmmlearn': 'hmmlearn', 'ruptures': 'ruptures',
'networkx': 'networkx', 'pywt': 'PyWavelets',
}
optional = {'prophet': 'prophet'}
print("🔍 环境检查\n" + "=" * 40)
ok = True
for mod, pkg in pkgs.items():
try:
__import__(mod)
print(f" ✅ {pkg}")
except ImportError:
print(f" ❌ {pkg}")
ok = False
print("\n📦 可选:")
for mod, pkg in optional.items():
try:
__import__(mod)
print(f" ✅ {pkg}")
except ImportError:
print(f" ⚪ {pkg}")
print("=" * 40)
print("✅ 就绪" if ok else "⚠️ 请运行: pip install -r requirements.txt")
return ok
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='yfinance 数据获取器')
parser.add_argument('symbol', nargs='?')
parser.add_argument('--check', action='store_true')
parser.add_argument('--period', default='1y')
parser.add_argument('--interval', default='1d')
parser.add_argument('--start', default=None)
parser.add_argument('--end', default=None)
parser.add_argument('--save', default=None)
args = parser.parse_args()
if args.check:
check_env()
elif args.symbol:
df = fetch(args.symbol, args.period, args.interval, args.start, args.end)
print(f"📈 {len(df)} 条 ({df.index[0].date()} ~ {df.index[-1].date()})")
print(df.tail(10))
if args.save:
df.to_csv(args.save)
print(f"💾 → {args.save}")
else:
parser.print_help()
#!/usr/bin/env python3
"""
技术指标计算库
所有函数接受 DataFrame (需含 Close, High, Low, Volume 列) 并返回新增列后的 DataFrame
用法:
from indicators import add_all, add_ma, add_macd, add_rsi, add_kdj, add_bollinger
df = add_all(df) # 一次性计算全部指标
df = add_macd(df) # 只加 MACD
signals = generate_signals(df) # 生成交易信号
"""
import pandas as pd
import numpy as np
# ============================================================
# 趋势指标
# ============================================================
def add_ma(df, periods=(5, 10, 20, 60, 120, 250), col='Close'):
"""简单移动平均线"""
for p in periods:
df[f'MA{p}'] = df[col].rolling(window=p).mean()
return df
def add_ema(df, periods=(12, 26, 50, 200), col='Close'):
"""指数移动平均线"""
for p in periods:
df[f'EMA{p}'] = df[col].ewm(span=p, adjust=False).mean()
return df
def add_macd(df, fast=12, slow=26, signal=9, col='Close'):
"""MACD 指标"""
ema_f = df[col].ewm(span=fast, adjust=False).mean()
ema_s = df[col].ewm(span=slow, adjust=False).mean()
df['MACD_DIF'] = ema_f - ema_s
df['MACD_DEA'] = df['MACD_DIF'].ewm(span=signal, adjust=False).mean()
df['MACD_HIST'] = 2 * (df['MACD_DIF'] - df['MACD_DEA'])
return df
# ============================================================
# 震荡指标
# ============================================================
def add_rsi(df, periods=(6, 14), col='Close'):
"""RSI 相对强弱指数"""
for p in periods:
delta = df[col].diff()
gain = delta.where(delta > 0, 0).rolling(window=p).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=p).mean()
rs = gain / loss
df[f'RSI{p}'] = 100 - (100 / (1 + rs))
return df
def add_kdj(df, n=9, col_close='Close', col_high='High', col_low='Low'):
"""KDJ 指标"""
low_min = df[col_low].rolling(window=n).min()
high_max = df[col_high].rolling(window=n).max()
denom = (high_max - low_min).replace(0, np.nan) # 避免涨跌停/停牌时除零
rsv = ((df[col_close] - low_min) / denom * 100).fillna(50) # 除零时 RSV 默认 50
df['KDJ_K'] = rsv.ewm(com=2, adjust=False).mean()
df['KDJ_D'] = df['KDJ_K'].ewm(com=2, adjust=False).mean()
df['KDJ_J'] = 3 * df['KDJ_K'] - 2 * df['KDJ_D']
return df
def add_bollinger(df, period=20, std_dev=2, col='Close'):
"""布林带"""
df['BOLL_MID'] = df[col].rolling(window=period).mean()
std = df[col].rolling(window=period).std()
df['BOLL_UPPER'] = df['BOLL_MID'] + std_dev * std
df['BOLL_LOWER'] = df['BOLL_MID'] - std_dev * std
return df
# ============================================================
# 成交量指标
# ============================================================
def add_obv(df, col_close='Close', col_volume='Volume'):
"""OBV 能量潮"""
df['OBV'] = (np.sign(df[col_close].diff()) * df[col_volume]).fillna(0).cumsum()
return df
def add_volume_ratio(df, period=5, col_volume='Volume'):
"""量比"""
df['VOL_RATIO'] = df[col_volume] / df[col_volume].rolling(window=period).mean()
return df
# ============================================================
# 波动率指标
# ============================================================
def add_atr(df, period=14, col_high='High', col_low='Low', col_close='Close'):
"""ATR 真实波动幅度均值"""
tr1 = df[col_high] - df[col_low]
tr2 = abs(df[col_high] - df[col_close].shift(1))
tr3 = abs(df[col_low] - df[col_close].shift(1))
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
df[f'ATR{period}'] = tr.rolling(window=period).mean()
return df
# ============================================================
# 组合函数
# ============================================================
def add_all(df):
"""一次性计算全部技术指标"""
df = add_ma(df)
df = add_ema(df)
df = add_macd(df)
df = add_rsi(df)
df = add_kdj(df)
df = add_bollinger(df)
df = add_obv(df)
df = add_volume_ratio(df)
df = add_atr(df)
return df
# ============================================================
# 信号生成
# ============================================================
def generate_signals(df):
"""
基于技术指标生成交易信号汇总
返回: [(信号名, 方向, 说明), ...]
"""
if len(df) < 2:
return [('⚠️ 数据不足', '无法判断', '需要至少2行数据才能生成信号')]
latest = df.iloc[-1]
prev = df.iloc[-2]
signals = []
# MACD
if 'MACD_DIF' in df.columns:
if prev['MACD_DIF'] < prev['MACD_DEA'] and latest['MACD_DIF'] > latest['MACD_DEA']:
signals.append(('🟢 MACD 金叉', '看多', 'DIF 上穿 DEA'))
elif prev['MACD_DIF'] > prev['MACD_DEA'] and latest['MACD_DIF'] < latest['MACD_DEA']:
signals.append(('🔴 MACD 死叉', '看空', 'DIF 下穿 DEA'))
# RSI
rsi = latest.get('RSI14', 50)
if rsi > 70:
signals.append(('🔴 RSI 超买', '看空', f'RSI={rsi:.1f} > 70'))
elif rsi < 30:
signals.append(('🟢 RSI 超卖', '看多', f'RSI={rsi:.1f} < 30'))
# KDJ
if 'KDJ_K' in df.columns:
if prev['KDJ_K'] < prev['KDJ_D'] and latest['KDJ_K'] > latest['KDJ_D']:
signals.append(('🟢 KDJ 金叉', '看多', 'K 上穿 D'))
elif prev['KDJ_K'] > prev['KDJ_D'] and latest['KDJ_K'] < latest['KDJ_D']:
signals.append(('🔴 KDJ 死叉', '看空', 'K 下穿 D'))
# 均线排列
if all(k in latest.index for k in ['MA20', 'MA60']):
if latest['Close'] > latest['MA20'] > latest['MA60']:
signals.append(('🟢 多头排列', '看多', '价格 > MA20 > MA60'))
elif latest['Close'] < latest['MA20'] < latest['MA60']:
signals.append(('🔴 空头排列', '看空', '价格 < MA20 < MA60'))
# 布林带
if 'BOLL_UPPER' in df.columns:
if latest['Close'] > latest['BOLL_UPPER']:
signals.append(('🔴 突破上轨', '注意', '可能高位回调'))
elif latest['Close'] < latest['BOLL_LOWER']:
signals.append(('🟢 跌破下轨', '注意', '可能低位反弹'))
# 量价
if 'VOL_RATIO' in df.columns and latest.get('VOL_RATIO', 1) > 2:
signals.append(('⚡ 放量', '关注', f'量比={latest["VOL_RATIO"]:.1f}'))
return signals
Related skills
How it compares
Choose financial-data-analysis when the goal is pre-build fundamental research and feature scoping rather than implementing live market data API connectors.
FAQ
What does financial-data-analysis help developers scope?
financial-data-analysis helps developers scope dashboards, trading assistants, and fintech analytics features. The skill stress-tests investment theses and compares company fundamentals before engineering effort is committed.
When should developers use financial-data-analysis?
financial-data-analysis fits early product validation when market and company datasets must inform feature design. Use it to compare fundamentals and define analytical workflows before building financial software.