migration: establish exact preserved app baseline
This commit is contained in:
+406
@@ -0,0 +1,406 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user