766 lines
32 KiB
Python
766 lines
32 KiB
Python
"""
|
||
市场情绪数据获取和计算模块
|
||
使用akshare获取市场情绪相关指标,包括ARBR、恐慌指数、市场资金情绪等
|
||
"""
|
||
|
||
import pandas as pd
|
||
import numpy as np
|
||
import akshare as ak
|
||
from datetime import datetime, timedelta
|
||
import warnings
|
||
import sys
|
||
import io
|
||
from data_source_manager import data_source_manager
|
||
|
||
warnings.filterwarnings('ignore')
|
||
|
||
# 设置标准输出编码为UTF-8(仅在命令行环境,避免streamlit冲突)
|
||
def _setup_stdout_encoding():
|
||
"""仅在命令行环境设置标准输出编码"""
|
||
if sys.platform == 'win32' and not hasattr(sys.stdout, '_original_stream'):
|
||
try:
|
||
# 检测是否在streamlit环境中
|
||
import streamlit
|
||
# 在streamlit中不修改stdout
|
||
return
|
||
except ImportError:
|
||
# 不在streamlit环境,可以安全修改
|
||
try:
|
||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='ignore')
|
||
except:
|
||
pass
|
||
|
||
_setup_stdout_encoding()
|
||
|
||
|
||
class MarketSentimentDataFetcher:
|
||
"""市场情绪数据获取和计算类"""
|
||
|
||
def __init__(self):
|
||
self.arbr_period = 26 # ARBR计算周期
|
||
|
||
def get_market_sentiment_data(self, symbol, stock_data=None):
|
||
"""
|
||
获取完整的市场情绪分析数据
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
stock_data: 股票历史数据(如果已有)
|
||
|
||
Returns:
|
||
dict: 包含各类市场情绪指标的字典
|
||
"""
|
||
sentiment_data = {
|
||
"symbol": symbol,
|
||
"arbr_data": None, # ARBR指标数据
|
||
"market_index": None, # 大盘指数数据
|
||
"sector_index": None, # 板块指数数据
|
||
"turnover_rate": None, # 换手率数据
|
||
"limit_up_down": None, # 涨跌停数据
|
||
"margin_trading": None, # 融资融券数据
|
||
"fear_greed_index": None, # 市场恐慌贪婪指数
|
||
"data_success": False
|
||
}
|
||
|
||
try:
|
||
# 判断是否为中国股票
|
||
is_chinese = self._is_chinese_stock(symbol)
|
||
|
||
if is_chinese:
|
||
# 1. 计算ARBR指标
|
||
print("📊 正在计算ARBR情绪指标...")
|
||
arbr_data = self._calculate_arbr(symbol, stock_data)
|
||
if arbr_data:
|
||
sentiment_data["arbr_data"] = arbr_data
|
||
|
||
# 2. 获取换手率数据
|
||
print("📊 正在获取换手率数据...")
|
||
turnover_data = self._get_turnover_rate(symbol)
|
||
if turnover_data:
|
||
sentiment_data["turnover_rate"] = turnover_data
|
||
|
||
# 3. 获取大盘情绪
|
||
print("📊 正在获取大盘情绪数据...")
|
||
market_data = self._get_market_index_sentiment()
|
||
if market_data:
|
||
sentiment_data["market_index"] = market_data
|
||
|
||
# 4. 获取涨跌停数据
|
||
print("📊 正在获取涨跌停数据...")
|
||
limit_data = self._get_limit_up_down_stats()
|
||
if limit_data:
|
||
sentiment_data["limit_up_down"] = limit_data
|
||
|
||
# 5. 获取融资融券数据
|
||
print("📊 正在获取融资融券数据...")
|
||
margin_data = self._get_margin_trading_data(symbol)
|
||
if margin_data:
|
||
sentiment_data["margin_trading"] = margin_data
|
||
|
||
# 6. 获取市场恐慌指数
|
||
print("📊 正在计算市场恐慌指数...")
|
||
fear_greed = self._get_fear_greed_index()
|
||
if fear_greed:
|
||
sentiment_data["fear_greed_index"] = fear_greed
|
||
|
||
sentiment_data["data_success"] = True
|
||
print("✅ 市场情绪数据获取完成")
|
||
else:
|
||
# 美股的情绪指标(简化版)
|
||
print("ℹ️ 美股暂不支持完整的市场情绪数据")
|
||
sentiment_data["error"] = "美股暂不支持完整的市场情绪数据"
|
||
|
||
except Exception as e:
|
||
print(f"❌ 获取市场情绪数据失败: {e}")
|
||
sentiment_data["error"] = str(e)
|
||
|
||
return sentiment_data
|
||
|
||
def _is_chinese_stock(self, symbol):
|
||
"""判断是否为中国股票"""
|
||
return symbol.isdigit() and len(symbol) == 6
|
||
|
||
def _calculate_arbr(self, symbol, stock_data=None):
|
||
"""
|
||
计算ARBR指标
|
||
AR = (N日内(H-O)之和 / N日内(O-L)之和) × 100
|
||
BR = (N日内(H-CY)之和 / N日内(CY-L)之和) × 100
|
||
"""
|
||
try:
|
||
# 如果没有提供stock_data,则重新获取(支持akshare和tushare自动切换)
|
||
if stock_data is None or stock_data.empty:
|
||
end_date = datetime.now().strftime('%Y%m%d')
|
||
start_date = (datetime.now() - timedelta(days=150)).strftime('%Y%m%d')
|
||
|
||
# 使用数据源管理器获取数据
|
||
df = data_source_manager.get_stock_hist_data(
|
||
symbol=symbol,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
adjust='qfq'
|
||
)
|
||
|
||
if df is None or df.empty:
|
||
return None
|
||
|
||
# 数据源管理器返回的数据列名已经是小写,无需重命名
|
||
else:
|
||
# 使用已有数据
|
||
df = stock_data.copy()
|
||
# 确保列名正确
|
||
if 'Open' in df.columns:
|
||
df = df.rename(columns={
|
||
'Open': 'open',
|
||
'Close': 'close',
|
||
'High': 'high',
|
||
'Low': 'low',
|
||
'Volume': 'volume'
|
||
})
|
||
df = df.reset_index()
|
||
if 'Date' in df.columns:
|
||
df = df.rename(columns={'Date': 'date'})
|
||
|
||
# 确保日期列为datetime类型
|
||
if 'date' in df.columns:
|
||
df['date'] = pd.to_datetime(df['date'])
|
||
|
||
# 计算各项差值
|
||
df['HO'] = df['high'] - df['open'] # 最高价-开盘价
|
||
df['OL'] = df['open'] - df['low'] # 开盘价-最低价
|
||
df['HCY'] = df['high'] - df['close'].shift(1) # 最高价-前收
|
||
df['CYL'] = df['close'].shift(1) - df['low'] # 前收-最低价
|
||
|
||
# 计算AR指标
|
||
df['AR'] = (df['HO'].rolling(window=self.arbr_period).sum() /
|
||
df['OL'].rolling(window=self.arbr_period).sum()) * 100
|
||
|
||
# 计算BR指标
|
||
df['BR'] = (df['HCY'].rolling(window=self.arbr_period).sum() /
|
||
df['CYL'].rolling(window=self.arbr_period).sum()) * 100
|
||
|
||
# 处理无穷大和空值
|
||
df['AR'] = df['AR'].replace([np.inf, -np.inf], np.nan)
|
||
df['BR'] = df['BR'].replace([np.inf, -np.inf], np.nan)
|
||
|
||
# 移除空值
|
||
df = df.dropna(subset=['AR', 'BR'])
|
||
|
||
if df.empty:
|
||
return None
|
||
|
||
# 获取最新值和统计信息
|
||
latest = df.iloc[-1]
|
||
ar_value = latest['AR']
|
||
br_value = latest['BR']
|
||
|
||
# 解读ARBR
|
||
interpretation = self._interpret_arbr(ar_value, br_value)
|
||
|
||
# 生成交易信号
|
||
signals = self._generate_arbr_signals(ar_value, br_value)
|
||
|
||
# 计算历史统计
|
||
stats = {
|
||
"ar_mean": df['AR'].mean(),
|
||
"ar_std": df['AR'].std(),
|
||
"ar_min": df['AR'].min(),
|
||
"ar_max": df['AR'].max(),
|
||
"br_mean": df['BR'].mean(),
|
||
"br_std": df['BR'].std(),
|
||
"br_min": df['BR'].min(),
|
||
"br_max": df['BR'].max(),
|
||
}
|
||
|
||
# 计算信号统计
|
||
df['ar_signal'] = 0
|
||
df['br_signal'] = 0
|
||
df.loc[df['AR'] > 150, 'ar_signal'] = -1
|
||
df.loc[df['AR'] < 70, 'ar_signal'] = 1
|
||
df.loc[df['BR'] > 300, 'br_signal'] = -1
|
||
df.loc[df['BR'] < 50, 'br_signal'] = 1
|
||
df['combined_signal'] = df['ar_signal'] + df['br_signal']
|
||
|
||
buy_signals = len(df[df['combined_signal'] > 0])
|
||
sell_signals = len(df[df['combined_signal'] < 0])
|
||
neutral_signals = len(df) - buy_signals - sell_signals
|
||
|
||
signal_stats = {
|
||
"buy_signals": buy_signals,
|
||
"sell_signals": sell_signals,
|
||
"neutral_signals": neutral_signals,
|
||
"total_signals": len(df),
|
||
"buy_ratio": f"{buy_signals/len(df)*100:.1f}%" if len(df) > 0 else "0%",
|
||
"sell_ratio": f"{sell_signals/len(df)*100:.1f}%" if len(df) > 0 else "0%"
|
||
}
|
||
|
||
return {
|
||
"latest_ar": float(ar_value),
|
||
"latest_br": float(br_value),
|
||
"interpretation": interpretation,
|
||
"signals": signals,
|
||
"statistics": stats,
|
||
"signal_statistics": signal_stats,
|
||
"calculation_date": latest.get('date', datetime.now()).strftime('%Y-%m-%d') if pd.notna(latest.get('date')) else datetime.now().strftime('%Y-%m-%d'),
|
||
"period": self.arbr_period
|
||
}
|
||
|
||
except Exception as e:
|
||
print(f"计算ARBR指标失败: {e}")
|
||
return None
|
||
|
||
def _interpret_arbr(self, ar_value, br_value):
|
||
"""解读ARBR数值的含义"""
|
||
interpretation = []
|
||
|
||
# AR指标解读
|
||
if ar_value > 180:
|
||
interpretation.append("AR极度超买(>180),市场过热,风险极高,建议谨慎")
|
||
elif ar_value > 150:
|
||
interpretation.append("AR超买(>150),市场情绪过热,注意回调风险")
|
||
elif ar_value < 40:
|
||
interpretation.append("AR极度超卖(<40),市场过冷,可能存在机会")
|
||
elif ar_value < 70:
|
||
interpretation.append("AR超卖(<70),市场情绪低迷,可关注反弹机会")
|
||
else:
|
||
interpretation.append(f"AR处于正常区间({ar_value:.2f}),市场情绪相对平稳")
|
||
|
||
# BR指标解读
|
||
if br_value > 400:
|
||
interpretation.append("BR极度超买(>400),投机情绪过热,警惕泡沫")
|
||
elif br_value > 300:
|
||
interpretation.append("BR超买(>300),投机情绪旺盛,注意风险")
|
||
elif br_value < 30:
|
||
interpretation.append("BR极度超卖(<30),投机情绪冰点,可能触底")
|
||
elif br_value < 50:
|
||
interpretation.append("BR超卖(<50),投机情绪低迷,关注企稳信号")
|
||
else:
|
||
interpretation.append(f"BR处于正常区间({br_value:.2f}),投机情绪适中")
|
||
|
||
# ARBR关系解读
|
||
if ar_value > 100 and br_value > 100:
|
||
interpretation.append("多头力量强劲(AR>100且BR>100),但需警惕过热风险")
|
||
elif ar_value < 100 and br_value < 100:
|
||
interpretation.append("空头力量占优(AR<100且BR<100),市场情绪偏空")
|
||
|
||
if ar_value > br_value:
|
||
interpretation.append("人气指标强于意愿指标(AR>BR),市场基础较好,投资者信心相对稳定")
|
||
else:
|
||
interpretation.append("意愿指标强于人气指标(BR>AR),投机性较强,需注意资金稳定性")
|
||
|
||
return interpretation
|
||
|
||
def _generate_arbr_signals(self, ar_value, br_value):
|
||
"""生成ARBR交易信号"""
|
||
signals = []
|
||
signal_strength = 0
|
||
|
||
# AR信号
|
||
if ar_value > 150:
|
||
signals.append("AR卖出信号")
|
||
signal_strength -= 1
|
||
elif ar_value < 70:
|
||
signals.append("AR买入信号")
|
||
signal_strength += 1
|
||
|
||
# BR信号
|
||
if br_value > 300:
|
||
signals.append("BR卖出信号")
|
||
signal_strength -= 1
|
||
elif br_value < 50:
|
||
signals.append("BR买入信号")
|
||
signal_strength += 1
|
||
|
||
# 综合信号
|
||
if signal_strength >= 2:
|
||
overall = "强烈买入信号"
|
||
elif signal_strength == 1:
|
||
overall = "买入信号"
|
||
elif signal_strength == -1:
|
||
overall = "卖出信号"
|
||
elif signal_strength <= -2:
|
||
overall = "强烈卖出信号"
|
||
else:
|
||
overall = "中性信号"
|
||
|
||
return {
|
||
"individual_signals": signals if signals else ["中性"],
|
||
"overall_signal": overall,
|
||
"signal_strength": signal_strength
|
||
}
|
||
|
||
def _get_turnover_rate(self, symbol):
|
||
"""获取换手率数据(支持akshare和tushare自动切换)"""
|
||
try:
|
||
# 优先使用akshare获取最近的换手率数据
|
||
print(f" [Akshare] 正在获取换手率数据...")
|
||
# 获取A股实时行情数据(不需要参数)
|
||
df = ak.stock_zh_a_spot_em()
|
||
if df is not None and not df.empty:
|
||
stock_data = df[df['代码'] == symbol]
|
||
if not stock_data.empty:
|
||
row = stock_data.iloc[0]
|
||
turnover_rate = row.get('换手率', 'N/A')
|
||
|
||
# 解读换手率
|
||
interpretation = ""
|
||
if turnover_rate != 'N/A':
|
||
try:
|
||
turnover = float(turnover_rate)
|
||
if turnover > 20:
|
||
interpretation = "换手率极高(>20%),资金活跃度极高,可能存在炒作"
|
||
elif turnover > 10:
|
||
interpretation = "换手率较高(>10%),交易活跃"
|
||
elif turnover > 5:
|
||
interpretation = "换手率正常(5%-10%),交易适中"
|
||
elif turnover > 2:
|
||
interpretation = "换手率偏低(2%-5%),交易相对清淡"
|
||
else:
|
||
interpretation = "换手率很低(<2%),交易清淡"
|
||
except:
|
||
pass
|
||
|
||
print(f" [Akshare] ✅ 成功获取换手率: {turnover_rate}%")
|
||
return {
|
||
"current_turnover_rate": turnover_rate,
|
||
"interpretation": interpretation
|
||
}
|
||
except Exception as e:
|
||
print(f" [Akshare] ❌ 获取换手率失败: {e}")
|
||
|
||
# akshare失败,尝试tushare
|
||
if data_source_manager.tushare_available:
|
||
try:
|
||
print(f" [Tushare] 正在获取换手率数据(备用数据源)...")
|
||
ts_code = data_source_manager._convert_to_ts_code(symbol)
|
||
|
||
# 获取最近一个交易日的数据
|
||
df = data_source_manager.tushare_api.daily_basic(
|
||
ts_code=ts_code,
|
||
trade_date=datetime.now().strftime('%Y%m%d')
|
||
)
|
||
|
||
if df is not None and not df.empty:
|
||
row = df.iloc[0]
|
||
turnover_rate = row.get('turnover_rate', 'N/A')
|
||
|
||
# 解读换手率
|
||
interpretation = ""
|
||
if turnover_rate != 'N/A':
|
||
try:
|
||
turnover = float(turnover_rate)
|
||
if turnover > 20:
|
||
interpretation = "换手率极高(>20%),资金活跃度极高,可能存在炒作"
|
||
elif turnover > 10:
|
||
interpretation = "换手率较高(>10%),交易活跃"
|
||
elif turnover > 5:
|
||
interpretation = "换手率正常(5%-10%),交易适中"
|
||
elif turnover > 2:
|
||
interpretation = "换手率偏低(2%-5%),交易相对清淡"
|
||
else:
|
||
interpretation = "换手率很低(<2%),交易清淡"
|
||
except:
|
||
pass
|
||
|
||
print(f" [Tushare] ✅ 成功获取换手率: {turnover_rate}%")
|
||
return {
|
||
"current_turnover_rate": turnover_rate,
|
||
"interpretation": interpretation
|
||
}
|
||
except Exception as te:
|
||
print(f" [Tushare] ❌ 获取失败: {te}")
|
||
|
||
return None
|
||
|
||
def _get_market_index_sentiment(self):
|
||
"""获取大盘指数情绪(支持akshare和tushare自动切换)"""
|
||
try:
|
||
# 优先使用akshare获取上证指数实时数据
|
||
print(f" [Akshare] 正在获取大盘指数数据...")
|
||
# 使用正确的symbol参数
|
||
df = ak.stock_zh_index_spot_em(symbol="上证系列指数")
|
||
if df is not None and not df.empty:
|
||
# 查找上证指数(代码为000001)
|
||
sh_index = df[df['代码'] == '000001']
|
||
if not sh_index.empty:
|
||
row = sh_index.iloc[0]
|
||
change_pct = row.get('涨跌幅', 0)
|
||
|
||
# 获取涨跌家数
|
||
try:
|
||
market_summary = ak.stock_zh_a_spot_em()
|
||
if market_summary is not None and not market_summary.empty:
|
||
up_count = len(market_summary[market_summary['涨跌幅'] > 0])
|
||
down_count = len(market_summary[market_summary['涨跌幅'] < 0])
|
||
total_count = len(market_summary)
|
||
flat_count = total_count - up_count - down_count
|
||
|
||
# 计算市场情绪指数
|
||
sentiment_score = (up_count - down_count) / total_count * 100
|
||
|
||
# 解读市场情绪
|
||
if sentiment_score > 30:
|
||
sentiment = "市场情绪极度乐观"
|
||
elif sentiment_score > 10:
|
||
sentiment = "市场情绪偏多"
|
||
elif sentiment_score > -10:
|
||
sentiment = "市场情绪中性"
|
||
elif sentiment_score > -30:
|
||
sentiment = "市场情绪偏空"
|
||
else:
|
||
sentiment = "市场情绪极度悲观"
|
||
|
||
print(f" [Akshare] ✅ 成功获取大盘数据")
|
||
return {
|
||
"index_name": "上证指数",
|
||
"change_percent": change_pct,
|
||
"up_count": up_count,
|
||
"down_count": down_count,
|
||
"flat_count": flat_count,
|
||
"total_count": total_count,
|
||
"sentiment_score": f"{sentiment_score:.2f}",
|
||
"sentiment_interpretation": sentiment
|
||
}
|
||
except Exception as e:
|
||
print(f" [Akshare] 获取涨跌家数失败: {e}")
|
||
|
||
print(f" [Akshare] ✅ 成功获取指数涨跌幅")
|
||
return {
|
||
"index_name": "上证指数",
|
||
"change_percent": change_pct
|
||
}
|
||
except Exception as e:
|
||
print(f" [Akshare] ❌ 获取大盘指数失败: {e}")
|
||
|
||
# akshare失败,尝试tushare
|
||
if data_source_manager.tushare_available:
|
||
try:
|
||
print(f" [Tushare] 正在获取大盘指数数据(备用数据源)...")
|
||
|
||
# 获取上证指数数据
|
||
df = data_source_manager.tushare_api.index_daily(
|
||
ts_code='000001.SH',
|
||
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]
|
||
change_pct = row.get('pct_chg', 0)
|
||
|
||
print(f" [Tushare] ✅ 成功获取大盘指数涨跌幅: {change_pct}%")
|
||
return {
|
||
"index_name": "上证指数",
|
||
"change_percent": change_pct
|
||
}
|
||
except Exception as te:
|
||
print(f" [Tushare] ❌ 获取失败: {te}")
|
||
|
||
return None
|
||
|
||
def _get_limit_up_down_stats(self):
|
||
"""获取涨跌停统计数据"""
|
||
try:
|
||
# 获取今日涨停和跌停统计
|
||
today = datetime.now().strftime('%Y%m%d')
|
||
|
||
# 获取涨停股票
|
||
try:
|
||
limit_up_df = ak.stock_zt_pool_em(date=today)
|
||
limit_up_count = len(limit_up_df) if limit_up_df is not None and not limit_up_df.empty else 0
|
||
except:
|
||
limit_up_count = 0
|
||
|
||
# 获取跌停股票
|
||
try:
|
||
limit_down_df = ak.stock_zt_pool_dtgc_em(date=today)
|
||
limit_down_count = len(limit_down_df) if limit_down_df is not None and not limit_down_df.empty else 0
|
||
except:
|
||
limit_down_count = 0
|
||
|
||
# 计算涨跌停比例
|
||
if limit_up_count + limit_down_count > 0:
|
||
limit_ratio = limit_up_count / (limit_up_count + limit_down_count) * 100
|
||
else:
|
||
limit_ratio = 50
|
||
|
||
# 解读涨跌停情况
|
||
if limit_ratio > 70:
|
||
interpretation = "涨停股远多于跌停股,市场情绪火热"
|
||
elif limit_ratio > 60:
|
||
interpretation = "涨停股多于跌停股,市场情绪较好"
|
||
elif limit_ratio > 40:
|
||
interpretation = "涨跌停数量相当,市场情绪分化"
|
||
elif limit_ratio > 30:
|
||
interpretation = "跌停股多于涨停股,市场情绪较弱"
|
||
else:
|
||
interpretation = "跌停股远多于涨停股,市场情绪低迷"
|
||
|
||
return {
|
||
"limit_up_count": limit_up_count,
|
||
"limit_down_count": limit_down_count,
|
||
"limit_ratio": f"{limit_ratio:.1f}%",
|
||
"interpretation": interpretation,
|
||
"date": today
|
||
}
|
||
except Exception as e:
|
||
print(f"获取涨跌停数据失败: {e}")
|
||
return None
|
||
|
||
def _get_margin_trading_data(self, symbol):
|
||
"""获取融资融券数据"""
|
||
try:
|
||
# 获取个股融资融券数据(尝试多个API)
|
||
try:
|
||
# 方法1:获取沪深融资融券明细
|
||
df = ak.stock_margin_underlying_info_szse(date=datetime.now().strftime('%Y%m%d'))
|
||
if df is not None and not df.empty:
|
||
stock_data = df[df['证券代码'] == symbol]
|
||
if not stock_data.empty:
|
||
latest = stock_data.iloc[0]
|
||
|
||
margin_balance = latest.get('融资余额', 0)
|
||
short_balance = latest.get('融券余额', 0)
|
||
|
||
# 解读融资融券
|
||
interpretation = []
|
||
if margin_balance > short_balance * 10:
|
||
interpretation.append("融资余额远大于融券余额,投资者看多情绪强")
|
||
elif margin_balance > short_balance * 3:
|
||
interpretation.append("融资余额大于融券余额,投资者偏看多")
|
||
else:
|
||
interpretation.append("融资融券相对平衡")
|
||
|
||
return {
|
||
"margin_balance": margin_balance,
|
||
"short_balance": short_balance,
|
||
"interpretation": interpretation,
|
||
"date": datetime.now().strftime('%Y-%m-%d')
|
||
}
|
||
except:
|
||
pass
|
||
|
||
# 方法2:获取融资融券汇总数据
|
||
try:
|
||
df = ak.stock_margin_szsh()
|
||
if df is not None and not df.empty:
|
||
# 获取最新数据
|
||
latest = df.iloc[-1]
|
||
return {
|
||
"margin_balance": latest.get('融资余额', 'N/A'),
|
||
"short_balance": latest.get('融券余额', 'N/A'),
|
||
"interpretation": ["市场整体融资融券数据"],
|
||
"date": latest.get('交易日期', 'N/A')
|
||
}
|
||
except:
|
||
pass
|
||
|
||
except Exception as e:
|
||
print(f"获取融资融券数据失败: {e}")
|
||
return None
|
||
|
||
def _get_fear_greed_index(self):
|
||
"""计算市场恐慌贪婪指数(基于多个指标综合计算)"""
|
||
try:
|
||
# 基于多个市场指标计算恐慌贪婪指数
|
||
# 1. 涨跌家数比例
|
||
# 2. 涨跌停比例
|
||
# 3. 成交量变化
|
||
|
||
score = 50 # 基准分数
|
||
factors = []
|
||
|
||
# 获取涨跌家数
|
||
try:
|
||
market_summary = ak.stock_zh_a_spot_em()
|
||
if market_summary is not None and not market_summary.empty:
|
||
up_count = len(market_summary[market_summary['涨跌幅'] > 0])
|
||
down_count = len(market_summary[market_summary['涨跌幅'] < 0])
|
||
total = len(market_summary)
|
||
|
||
up_ratio = up_count / total
|
||
# 根据涨跌家数比例调整分数(权重30%)
|
||
score += (up_ratio - 0.5) * 60
|
||
factors.append(f"涨跌家数比例: {up_ratio:.1%}")
|
||
except:
|
||
pass
|
||
|
||
# 确保分数在0-100之间
|
||
score = max(0, min(100, score))
|
||
|
||
# 解读恐慌贪婪指数
|
||
if score >= 75:
|
||
level = "极度贪婪"
|
||
interpretation = "市场情绪极度乐观,投资者贪婪,需警惕回调风险"
|
||
elif score >= 60:
|
||
level = "贪婪"
|
||
interpretation = "市场情绪乐观,投资者偏向贪婪"
|
||
elif score >= 40:
|
||
level = "中性"
|
||
interpretation = "市场情绪中性,投资者相对理性"
|
||
elif score >= 25:
|
||
level = "恐慌"
|
||
interpretation = "市场情绪悲观,投资者偏向恐慌"
|
||
else:
|
||
level = "极度恐慌"
|
||
interpretation = "市场情绪极度悲观,投资者恐慌,可能存在超卖机会"
|
||
|
||
return {
|
||
"score": f"{score:.1f}",
|
||
"level": level,
|
||
"interpretation": interpretation,
|
||
"factors": factors
|
||
}
|
||
except Exception as e:
|
||
print(f"计算恐慌贪婪指数失败: {e}")
|
||
return None
|
||
|
||
def format_sentiment_data_for_ai(self, sentiment_data):
|
||
"""
|
||
将市场情绪数据格式化为适合AI阅读的文本
|
||
"""
|
||
if not sentiment_data or not sentiment_data.get("data_success"):
|
||
return "未能获取市场情绪数据"
|
||
|
||
text_parts = []
|
||
|
||
# ARBR指标
|
||
if sentiment_data.get("arbr_data"):
|
||
arbr = sentiment_data["arbr_data"]
|
||
text_parts.append(f"""
|
||
【ARBR市场情绪指标】
|
||
- 计算周期:{arbr.get('period', 26)}日
|
||
- AR值:{arbr.get('latest_ar', 'N/A'):.2f}(人气指标)
|
||
- BR值:{arbr.get('latest_br', 'N/A'):.2f}(意愿指标)
|
||
- 信号:{arbr.get('signals', {}).get('overall_signal', 'N/A')}
|
||
- 解读:
|
||
{chr(10).join([' * ' + item for item in arbr.get('interpretation', [])])}
|
||
|
||
ARBR统计数据:
|
||
- AR历史均值:{arbr.get('statistics', {}).get('ar_mean', 0):.2f}
|
||
- BR历史均值:{arbr.get('statistics', {}).get('br_mean', 0):.2f}
|
||
- 历史买入信号比例:{arbr.get('signal_statistics', {}).get('buy_ratio', 'N/A')}
|
||
- 历史卖出信号比例:{arbr.get('signal_statistics', {}).get('sell_ratio', 'N/A')}
|
||
""")
|
||
|
||
# 换手率
|
||
if sentiment_data.get("turnover_rate"):
|
||
turnover = sentiment_data["turnover_rate"]
|
||
text_parts.append(f"""
|
||
【换手率数据】
|
||
- 当前换手率:{turnover.get('current_turnover_rate', 'N/A')}%
|
||
- 解读:{turnover.get('interpretation', 'N/A')}
|
||
""")
|
||
|
||
# 大盘情绪
|
||
if sentiment_data.get("market_index"):
|
||
market = sentiment_data["market_index"]
|
||
text_parts.append(f"""
|
||
【大盘市场情绪】
|
||
- 指数:{market.get('index_name', 'N/A')}
|
||
- 涨跌幅:{market.get('change_percent', 'N/A')}%
|
||
""")
|
||
if market.get('sentiment_score'):
|
||
text_parts.append(f"""- 市场情绪得分:{market.get('sentiment_score', 'N/A')}
|
||
- 涨家数:{market.get('up_count', 'N/A')}只
|
||
- 跌家数:{market.get('down_count', 'N/A')}只
|
||
- 平家数:{market.get('flat_count', 'N/A')}只
|
||
- 市场情绪:{market.get('sentiment_interpretation', 'N/A')}
|
||
""")
|
||
|
||
# 涨跌停统计
|
||
if sentiment_data.get("limit_up_down"):
|
||
limit = sentiment_data["limit_up_down"]
|
||
text_parts.append(f"""
|
||
【涨跌停统计】
|
||
- 涨停股数量:{limit.get('limit_up_count', 0)}只
|
||
- 跌停股数量:{limit.get('limit_down_count', 0)}只
|
||
- 涨停占比:{limit.get('limit_ratio', 'N/A')}
|
||
- 解读:{limit.get('interpretation', 'N/A')}
|
||
""")
|
||
|
||
# 融资融券
|
||
if sentiment_data.get("margin_trading"):
|
||
margin = sentiment_data["margin_trading"]
|
||
text_parts.append(f"""
|
||
【融资融券数据】
|
||
- 融资余额:{margin.get('margin_balance', 'N/A')}元
|
||
- 融券余额:{margin.get('short_balance', 'N/A')}元
|
||
- 融资买入额:{margin.get('margin_buy', 'N/A')}元
|
||
- 解读:{'; '.join(margin.get('interpretation', []))}
|
||
""")
|
||
|
||
# 恐慌贪婪指数
|
||
if sentiment_data.get("fear_greed_index"):
|
||
fear_greed = sentiment_data["fear_greed_index"]
|
||
text_parts.append(f"""
|
||
【市场恐慌贪婪指数】
|
||
- 指数得分:{fear_greed.get('score', 'N/A')}/100
|
||
- 情绪等级:{fear_greed.get('level', 'N/A')}
|
||
- 解读:{fear_greed.get('interpretation', 'N/A')}
|
||
""")
|
||
|
||
return "\n".join(text_parts)
|
||
|
||
|
||
# 测试函数
|
||
if __name__ == "__main__":
|
||
print("测试市场情绪数据获取...")
|
||
fetcher = MarketSentimentDataFetcher()
|
||
|
||
# 测试平安银行
|
||
symbol = "000001"
|
||
print(f"\n正在获取 {symbol} 的市场情绪数据...")
|
||
|
||
sentiment_data = fetcher.get_market_sentiment_data(symbol)
|
||
|
||
if sentiment_data.get("data_success"):
|
||
print("\n" + "="*60)
|
||
print("市场情绪数据获取成功!")
|
||
print("="*60)
|
||
|
||
formatted_text = fetcher.format_sentiment_data_for_ai(sentiment_data)
|
||
print(formatted_text)
|
||
else:
|
||
print(f"\n获取失败: {sentiment_data.get('error', '未知错误')}")
|
||
|