Fix Chinese font rendering in all chart outputs

- Add src/font_config.py: centralized font detection that auto-selects
  from Noto Sans SC > Hiragino Sans GB > STHeiti > Arial Unicode MS
- Replace hardcoded font lists in all 18 modules with unified config
- Add .gitignore for __pycache__, .DS_Store, venv, etc.
- Regenerate all 70 charts with correct Chinese rendering

Previously, 7 modules (fft, wavelet, acf, fractal, hurst, indicators,
patterns) had no Chinese font config at all, causing □□□ rendering.
The remaining 11 modules used a hardcoded fallback list that didn't
prioritize the best available system font.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 11:21:01 +08:00
parent f4c4408708
commit 704cc2267d
92 changed files with 1029 additions and 36 deletions

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
*.egg
dist/
build/
# Virtual environments
.venv/
venv/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Testing
.pytest_cache/
.coverage
htmlcov/
# Jupyter
.ipynb_checkpoints/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 KiB

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 810 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -15,8 +15,23 @@ BTC/USDT 价格规律性分析 — 综合结论报告
----------------------------------------------------------------------
模块 得分 强度 发现数
----------------------------------------------------------------------
fft 0.00 none 0
fractal 0.00 none 0
power_law 0.00 none 0
wavelet 0.00 none 0
acf 0.00 none 0
returns 0.00 none 0
volatility 0.00 none 0
hurst 0.00 none 0
volume_price 0.00 none 0
time_series 0.00 none 0
causality 0.00 none 0
calendar 0.00 none 0
halving 0.00 none 0
indicators 0.00 none 0
patterns 0.00 none 0
clustering 0.00 none 0
anomaly 0.00 none 0
----------------------------------------------------------------------
## 强证据规律(可重复、有经济意义):
@@ -26,8 +41,23 @@ patterns 0.00 none 0
(无)
## 弱证据/不显著:
* indicators
* fft
* time_series
* clustering
* patterns
* indicators
* halving
* calendar
* causality
* volume_price
* fractal
* hurst
* volatility
* returns
* acf
* wavelet
* power_law
* anomaly
======================================================================
注: 得分基于各模块自报告的统计检验结果。

View File

@@ -10,6 +10,9 @@ import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from src.font_config import configure_chinese_font
configure_chinese_font()
from statsmodels.tsa.stattools import acf, pacf
from statsmodels.stats.diagnostic import acorr_ljungbox
from pathlib import Path

View File

@@ -705,9 +705,8 @@ def run_anomaly_analysis(
print(f"数据范围: {df.index.min()} ~ {df.index.max()}")
print(f"样本数量: {len(df)}")
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
# --- 集成异常检测 ---
print("\n>>> [1/5] 执行集成异常检测...")

View File

@@ -12,9 +12,8 @@ from pathlib import Path
from itertools import combinations
from scipy import stats
# 中文显示配置
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
# 星期名称映射(中英文)
WEEKDAY_NAMES_CN = {0: '周一', 1: '周二', 2: '周三', 3: '周四',

View File

@@ -543,9 +543,8 @@ def run_causality_analysis(
print(f"因果变量对数: {len(CAUSALITY_PAIRS)}")
print(f"总检验次数(含所有滞后): {len(CAUSALITY_PAIRS) * len(TEST_LAGS)}")
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
# --- 日线级 Granger 因果检验 ---
print("\n>>> [1/4] 执行日线级 Granger 因果检验...")

View File

@@ -632,9 +632,8 @@ def run_clustering_analysis(df: pd.DataFrame, output_dir: "str | Path" = "output
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# 设置中文字体macOS
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
print("=" * 60)
print(" BTC 市场状态聚类与马尔可夫链分析")

View File

@@ -3,6 +3,9 @@
import matplotlib
matplotlib.use("Agg")
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

60
src/font_config.py Normal file
View File

@@ -0,0 +1,60 @@
"""
统一 matplotlib 中文字体配置。
所有绘图模块在创建图表前应调用 configure_chinese_font()。
"""
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
_configured = False
# 按优先级排列的中文字体候选列表
_CHINESE_FONT_CANDIDATES = [
'Noto Sans SC', # Google 思源黑体(最佳渲染质量)
'Hiragino Sans GB', # macOS 系统自带
'STHeiti', # macOS 系统自带
'Arial Unicode MS', # macOS/Windows 通用
'SimHei', # Windows 黑体
'WenQuanYi Micro Hei', # Linux 文泉驿
'DejaVu Sans', # 最终回退(不支持中文,但不会崩溃)
]
def _find_available_chinese_fonts():
"""检测系统中实际可用的中文字体。"""
available = []
for font_name in _CHINESE_FONT_CANDIDATES:
try:
path = fm.findfont(
fm.FontProperties(family=font_name),
fallback_to_default=False
)
if path and 'LastResort' not in path:
available.append(font_name)
except Exception:
continue
return available if available else ['DejaVu Sans']
def configure_chinese_font():
"""
配置 matplotlib 使用中文字体。
- 自动检测系统可用的中文字体
- 设置 sans-serif 字体族
- 修复负号显示问题
- 仅在首次调用时执行,后续调用为空操作
"""
global _configured
if _configured:
return
available = _find_available_chinese_fonts()
plt.rcParams['font.sans-serif'] = available
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.family'] = 'sans-serif'
_configured = True

View File

@@ -13,6 +13,9 @@
import matplotlib
matplotlib.use('Agg')
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

View File

@@ -10,9 +10,8 @@ import matplotlib.ticker as mticker
from pathlib import Path
from scipy import stats
# 中文显示配置
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
# BTC 减半日期(数据范围 2017-2026 内的两次减半)
HALVING_DATES = [

View File

@@ -15,6 +15,9 @@ Hurst指数分析模块
import matplotlib
matplotlib.use('Agg')
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

View File

@@ -9,6 +9,9 @@
import matplotlib
matplotlib.use('Agg')
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

View File

@@ -8,6 +8,9 @@ K线形态识别与统计验证模块
import matplotlib
matplotlib.use('Agg')
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

View File

@@ -15,9 +15,8 @@ from scipy.optimize import curve_fit
from pathlib import Path
from typing import Tuple, Dict
# 中文显示支持
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
def _compute_days_since_start(df: pd.DataFrame) -> np.ndarray:

View File

@@ -446,9 +446,8 @@ def run_returns_analysis(df: pd.DataFrame, output_dir: str = "output/returns"):
# --- 生成可视化 ---
print("\n>>> 生成可视化图表...")
# 设置中文字体(兼容多系统)
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
plot_histogram_vs_normal(daily_returns, output_dir)
plot_qq(daily_returns, output_dir)

View File

@@ -643,9 +643,8 @@ def run_time_series_analysis(df: pd.DataFrame, output_dir: "str | Path" = "outpu
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# 设置中文字体macOS
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
print("=" * 60)
print(" BTC 时间序列预测分析")

View File

@@ -55,11 +55,8 @@ EVIDENCE_COLORS = {
def apply_style():
"""应用全局matplotlib样式"""
plt.rcParams.update(STYLE_CONFIG)
try:
plt.rcParams["font.sans-serif"] = ["Arial Unicode MS", "SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
except Exception:
pass
from src.font_config import configure_chinese_font
configure_chinese_font()
def ensure_dir(path):

View File

@@ -584,9 +584,8 @@ def run_volatility_analysis(df: pd.DataFrame, output_dir: str = "output/volatili
daily_returns = log_returns(df['close'])
print(f"日对数收益率样本数: {len(daily_returns)}")
# 设置中文字体(兼容多系统)
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
# 固定随机种子以保证杠杆效应散点图采样可复现
np.random.seed(42)

View File

@@ -15,9 +15,8 @@ from statsmodels.tsa.stattools import grangercausalitytests
from pathlib import Path
from typing import Dict, List, Tuple
# 中文显示支持
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
from src.font_config import configure_chinese_font
configure_chinese_font()
# =============================================================================

View File

@@ -3,6 +3,9 @@
import matplotlib
matplotlib.use('Agg')
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np
import pandas as pd
import pywt