Files

407 lines
17 KiB
Python

from __future__ import annotations
from collections import Counter
import math
from datetime import datetime, timedelta
from typing import Any
from sentiment_engine import apply_sentiment_to_dashboard
DEMO_LIMITS = [
("600664", "哈药股份", 4.94, 10.02, "医药", "创新药+医药流通", "09:25:00", "09:25:00", 0, 5, 11.78, 14.65, 26458),
("603580", "艾艾精工", 40.84, 9.99, "机器人", "实控人变更+机器人", "09:25:01", "09:25:01", 0, 3, 0.11, 0.53, 27190),
("600785", "新华百货", 9.32, 10.04, "零售", "新零售+股权转让", "10:32:33", "10:32:33", 2, 2, 9.57, 29.44, 4285),
("002739", "万达电影", 10.32, 10.02, "文化传媒", "影视院线+AI视频", "09:30:33", "09:30:33", 0, 2, 3.95, 217.94, 25014),
("000504", "南华生物", 9.36, 9.99, "医药", "细胞医疗+中报预增", "09:39:18", "09:39:18", 1, 2, 8.73, 30.89, 1962),
("000676", "智度股份", 6.22, 10.09, "端侧AI", "AI营销+端侧AI", "09:46:45", "09:46:45", 0, 2, 6.61, 78.36, 10368),
("600162", "香江控股", 2.78, 9.88, "房地产", "房地产+地产链", "09:30:57", "09:30:57", 1, 2, 10.56, 90.86, 4540),
("002365", "永安药业", 13.18, 10.02, "医药", "医药+宠物经济", "09:33:24", "09:33:24", 0, 2, 10.52, 38.84, 8277),
("000566", "海南海药", 5.67, 10.10, "脑机接口", "创新药+脑机接口", "11:01:12", "11:03:48", 2, 2, 22.75, 73.56, 8769),
("002632", "道明光学", 9.63, 10.06, "端侧AI", "AI手机+反光材料", "09:25:00", "09:25:00", 0, 1, 2.63, 60.15, 13417),
("000892", "欢瑞世纪", 3.87, 9.94, "文化传媒", "短剧+AI应用", "09:34:57", "09:34:57", 0, 1, 10.80, 37.96, 5635),
("603496", "恒为科技", 25.08, 10.00, "云计算", "算力+华为", "09:58:12", "10:46:30", 1, 1, 7.65, 80.31, 16611),
("603327", "福蓉科技", 8.57, 10.01, "端侧AI", "AI手机+消费电子", "09:30:02", "09:30:02", 0, 1, 7.02, 77.84, 7784),
("300968", "格林精密", 10.24, 20.00, "端侧AI", "折叠屏+AI眼镜", "09:36:33", "09:36:33", 0, 1, 20.06, 48.23, 7850),
("002045", "国光电器", 8.34, 10.03, "消费电子", "音响电声+AI眼镜", "09:37:45", "09:37:45", 0, 1, 7.11, 66.04, 4517),
("600203", "福日电子", 11.92, 9.96, "消费电子", "华为产业链+机器人", "09:45:03", "09:45:03", 0, 1, 12.04, 105.50, 10554),
("002881", "美格智能", 39.05, 10.00, "端侧AI", "物理AI+算力模组", "10:07:42", "10:07:42", 0, 1, 14.32, 128.20, 4299),
]
DEMO_BROKEN = [
("002141", "贤丰控股", 5.91, 5.35, "PCB板", "PCB板+资产重组", "09:37:03", "14:56:24", 3, 18.95, 61.05),
("002432", "九安医疗", 72.00, 7.48, "医药", "业绩增长+AI应用", "10:53:00", "14:09:45", 5, 14.13, 335.00),
("002980", "华盛昌", 107.37, 5.12, "光通信", "光通信+仪器仪表", "09:59:18", "14:38:36", 1, 17.94, 108.75),
("603725", "天安新材", 14.08, 7.40, "机器人", "机器人+新材料", "09:36:34", "14:37:19", 5, 13.58, 42.92),
("603127", "昭衍新药", 53.25, 5.20, "医药", "创新药+CRO", "10:35:49", "10:46:55", 2, 19.56, 335.66),
("002261", "拓维信息", 29.95, 6.47, "云计算", "算力+华为", "10:48:15", "10:53:54", 3, 12.04, 343.26),
("603893", "瑞芯微", 222.24, 5.58, "国产芯片", "国产芯片+端侧AI", "09:55:26", "13:31:14", 1, 7.60, 939.80),
("603103", "横店影视", 14.94, 5.21, "文化传媒", "影视院线+暑期档", "13:01:06", "13:01:51", 1, 2.79, 94.75),
]
DEMO_DOWN = [
("603683", "晶华新材", 25.56, -10.00, "新材料", "高位股风险释放", 4.41, 173.67, 1),
("603928", "兴业股份", 12.34, -9.99, "化工", "连续上涨后补跌", 11.96, 42.04, 4),
("000988", "华工科技", 130.69, -10.00, "光通信", "高位成交放大", 6.28, 1313.42, 1),
("603137", "恒尚节能", 32.05, -10.00, "建筑", "昨日涨停断板", 1.48, 58.63, 1),
("603115", "海星股份", 81.06, -10.00, "有色金属", "板块退潮", 3.02, 196.08, 1),
("605376", "博迁新材", 166.02, -10.00, "新材料", "资金兑现", 5.35, 434.31, 1),
("003020", "立方制药", 19.72, -10.00, "医药", "医药分化", 22.88, 45.00, 1),
("605255", "天普股份", 78.47, -10.00, "汽车零部件", "连板失败", 2.12, 105.21, 1),
("002123", "梦网科技", 7.68, -9.96, "通信", "板块调整", 1.39, 61.86, 2),
("603713", "密尔克卫", 64.80, -10.00, "物流", "业绩预期调整", 3.99, 103.43, 1),
]
def _stock_rows() -> list[dict[str, Any]]:
return [
{
"code": code,
"ts_code": code,
"name": name,
"price": price,
"change": change,
"sector": sector,
"reason": reason,
"first_time": first_time,
"last_time": last_time,
"open_times": open_times,
"streak": streak,
"turnover_rate": turnover,
"amount_billion": amount,
"seal_amount_million": seal,
"float_mv_billion": round(amount * 3.2, 1),
"status": "涨停",
}
for code, name, price, change, sector, reason, first_time, last_time,
open_times, streak, turnover, amount, seal in DEMO_LIMITS
]
def _broken_rows() -> list[dict[str, Any]]:
return [
{
"code": code,
"ts_code": code,
"name": name,
"price": price,
"change": change,
"sector": sector,
"reason": reason,
"first_time": first_time,
"last_time": last_time,
"open_times": open_times,
"streak": 1,
"turnover_rate": turnover,
"amount_billion": amount,
"seal_amount_million": 0,
"float_mv_billion": round(amount * 3.5, 1),
"status": "炸板",
}
for code, name, price, change, sector, reason, first_time, last_time,
open_times, turnover, amount in DEMO_BROKEN
]
def _down_rows() -> list[dict[str, Any]]:
return [
{
"code": code,
"ts_code": code,
"name": name,
"price": price,
"change": change,
"sector": sector,
"reason": reason,
"first_time": "--",
"last_time": "--",
"open_times": 0,
"streak": streak,
"turnover_rate": turnover,
"amount_billion": amount,
"seal_amount_million": 0,
"float_mv_billion": round(amount * 4.1, 1),
"status": "跌停",
}
for code, name, price, change, sector, reason, turnover, amount, streak in DEMO_DOWN
]
def _ladders(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
result = []
for level in sorted({row["streak"] for row in rows}, reverse=True):
stocks = [row for row in rows if row["streak"] == level]
result.append(
{
"level": level,
"label": "首板" if level == 1 else f"{level}板",
"count": len(stocks),
"stocks": stocks,
}
)
return result
def _sectors(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
counts = Counter(row["sector"] for row in rows)
result = []
for name, count in counts.most_common():
stocks = [row for row in rows if row["sector"] == name]
result.append(
{
"name": name,
"count": count,
"strength": min(99, 48 + count * 9 + max(row["streak"] for row in stocks) * 4),
"amount_billion": round(sum(row["amount_billion"] for row in stocks), 1),
"leader": max(stocks, key=lambda row: (row["streak"], row["amount_billion"]))["name"],
"change": round(sum(row["change"] for row in stocks) / count, 2),
"max_streak": max(row["streak"] for row in stocks),
}
)
return result
def _yesterday_rows(current: list[dict[str, Any]]) -> list[dict[str, Any]]:
current_map = {row["code"]: row for row in current}
definitions = [
("600664", "哈药股份", 4, 10.02, "晋级"),
("603580", "艾艾精工", 2, 9.99, "晋级"),
("600785", "新华百货", 1, 10.04, "晋级"),
("002739", "万达电影", 1, 10.02, "晋级"),
("000504", "南华生物", 1, 9.99, "晋级"),
("000676", "智度股份", 1, 10.09, "晋级"),
("603127", "昭衍新药", 1, 5.20, "炸板"),
("002432", "九安医疗", 2, 7.48, "炸板"),
("001388", "信通电子", 3, -5.33, "断板"),
("605255", "天普股份", 2, -10.00, "跌停"),
("600403", "大有能源", 1, -6.75, "断板"),
("002185", "华天科技", 1, -10.00, "跌停"),
("600829", "人民同泰", 1, 2.30, "断板"),
("600844", "金煤科技", 1, 1.18, "断板"),
]
rows = []
for code, name, prior_streak, current_change, outcome in definitions:
current_row = current_map.get(code, {})
rows.append(
{
"code": code,
"name": name,
"prior_streak": prior_streak,
"current_streak": current_row.get("streak", 0),
"current_change": current_change,
"current_price": current_row.get("price", 0),
"sector": current_row.get("sector", "其他"),
"reason": current_row.get("reason", "昨日涨停股表现跟踪"),
"outcome": outcome,
}
)
return rows
def _performance(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
result = []
for level in sorted({row["prior_streak"] for row in rows}, reverse=True):
group = [row for row in rows if row["prior_streak"] == level]
advanced = sum(row["outcome"] == "晋级" for row in group)
positive = sum(row["current_change"] > 0 for row in group)
result.append(
{
"level": level,
"label": "昨日首板" if level == 1 else f"昨日{level}板",
"count": len(group),
"advanced": advanced,
"advance_rate": round(advanced / len(group) * 100, 1),
"positive_rate": round(positive / len(group) * 100, 1),
"average_change": round(sum(row["current_change"] for row in group) / len(group), 2),
}
)
return result
def _rotation(sectors: list[dict[str, Any]]) -> list[dict[str, Any]]:
previous_counts = {
"端侧AI": 7,
"医药": 5,
"文化传媒": 1,
"消费电子": 1,
"机器人": 3,
"房地产": 2,
"零售": 0,
"云计算": 2,
"脑机接口": 1,
}
result = []
for index, sector in enumerate(sectors, start=1):
previous = previous_counts.get(sector["name"], 0)
delta = sector["count"] - previous
result.append(
{
**sector,
"rank": index,
"previous_count": previous,
"delta": delta,
"trend": "升温" if delta > 0 else "降温" if delta < 0 else "持平",
}
)
return result
def build_demo_dashboard(trade_date: str, notice: str = "") -> dict[str, Any]:
limits = _stock_rows()
broken = _broken_rows()
down_limits = _down_rows()
ladders = _ladders(limits)
sectors = _sectors(limits)
yesterday = _yesterday_rows(limits)
dashboard = {
"meta": {
"trade_date": f"{trade_date[:4]}-{trade_date[4:6]}-{trade_date[6:8]}",
"previous_trade_date": "2026-07-16",
"source": "demo",
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"notice": notice or "当前展示演示数据,配置 Tushare Token 后可读取真实行情。",
},
"overview": {
"up_count": 2344,
"down_count": 2695,
"flat_count": 33,
"limit_up_count": 41,
"limit_down_count": 3,
"broken_count": 25,
"amount_billion": 24035.6,
"seal_rate": 62.1,
},
"limits": limits,
"broken": broken,
"down_limits": down_limits,
"yesterday_limits": yesterday,
"limit_performance": _performance(yesterday),
"ladders": ladders,
"sectors": sectors,
"sector_rotation": _rotation(sectors),
}
return apply_sentiment_to_dashboard(dashboard)
def build_demo_dragon_tiger(trade_date: str, notice: str = "") -> dict[str, Any]:
stocks = _stock_rows()[:10]
seat_names = [
"机构专用",
"沪股通专用",
"深股通专用",
"中信证券股份有限公司上海分公司",
"国泰海通证券股份有限公司南京太平南路证券营业部",
]
rows = []
for index, stock in enumerate(stocks):
buy = round(86.5 - index * 6.3, 2)
sell = round(22.8 + index * 3.1, 2)
net = round(buy - sell, 2)
institutions = [
{
"seat_name": seat_names[index % len(seat_names)],
"buy_million": buy,
"sell_million": sell,
"net_buy_million": net,
},
{
"seat_name": seat_names[(index + 2) % len(seat_names)],
"buy_million": round(buy * 0.42, 2),
"sell_million": round(sell * 0.65, 2),
"net_buy_million": round(buy * 0.42 - sell * 0.65, 2),
},
]
rows.append(
{
"code": stock["code"],
"ts_code": stock["code"] + (".SH" if stock["code"].startswith("6") else ".SZ"),
"name": stock["name"],
"price": stock["price"],
"change": stock["change"],
"turnover_rate": stock["turnover_rate"],
"amount_billion": stock["amount_billion"],
"buy_million": buy,
"sell_million": sell,
"net_buy_million": net,
"net_rate": round(net / max(buy + sell, 1) * 100, 2),
"reason": "日涨幅偏离值达到7%" if index % 2 == 0 else "连续三个交易日涨幅偏离值累计达到20%",
"institutions": institutions,
}
)
return {
"meta": {
"trade_date": f"{trade_date[:4]}-{trade_date[4:6]}-{trade_date[6:8]}",
"source": "demo",
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"notice": notice or "龙虎榜当前展示演示数据。",
},
"summary": {
"stock_count": len(rows),
"institution_count": sum(len(row["institutions"]) for row in rows),
"net_buy_million": round(sum(row["net_buy_million"] for row in rows), 2),
"positive_count": sum(row["net_buy_million"] > 0 for row in rows),
},
"rows": rows,
}
def build_demo_stock_detail(
code: str,
trade_date: str,
name: str = "示例股票",
industry: str = "其他",
notice: str = "",
) -> dict[str, Any]:
end = datetime.strptime(trade_date, "%Y%m%d")
seed = sum(ord(character) for character in code)
base = 8 + seed % 45
prices = []
close = float(base)
for index in range(90):
day = end - timedelta(days=(89 - index))
drift = math.sin((index + seed) / 6) * 0.018 + 0.002
open_price = close * (1 + math.sin(index * 1.7) * 0.006)
close = max(1, close * (1 + drift))
high = max(open_price, close) * (1.012 + (index % 3) * 0.002)
low = min(open_price, close) * (0.988 - (index % 2) * 0.002)
prices.append(
{
"trade_date": day.strftime("%Y-%m-%d"),
"open": round(open_price, 2),
"high": round(high, 2),
"low": round(low, 2),
"close": round(close, 2),
"change": round((close / open_price - 1) * 100, 2),
"volume": 180000 + (index % 11) * 26000 + seed * 10,
"amount_billion": round(1.8 + (index % 9) * 0.36, 2),
}
)
return {
"meta": {
"trade_date": f"{trade_date[:4]}-{trade_date[4:6]}-{trade_date[6:8]}",
"source": "demo",
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"notice": notice or "个股详情当前展示演示数据。",
},
"stock": {
"code": code,
"ts_code": code + (".SH" if code.startswith("6") else ".SZ"),
"name": name,
"industry": industry,
"area": "--",
"market": "主板",
"list_date": "--",
"price": prices[-1]["close"],
"change": prices[-1]["change"],
},
"prices": prices,
"moneyflow": {
"net_million": 18.62,
"large_million": 31.48,
"medium_million": -4.12,
"small_million": -8.74,
},
}