tushare
This commit is contained in:
+159
-130
@@ -11,6 +11,10 @@ from dotenv import load_dotenv
|
||||
# 加载环境变量
|
||||
load_dotenv()
|
||||
|
||||
# 注入外部请求默认超时(覆盖akshare、tushare等基于requests的调用)
|
||||
from http_timeout import install_default_requests_timeout
|
||||
install_default_requests_timeout()
|
||||
|
||||
|
||||
class DataSourceManager:
|
||||
"""数据源管理器 - 实现akshare与tushare自动切换"""
|
||||
@@ -25,7 +29,7 @@ class DataSourceManager:
|
||||
try:
|
||||
import tushare as ts
|
||||
ts.set_token(self.tushare_token)
|
||||
self.tushare_api = ts.pro_api()
|
||||
self.tushare_api = ts.pro_api(timeout=float(os.getenv('TUSHARE_TIMEOUT', '15')))
|
||||
self.tushare_available = True
|
||||
print("✅ Tushare数据源初始化成功")
|
||||
except Exception as e:
|
||||
@@ -36,7 +40,7 @@ class DataSourceManager:
|
||||
|
||||
def get_stock_hist_data(self, symbol, start_date=None, end_date=None, adjust='qfq'):
|
||||
"""
|
||||
获取股票历史数据(优先akshare,失败时使用tushare)
|
||||
获取股票历史数据(优先tushare,失败时使用akshare)
|
||||
|
||||
Args:
|
||||
symbol: 股票代码(6位数字)
|
||||
@@ -55,10 +59,63 @@ class DataSourceManager:
|
||||
else:
|
||||
end_date = datetime.now().strftime('%Y%m%d')
|
||||
|
||||
# 优先使用akshare
|
||||
# 优先使用tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
import tushare as ts
|
||||
print(f"[Tushare] 正在获取 {symbol} 的历史数据(主要数据源)...")
|
||||
|
||||
# 转换股票代码格式(添加市场后缀)
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
|
||||
# 转换复权类型
|
||||
adj_dict = {'qfq': 'qfq', 'hfq': 'hfq', '': None}
|
||||
adj = adj_dict.get(adjust, 'qfq')
|
||||
|
||||
if adj is None:
|
||||
# 不复权数据直接使用daily接口
|
||||
df = self.tushare_api.daily(
|
||||
ts_code=ts_code,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
else:
|
||||
# 复权数据使用pro_bar(daily接口不支持adj参数)
|
||||
df = ts.pro_bar(
|
||||
api=self.tushare_api,
|
||||
ts_code=ts_code,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
adj=adj,
|
||||
retry_count=1
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
# 标准化列名和数据格式
|
||||
df = df.rename(columns={
|
||||
'trade_date': 'date',
|
||||
'vol': 'volume',
|
||||
'amount': 'amount'
|
||||
})
|
||||
df['date'] = pd.to_datetime(df['date'])
|
||||
df = df.sort_values('date')
|
||||
|
||||
# 转换成交量单位(tushare单位是手,转换为股)
|
||||
df['volume'] = df['volume'] * 100
|
||||
# 转换成交额单位(tushare单位是千元,转换为元)
|
||||
df['amount'] = df['amount'] * 1000
|
||||
|
||||
print(f"[Tushare] ✅ 成功获取 {len(df)} 条数据")
|
||||
return df
|
||||
else:
|
||||
print(f"[Tushare] ❌ 未获取到数据,尝试备用数据源")
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
# tushare失败,回退到akshare
|
||||
try:
|
||||
import akshare as ak
|
||||
print(f"[Akshare] 正在获取 {symbol} 的历史数据...")
|
||||
print(f"[Akshare] 正在获取 {symbol} 的历史数据(备用数据源)...")
|
||||
|
||||
df = ak.stock_zh_a_hist(
|
||||
symbol=symbol,
|
||||
@@ -86,60 +143,18 @@ class DataSourceManager:
|
||||
df['date'] = pd.to_datetime(df['date'])
|
||||
print(f"[Akshare] ✅ 成功获取 {len(df)} 条数据")
|
||||
return df
|
||||
else:
|
||||
print(f"[Akshare] ❌ 未获取到数据")
|
||||
except Exception as e:
|
||||
print(f"[Akshare] ❌ 获取失败: {e}")
|
||||
|
||||
# akshare失败,尝试tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
print(f"[Tushare] 正在获取 {symbol} 的历史数据(备用数据源)...")
|
||||
|
||||
# 转换股票代码格式(添加市场后缀)
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
|
||||
# 转换复权类型
|
||||
adj_dict = {'qfq': 'qfq', 'hfq': 'hfq', '': None}
|
||||
adj = adj_dict.get(adjust, 'qfq')
|
||||
|
||||
# 格式化日期
|
||||
start = f"{start_date[:4]}-{start_date[4:6]}-{start_date[6:]}" if start_date else None
|
||||
end = f"{end_date[:4]}-{end_date[4:6]}-{end_date[6:]}" if end_date else None
|
||||
|
||||
# 获取数据
|
||||
df = self.tushare_api.daily(
|
||||
ts_code=ts_code,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
adj=adj
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
# 标准化列名和数据格式
|
||||
df = df.rename(columns={
|
||||
'trade_date': 'date',
|
||||
'vol': 'volume',
|
||||
'amount': 'amount'
|
||||
})
|
||||
df['date'] = pd.to_datetime(df['date'])
|
||||
df = df.sort_values('date')
|
||||
|
||||
# 转换成交量单位(tushare单位是手,转换为股)
|
||||
df['volume'] = df['volume'] * 100
|
||||
# 转换成交额单位(tushare单位是千元,转换为元)
|
||||
df['amount'] = df['amount'] * 1000
|
||||
|
||||
print(f"[Tushare] ✅ 成功获取 {len(df)} 条数据")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
# 两个数据源都失败
|
||||
print("❌ 所有数据源均获取失败")
|
||||
return None
|
||||
|
||||
def get_stock_basic_info(self, symbol):
|
||||
"""
|
||||
获取股票基本信息(优先akshare,失败时使用tushare)
|
||||
获取股票基本信息(优先tushare,失败时使用akshare)
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
@@ -154,10 +169,34 @@ class DataSourceManager:
|
||||
"market": "未知"
|
||||
}
|
||||
|
||||
# 优先使用akshare
|
||||
# 优先使用tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
print(f"[Tushare] 正在获取 {symbol} 的基本信息(主要数据源)...")
|
||||
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
df = self.tushare_api.stock_basic(
|
||||
ts_code=ts_code,
|
||||
fields='ts_code,name,area,industry,market,list_date'
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
info['name'] = df.iloc[0]['name']
|
||||
info['industry'] = df.iloc[0]['industry']
|
||||
info['market'] = df.iloc[0]['market']
|
||||
info['list_date'] = df.iloc[0]['list_date']
|
||||
|
||||
print(f"[Tushare] ✅ 成功获取基本信息")
|
||||
return info
|
||||
else:
|
||||
print(f"[Tushare] ❌ 未获取到基本信息,尝试备用数据源")
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
# tushare失败,回退akshare
|
||||
try:
|
||||
import akshare as ak
|
||||
print(f"[Akshare] 正在获取 {symbol} 的基本信息...")
|
||||
print(f"[Akshare] 正在获取 {symbol} 的基本信息(备用数据源)...")
|
||||
|
||||
stock_info = ak.stock_individual_info_em(symbol=symbol)
|
||||
if stock_info is not None and not stock_info.empty:
|
||||
@@ -181,33 +220,11 @@ class DataSourceManager:
|
||||
except Exception as e:
|
||||
print(f"[Akshare] ❌ 获取失败: {e}")
|
||||
|
||||
# akshare失败,尝试tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
print(f"[Tushare] 正在获取 {symbol} 的基本信息(备用数据源)...")
|
||||
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
df = self.tushare_api.stock_basic(
|
||||
ts_code=ts_code,
|
||||
fields='ts_code,name,area,industry,market,list_date'
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
info['name'] = df.iloc[0]['name']
|
||||
info['industry'] = df.iloc[0]['industry']
|
||||
info['market'] = df.iloc[0]['market']
|
||||
info['list_date'] = df.iloc[0]['list_date']
|
||||
|
||||
print(f"[Tushare] ✅ 成功获取基本信息")
|
||||
return info
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
return info
|
||||
|
||||
def get_realtime_quotes(self, symbol):
|
||||
"""
|
||||
获取实时行情数据(优先akshare,失败时使用tushare)
|
||||
获取实时行情数据(优先tushare,失败时使用akshare)
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
@@ -217,10 +234,51 @@ class DataSourceManager:
|
||||
"""
|
||||
quotes = {}
|
||||
|
||||
# 优先使用akshare
|
||||
# 优先使用tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
print(f"[Tushare] 正在获取 {symbol} 的实时行情(主要数据源)...")
|
||||
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
today = datetime.now().strftime('%Y%m%d')
|
||||
df = self.tushare_api.daily(
|
||||
ts_code=ts_code,
|
||||
start_date=today,
|
||||
end_date=today
|
||||
)
|
||||
if df is None or df.empty:
|
||||
# 非交易日时,获取最近10个交易日的最新数据
|
||||
start = (datetime.now() - timedelta(days=10)).strftime('%Y%m%d')
|
||||
df = self.tushare_api.daily(
|
||||
ts_code=ts_code,
|
||||
start_date=start,
|
||||
end_date=today
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
row = df.iloc[0]
|
||||
quotes = {
|
||||
'symbol': symbol,
|
||||
'price': row['close'],
|
||||
'change_percent': row['pct_chg'],
|
||||
'volume': row['vol'] * 100,
|
||||
'amount': row['amount'] * 1000,
|
||||
'high': row['high'],
|
||||
'low': row['low'],
|
||||
'open': row['open'],
|
||||
'pre_close': row['pre_close']
|
||||
}
|
||||
print(f"[Tushare] ✅ 成功获取实时行情")
|
||||
return quotes
|
||||
else:
|
||||
print(f"[Tushare] ❌ 未获取到实时行情,尝试备用数据源")
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
# tushare失败,回退akshare
|
||||
try:
|
||||
import akshare as ak
|
||||
print(f"[Akshare] 正在获取 {symbol} 的实时行情...")
|
||||
print(f"[Akshare] 正在获取 {symbol} 的实时行情(备用数据源)...")
|
||||
|
||||
df = ak.stock_zh_a_spot_em()
|
||||
stock_df = df[df['代码'] == symbol]
|
||||
@@ -245,41 +303,11 @@ class DataSourceManager:
|
||||
except Exception as e:
|
||||
print(f"[Akshare] ❌ 获取失败: {e}")
|
||||
|
||||
# akshare失败,尝试tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
print(f"[Tushare] 正在获取 {symbol} 的实时行情(备用数据源)...")
|
||||
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
df = self.tushare_api.daily(
|
||||
ts_code=ts_code,
|
||||
start_date=datetime.now().strftime('%Y%m%d'),
|
||||
end_date=datetime.now().strftime('%Y%m%d')
|
||||
)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
row = df.iloc[0]
|
||||
quotes = {
|
||||
'symbol': symbol,
|
||||
'price': row['close'],
|
||||
'change_percent': row['pct_chg'],
|
||||
'volume': row['vol'] * 100,
|
||||
'amount': row['amount'] * 1000,
|
||||
'high': row['high'],
|
||||
'low': row['low'],
|
||||
'open': row['open'],
|
||||
'pre_close': row['pre_close']
|
||||
}
|
||||
print(f"[Tushare] ✅ 成功获取实时行情")
|
||||
return quotes
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
return quotes
|
||||
|
||||
def get_financial_data(self, symbol, report_type='income'):
|
||||
"""
|
||||
获取财务数据(优先akshare,失败时使用tushare)
|
||||
获取财务数据(优先tushare,失败时使用akshare)
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
@@ -288,30 +316,10 @@ class DataSourceManager:
|
||||
Returns:
|
||||
DataFrame: 财务数据
|
||||
"""
|
||||
# 优先使用akshare
|
||||
try:
|
||||
import akshare as ak
|
||||
print(f"[Akshare] 正在获取 {symbol} 的财务数据...")
|
||||
|
||||
if report_type == 'income':
|
||||
df = ak.stock_financial_report_sina(stock=symbol, symbol="利润表")
|
||||
elif report_type == 'balance':
|
||||
df = ak.stock_financial_report_sina(stock=symbol, symbol="资产负债表")
|
||||
elif report_type == 'cashflow':
|
||||
df = ak.stock_financial_report_sina(stock=symbol, symbol="现金流量表")
|
||||
else:
|
||||
df = None
|
||||
|
||||
if df is not None and not df.empty:
|
||||
print(f"[Akshare] ✅ 成功获取财务数据")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"[Akshare] ❌ 获取失败: {e}")
|
||||
|
||||
# akshare失败,尝试tushare
|
||||
# 优先使用tushare
|
||||
if self.tushare_available:
|
||||
try:
|
||||
print(f"[Tushare] 正在获取 {symbol} 的财务数据(备用数据源)...")
|
||||
print(f"[Tushare] 正在获取 {symbol} 的财务数据(主要数据源)...")
|
||||
|
||||
ts_code = self._convert_to_ts_code(symbol)
|
||||
|
||||
@@ -327,9 +335,31 @@ class DataSourceManager:
|
||||
if df is not None and not df.empty:
|
||||
print(f"[Tushare] ✅ 成功获取财务数据")
|
||||
return df
|
||||
else:
|
||||
print(f"[Tushare] ❌ 未获取到财务数据,尝试备用数据源")
|
||||
except Exception as e:
|
||||
print(f"[Tushare] ❌ 获取失败: {e}")
|
||||
|
||||
# tushare失败,回退akshare
|
||||
try:
|
||||
import akshare as ak
|
||||
print(f"[Akshare] 正在获取 {symbol} 的财务数据(备用数据源)...")
|
||||
|
||||
if report_type == 'income':
|
||||
df = ak.stock_financial_report_sina(stock=symbol, symbol="利润表")
|
||||
elif report_type == 'balance':
|
||||
df = ak.stock_financial_report_sina(stock=symbol, symbol="资产负债表")
|
||||
elif report_type == 'cashflow':
|
||||
df = ak.stock_financial_report_sina(stock=symbol, symbol="现金流量表")
|
||||
else:
|
||||
df = None
|
||||
|
||||
if df is not None and not df.empty:
|
||||
print(f"[Akshare] ✅ 成功获取财务数据")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"[Akshare] ❌ 获取失败: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def _convert_to_ts_code(self, symbol):
|
||||
@@ -376,4 +406,3 @@ class DataSourceManager:
|
||||
|
||||
# 全局数据源管理器实例
|
||||
data_source_manager = DataSourceManager()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user