fetch_a50_data.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. A50期货 - 30分钟K线数据获取
  5. 标的:富时中国A50指数期货 (CN0Y) / 华夏A50ETF (159601) 作为替代
  6. 数据周期:近40天
  7. 注意:由于网络限制,部分数据源可能无法访问
  8. """
  9. import pandas as pd
  10. import numpy as np
  11. import akshare as ak
  12. import requests
  13. import json
  14. from datetime import datetime, timedelta
  15. import warnings
  16. warnings.filterwarnings('ignore')
  17. class A50FuturesDataFetcher:
  18. """A50期货数据获取类"""
  19. def __init__(self):
  20. self.symbol = "CN0Y" # A50期货主力合约代码
  21. print(f"A50期货数据获取器初始化")
  22. print(f"标的代码: {self.symbol}")
  23. def fetch_30min_data(self, days=40):
  24. """
  25. 获取A50相关指数30分钟K线数据
  26. Parameters:
  27. days: 获取近N天的数据(默认40天)
  28. Returns:
  29. DataFrame with columns: datetime, open, high, low, close, volume
  30. """
  31. end_date = datetime.now()
  32. start_date = end_date - timedelta(days=days+5)
  33. print(f"\n{'='*60}")
  34. print(f"获取A50相关数据 (近{days}天)")
  35. print(f"时间范围: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
  36. print(f"{'='*60}")
  37. # 尝试多个数据源
  38. df = None
  39. # 方法1: 新浪财经A50期货
  40. df = self._fetch_sina_a50_futures(start_date, end_date)
  41. if df is not None and len(df) > 0:
  42. print(f"\n✅ 使用新浪财经A50期货数据")
  43. return df
  44. # 方法2: 使用A50 ETF作为替代
  45. df = self._fetch_a50_etf(start_date, end_date)
  46. if df is not None and len(df) > 0:
  47. print(f"\n✅ 使用A50 ETF数据作为替代")
  48. return df
  49. # 方法3: 使用上证50指数
  50. df = self._fetch_sz50_index(start_date, end_date)
  51. if df is not None and len(df) > 0:
  52. print(f"\n✅ 使用上证50指数作为替代")
  53. return df
  54. # 方法4: 生成模拟数据(用于测试)
  55. print("\n⚠️ 无法获取实时数据,生成模拟数据用于测试...")
  56. df = self._generate_sample_data(days)
  57. return df
  58. def _fetch_sina_a50_futures(self, start_date, end_date):
  59. """从新浪财经获取A50期货30分钟数据"""
  60. try:
  61. print("\n[数据源0] 新浪财经A50期货...")
  62. import requests
  63. import json
  64. # 新浪财经A50期货接口
  65. url = "https://stock.finance.sina.com.cn/futures/api/jsonp.php/var_a50=/GlobalService.getMink?symbol=CN0Y&scale=30"
  66. headers = {
  67. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
  68. 'Referer': 'https://finance.sina.com.cn/'
  69. }
  70. response = requests.get(url, headers=headers, timeout=15)
  71. # 解析JSONP响应
  72. json_start = response.text.find('[')
  73. json_end = response.text.rfind(']') + 1
  74. json_str = response.text[json_start:json_end]
  75. data_dict = json.loads(json_str)
  76. if data_dict and isinstance(data_dict, list):
  77. data_list = []
  78. for item in data_dict:
  79. try:
  80. data_list.append({
  81. 'datetime': item.get('day'),
  82. 'open': float(item.get('open', 0)),
  83. 'high': float(item.get('high', 0)),
  84. 'low': float(item.get('low', 0)),
  85. 'close': float(item.get('close', 0)),
  86. 'volume': float(item.get('volume', 0))
  87. })
  88. except Exception:
  89. continue
  90. if data_list:
  91. df = pd.DataFrame(data_list)
  92. df['datetime'] = pd.to_datetime(df['datetime'])
  93. df = df.set_index('datetime').sort_index()
  94. # 筛选时间范围
  95. df = df[(df.index >= start_date) & (df.index <= end_date)]
  96. if len(df) > 10:
  97. print(f" ✅ 获取成功: {len(df)}条")
  98. return df
  99. print(f" 数据不足")
  100. except Exception as e:
  101. print(f" 获取失败: {e}")
  102. return None
  103. def _fetch_a50_etf(self, start_date, end_date):
  104. """获取华夏A50ETF数据作为A50期货的替代"""
  105. try:
  106. print("\n[数据源1] 获取华夏A50ETF(159601)数据...")
  107. # 使用akshare获取ETF分钟数据
  108. df = ak.fund_etf_hist_min_em(symbol="159601", period="30",
  109. start_date=start_date.strftime('%Y%m%d%H%M%S'),
  110. end_date=end_date.strftime('%Y%m%d%H%M%S'))
  111. if df is not None and not df.empty and len(df) > 10:
  112. # 标准化列名
  113. df = df.rename(columns={
  114. '时间': 'datetime',
  115. '开盘': 'open',
  116. '收盘': 'close',
  117. '最高': 'high',
  118. '最低': 'low',
  119. '成交量': 'volume'
  120. })
  121. df['datetime'] = pd.to_datetime(df['datetime'])
  122. df = df.set_index('datetime').sort_index()
  123. print(f" ✅ 获取成功: {len(df)}条")
  124. return df
  125. else:
  126. print(f" 数据不足")
  127. except Exception as e:
  128. print(f" 获取失败: {e}")
  129. return None
  130. def _fetch_sz50_index(self, start_date, end_date):
  131. """获取上证50指数作为替代"""
  132. try:
  133. print("\n[数据源2] 获取上证50指数(000016)数据...")
  134. df = ak.index_zh_a_hist_min_em(symbol="000016", period="30",
  135. start_date=start_date.strftime('%Y%m%d%H%M%S'),
  136. end_date=end_date.strftime('%Y%m%d%H%M%S'))
  137. if df is not None and not df.empty and len(df) > 10:
  138. df = df.rename(columns={
  139. '时间': 'datetime',
  140. '开盘': 'open',
  141. '收盘': 'close',
  142. '最高': 'high',
  143. '最低': 'low',
  144. '成交量': 'volume'
  145. })
  146. df['datetime'] = pd.to_datetime(df['datetime'])
  147. df = df.set_index('datetime').sort_index()
  148. print(f" ✅ 获取成功: {len(df)}条")
  149. return df
  150. else:
  151. print(f" 数据不足")
  152. except Exception as e:
  153. print(f" 获取失败: {e}")
  154. return None
  155. def _generate_sample_data(self, days=40):
  156. """生成A50风格的模拟数据(用于测试)"""
  157. print("\n[模拟数据] 生成A50风格模拟数据...")
  158. # 生成时间序列(交易日9:30-11:30, 13:00-15:00)
  159. dates = pd.date_range(end=datetime.now(), periods=days, freq='B') # 工作日
  160. all_times = []
  161. for date in dates:
  162. # 上午 9:30-11:30 (4个30分钟)
  163. for hour, minute in [(9, 30), (10, 0), (10, 30), (11, 0)]:
  164. all_times.append(date.replace(hour=hour, minute=minute))
  165. # 下午 13:00-15:00 (4个30分钟)
  166. for hour, minute in [(13, 0), (13, 30), (14, 0), (14, 30)]:
  167. all_times.append(date.replace(hour=hour, minute=minute))
  168. all_times = sorted([t for t in all_times if t <= datetime.now()])
  169. n = len(all_times)
  170. # A50基准价约13500点
  171. base_price = 13500
  172. # 生成随机价格序列(带趋势)
  173. np.random.seed(42)
  174. returns = np.random.normal(0.0002, 0.008, n) # A50波动率
  175. # 添加一些趋势
  176. trend = np.sin(np.linspace(0, 4*np.pi, n)) * 0.002
  177. returns += trend
  178. # 计算价格
  179. prices = base_price * np.exp(np.cumsum(returns))
  180. # 生成OHLC数据
  181. data = []
  182. for i, (time, price) in enumerate(zip(all_times, prices)):
  183. volatility = price * 0.003 # 0.3%波动
  184. open_price = price + np.random.normal(0, volatility * 0.3)
  185. high_price = max(open_price, price) + np.random.uniform(0, volatility)
  186. low_price = min(open_price, price) - np.random.uniform(0, volatility)
  187. close_price = price
  188. volume = np.random.randint(100000, 500000)
  189. data.append({
  190. 'datetime': time,
  191. 'open': round(open_price, 2),
  192. 'high': round(high_price, 2),
  193. 'low': round(low_price, 2),
  194. 'close': round(close_price, 2),
  195. 'volume': volume
  196. })
  197. df = pd.DataFrame(data)
  198. df = df.set_index('datetime').sort_index()
  199. print(f" ✅ 生成模拟数据: {len(df)}条")
  200. print(f" ⚠️ 注意:此为模拟数据,仅用于测试")
  201. return df
  202. def save_to_csv(self, df, filename=None):
  203. """保存数据到CSV文件"""
  204. if df is None or len(df) == 0:
  205. print("❌ 无数据可保存")
  206. return None
  207. if filename is None:
  208. filename = f"A50_futures_30min_{datetime.now().strftime('%Y%m%d')}.csv"
  209. try:
  210. df.to_csv(filename)
  211. import os
  212. print(f"\n💾 数据已保存: {filename}")
  213. print(f" 数据条数: {len(df)}")
  214. print(f" 文件大小: {round(os.path.getsize(filename)/1024, 2)} KB")
  215. return filename
  216. except Exception as e:
  217. print(f"❌ 保存失败: {e}")
  218. return None
  219. def show_statistics(self, df):
  220. """显示数据统计信息"""
  221. if df is None or len(df) == 0:
  222. return
  223. print(f"\n{'='*60}")
  224. print(f"数据统计信息")
  225. print(f"{'='*60}")
  226. print(f"\n【基本信息】")
  227. print(f" 数据条数: {len(df)}")
  228. print(f" 时间区间: {df.index[0]} 至 {df.index[-1]}")
  229. unique_dates = set([d.date() for d in pd.to_datetime(df.index)])
  230. print(f" 交易天数: {len(unique_dates)}天")
  231. print(f"\n【价格统计】")
  232. print(f" 最高价: {df['high'].max():.2f}")
  233. print(f" 最低价: {df['low'].min():.2f}")
  234. print(f" 最新价: {df['close'].iloc[-1]:.2f}")
  235. # 计算涨跌幅
  236. price_change = (df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0] * 100
  237. print(f" 区间涨跌: {price_change:+.2f}%")
  238. print(f"\n【成交量统计】")
  239. print(f" 总成交量: {df['volume'].sum():,.0f}")
  240. print(f" 日均成交: {df['volume'].mean():,.0f}")
  241. print(f"\n【波动率统计】")
  242. df['returns'] = df['close'].pct_change()
  243. volatility = df['returns'].std() * np.sqrt(48) * 100 # 年化波动率
  244. print(f" 年化波动率: {volatility:.2f}%")
  245. def main():
  246. """主程序"""
  247. print("="*70)
  248. print("A50期货30分钟K线数据获取工具")
  249. print("="*70)
  250. print(f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
  251. # 创建数据获取器
  252. fetcher = A50FuturesDataFetcher()
  253. # 获取近40天数据
  254. df = fetcher.fetch_30min_data(days=40)
  255. if df is not None and len(df) > 0:
  256. # 显示统计信息
  257. fetcher.show_statistics(df)
  258. # 保存到CSV
  259. fetcher.save_to_csv(df)
  260. # 显示最新10条数据
  261. print(f"\n{'='*60}")
  262. print(f"最新10条数据")
  263. print(f"{'='*60}")
  264. print(df.tail(10)[['open', 'high', 'low', 'close', 'volume']].to_string())
  265. print(f"\n✅ 数据获取完成!")
  266. else:
  267. print(f"\n❌ 数据获取失败")
  268. print("="*70)
  269. if __name__ == "__main__":
  270. import os
  271. main()