本帖最后由 isaac 于 2025-1-10 12:13 编辑
import akshare as ak
import cnlunar
import datetime
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
import logging
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 1. 获取指数数据
def get_index_data(symbol):
try:
today = datetime.datetime.now() # 结束日期为今天
start_date = datetime.datetime(today.year - 1, 1, 1) # 起始日期为去年1月1日
index_df = ak.stock_zh_index_daily(symbol=symbol) # 获取指数数据
# 检查数据是否为空
if index_df.empty:
raise ValueError(f"{symbol} 数据为空,请检查数据源或网络连接。")
# 将日期列转换为 datetime 类型,并设置为索引
index_df['date'] = pd.to_datetime(index_df['date'])
index_df.set_index('date', inplace=True)
# 过滤数据,确保在去年1月1日到今天之间
index_df = index_df[(index_df.index >= start_date) & (index_df.index <= today)]
return index_df
except Exception as e:
logging.error(f"获取 {symbol} 数据时出错: {e}")
raise
# 2. 获取二十四节气日期
def get_solar_terms():
try:
today = datetime.datetime.now()
start_date = datetime.datetime(today.year - 1, 1, 1) # 起始日期为去年1月1日
# 获取当前年份和上一年的节气数据
solar_terms = {}
for year in [today.year - 1, today.year]:
date = datetime.datetime(year, 1, 1)
lunar = cnlunar.Lunar(date, godType='8char')
# 更新节气字典
solar_terms.update(lunar.thisYearSolarTermsDic)
# 将节气数据整理为 DataFrame
solar_terms_list = []
for term, date_tuple in solar_terms.items():
try:
# 将 (月, 日) 转换为完整日期
month, day = date_tuple
date = datetime.datetime(today.year, month, day) # 使用当前年份
# 如果日期超过今天,则使用上一年份
if date > today:
date = datetime.datetime(today.year - 1, month, day)
# 确保日期在去年1月1日到今天之间
if start_date <= date <= today:
solar_terms_list.append({'Date': date, 'Solar Term': term}) # 确保列名为 'Date' 和 'Solar Term'
except (TypeError, ValueError) as e:
logging.warning(f"跳过无效日期: {date_tuple}, 错误: {e}")
# 转换为 DataFrame
solar_terms_df = pd.DataFrame(solar_terms_list)
solar_terms_df.set_index('Date', inplace=True) # 将 'Date' 列设置为索引
return solar_terms_df
except Exception as e:
logging.error(f"获取二十四节气数据时出错: {e}")
raise
# 3. 绘制指数走势图并标注节气
def plot_index_with_solar_terms(index_dfs, solar_terms_df, titles):
try:
fig, axes = plt.subplots(len(index_dfs), 1, figsize=(14, 10), sharex=True)
fig.suptitle('上证、深证、沪深300指数走势图(含节气标注)', fontsize=16)
for i, (ax, index_df, title) in enumerate(zip(axes, index_dfs, titles)):
# 绘制指数趋势线
ax.plot(index_df.index, index_df['close'], label=f'{title}收盘价', color='b')
# 标记是否已添加图例
added_trading_day = False
added_non_trading_day = False
# 用散点图标注节气
for date, row in solar_terms_df.iterrows():
# 找到指数数据中最接近的日期
closest_date = min(index_df.index, key=lambda x: abs(x - date))
# 判断是否为交易日
if closest_date == date:
# 如果是交易日,用实心圆标注
if not added_trading_day:
ax.scatter(closest_date, index_df.loc[closest_date, 'close'], color='red', marker='o', s=100, label='节气(交易日)')
added_trading_day = True
else:
ax.scatter(closest_date, index_df.loc[closest_date, 'close'], color='red', marker='o', s=100)
else:
# 如果不是交易日,用空心圆标注
if not added_non_trading_day:
ax.scatter(closest_date, index_df.loc[closest_date, 'close'], color='red', marker='o', s=100, facecolors='none', label='节气(非交易日)')
added_non_trading_day = True
else:
ax.scatter(closest_date, index_df.loc[closest_date, 'close'], color='red', marker='o', s=100, facecolors='none')
# 标注节气名称
ax.text(closest_date, index_df.loc[closest_date, 'close'], row['Solar Term'], fontsize=10, ha='right', va='bottom')
# 设置子图属性
ax.set_title(title, fontsize=14)
ax.set_ylabel('收盘价', fontsize=12)
ax.grid(True)
ax.legend()
# 设置共享的 x 轴属性
axes[-1].set_xlabel('日期', fontsize=12)
axes[-1].xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) # 设置日期格式
axes[-1].xaxis.set_major_locator(mdates.MonthLocator()) # 按月显示刻度
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
except Exception as e:
logging.error(f"绘制图表时出错: {e}")
raise
# 主程序
if __name__ == "__main__":
try:
# 获取数据
sh_df = get_index_data("sh000001") # 上证指数
sz_df = get_index_data("sz399001") # 深证成指
hs300_df = get_index_data("sh000300") # 沪深300指数
solar_terms_df = get_solar_terms()
# 打印数据以检查
logging.info("上证指数数据:")
logging.info(sh_df.head())
logging.info("深证成指数据:")
logging.info(sz_df.head())
logging.info("沪深300指数数据:")
logging.info(hs300_df.head())
logging.info("\n二十四节气数据:")
logging.info(solar_terms_df)
# 绘制走势图并标注节气
plot_index_with_solar_terms(
[sh_df, sz_df, hs300_df], # 指数数据列表
solar_terms_df, # 节气数据
["上证指数", "深证成指", "沪深300指数"] # 图表标题列表
)
except Exception as e:
logging.error(f"程序运行出错: {e}") |