| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 麦儒数据 (mairui) API - 获取创业板50指数30分钟K线数据
- 文档: https://www.mairui.club/
- Token: AE17EE23-AAE4-492F-A959-EC883DFA5A76
- """
- import pandas as pd
- import requests
- import json
- import os
- from datetime import datetime, timedelta
- import time
- import warnings
- warnings.filterwarnings('ignore')
- class MairuiDataFetcher:
- """麦儒数据获取器"""
- def __init__(self):
- self.token = "AE17EE23-AAE4-492F-A959-EC883DFA5A76"
- self.base_url = "https://api.mairuiapi.com"
- self.data_dir = "data"
- os.makedirs(self.data_dir, exist_ok=True)
- def get_index_minute_data(self, index_code="399673.SZ", period="30", start_date=None, end_date=None):
- """
- 获取指数分钟历史数据
- :param index_code: 指数代码,创业板50 = 399673.SZ
- :param period: 周期,支持 1,5,15,30,60,d(日线)
- :param start_date: 开始时间 (YYYYMMDD)
- :param end_date: 结束时间 (YYYYMMDD)
- :return: DataFrame
- """
- try:
- # 麦儒API接口:获取指数历史K线数据
- # 格式: /hsindex/history/指数代码/周期/Token?st=开始时间&et=结束时间
- url = f"{self.base_url}/hsindex/history/{index_code}/{period}/{self.token}"
- # 添加时间参数
- params = {}
- if start_date:
- params['st'] = start_date
- if end_date:
- params['et'] = end_date
- if params:
- url += "?" + "&".join([f"{k}={v}" for k, v in params.items()])
- print(f"[麦儒数据] 请求URL: {url}")
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
- }
- response = requests.get(url, headers=headers, timeout=30)
- response.encoding = 'utf-8'
- if response.status_code != 200:
- print(f"[错误] HTTP状态码: {response.status_code}")
- return None
- data = response.json()
- if isinstance(data, dict) and 'error' in data:
- print(f"[错误] API返回错误: {data['error']}")
- return None
- if not data or not isinstance(data, list):
- print(f"[错误] 返回数据格式不正确")
- return None
- # 转换为DataFrame
- df = pd.DataFrame(data)
- # 标准化列名
- column_mapping = {
- 'd': 'DateTime', # 日期时间
- 'o': 'Open', # 开盘
- 'h': 'High', # 最高
- 'l': 'Low', # 最低
- 'c': 'Close', # 收盘
- 'v': 'Volume', # 成交量
- 'e': 'Amount', # 成交额
- 't': 'DateTime' # 有些接口用t表示时间
- }
- df.rename(columns=column_mapping, inplace=True)
- # 转换时间格式
- if 'DateTime' in df.columns:
- df['DateTime'] = pd.to_datetime(df['DateTime'])
- df.set_index('DateTime', inplace=True)
- df.sort_index(inplace=True)
- # 转换数值类型
- numeric_cols = ['Open', 'High', 'Low', 'Close', 'Volume', 'Amount']
- for col in numeric_cols:
- if col in df.columns:
- df[col] = pd.to_numeric(df[col], errors='coerce')
- print(f"[麦儒数据] 成功获取 {len(df)} 条数据")
- print(f"[麦儒数据] 时间范围: {df.index[0]} 至 {df.index[-1]}")
- return df
- except Exception as e:
- print(f"[错误] 获取数据失败: {e}")
- return None
- def get_index_history(self, index_code="399673"):
- """
- 获取指数历史数据(日线)
- :param index_code: 指数代码
- :return: DataFrame
- """
- try:
- # 麦儒API接口:获取指数历史数据
- url = f"{self.base_url}/zslsh/{index_code}/{self.token}"
- print(f"[麦儒数据] 获取历史数据: {url}")
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
- }
- response = requests.get(url, headers=headers, timeout=30)
- response.encoding = 'utf-8'
- if response.status_code != 200:
- print(f"[错误] HTTP状态码: {response.status_code}")
- return None
- data = response.json()
- if not data or not isinstance(data, list):
- print(f"[错误] 返回数据格式不正确")
- return None
- df = pd.DataFrame(data)
- # 标准化列名
- column_mapping = {
- 'd': 'DateTime',
- 'o': 'Open',
- 'h': 'High',
- 'l': 'Low',
- 'c': 'Close',
- 'v': 'Volume',
- 'e': 'Amount',
- 'zf': 'Change_Pct', # 涨跌幅
- 'zde': 'Change_Amount' # 涨跌额
- }
- df.rename(columns=column_mapping, inplace=True)
- if 'DateTime' in df.columns:
- df['DateTime'] = pd.to_datetime(df['DateTime'])
- df.set_index('DateTime', inplace=True)
- df.sort_index(inplace=True)
- return df
- except Exception as e:
- print(f"[错误] 获取历史数据失败: {e}")
- return None
- def get_cyb50_30min_2023_to_now(self):
- """
- 获取创业板50指数2023年至今的30分钟数据
- """
- print("=" * 70)
- print("麦儒数据 - 创业板50指数30分钟数据获取")
- print("=" * 70)
- # 设置时间范围
- start_date = "20230101"
- end_date = datetime.now().strftime("%Y%m%d")
- print(f"时间范围: {start_date} 至 {end_date}")
- # 获取30分钟数据
- data = self.get_index_minute_data(
- index_code="399673.SZ",
- period="30",
- start_date=start_date,
- end_date=end_date
- )
- if data is not None and not data.empty:
- # 筛选2023年至今的数据
- start_date = pd.to_datetime("2023-01-01")
- data = data[data.index >= start_date]
- print(f"\n筛选后数据: {len(data)} 条")
- print(f"时间范围: {data.index[0]} 至 {data.index[-1]}")
- return data
- return None
- def save_data(self, data, filename=None):
- """保存数据到CSV和TXT格式"""
- if data is None or data.empty:
- print("[错误] 没有数据可保存")
- return None
- if filename is None:
- timestamp = datetime.now().strftime("%Y%m%d")
- filename = f"cyb50_30min_2023_to_{timestamp}.csv"
- filepath = os.path.join(self.data_dir, filename)
- # 保存CSV
- data.to_csv(filepath, encoding='utf-8-sig')
- # 保存TXT(兼容原有格式)
- txt_filename = filename.replace('.csv', '.txt')
- txt_filepath = os.path.join(self.data_dir, txt_filename)
- with open(txt_filepath, 'w', encoding='utf-8') as f:
- f.write("创业板50指数 30分钟数据 (来源:麦儒数据)\n")
- f.write("Date Time Open High Low Close Volume Amount\n")
- for idx, row in data.iterrows():
- date_str = idx.strftime('%Y/%m/%d')
- time_str = idx.strftime('%H%M')
- f.write(f"{date_str} {time_str} "
- f"{row['Open']:.2f} {row['High']:.2f} {row['Low']:.2f} {row['Close']:.2f} "
- f"{row['Volume']:.0f} {row.get('Amount', 0):.2f}\n")
- print(f"\n[保存成功]")
- print(f" CSV: {filepath}")
- print(f" TXT: {txt_filepath}")
- print(f" 记录数: {len(data)}")
- print(f" 文件大小: {os.path.getsize(filepath) / 1024:.2f} KB")
- return filepath
- def main():
- """主函数"""
- print("\n" + "=" * 70)
- print(" 麦儒数据 - 创业板50指数 30分钟K线数据获取")
- print(" 时间范围: 2023年至今")
- print("=" * 70 + "\n")
- fetcher = MairuiDataFetcher()
- # 获取数据
- data = fetcher.get_cyb50_30min_2023_to_now()
- if data is not None and not data.empty:
- # 保存数据
- fetcher.save_data(data)
- # 数据统计
- print("\n" + "=" * 70)
- print("数据统计")
- print("=" * 70)
- print(f"总记录数: {len(data)}")
- print(f"交易日数: {len(set(data.index.date))}")
- print(f"时间范围: {data.index[0]} 至 {data.index[-1]}")
- print(f"\n价格统计:")
- print(f" 最高价: {data['High'].max():.2f}")
- print(f" 最低价: {data['Low'].min():.2f}")
- print(f" 最新价: {data['Close'].iloc[-1]:.2f}")
- print(f" 涨跌幅: {(data['Close'].iloc[-1] / data['Close'].iloc[0] - 1) * 100:.2f}%")
- print("=" * 70 + "\n")
- else:
- print("\n[错误] 数据获取失败\n")
- if __name__ == "__main__":
- main()
|