从数据抓取到策略回测:用Python和yfinance打造你的智能股票分析引擎
如果你已经能用Python写几行代码,从yfinance里拉出股票的历史价格,画几条移动平均线,觉得这就是“量化分析”了,那我得说,你可能才刚刚推开这扇大门的一条缝。真正的乐趣和挑战,在于如何将这些零散的数据点、指标公式,组装成一个能够自主运行、持续学习并辅助你决策的自动化系统。这不再是简单的数据可视化,而是构建一个属于你自己的、24小时不间断的“数字交易员”雏形。今天,我们就来聊聊,如何超越基础的数据获取,用yfinance为核心,搭建一个具备技术指标计算、信号生成、甚至初步回测能力的自动化分析工具。这个过程,就像在组装一台精密的仪器,每一个齿轮(代码模块)都必须严丝合缝。
1. 超越基础:构建模块化、可扩展的数据引擎
直接从yf.Ticker(“AAPL”).history()开始写分析代码,是大多数教程的起点,但也是项目后期难以维护的根源。一个健壮的分析系统,其数据层必须是独立、可靠且易于管理的。
1.1 设计一个稳健的数据获取与缓存层
直接、反复地从网络API获取数据,不仅效率低下,还容易因网络波动或API限制导致程序中断。我们的第一步,是创建一个带有本地缓存功能的数据管理器。
import yfinance as yf
import pandas as pd
import os
import pickle
from datetime import datetime, timedelta
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class StockDataFetcher:
def __init__(self, cache_dir='./stock_cache'):
self.cache_dir = cache_dir
os.makedirs(cache_dir, exist_ok=True)
def _get_cache_path(self, ticker, start_date, end_date, interval):
"""生成唯一的缓存文件名"""
filename = f"{ticker}_{start_date}_{end_date}_{interval}.pkl"
return os.path.join(self.cache_dir, filename)
def fetch_historical_data(self, ticker, start_date, end_date, interval='1d', force_update=False):
"""
获取历史数据,优先使用缓存
:param ticker: 股票代码
:param start_date: 开始日期 'YYYY-MM-DD'
:param end_date: 结束日期 'YYYY-MM-DD'
:param interval: 数据间隔 ('1d', '1h', '1wk'等)
:param force_update: 是否强制更新缓存
:return: pandas DataFrame
"""
cache_path = self._get_cache_path(ticker, start_date, end_date, interval)
# 检查缓存是否存在且未过期(例如,设定缓存有效期为1天)
if not force_update and os.path.exists(cache_path):
file_mtime = datetime.fromtimestamp(os.path.getmtime(cache_path))
if datetime.now() - file_mtime < timedelta(days=1):
logger.info(f"从缓存加载数据: {ticker}")
with open(cache_path, 'rb') as f:
return pickle.load(f)
# 从yfinance获取数据
logger.info(f"从网络获取数据: {ticker}")
try:
stock = yf.Ticker(ticker)
# 注意:对于非日线数据,period参数可能更可靠
if interval == '1d':
df = stock.history(start=start_date, end=end_date, interval=interval)
else:
# 对于日内数据,使用period参数可能更合适,这里做简单处理
df = stock.history(start=start_date, end=end_date, interval=interval, prepost=False)
if df.empty:
raise ValueError(f"未获取到{ticker}在指定时间段的数据")
# 缓存数据
with open(cache_path, 'wb') as f:
pickle.dump(df, f)
logger.info(f"数据已缓存至: {cache_path}")
return df
except Exception as e:
logger.error(f"获取{ticker}数据失败: {e}")
# 如果失败,尝试返回旧的缓存(如果有)
if os.path.exists(cache_path):
logger.warning(f"尝试使用过期缓存")
with open(cache_path, 'rb') as f:
return pickle.load(f)
raise
# 使用示例
if __name__ == "__main__":
fetcher = StockDataFetcher()
# 第一次运行会从网络获取并缓存
aapl_data = fetcher.fetch_historical_data('AAPL', '2023-01-01', '2024-01-01')
# 第二次运行(同一天内)会直接读取缓存,速度极快
aapl_data_cached = fetcher.fetch_historical_data('AAPL', '2023-01-01', '2024-01-01')
print(aapl_data_cached.head())
这个类做了几件关键的事:本地缓存避免了重复请求,异常处理确保了程序的鲁棒性,日志记录让你能追踪数据流。这是构建自动化系统的基石——你总不希望你的分析脚本因为临时的网络问题而崩溃。
1.2 高效获取并结构化基本面数据
yfinance的.info属性返回一个庞大的字典,包含上百个字段。直接使用这个字典既混乱又低效。我们需要一个解析器,将其转化为结构化的、易于查询的DataFrame,并特别关注那些对分析真正有用的基本面分析指标。
class FundamentalDataParser:
# 定义我们关心的关键财务与市场指标类别
CATEGORIES = {
'公司概况': ['longName', 'sector', 'industry', 'fullTimeEmployees', 'website'],
'市值与交易': ['marketCap', 'averageVolume', 'averageVolume10days', 'volume', 'sharesOutstanding'],
'估值指标': ['trailingPE', 'forwardPE', 'priceToBook', 'priceToSalesTrailing12Months', 'enterpriseToRevenue', 'enterpriseToEbitda'],
'盈利能力': ['trailingEps', 'forwardEps', 'profitMargins', 'operatingMargins', 'returnOnEquity', 'returnOnAssets'],
'财务健康度': ['totalDebt', 'debtToEquity', 'currentRatio', 'quickRatio', 'totalCash', 'freeCashflow'],
'股息与回报': ['dividendYield', 'dividendRate', 'payoutRatio', 'fiveYearAvgDividendYield'],
'增长预期': ['earningsQuarterlyGrowth', 'revenueQuarterlyGrowth', 'earningsGrowth', 'revenueGrowth']
}
@staticmethod
def parse_to_dataframe(info_dict):
"""将info字典按类别解析为结构化的DataFrame"""
records = []
for categor

&spm=1001.2101.3001.5002&articleId=150374649&d=1&t=3&u=04a68a61c6fb46f98a725bf68c3d6889)
2639

被折叠的 条评论
为什么被折叠?



