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 277a5f067d
commit 68b1c6b45d
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 indicators 0.00 none 0
patterns 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 * 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 import matplotlib
matplotlib.use('Agg') matplotlib.use('Agg')
import matplotlib.pyplot as plt 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.tsa.stattools import acf, pacf
from statsmodels.stats.diagnostic import acorr_ljungbox from statsmodels.stats.diagnostic import acorr_ljungbox
from pathlib import Path from pathlib import Path

View File

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

View File

@@ -12,9 +12,8 @@ from pathlib import Path
from itertools import combinations from itertools import combinations
from scipy import stats from scipy import stats
# 中文显示配置 from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
# 星期名称映射(中英文) # 星期名称映射(中英文)
WEEKDAY_NAMES_CN = {0: '周一', 1: '周二', 2: '周三', 3: '周四', 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)}")
print(f"总检验次数(含所有滞后): {len(CAUSALITY_PAIRS) * len(TEST_LAGS)}") print(f"总检验次数(含所有滞后): {len(CAUSALITY_PAIRS) * len(TEST_LAGS)}")
# 设置中文字体 from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
# --- 日线级 Granger 因果检验 --- # --- 日线级 Granger 因果检验 ---
print("\n>>> [1/4] 执行日线级 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 = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)
# 设置中文字体macOS from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
print("=" * 60) print("=" * 60)
print(" BTC 市场状态聚类与马尔可夫链分析") print(" BTC 市场状态聚类与马尔可夫链分析")

View File

@@ -3,6 +3,9 @@
import matplotlib import matplotlib
matplotlib.use("Agg") matplotlib.use("Agg")
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt 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 import matplotlib
matplotlib.use('Agg') matplotlib.use('Agg')
from src.font_config import configure_chinese_font
configure_chinese_font()
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,9 +15,8 @@ from scipy.optimize import curve_fit
from pathlib import Path from pathlib import Path
from typing import Tuple, Dict from typing import Tuple, Dict
# 中文显示支持 from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
def _compute_days_since_start(df: pd.DataFrame) -> np.ndarray: 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>>> 生成可视化图表...") print("\n>>> 生成可视化图表...")
# 设置中文字体(兼容多系统) from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
plot_histogram_vs_normal(daily_returns, output_dir) plot_histogram_vs_normal(daily_returns, output_dir)
plot_qq(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 = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)
# 设置中文字体macOS from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
print("=" * 60) print("=" * 60)
print(" BTC 时间序列预测分析") print(" BTC 时间序列预测分析")

View File

@@ -55,11 +55,8 @@ EVIDENCE_COLORS = {
def apply_style(): def apply_style():
"""应用全局matplotlib样式""" """应用全局matplotlib样式"""
plt.rcParams.update(STYLE_CONFIG) plt.rcParams.update(STYLE_CONFIG)
try: from src.font_config import configure_chinese_font
plt.rcParams["font.sans-serif"] = ["Arial Unicode MS", "SimHei", "DejaVu Sans"] configure_chinese_font()
plt.rcParams["axes.unicode_minus"] = False
except Exception:
pass
def ensure_dir(path): 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']) daily_returns = log_returns(df['close'])
print(f"日对数收益率样本数: {len(daily_returns)}") print(f"日对数收益率样本数: {len(daily_returns)}")
# 设置中文字体(兼容多系统) from src.font_config import configure_chinese_font
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] configure_chinese_font()
plt.rcParams['axes.unicode_minus'] = False
# 固定随机种子以保证杠杆效应散点图采样可复现 # 固定随机种子以保证杠杆效应散点图采样可复现
np.random.seed(42) np.random.seed(42)

View File

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

View File

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