|
|
@@ -71,60 +71,139 @@ class DataFetcher:
|
|
|
|
|
|
@staticmethod
|
|
|
def fetch_recent_2months():
|
|
|
- """获取近2个月数据 - 使用实时在线数据"""
|
|
|
+ """获取近2个月数据 - 使用实时在线数据(东方财富+新浪财经双数据源)"""
|
|
|
end_date = datetime.now()
|
|
|
start_date = end_date - timedelta(days=70) # 2个月+10天缓冲
|
|
|
|
|
|
print(f"获取数据: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
|
|
|
|
|
|
- # 尝试在线获取(带重试机制)
|
|
|
- max_retries = 3
|
|
|
- for attempt in range(max_retries):
|
|
|
- try:
|
|
|
- print(f"[尝试 {attempt + 1}/{max_retries}] 正在使用东方财富30分钟K线接口...")
|
|
|
+ # 尝试东方财富数据源
|
|
|
+ df = DataFetcher._fetch_eastmoney_data(start_date, end_date)
|
|
|
+ if df is not None:
|
|
|
+ return df
|
|
|
+
|
|
|
+ # 东方财富失败,尝试新浪财经
|
|
|
+ print("⚠️ 东方财富数据源失败,尝试新浪财经...")
|
|
|
+ df = DataFetcher._fetch_sina_data(start_date, end_date)
|
|
|
+ if df is not None:
|
|
|
+ return df
|
|
|
+
|
|
|
+ # 所有数据源都失败
|
|
|
+ raise Exception("无法获取实时数据,东方财富和新浪财经均失败。请检查网络连接或稍后重试。")
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def _fetch_eastmoney_data(start_date, end_date):
|
|
|
+ """从东方财富获取数据"""
|
|
|
+ try:
|
|
|
+ print("[数据源1] 正在使用东方财富30分钟K线接口...")
|
|
|
+
|
|
|
+ # 使用东方财富接口获取30分钟K线
|
|
|
+ df = ak.index_zh_a_hist_min_em(symbol="399673", period="30")
|
|
|
+
|
|
|
+ if df is not None and not df.empty and len(df) >= 50:
|
|
|
+ # 标准化列名
|
|
|
+ df = df.rename(columns={
|
|
|
+ '时间': 'datetime',
|
|
|
+ '开盘': 'open',
|
|
|
+ '收盘': 'close',
|
|
|
+ '最高': 'high',
|
|
|
+ '最低': 'low',
|
|
|
+ '成交量': 'volume'
|
|
|
+ })
|
|
|
|
|
|
- # 使用东方财富接口获取30分钟K线
|
|
|
- df = ak.index_zh_a_hist_min_em(symbol="399673", period="30")
|
|
|
+ df['datetime'] = pd.to_datetime(df['datetime'])
|
|
|
+ df = df.set_index('datetime').sort_index()
|
|
|
|
|
|
- if df is not None and not df.empty and len(df) >= 50:
|
|
|
- # 标准化列名
|
|
|
- df = df.rename(columns={
|
|
|
- '时间': 'datetime',
|
|
|
- '开盘': 'open',
|
|
|
- '收盘': 'close',
|
|
|
- '最高': 'high',
|
|
|
- '最低': 'low',
|
|
|
- '成交量': 'volume'
|
|
|
- })
|
|
|
-
|
|
|
+ # 只保留最近2个月的数据用于回测
|
|
|
+ backtest_start = end_date - timedelta(days=60)
|
|
|
+ df_backtest = df[df.index >= backtest_start]
|
|
|
+
|
|
|
+ print(f"✅ 东方财富数据获取成功: 共{len(df_backtest)}条30分钟K线")
|
|
|
+ print(f" 数据区间: {df_backtest.index[0]} 至 {df_backtest.index[-1]}")
|
|
|
+
|
|
|
+ # 检查数据时效性
|
|
|
+ latest_time = df_backtest.index[-1]
|
|
|
+ time_delay = end_date - latest_time
|
|
|
+ print(f" 数据延迟: {time_delay}")
|
|
|
+
|
|
|
+ return df_backtest
|
|
|
+ else:
|
|
|
+ print(f"⚠️ 东方财富数据不足: {len(df) if df is not None else 0}条")
|
|
|
+ return None
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ print(f"❌ 东方财富数据源失败: {e}")
|
|
|
+ return None
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def _fetch_sina_data(start_date, end_date):
|
|
|
+ """从新浪财经获取数据"""
|
|
|
+ try:
|
|
|
+ print("[数据源2] 正在使用新浪财经30分钟K线接口...")
|
|
|
+
|
|
|
+ import requests
|
|
|
+ import json
|
|
|
+ import re
|
|
|
+
|
|
|
+ symbol = "sz399673"
|
|
|
+ url = f"https://quotes.sina.cn/cn/api/jsonp_v2.php/var_{symbol}_30_/CN_MarketDataService.getKLineData?symbol={symbol}&scale=30&ma=no&datalen=1023"
|
|
|
+
|
|
|
+ headers = {
|
|
|
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
|
|
+ 'Referer': 'https://finance.sina.com.cn/'
|
|
|
+ }
|
|
|
+
|
|
|
+ response = requests.get(url, headers=headers, timeout=15)
|
|
|
+ response_text = response.text
|
|
|
+
|
|
|
+ # 解析JSONP响应
|
|
|
+ json_start = response_text.find('[')
|
|
|
+ json_end = response_text.rfind(']') + 1
|
|
|
+ if json_start >= 0 and json_end > json_start:
|
|
|
+ json_str = response_text[json_start:json_end]
|
|
|
+ else:
|
|
|
+ raise Exception("无法解析JSONP响应")
|
|
|
+
|
|
|
+ data_dict = json.loads(json_str)
|
|
|
+
|
|
|
+ if data_dict and isinstance(data_dict, list) and len(data_dict) > 0:
|
|
|
+ data_list = []
|
|
|
+ for item in data_dict:
|
|
|
+ try:
|
|
|
+ data_list.append({
|
|
|
+ 'datetime': item.get('day'),
|
|
|
+ 'open': float(item.get('open', 0)),
|
|
|
+ 'high': float(item.get('high', 0)),
|
|
|
+ 'low': float(item.get('low', 0)),
|
|
|
+ 'close': float(item.get('close', 0)),
|
|
|
+ 'volume': float(item.get('volume', 0))
|
|
|
+ })
|
|
|
+ except Exception:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if data_list:
|
|
|
+ df = pd.DataFrame(data_list)
|
|
|
df['datetime'] = pd.to_datetime(df['datetime'])
|
|
|
df = df.set_index('datetime').sort_index()
|
|
|
|
|
|
- # 只保留最近2个月的数据用于回测
|
|
|
+ # 只保留最近2个月的数据
|
|
|
backtest_start = end_date - timedelta(days=60)
|
|
|
df_backtest = df[df.index >= backtest_start]
|
|
|
|
|
|
- print(f"✅ 数据获取成功: 共{len(df_backtest)}条30分钟K线")
|
|
|
+ print(f"✅ 新浪财经数据获取成功: 共{len(df_backtest)}条30分钟K线")
|
|
|
print(f" 数据区间: {df_backtest.index[0]} 至 {df_backtest.index[-1]}")
|
|
|
|
|
|
- # 检查数据时效性
|
|
|
- latest_time = df_backtest.index[-1]
|
|
|
- time_delay = end_date - latest_time
|
|
|
- print(f" 数据延迟: {time_delay}")
|
|
|
-
|
|
|
return df_backtest
|
|
|
else:
|
|
|
- print(f"⚠️ 获取到的数据不足: {len(df) if df is not None else 0}条")
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- print(f"❌ 尝试 {attempt + 1} 失败: {e}")
|
|
|
- if attempt < max_retries - 1:
|
|
|
- import time
|
|
|
- print(f" 等待3秒后重试...")
|
|
|
- time.sleep(3)
|
|
|
-
|
|
|
- # 所有尝试都失败
|
|
|
- raise Exception("无法获取实时数据,所有数据源均失败。请检查网络连接或稍后重试。")
|
|
|
+ print("❌ 新浪财经数据解析失败")
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ print("❌ 新浪财经返回数据格式错误")
|
|
|
+ return None
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ print(f"❌ 新浪财经数据源失败: {e}")
|
|
|
+ return None
|
|
|
|
|
|
|
|
|
# ==================== 策略类 ====================
|