增加智瞰龙虎板块
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
"""
|
||||
智瞰龙虎AI智能评分模块
|
||||
对龙虎榜上榜股票进行综合评分排名
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from typing import Dict, List
|
||||
from collections import Counter
|
||||
|
||||
|
||||
class LonghubangScoring:
|
||||
"""龙虎榜股票智能评分系统"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化评分系统"""
|
||||
# 顶级游资名单(根据市场知名度和历史战绩)
|
||||
self.top_youzi = [
|
||||
'赵老哥', '章盟主', '92科比', '瑞鹤仙', '小鳄鱼',
|
||||
'养家心法', '欢乐海岸', '古北路', '成都系', '佛山系',
|
||||
'方新侠', '乔帮主', '淮海路', '东方财富',
|
||||
'国信深圳', '华泰深圳', '中信杭州', '招商深圳'
|
||||
]
|
||||
|
||||
# 知名游资(次一级)
|
||||
self.famous_youzi = [
|
||||
'深股通', '沪股通', '北向资金',
|
||||
'中金公司', '中信证券', '国泰君安', '海通证券',
|
||||
'广发证券', '华泰证券', '招商证券'
|
||||
]
|
||||
|
||||
# 机构关键词
|
||||
self.institution_keywords = [
|
||||
'机构专用', '机构', '基金', '保险', '社保',
|
||||
'QFII', 'RQFII', '券商', '信托'
|
||||
]
|
||||
|
||||
print("[智瞰龙虎] 评分系统初始化完成")
|
||||
|
||||
def calculate_stock_score(self, stock_data: List[Dict]) -> float:
|
||||
"""
|
||||
计算单个股票的综合评分
|
||||
|
||||
Args:
|
||||
stock_data: 该股票的所有龙虎榜记录
|
||||
|
||||
Returns:
|
||||
综合评分 (0-100分)
|
||||
"""
|
||||
if not stock_data:
|
||||
return 0.0
|
||||
|
||||
# 1. 买入资金含金量评分 (0-30分)
|
||||
capital_quality_score = self._calculate_capital_quality(stock_data)
|
||||
|
||||
# 2. 净买入额评分 (0-25分)
|
||||
net_inflow_score = self._calculate_net_inflow_score(stock_data)
|
||||
|
||||
# 3. 卖出压力评分 (0-20分)
|
||||
sell_pressure_score = self._calculate_sell_pressure_score(stock_data)
|
||||
|
||||
# 4. 机构共振评分 (0-15分)
|
||||
institution_score = self._calculate_institution_score(stock_data)
|
||||
|
||||
# 5. 其他加分项 (0-10分)
|
||||
bonus_score = self._calculate_bonus_score(stock_data)
|
||||
|
||||
# 综合评分
|
||||
total_score = (
|
||||
capital_quality_score +
|
||||
net_inflow_score +
|
||||
sell_pressure_score +
|
||||
institution_score +
|
||||
bonus_score
|
||||
)
|
||||
|
||||
return round(total_score, 1)
|
||||
|
||||
def _calculate_capital_quality(self, stock_data: List[Dict]) -> float:
|
||||
"""
|
||||
计算买入资金含金量评分 (0-30分)
|
||||
顶级游资加分多,普通游资加分少
|
||||
"""
|
||||
score = 0.0
|
||||
max_score = 30.0
|
||||
|
||||
buyers = []
|
||||
for record in stock_data:
|
||||
buy_amount = record.get('买入金额', 0) or record.get('mrje', 0)
|
||||
# 确保转换为数值类型
|
||||
try:
|
||||
buy_amount = float(buy_amount) if buy_amount else 0
|
||||
except (ValueError, TypeError):
|
||||
buy_amount = 0
|
||||
|
||||
if buy_amount > 0:
|
||||
youzi_name = record.get('游资名称', '') or record.get('yzmc', '')
|
||||
yingye_bu = record.get('营业部', '') or record.get('yyb', '')
|
||||
buyers.append({
|
||||
'name': youzi_name,
|
||||
'yingye_bu': yingye_bu,
|
||||
'amount': float(buy_amount)
|
||||
})
|
||||
|
||||
if not buyers:
|
||||
return 0.0
|
||||
|
||||
# 顶级游资:每个加8-10分
|
||||
top_youzi_count = 0
|
||||
for buyer in buyers:
|
||||
for top in self.top_youzi:
|
||||
if top in buyer['name'] or top in buyer['yingye_bu']:
|
||||
top_youzi_count += 1
|
||||
score += 10.0
|
||||
break
|
||||
|
||||
# 知名游资:每个加4-6分
|
||||
famous_youzi_count = 0
|
||||
for buyer in buyers:
|
||||
is_top = any(top in buyer['name'] or top in buyer['yingye_bu']
|
||||
for top in self.top_youzi)
|
||||
if not is_top:
|
||||
for famous in self.famous_youzi:
|
||||
if famous in buyer['name'] or famous in buyer['yingye_bu']:
|
||||
famous_youzi_count += 1
|
||||
score += 5.0
|
||||
break
|
||||
|
||||
# 普通游资:每个加1-2分
|
||||
ordinary_count = len(buyers) - top_youzi_count - famous_youzi_count
|
||||
score += ordinary_count * 1.5
|
||||
|
||||
# 限制最高分
|
||||
return min(score, max_score)
|
||||
|
||||
def _calculate_net_inflow_score(self, stock_data: List[Dict]) -> float:
|
||||
"""
|
||||
计算净买入额评分 (0-25分)
|
||||
真金白银越多分数越高
|
||||
"""
|
||||
max_score = 25.0
|
||||
|
||||
# 计算总净流入
|
||||
total_net_inflow = 0.0
|
||||
for record in stock_data:
|
||||
net_inflow = record.get('净流入金额', 0) or record.get('jlrje', 0)
|
||||
try:
|
||||
net_inflow = float(net_inflow) if net_inflow else 0
|
||||
total_net_inflow += net_inflow
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if total_net_inflow <= 0:
|
||||
return 0.0
|
||||
|
||||
# 净流入分段评分
|
||||
# 1000万以下:0-10分
|
||||
# 1000-5000万:10-18分
|
||||
# 5000万-1亿:18-22分
|
||||
# 1亿以上:22-25分
|
||||
net_inflow_wan = total_net_inflow / 10000 # 转换为万元
|
||||
|
||||
if net_inflow_wan < 1000:
|
||||
score = (net_inflow_wan / 1000) * 10
|
||||
elif net_inflow_wan < 5000:
|
||||
score = 10 + ((net_inflow_wan - 1000) / 4000) * 8
|
||||
elif net_inflow_wan < 10000:
|
||||
score = 18 + ((net_inflow_wan - 5000) / 5000) * 4
|
||||
else:
|
||||
score = 22 + min((net_inflow_wan - 10000) / 10000, 1) * 3
|
||||
|
||||
return min(score, max_score)
|
||||
|
||||
def _calculate_sell_pressure_score(self, stock_data: List[Dict]) -> float:
|
||||
"""
|
||||
计算卖出压力评分 (0-20分)
|
||||
卖出压力越小分数越高
|
||||
"""
|
||||
max_score = 20.0
|
||||
|
||||
total_buy = 0.0
|
||||
total_sell = 0.0
|
||||
|
||||
for record in stock_data:
|
||||
buy_amount = record.get('买入金额', 0) or record.get('mrje', 0)
|
||||
sell_amount = record.get('卖出金额', 0) or record.get('mcje', 0)
|
||||
|
||||
try:
|
||||
buy_amount = float(buy_amount) if buy_amount else 0
|
||||
total_buy += buy_amount
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
sell_amount = float(sell_amount) if sell_amount else 0
|
||||
total_sell += sell_amount
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if total_buy == 0:
|
||||
return 0.0
|
||||
|
||||
# 计算卖出比例
|
||||
sell_ratio = total_sell / total_buy if total_buy > 0 else 1.0
|
||||
|
||||
# 卖出压力评分
|
||||
# 卖出比例0-10%:20分
|
||||
# 卖出比例10-30%:15-20分
|
||||
# 卖出比例30-50%:10-15分
|
||||
# 卖出比例50-80%:5-10分
|
||||
# 卖出比例80%以上:0-5分
|
||||
if sell_ratio < 0.1:
|
||||
score = 20.0
|
||||
elif sell_ratio < 0.3:
|
||||
score = 20.0 - (sell_ratio - 0.1) / 0.2 * 5
|
||||
elif sell_ratio < 0.5:
|
||||
score = 15.0 - (sell_ratio - 0.3) / 0.2 * 5
|
||||
elif sell_ratio < 0.8:
|
||||
score = 10.0 - (sell_ratio - 0.5) / 0.3 * 5
|
||||
else:
|
||||
score = 5.0 - min(sell_ratio - 0.8, 0.2) / 0.2 * 5
|
||||
|
||||
return max(0, min(score, max_score))
|
||||
|
||||
def _calculate_institution_score(self, stock_data: List[Dict]) -> float:
|
||||
"""
|
||||
计算机构共振评分 (0-15分)
|
||||
机构+游资共振最高分
|
||||
"""
|
||||
max_score = 15.0
|
||||
|
||||
has_institution = False
|
||||
has_youzi = False
|
||||
institution_count = 0
|
||||
youzi_count = 0
|
||||
|
||||
for record in stock_data:
|
||||
buy_amount = record.get('买入金额', 0) or record.get('mrje', 0)
|
||||
try:
|
||||
buy_amount = float(buy_amount) if buy_amount else 0
|
||||
except (ValueError, TypeError):
|
||||
buy_amount = 0
|
||||
|
||||
if buy_amount <= 0:
|
||||
continue
|
||||
|
||||
youzi_name = record.get('游资名称', '') or record.get('yzmc', '')
|
||||
yingye_bu = record.get('营业部', '') or record.get('yyb', '')
|
||||
|
||||
# 检查是否是机构
|
||||
if any(keyword in youzi_name or keyword in yingye_bu
|
||||
for keyword in self.institution_keywords):
|
||||
has_institution = True
|
||||
institution_count += 1
|
||||
else:
|
||||
has_youzi = True
|
||||
youzi_count += 1
|
||||
|
||||
# 评分逻辑
|
||||
if has_institution and has_youzi:
|
||||
# 机构+游资共振:最高分
|
||||
score = 15.0
|
||||
elif has_institution:
|
||||
# 仅机构:8-12分
|
||||
score = min(8 + institution_count * 2, 12)
|
||||
elif has_youzi:
|
||||
# 仅游资:5-10分
|
||||
score = min(5 + youzi_count * 1, 10)
|
||||
else:
|
||||
score = 0.0
|
||||
|
||||
return min(score, max_score)
|
||||
|
||||
def _calculate_bonus_score(self, stock_data: List[Dict]) -> float:
|
||||
"""
|
||||
计算其他加分项 (0-10分)
|
||||
买卖比例、主力集中度、热门概念等
|
||||
"""
|
||||
max_score = 10.0
|
||||
score = 0.0
|
||||
|
||||
if not stock_data:
|
||||
return 0.0
|
||||
|
||||
# 1. 主力集中度加分 (0-3分)
|
||||
# 如果资金集中在少数几个席位,说明主力信心强
|
||||
seat_count = len(stock_data)
|
||||
if seat_count == 1:
|
||||
score += 3.0
|
||||
elif seat_count == 2:
|
||||
score += 2.5
|
||||
elif seat_count == 3:
|
||||
score += 2.0
|
||||
elif seat_count <= 5:
|
||||
score += 1.5
|
||||
else:
|
||||
score += 1.0
|
||||
|
||||
# 2. 热门概念加分 (0-3分)
|
||||
all_concepts = []
|
||||
for record in stock_data:
|
||||
concepts = record.get('概念', '') or record.get('gl', '')
|
||||
if concepts:
|
||||
all_concepts.extend([c.strip() for c in str(concepts).split(',')])
|
||||
|
||||
hot_keywords = [
|
||||
'人工智能', 'AI', 'ChatGPT', '算力', '新能源', '芯片', '半导体',
|
||||
'军工', '医药', '消费', '5G', '新材料', '量子', '光伏',
|
||||
'储能', '锂电池', '汽车', '游戏', '传媒', '元宇宙'
|
||||
]
|
||||
|
||||
concept_score = 0
|
||||
for concept in all_concepts:
|
||||
if any(keyword in concept for keyword in hot_keywords):
|
||||
concept_score += 0.3
|
||||
|
||||
score += min(concept_score, 3.0)
|
||||
|
||||
# 3. 连续上榜加分 (0-2分)
|
||||
# 这里简化处理,如果有多条记录可能表示连续上榜
|
||||
if len(stock_data) >= 3:
|
||||
score += 2.0
|
||||
elif len(stock_data) == 2:
|
||||
score += 1.0
|
||||
|
||||
# 4. 买卖比例优秀加分 (0-2分)
|
||||
total_buy = 0.0
|
||||
total_sell = 0.0
|
||||
for r in stock_data:
|
||||
try:
|
||||
buy = float(r.get('买入金额', 0) or r.get('mrje', 0) or 0)
|
||||
total_buy += buy
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
try:
|
||||
sell = float(r.get('卖出金额', 0) or r.get('mcje', 0) or 0)
|
||||
total_sell += sell
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if total_buy > 0:
|
||||
buy_sell_ratio = total_buy / (total_sell + 1)
|
||||
if buy_sell_ratio >= 10:
|
||||
score += 2.0
|
||||
elif buy_sell_ratio >= 5:
|
||||
score += 1.5
|
||||
elif buy_sell_ratio >= 3:
|
||||
score += 1.0
|
||||
|
||||
return min(score, max_score)
|
||||
|
||||
def score_all_stocks(self, data_list: List[Dict]) -> pd.DataFrame:
|
||||
"""
|
||||
对所有上榜股票进行评分排名
|
||||
|
||||
Args:
|
||||
data_list: 龙虎榜数据列表
|
||||
|
||||
Returns:
|
||||
评分排名DataFrame
|
||||
"""
|
||||
if not data_list:
|
||||
return pd.DataFrame()
|
||||
|
||||
# 按股票代码分组
|
||||
stocks_dict = {}
|
||||
for record in data_list:
|
||||
code = record.get('股票代码') or record.get('gpdm')
|
||||
name = record.get('股票名称') or record.get('gpmc')
|
||||
|
||||
if not code:
|
||||
continue
|
||||
|
||||
if code not in stocks_dict:
|
||||
stocks_dict[code] = {
|
||||
'code': code,
|
||||
'name': name,
|
||||
'records': []
|
||||
}
|
||||
|
||||
stocks_dict[code]['records'].append(record)
|
||||
|
||||
# 计算每只股票的评分
|
||||
results = []
|
||||
for code, stock_info in stocks_dict.items():
|
||||
records = stock_info['records']
|
||||
|
||||
# 计算各维度评分
|
||||
capital_quality = self._calculate_capital_quality(records)
|
||||
net_inflow = self._calculate_net_inflow_score(records)
|
||||
sell_pressure = self._calculate_sell_pressure_score(records)
|
||||
institution = self._calculate_institution_score(records)
|
||||
bonus = self._calculate_bonus_score(records)
|
||||
|
||||
total_score = capital_quality + net_inflow + sell_pressure + institution + bonus
|
||||
|
||||
# 计算实际数据(安全转换)
|
||||
total_buy = 0.0
|
||||
total_sell = 0.0
|
||||
total_net = 0.0
|
||||
for r in records:
|
||||
try:
|
||||
buy = float(r.get('买入金额', 0) or r.get('mrje', 0) or 0)
|
||||
total_buy += buy
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
try:
|
||||
sell = float(r.get('卖出金额', 0) or r.get('mcje', 0) or 0)
|
||||
total_sell += sell
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
try:
|
||||
net = float(r.get('净流入金额', 0) or r.get('jlrje', 0) or 0)
|
||||
total_net += net
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# 统计买入席位数(安全比较)
|
||||
buy_seats = 0
|
||||
for r in records:
|
||||
try:
|
||||
buy = float(r.get('买入金额', 0) or r.get('mrje', 0) or 0)
|
||||
if buy > 0:
|
||||
buy_seats += 1
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# 统计机构数量
|
||||
institution_count = sum(1 for r in records
|
||||
if any(kw in (r.get('游资名称', '') or r.get('yzmc', '')) or
|
||||
kw in (r.get('营业部', '') or r.get('yyb', ''))
|
||||
for kw in self.institution_keywords))
|
||||
|
||||
# 获取概念
|
||||
concepts_list = []
|
||||
for r in records:
|
||||
concepts = r.get('概念', '') or r.get('gl', '')
|
||||
if concepts:
|
||||
concepts_list.extend([c.strip() for c in str(concepts).split(',')])
|
||||
|
||||
top_concepts = Counter(concepts_list).most_common(3)
|
||||
concept_str = ','.join([c[0] for c in top_concepts]) if top_concepts else ''
|
||||
|
||||
# 判断机构参与
|
||||
has_institution = institution_count > 0
|
||||
|
||||
results.append({
|
||||
'排名': 0, # 稍后填充
|
||||
'股票名称': stock_info['name'],
|
||||
'股票代码': code,
|
||||
'综合评分': round(total_score, 1),
|
||||
'资金含金量': round(capital_quality, 0),
|
||||
'净买入额': round(net_inflow, 0),
|
||||
'卖出压力': round(sell_pressure, 0),
|
||||
'机构共振': round(institution, 0),
|
||||
'加分项': round(bonus, 0),
|
||||
'顶级游资': self._count_top_youzi(records),
|
||||
'买方数': buy_seats,
|
||||
'机构参与': '✅' if has_institution else '❌',
|
||||
'净流入': round(total_net, 2)
|
||||
})
|
||||
|
||||
# 转换为DataFrame并排序
|
||||
df = pd.DataFrame(results)
|
||||
if df.empty:
|
||||
return df
|
||||
|
||||
df = df.sort_values('综合评分', ascending=False).reset_index(drop=True)
|
||||
df['排名'] = range(1, len(df) + 1)
|
||||
|
||||
# 添加奖牌
|
||||
if len(df) >= 1:
|
||||
df.loc[0, '排名'] = '🥇 1'
|
||||
if len(df) >= 2:
|
||||
df.loc[1, '排名'] = '🥈 2'
|
||||
if len(df) >= 3:
|
||||
df.loc[2, '排名'] = '🥉 3'
|
||||
|
||||
return df
|
||||
|
||||
def _count_top_youzi(self, records: List[Dict]) -> int:
|
||||
"""统计顶级游资数量"""
|
||||
count = 0
|
||||
for record in records:
|
||||
buy_amount = record.get('买入金额', 0) or record.get('mrje', 0)
|
||||
try:
|
||||
buy_amount = float(buy_amount) if buy_amount else 0
|
||||
except (ValueError, TypeError):
|
||||
buy_amount = 0
|
||||
|
||||
if buy_amount <= 0:
|
||||
continue
|
||||
|
||||
youzi_name = record.get('游资名称', '') or record.get('yzmc', '')
|
||||
yingye_bu = record.get('营业部', '') or record.get('yyb', '')
|
||||
|
||||
if any(top in youzi_name or top in yingye_bu for top in self.top_youzi):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def get_score_explanation(self) -> str:
|
||||
"""获取评分维度说明"""
|
||||
explanation = """
|
||||
【AI智能评分维度说明】
|
||||
|
||||
📊 总分100分,由5个维度组成:
|
||||
|
||||
1️⃣ 买入资金含金量 (0-30分)
|
||||
- 顶级游资(赵老哥、章盟主等):每个+10分
|
||||
- 知名游资(深股通、中信等):每个+5分
|
||||
- 普通游资:每个+1.5分
|
||||
|
||||
2️⃣ 净买入额评分 (0-25分)
|
||||
- 净流入1000万以下:0-10分
|
||||
- 净流入1000-5000万:10-18分
|
||||
- 净流入5000万-1亿:18-22分
|
||||
- 净流入1亿以上:22-25分
|
||||
|
||||
3️⃣ 卖出压力评分 (0-20分)
|
||||
- 卖出比例0-10%:20分(压力极小)
|
||||
- 卖出比例10-30%:15-20分(压力较小)
|
||||
- 卖出比例30-50%:10-15分(压力中等)
|
||||
- 卖出比例50-80%:5-10分(压力较大)
|
||||
- 卖出比例80%以上:0-5分(压力极大)
|
||||
|
||||
4️⃣ 机构共振评分 (0-15分)
|
||||
- 机构+游资共振:15分(最强信号)
|
||||
- 仅机构买入:8-12分
|
||||
- 仅游资买入:5-10分
|
||||
|
||||
5️⃣ 其他加分项 (0-10分)
|
||||
- 主力集中度:席位越少越集中,+1-3分
|
||||
- 热门概念:AI、新能源、芯片等,+0-3分
|
||||
- 连续上榜:连续多日上榜,+0-2分
|
||||
- 买卖比例优秀:买入远大于卖出,+0-2分
|
||||
|
||||
💡 评分越高,表示该股票受到资金青睐程度越高,
|
||||
但仍需结合市场环境、技术面等因素综合判断!
|
||||
"""
|
||||
return explanation
|
||||
|
||||
|
||||
# 测试函数
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("测试智瞰龙虎评分系统")
|
||||
print("=" * 60)
|
||||
|
||||
# 创建测试数据
|
||||
test_data = [
|
||||
{
|
||||
'股票代码': '001337',
|
||||
'股票名称': '四川黄金',
|
||||
'游资名称': '92科比',
|
||||
'营业部': '兴业证券股份有限公司南京天元东路证券营业部',
|
||||
'买入金额': 14470401,
|
||||
'卖出金额': 15080,
|
||||
'净流入金额': 14455321,
|
||||
'概念': '贵金属,黄金概念,次新股'
|
||||
},
|
||||
{
|
||||
'股票代码': '001337',
|
||||
'股票名称': '四川黄金',
|
||||
'游资名称': '赵老哥',
|
||||
'营业部': '某证券公司',
|
||||
'买入金额': 10000000,
|
||||
'卖出金额': 0,
|
||||
'净流入金额': 10000000,
|
||||
'概念': '贵金属,黄金概念'
|
||||
}
|
||||
]
|
||||
|
||||
scoring = LonghubangScoring()
|
||||
|
||||
# 测试评分
|
||||
df_result = scoring.score_all_stocks(test_data)
|
||||
|
||||
print("\n评分结果:")
|
||||
print(df_result)
|
||||
|
||||
print("\n" + scoring.get_score_explanation())
|
||||
|
||||
Reference in New Issue
Block a user