fetch_mairui_cyb50.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 麦儒数据 (mairui) API - 获取创业板50指数30分钟K线数据
  5. 文档: https://www.mairui.club/
  6. Token: AE17EE23-AAE4-492F-A959-EC883DFA5A76
  7. """
  8. import pandas as pd
  9. import requests
  10. import json
  11. import os
  12. from datetime import datetime, timedelta
  13. import time
  14. import warnings
  15. warnings.filterwarnings('ignore')
  16. class MairuiDataFetcher:
  17. """麦儒数据获取器"""
  18. def __init__(self):
  19. self.token = "AE17EE23-AAE4-492F-A959-EC883DFA5A76"
  20. self.base_url = "https://api.mairuiapi.com"
  21. self.data_dir = "data"
  22. os.makedirs(self.data_dir, exist_ok=True)
  23. def get_index_minute_data(self, index_code="399673.SZ", period="30", start_date=None, end_date=None):
  24. """
  25. 获取指数分钟历史数据
  26. :param index_code: 指数代码,创业板50 = 399673.SZ
  27. :param period: 周期,支持 1,5,15,30,60,d(日线)
  28. :param start_date: 开始时间 (YYYYMMDD)
  29. :param end_date: 结束时间 (YYYYMMDD)
  30. :return: DataFrame
  31. """
  32. try:
  33. # 麦儒API接口:获取指数历史K线数据
  34. # 格式: /hsindex/history/指数代码/周期/Token?st=开始时间&et=结束时间
  35. url = f"{self.base_url}/hsindex/history/{index_code}/{period}/{self.token}"
  36. # 添加时间参数
  37. params = {}
  38. if start_date:
  39. params['st'] = start_date
  40. if end_date:
  41. params['et'] = end_date
  42. if params:
  43. url += "?" + "&".join([f"{k}={v}" for k, v in params.items()])
  44. print(f"[麦儒数据] 请求URL: {url}")
  45. headers = {
  46. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  47. }
  48. response = requests.get(url, headers=headers, timeout=30)
  49. response.encoding = 'utf-8'
  50. if response.status_code != 200:
  51. print(f"[错误] HTTP状态码: {response.status_code}")
  52. return None
  53. data = response.json()
  54. if isinstance(data, dict) and 'error' in data:
  55. print(f"[错误] API返回错误: {data['error']}")
  56. return None
  57. if not data or not isinstance(data, list):
  58. print(f"[错误] 返回数据格式不正确")
  59. return None
  60. # 转换为DataFrame
  61. df = pd.DataFrame(data)
  62. # 标准化列名
  63. column_mapping = {
  64. 'd': 'DateTime', # 日期时间
  65. 'o': 'Open', # 开盘
  66. 'h': 'High', # 最高
  67. 'l': 'Low', # 最低
  68. 'c': 'Close', # 收盘
  69. 'v': 'Volume', # 成交量
  70. 'e': 'Amount', # 成交额
  71. 't': 'DateTime' # 有些接口用t表示时间
  72. }
  73. df.rename(columns=column_mapping, inplace=True)
  74. # 转换时间格式
  75. if 'DateTime' in df.columns:
  76. df['DateTime'] = pd.to_datetime(df['DateTime'])
  77. df.set_index('DateTime', inplace=True)
  78. df.sort_index(inplace=True)
  79. # 转换数值类型
  80. numeric_cols = ['Open', 'High', 'Low', 'Close', 'Volume', 'Amount']
  81. for col in numeric_cols:
  82. if col in df.columns:
  83. df[col] = pd.to_numeric(df[col], errors='coerce')
  84. print(f"[麦儒数据] 成功获取 {len(df)} 条数据")
  85. print(f"[麦儒数据] 时间范围: {df.index[0]} 至 {df.index[-1]}")
  86. return df
  87. except Exception as e:
  88. print(f"[错误] 获取数据失败: {e}")
  89. return None
  90. def get_index_history(self, index_code="399673"):
  91. """
  92. 获取指数历史数据(日线)
  93. :param index_code: 指数代码
  94. :return: DataFrame
  95. """
  96. try:
  97. # 麦儒API接口:获取指数历史数据
  98. url = f"{self.base_url}/zslsh/{index_code}/{self.token}"
  99. print(f"[麦儒数据] 获取历史数据: {url}")
  100. headers = {
  101. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  102. }
  103. response = requests.get(url, headers=headers, timeout=30)
  104. response.encoding = 'utf-8'
  105. if response.status_code != 200:
  106. print(f"[错误] HTTP状态码: {response.status_code}")
  107. return None
  108. data = response.json()
  109. if not data or not isinstance(data, list):
  110. print(f"[错误] 返回数据格式不正确")
  111. return None
  112. df = pd.DataFrame(data)
  113. # 标准化列名
  114. column_mapping = {
  115. 'd': 'DateTime',
  116. 'o': 'Open',
  117. 'h': 'High',
  118. 'l': 'Low',
  119. 'c': 'Close',
  120. 'v': 'Volume',
  121. 'e': 'Amount',
  122. 'zf': 'Change_Pct', # 涨跌幅
  123. 'zde': 'Change_Amount' # 涨跌额
  124. }
  125. df.rename(columns=column_mapping, inplace=True)
  126. if 'DateTime' in df.columns:
  127. df['DateTime'] = pd.to_datetime(df['DateTime'])
  128. df.set_index('DateTime', inplace=True)
  129. df.sort_index(inplace=True)
  130. return df
  131. except Exception as e:
  132. print(f"[错误] 获取历史数据失败: {e}")
  133. return None
  134. def get_cyb50_30min_2023_to_now(self):
  135. """
  136. 获取创业板50指数2023年至今的30分钟数据
  137. """
  138. print("=" * 70)
  139. print("麦儒数据 - 创业板50指数30分钟数据获取")
  140. print("=" * 70)
  141. # 设置时间范围
  142. start_date = "20230101"
  143. end_date = datetime.now().strftime("%Y%m%d")
  144. print(f"时间范围: {start_date} 至 {end_date}")
  145. # 获取30分钟数据
  146. data = self.get_index_minute_data(
  147. index_code="399673.SZ",
  148. period="30",
  149. start_date=start_date,
  150. end_date=end_date
  151. )
  152. if data is not None and not data.empty:
  153. # 筛选2023年至今的数据
  154. start_date = pd.to_datetime("2023-01-01")
  155. data = data[data.index >= start_date]
  156. print(f"\n筛选后数据: {len(data)} 条")
  157. print(f"时间范围: {data.index[0]} 至 {data.index[-1]}")
  158. return data
  159. return None
  160. def save_data(self, data, filename=None):
  161. """保存数据到CSV和TXT格式"""
  162. if data is None or data.empty:
  163. print("[错误] 没有数据可保存")
  164. return None
  165. if filename is None:
  166. timestamp = datetime.now().strftime("%Y%m%d")
  167. filename = f"cyb50_30min_2023_to_{timestamp}.csv"
  168. filepath = os.path.join(self.data_dir, filename)
  169. # 保存CSV
  170. data.to_csv(filepath, encoding='utf-8-sig')
  171. # 保存TXT(兼容原有格式)
  172. txt_filename = filename.replace('.csv', '.txt')
  173. txt_filepath = os.path.join(self.data_dir, txt_filename)
  174. with open(txt_filepath, 'w', encoding='utf-8') as f:
  175. f.write("创业板50指数 30分钟数据 (来源:麦儒数据)\n")
  176. f.write("Date Time Open High Low Close Volume Amount\n")
  177. for idx, row in data.iterrows():
  178. date_str = idx.strftime('%Y/%m/%d')
  179. time_str = idx.strftime('%H%M')
  180. f.write(f"{date_str} {time_str} "
  181. f"{row['Open']:.2f} {row['High']:.2f} {row['Low']:.2f} {row['Close']:.2f} "
  182. f"{row['Volume']:.0f} {row.get('Amount', 0):.2f}\n")
  183. print(f"\n[保存成功]")
  184. print(f" CSV: {filepath}")
  185. print(f" TXT: {txt_filepath}")
  186. print(f" 记录数: {len(data)}")
  187. print(f" 文件大小: {os.path.getsize(filepath) / 1024:.2f} KB")
  188. return filepath
  189. def main():
  190. """主函数"""
  191. print("\n" + "=" * 70)
  192. print(" 麦儒数据 - 创业板50指数 30分钟K线数据获取")
  193. print(" 时间范围: 2023年至今")
  194. print("=" * 70 + "\n")
  195. fetcher = MairuiDataFetcher()
  196. # 获取数据
  197. data = fetcher.get_cyb50_30min_2023_to_now()
  198. if data is not None and not data.empty:
  199. # 保存数据
  200. fetcher.save_data(data)
  201. # 数据统计
  202. print("\n" + "=" * 70)
  203. print("数据统计")
  204. print("=" * 70)
  205. print(f"总记录数: {len(data)}")
  206. print(f"交易日数: {len(set(data.index.date))}")
  207. print(f"时间范围: {data.index[0]} 至 {data.index[-1]}")
  208. print(f"\n价格统计:")
  209. print(f" 最高价: {data['High'].max():.2f}")
  210. print(f" 最低价: {data['Low'].min():.2f}")
  211. print(f" 最新价: {data['Close'].iloc[-1]:.2f}")
  212. print(f" 涨跌幅: {(data['Close'].iloc[-1] / data['Close'].iloc[0] - 1) * 100:.2f}%")
  213. print("=" * 70 + "\n")
  214. else:
  215. print("\n[错误] 数据获取失败\n")
  216. if __name__ == "__main__":
  217. main()