查看全课大纲(第 15 / 52 节)
- 1.量化分析基本概念:从咖啡馆到华尔街
- 2.量化交易员学习路线:从零基础到策略开发
- 3.量化策略生命周期:数据分析的五大流程
- 4.量化数据类型:数值型与类别型数据
- 5.量化沙盒搭建(1):Python与Anaconda安装
- 6.量化沙盒搭建(2):Jupyter Notebook与库配置
- 7.本地多源数据:量化历史回测的基石
- 8.TXT数据流操作:处理金融交易日志
- 9.JSON数据流操作:解析股票行情API响应
- 10.CSV行情文件操作:读取与存储历史K线
- 11.Excel报表操作:清洗财报与财务指标
- 12.量化数据库:SQL常用查询语法
- 13.量化数据库:Python连接与写入
- 14.量化多表连接:关联股票信息与行情
- 15.实战:小象咖啡店营业数据与个股财务指标的多源数据分析
- 16.量化数据采集:爬虫工作原理与合规
- 17.量化爬虫核心:URL管理与去重策略
- 18.量化网页下载:防反爬与请求头伪装
- 19.网页解析(1):使用XPath定位财经新闻
- 20.网页解析(2):使用CSS选择器与ItemLoader
- 21.Scrapy框架:构建工业级量化爬虫
- 22.Scrapy实战(1):新建项目与数据模型定义
- 23.Scrapy实战(2):编写财经爬虫逻辑
- 24.Scrapy实战(3):启用数据管道清洗行情
- 25.Scrapy实战(4):防反爬中间件配置
- 26.实战:构建每日热门个股行情自动采集系统
- 27.NumPy与SciPy:加速量化矩阵计算
- 28.NumPy数组:构建股票价格矩阵
- 29.数组切片:提取特定股票与交易日数据
- 30.NumPy常用方法:计算均值与波动率
- 31.向量化计算:摆脱缓慢的For循环
- 32.通用函数ufunc:计算对数收益率与累计收益
- 33.实战:股票组合资产收益率与协方差矩阵计算
- 34.Pandas Series:管理单只股票收盘价
- 35.Pandas DataFrame:构建多只股票行情面板
- 36.Pandas Index:时间序列索引与对齐
- 37.DataFrame索引操作:loc与iloc定位行情
- 38.索引总结:量化多字段筛选与定位
- 39.运算与对齐:合并不同交易日的数据
- 40.Pandas map:对股票代码与状态做映射
- 41.Pandas apply:滚动计算技术指标
- 42.排序操作:按市盈率与收益率对股票排名
- 43.数据清洗:填充与剔除停牌股票缺失值
- 44.数据清洗:剔除重复报价与异常交易数据
- 45.常用统计:量化最大回撤与分位数风险评估
- 46.极值定位:寻找历史最高价与最低价的发生日
- 47.风险与累计收益:计算年化波动率与累计回报
- 48.实战:小象咖啡店营业数据与热门个股时间序列清洗分析
- 49.Series索引操作:按位置与标签访问行情数据
- 50.文件读写操作:CSV行情数据导入导出
- 51.数据清洗:替换异常报价数据
- 52.常用统计:sum/mean/median/count聚合指标
实战:小象咖啡店营业数据与个股财务指标的多源数据分析
约 12 分钟
实战:欧洲职业足球比赛数据的多源数据分析
小象实战讲义 · 量化交易Python入门:从咖啡馆到华尔街
在量化交易中,历史回测数据往往以多种异构本地文件(TXT、CSV、JSON、Excel)和关系数据库形式存在。本章你将编写数据接口,读取小象咖啡的门店营收数据并解析股票行情API响应,打通量化回测的数据通道!
💡 核心导读
本节将带领读者完成欧洲职业足球数据的采集、清洗与分析全流程。通过Python数据科学工具链操作本地存储的SQLite数据库与CSV文件,掌握多源数据合并、特征工程与可视化分析技巧。核心技能包括:1) 使用Pandas进行多表关联查询 2) 构建比赛结果预测特征 3) 制作热力图展示球队表现矩阵。
数据准备与环境配置
数据库连接与探查
import sqlite3
import pandas as pd
from pathlib import Path
# 在内存中创建数据库并填充示例数据
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# 创建示例表结构并插入少量数据
cursor.execute('''
CREATE TABLE Match (
match_id INTEGER PRIMARY KEY,
season TEXT,
home_team_api_id INTEGER,
away_team_api_id INTEGER,
home_team_goal INTEGER,
away_team_goal INTEGER,
B365H REAL,
B365D REAL,
B365A REAL,
league_id INTEGER
)
''')
matches_data = [
(1, '2015/2016', 8634, 10281, 2, 1, 1.85, 3.60, 4.20, 1),
(2, '2015/2016', 9991, 9993, 1, 1, 2.10, 3.30, 3.50, 1),
(3, '2015/2016', 8635, 10282, 3, 0, 1.50, 4.00, 6.50, 2),
(4, '2014/2015', 8634, 10281, 1, 2, 2.00, 3.40, 3.80, 1),
]
cursor.executemany('INSERT INTO Match VALUES (?,?,?,?,?,?,?,?,?,?)', matches_data)
cursor.execute('''
CREATE TABLE Team (
team_api_id INTEGER PRIMARY KEY,
team_long_name TEXT
)
''')
teams_data = [
(8634, 'Real Madrid CF'),
(10281, 'FC Barcelona'),
(9991, 'Manchester United'),
(9993, 'Chelsea'),
(8635, 'FC Bayern Munich'),
(10282, 'Borussia Dortmund'),
]
cursor.executemany('INSERT INTO Team VALUES (?,?)', teams_data)
conn.commit()
# 查询表名
tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)
print(tables)name
0 Match
1 Team多源数据加载
import pandas as pd
import sqlite3
# 重新建立连接并查询
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# 重建Match表和数据
cursor.execute('''
CREATE TABLE Match (
match_id INTEGER PRIMARY KEY,
season TEXT,
home_team_api_id INTEGER,
away_team_api_id INTEGER,
home_team_goal INTEGER,
away_team_goal INTEGER,
B365H REAL,
B365D REAL,
B365A REAL,
league_id INTEGER
)
''')
matches_data = [
(1, '2015/2016', 8634, 10281, 2, 1, 1.85, 3.60, 4.20, 1),
(2, '2015/2016', 9991, 9993, 1, 1, 2.10, 3.30, 3.50, 1),
(3, '2015/2016', 8635, 10282, 3, 0, 1.50, 4.00, 6.50, 2),
]
cursor.executemany('INSERT INTO Match VALUES (?,?,?,?,?,?,?,?,?,?)', matches_data)
# 查询比赛数据
matches = pd.read_sql("SELECT * FROM Match WHERE season='2015/2016'", conn)
# 构造球员评分数据(模拟读取CSV)
ratings_data = {
'player_id': [101, 102, 103],
'player_name': ['Lionel Messi', 'Cristiano Ronaldo', 'Robert Lewandowski'],
'overall_rating': [94, 93, 91]
}
ratings = pd.DataFrame(ratings_data)
print("比赛数据字段示例:")
print(matches[['home_team_api_id', 'away_team_api_id', 'home_team_goal']].head(2))
print("\n球员评分数据示例:")
print(ratings.head(2))比赛数据字段示例:
home_team_api_id away_team_api_id home_team_goal
0 8634 10281 2
1 9991 9993 1
球员评分数据示例:
player_id player_name overall_rating
0 101 Lionel Messi 94
1 102 Cristiano Ronaldo 93核心分析流程
数据关联与特征工程
import pandas as pd
import sqlite3
# 重新建立连接并查询
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# 重建Match表和数据
cursor.execute('''
CREATE TABLE Match (
match_id INTEGER PRIMARY KEY,
season TEXT,
home_team_api_id INTEGER,
away_team_api_id INTEGER,
home_team_goal INTEGER,
away_team_goal INTEGER,
B365H REAL,
B365D REAL,
B365A REAL,
league_id INTEGER
)
''')
matches_data = [
(1, '2015/2016', 8634, 10281, 2, 1, 1.85, 3.60, 4.20, 1),
(2, '2015/2016', 9991, 9993, 1, 1, 2.10, 3.30, 3.50, 1),
(3, '2015/2016', 8635, 10282, 3, 0, 1.50, 4.00, 6.50, 2),
]
cursor.executemany('INSERT INTO Match VALUES (?,?,?,?,?,?,?,?,?,?)', matches_data)
# 重建Team表和数据
cursor.execute('''
CREATE TABLE Team (
team_api_id INTEGER PRIMARY KEY,
team_long_name TEXT
)
''')
teams_data = [
(8634, 'Real Madrid CF'),
(10281, 'FC Barcelona'),
(9991, 'Manchester United'),
(9993, 'Chelsea'),
(8635, 'FC Bayern Munich'),
(10282, 'Borussia Dortmund'),
]
cursor.executemany('INSERT INTO Team VALUES (?,?)', teams_data)
conn.commit()
# 查询数据
matches = pd.read_sql("SELECT * FROM Match WHERE season='2015/2016'", conn)
teams = pd.read_sql("SELECT * FROM Team", conn)
# 数据增强
enhanced_matches = matches.merge(
teams[['team_api_id', 'team_long_name']],
left_on='home_team_api_id',
right_on='team_api_id'
).rename(columns={'team_long_name': 'home_team_name'})
enhanced_matches['result'] = enhanced_matches.apply(
lambda row: 'home_win' if row['home_team_goal'] > row['away_team_goal'] else
('draw' if row['home_team_goal'] == row['away_team_goal'] else 'away_win'),
axis=1
)
features = enhanced_matches[[
'home_team_name',
'B365H',
'B365D',
'B365A',
'result'
]].dropna()
print("增强后的比赛数据特征:")
print(features)增强后的比赛数据特征:
home_team_name B365H B365D B365A result
0 Real Madrid CF 1.85 3.6 4.2 home_win
1 Manchester United 2.10 3.3 3.5 draw
2 FC Bayern Munich 1.50 4.0 6.5 home_win可视化分析
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
# 构造比赛数据
matches_data = {
'match_id': [1, 2, 3, 4, 5],
'league_id': [1, 1, 2, 2, 1],
'home_team_goal': [2, 1, 3, 0, 2],
'away_team_goal': [1, 1, 0, 2, 0]
}
matches = pd.DataFrame(matches_data)
matches['total_goals'] = matches['home_team_goal'] + matches['away_team_goal']
plt.figure(figsize=(12,6))
sns.boxplot(
x='league_id',
y='total_goals',
data=matches
)
plt.title('2015/2016赛季各联赛进球数分布')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
plt.close()高级分析技巧
球队对战热力图
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# 构造增强的比赛数据
data = {
'home_team_name': ['Real Madrid CF', 'Manchester United', 'FC Bayern Munich',
'Real Madrid CF', 'Manchester United'],
'away_team_name': ['FC Barcelona', 'Chelsea', 'Borussia Dortmund',
'Chelsea', 'Borussia Dortmund'],
'home_team_goal': [2, 1, 3, 1, 2],
'away_team_goal': [1, 1, 0, 0, 1]
}
enhanced_matches = pd.DataFrame(data)
# 计算主队场均进球对阵表现
team_pairs = enhanced_matches.groupby([
'home_team_name',
'away_team_name'
])['home_team_goal'].mean().unstack()
# 填充缺失值为0用于热力图
team_pairs_filled = team_pairs.fillna(0)
plt.figure(figsize=(10, 8))
sns.heatmap(
team_pairs_filled,
cmap='YlOrRd',
annot=True,
fmt='.1f',
linewidths=.5,
cbar_kws={'label': '场均进球'}
)
plt.title('主队场均进球对阵表现')
plt.tight_layout()
plt.show()
plt.close()比赛结果预测
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# 构造特征数据
features_data = {
'home_team_name': ['Real Madrid CF', 'Manchester United', 'FC Bayern Munich',
'Real Madrid CF', 'Manchester United'],
'B365H': [1.85, 2.10, 1.50, 1.90, 2.05],
'B365D': [3.60, 3.30, 4.00, 3.50, 3.40],
'B365A': [4.20, 3.50, 6.50, 4.00, 3.60],
'result': ['home_win', 'draw', 'home_win', 'home_win', 'home_win']
}
features = pd.DataFrame(features_data)
X = features[['B365H', 'B365D', 'B365A']]
y = features['result']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=42)
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
accuracy = model.score(X_test, y_test)
print(f"模型准确率:{accuracy:.2%}")模型准确率:50.00%📝 动手练一练
第 1 题 · 概念应用
在量化策略开发中,你正在构建一个多因子模型。其中一个因子是“动量因子”,定义为过去 20 个交易日的累计收益率。你从两个数据源获取数据:price_df(CSV 文件,包含每日收盘价)和 benchmark_df(SQLite 数据库查询结果,包含每日基准指数收益率)。你需要计算个股动量因子与基准动量因子的相关性。
以下关于数据对齐和处理的步骤描述中,哪一项最合理?
- A. 由于两个数据源都包含日期,可以直接使用
pd.concat([price_df, benchmark_df], axis=1)进行横向拼接,然后计算相关性。 - B. 先把两个 DataFrame 的日期列统一转换为
datetime类型,再使用pd.merge(price_df, benchmark_df, on='date', how='inner')进行内连接对齐,确保分析基于同一组交易日。 - C. 基准在周末没有数据,就用上周五的基准收益率填充周末,并把这些填充出来的周末行当作真实交易日参与 20 日累计收益率的计算。
- D. 计算 20 日累计收益率时,应使用原始价格序列直接计算,即使中间有缺失的交易日(如停牌),也无需进行填充或特殊处理。
👉 点击查看参考答案
答案:B
推理过程:
- A 错误:
pd.concat默认按索引拼接,如果两个 DataFrame 的索引(日期)顺序或数量不完全一致,会错误地对齐数据,导致“日期错位”问题,相关性计算无效。 - B 正确:统一为
datetime后再用inner连接,只保留两个数据源共有的交易日,这是同期相关性分析的标准流程(同格式的日期字符串也能合并,但先转 datetime 更稳妥、便于后续时间序列运算)。 - C 错误:把上周五的收益率填到周末、再把这些并不存在的交易日当作真实样本计入 20 日累计收益率,等于人为伪造了交易数据,会系统性扭曲因子与相关性。正确做法是先统一到交易日频率(如
.asfreq('B')),只在真实交易日上对齐。 - D 错误:股票停牌会导致价格序列出现缺失值,如果不处理(如向前填充),计算出的收益率序列会出现
NaN,进而影响后续相关性计算。通常需要对缺失价格进行填充(如停牌期间价格不变),或确保在计算收益率时,使用的价格序列是连续的。
💡 小象贴士:在量化分析中,确保不同数据源在时间维度上的严格对齐是避免“前视偏差”的关键一步。错误的日期对齐会使得回测结果完全失真。
第 2 题 · 代码填空
在分析中,你需要将球队信息表 (team_df) 与比赛数据表 (match_df) 进行关联,以获取主队和客队的完整名称。已知两个表的关联键名称不同:match_df 中使用 home_id 和 away_id,而 team_df 中使用 team_id 和 name。
请补全下面代码中 pd.merge 函数的参数,实现正确的关联,生成包含 home_team_name 和 away_team_name 的新 DataFrame enhanced_df。
import pandas as pd
# 模拟比赛数据
match_data = {
'match_id': [1, 2],
'home_id': [8634, 9991],
'away_id': [10281, 9993],
'home_goals': [2, 1],
'away_goals': [1, 1]
}
match_df = pd.DataFrame(match_data)
# 模拟球队数据
team_data = {
'team_id': [8634, 10281, 9991, 9993],
'name': ['Real Madrid CF', 'FC Barcelona', 'Manchester United', 'Chelsea']
}
team_df = pd.DataFrame(team_data)
# 关联获取主队名称
enhanced_df = pd.merge(match_df, team_df, left_on=______, right_on='team_id')
enhanced_df = enhanced_df.rename(columns={'name': 'home_team_name'})
# 关联获取客队名称
enhanced_df = pd.merge(enhanced_df, team_df, left_on=______, right_on='team_id')
enhanced_df = enhanced_df.rename(columns={'name': 'away_team_name'})
# 选择并展示关键列
result = enhanced_df[['match_id', 'home_team_name', 'away_team_name', 'home_goals', 'away_goals']]
print(result)👉 点击查看参考答案
空位应填:‘home_id’ 和 ‘away_id’
补全后的完整可运行代码:
import pandas as pd
# 模拟比赛数据
match_data = {
'match_id': [1, 2],
'home_id': [8634, 9991],
'away_id': [10281, 9993],
'home_goals': [2, 1],
'away_goals': [1, 1]
}
match_df = pd.DataFrame(match_data)
# 模拟球队数据
team_data = {
'team_id': [8634, 10281, 9991, 9993],
'name': ['Real Madrid CF', 'FC Barcelona', 'Manchester United', 'Chelsea']
}
team_df = pd.DataFrame(team_data)
# 关联获取主队名称
enhanced_df = pd.merge(match_df, team_df, left_on='home_id', right_on='team_id')
enhanced_df = enhanced_df.rename(columns={'name': 'home_team_name'})
# 关联获取客队名称
enhanced_df = pd.merge(enhanced_df, team_df, left_on='away_id', right_on='team_id')
enhanced_df = enhanced_df.rename(columns={'name': 'away_team_name'})
# 选择并展示关键列
result = enhanced_df[['match_id', 'home_team_name', 'away_team_name', 'home_goals', 'away_goals']]
print(result)match_id home_team_name away_team_name home_goals away_goals
0 1 Real Madrid CF FC Barcelona 2 1
1 2 Manchester United Chelsea 1 1本章小结
📋 行动清单
- 在本地SQLite数据库中执行跨表查询,合并Match表与Team表的主客场球队名称
- 使用Seaborn绘制两支特定球队的历史对战进球数折线图
- 修改预测模型,加入球队近期比赛结果作为时序特征
—— 小象教研组