fix: 连续上榜改为严格N日每天上榜,缺一日都不行
This commit is contained in:
@@ -90,73 +90,52 @@ async def active_core_stocks(days: int = Query(10, ge=1, le=30, description="交
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/consecutive", summary="连续上榜的核心股")
|
@router.get("/consecutive", summary="最近N日每天上榜的核心股")
|
||||||
async def consecutive_core_stocks(min_days: int = Query(3, ge=2, le=10, description="最小连续天数")):
|
async def consecutive_core_stocks(days: int = Query(3, ge=2, le=10, description="交易日天数")):
|
||||||
from datetime import date as date_cls
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
try:
|
try:
|
||||||
# 最近30个有数据的交易日
|
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC LIMIT 30"
|
"SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC LIMIT ?",
|
||||||
|
(days,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
all_dates = [r["trade_date"] for r in reversed(rows)]
|
recent_dates = [r["trade_date"] for r in reversed(rows)]
|
||||||
|
|
||||||
if not all_dates:
|
if len(recent_dates) < days:
|
||||||
return JSONResponse({"stocks": []}, headers=_NO_CACHE_HEADERS)
|
return JSONResponse({"days": days, "stocks": []}, headers=_NO_CACHE_HEADERS)
|
||||||
|
|
||||||
# 查询所有上榜记录
|
placeholders = ",".join("?" * len(recent_dates))
|
||||||
placeholders = ",".join("?" * len(all_dates))
|
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
f"""SELECT trade_date, stock_code, stock_name, f3, cover_count
|
f"""SELECT trade_date, stock_code, stock_name, f3, cover_count
|
||||||
FROM daily_core_stocks
|
FROM daily_core_stocks
|
||||||
WHERE trade_date IN ({placeholders})
|
WHERE trade_date IN ({placeholders})""",
|
||||||
ORDER BY stock_code, trade_date""",
|
recent_dates,
|
||||||
all_dates,
|
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
# 按股票分组,记录上榜日期
|
|
||||||
stock_dates: dict[str, dict] = {}
|
stock_dates: dict[str, dict] = {}
|
||||||
for r in rows:
|
for r in rows:
|
||||||
code = r["stock_code"]
|
code = r["stock_code"]
|
||||||
s = stock_dates.setdefault(code, {
|
s = stock_dates.setdefault(code, {
|
||||||
"stockCode": code,
|
"stockCode": code,
|
||||||
"stockName": r["stock_name"],
|
"stockName": r["stock_name"],
|
||||||
"dates": [],
|
"dates": set(),
|
||||||
"dailyGains": {},
|
"dailyGains": {},
|
||||||
})
|
})
|
||||||
s["dates"].append(r["trade_date"])
|
s["dates"].add(r["trade_date"])
|
||||||
s["dailyGains"][r["trade_date"]] = r["f3"]
|
s["dailyGains"][r["trade_date"]] = r["f3"]
|
||||||
|
|
||||||
# 计算连续上榜天数(从最新交易日往回数)
|
|
||||||
results = []
|
results = []
|
||||||
for code, s in stock_dates.items():
|
for code, s in stock_dates.items():
|
||||||
dates_list = sorted(s["dates"])
|
if len(s["dates"]) >= days:
|
||||||
consecutive = 0
|
|
||||||
last_n = []
|
|
||||||
for i in range(len(dates_list) - 1, -1, -1):
|
|
||||||
if i == len(dates_list) - 1:
|
|
||||||
consecutive = 1
|
|
||||||
last_n.append(dates_list[i])
|
|
||||||
else:
|
|
||||||
prev_idx = all_dates.index(dates_list[i + 1]) if dates_list[i + 1] in all_dates else -1
|
|
||||||
curr_idx = all_dates.index(dates_list[i]) if dates_list[i] in all_dates else -1
|
|
||||||
if prev_idx - curr_idx <= 2:
|
|
||||||
consecutive += 1
|
|
||||||
last_n.append(dates_list[i])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if consecutive >= min_days:
|
|
||||||
results.append({
|
results.append({
|
||||||
"stockCode": code,
|
"stockCode": code,
|
||||||
"stockName": s["stockName"],
|
"stockName": s["stockName"],
|
||||||
"consecutiveDays": consecutive,
|
"consecutiveDays": len(s["dates"]),
|
||||||
"recentDates": last_n,
|
"recentDates": sorted(s["dates"]),
|
||||||
"dailyGains": {d: s["dailyGains"].get(d) for d in last_n},
|
"dailyGains": s["dailyGains"],
|
||||||
})
|
})
|
||||||
|
|
||||||
results.sort(key=lambda x: -x["consecutiveDays"])
|
results.sort(key=lambda x: -x["consecutiveDays"])
|
||||||
return JSONResponse({"stocks": results}, headers=_NO_CACHE_HEADERS)
|
return JSONResponse({"days": days, "recentDates": recent_dates, "stocks": results}, headers=_NO_CACHE_HEADERS)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ SYSTEM_PROMPT = """你是一位专业的A股市场分析师,擅长从数据中
|
|||||||
- get_market_dashboard: 获取市场整体数据(指数/涨跌统计/市场温度/连板梯队/行业强度/板块资金流/两融/海外指数与国内期货/事件情报)
|
- get_market_dashboard: 获取市场整体数据(指数/涨跌统计/市场温度/连板梯队/行业强度/板块资金流/两融/海外指数与国内期货/事件情报)
|
||||||
- get_theme_history: 获取指定日期的题材涨幅排行
|
- get_theme_history: 获取指定日期的题材涨幅排行
|
||||||
- get_active_core_stocks: 获取核心股追踪数据(10日涨幅矩阵+所属题材)
|
- get_active_core_stocks: 获取核心股追踪数据(10日涨幅矩阵+所属题材)
|
||||||
- get_consecutive_core_stocks: 获取连续上榜的核心股列表(近3日及以上连续上榜),用于识别持续活跃的热点股
|
- get_consecutive_core_stocks: 获取最近N个交易日每天都上榜的核心股(缺一日都不行),用于识别持续活跃的热点股
|
||||||
- get_stock_quote: 获取个股实时行情
|
- get_stock_quote: 获取个股实时行情
|
||||||
- get_fund_flow: 获取个股资金流向
|
- get_fund_flow: 获取个股资金流向
|
||||||
- get_news: 获取财经快讯(新浪7x24,用于重要消息面)
|
- get_news: 获取财经快讯(新浪7x24,用于重要消息面)
|
||||||
@@ -81,7 +81,7 @@ DAILY_ANALYSIS_PROMPT = """请对 {trade_date}(A股交易日) 的A股市场
|
|||||||
1. get_market_dashboard() —— 指数、涨跌统计、市场温度、连板梯队 limitLadder、板块资金流 sectorFundFlow、两融、海外指数与国内期货 globalMarkets
|
1. get_market_dashboard() —— 指数、涨跌统计、市场温度、连板梯队 limitLadder、板块资金流 sectorFundFlow、两融、海外指数与国内期货 globalMarkets
|
||||||
2. get_theme_history(date="{trade_date}") —— 今日题材涨幅榜
|
2. get_theme_history(date="{trade_date}") —— 今日题材涨幅榜
|
||||||
3. get_active_core_stocks() —— 核心股 10 日涨幅矩阵与所属题材
|
3. get_active_core_stocks() —— 核心股 10 日涨幅矩阵与所属题材
|
||||||
4. get_consecutive_core_stocks(min_days=3) —— 连续3日及以上上榜的核心股
|
4. get_consecutive_core_stocks(days=3) —— 最近3个交易日每天都上榜的核心股(缺一日都不行)
|
||||||
5. get_news(limit=30) —— 今日财经快讯
|
5. get_news(limit=30) —— 今日财经快讯
|
||||||
只有在需要核实某只具体个股时,才额外调用 get_stock_quote / get_fund_flow,合计不超过 2 次。
|
只有在需要核实某只具体个股时,才额外调用 get_stock_quote / get_fund_flow,合计不超过 2 次。
|
||||||
|
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ TOOLS = [
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "get_consecutive_core_stocks",
|
"name": "get_consecutive_core_stocks",
|
||||||
"description": "获取连续上榜的核心股列表(近3日及以上连续上榜),用于识别持续活跃的热点股",
|
"description": "获取最近N个交易日每天都上榜的核心股(缺一日都不行),用于识别持续活跃的热点股",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"min_days": {"type": "integer", "description": "最小连续天数,默认3"}
|
"days": {"type": "integer", "description": "交易日天数,默认3"}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ async def execute_tool(tool_name: str, arguments: dict) -> str:
|
|||||||
elif tool_name == "get_active_core_stocks":
|
elif tool_name == "get_active_core_stocks":
|
||||||
return await _get_active_core_stocks()
|
return await _get_active_core_stocks()
|
||||||
elif tool_name == "get_consecutive_core_stocks":
|
elif tool_name == "get_consecutive_core_stocks":
|
||||||
return await _get_consecutive_core_stocks(arguments.get("min_days", 3))
|
return await _get_consecutive_core_stocks(arguments.get("days", 3))
|
||||||
elif tool_name == "get_stock_quote":
|
elif tool_name == "get_stock_quote":
|
||||||
return await _get_stock_quote(arguments.get("code", ""))
|
return await _get_stock_quote(arguments.get("code", ""))
|
||||||
elif tool_name == "get_fund_flow":
|
elif tool_name == "get_fund_flow":
|
||||||
@@ -235,28 +235,28 @@ async def _get_active_core_stocks() -> str:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def _get_consecutive_core_stocks(min_days: int = 3) -> str:
|
async def _get_consecutive_core_stocks(days: int = 3) -> str:
|
||||||
"""获取连续上榜的核心股列表"""
|
"""获取最近N个交易日**每个交易日都上榜**的核心股,缺一日都不行"""
|
||||||
from database import get_connection
|
from database import get_connection
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
try:
|
try:
|
||||||
# 最近30个有数据的交易日
|
# 最近N个有数据的交易日
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC LIMIT 30"
|
"SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC LIMIT ?",
|
||||||
|
(days,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
all_dates = [r["trade_date"] for r in reversed(rows)]
|
recent_dates = [r["trade_date"] for r in reversed(rows)]
|
||||||
|
|
||||||
if not all_dates:
|
if len(recent_dates) < days:
|
||||||
return json.dumps({"dates": [], "stocks": []}, ensure_ascii=False)
|
return json.dumps({"days": days, "stocks": []}, ensure_ascii=False)
|
||||||
|
|
||||||
# 查询所有上榜记录
|
# 查询这些日期的上榜记录
|
||||||
placeholders = ",".join("?" * len(all_dates))
|
placeholders = ",".join("?" * len(recent_dates))
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
f"""SELECT trade_date, stock_code, stock_name, f3, cover_count
|
f"""SELECT trade_date, stock_code, stock_name, f3, cover_count
|
||||||
FROM daily_core_stocks
|
FROM daily_core_stocks
|
||||||
WHERE trade_date IN ({placeholders})
|
WHERE trade_date IN ({placeholders})""",
|
||||||
ORDER BY stock_code, trade_date""",
|
recent_dates,
|
||||||
all_dates,
|
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
# 按股票分组,记录上榜日期
|
# 按股票分组,记录上榜日期
|
||||||
@@ -266,48 +266,30 @@ async def _get_consecutive_core_stocks(min_days: int = 3) -> str:
|
|||||||
s = stock_dates.setdefault(code, {
|
s = stock_dates.setdefault(code, {
|
||||||
"stockCode": code,
|
"stockCode": code,
|
||||||
"stockName": r["stock_name"],
|
"stockName": r["stock_name"],
|
||||||
"dates": [],
|
"dates": set(),
|
||||||
"dailyGains": {},
|
"dailyGains": {},
|
||||||
})
|
})
|
||||||
s["dates"].append(r["trade_date"])
|
s["dates"].add(r["trade_date"])
|
||||||
s["dailyGains"][r["trade_date"]] = r["f3"]
|
s["dailyGains"][r["trade_date"]] = r["f3"]
|
||||||
|
|
||||||
# 计算连续上榜天数(从最新交易日往回数)
|
# 只保留N个交易日全部上榜的股票
|
||||||
results = []
|
results = []
|
||||||
for code, s in stock_dates.items():
|
for code, s in stock_dates.items():
|
||||||
dates_list = sorted(s["dates"])
|
if len(s["dates"]) >= days:
|
||||||
# 从最新的日期往回数连续天数
|
|
||||||
consecutive = 0
|
|
||||||
last_n = []
|
|
||||||
for i in range(len(dates_list) - 1, -1, -1):
|
|
||||||
if i == len(dates_list) - 1:
|
|
||||||
consecutive = 1
|
|
||||||
last_n.append(dates_list[i])
|
|
||||||
else:
|
|
||||||
# 检查是否连续(允许间隔1天非交易日)
|
|
||||||
prev_idx = all_dates.index(dates_list[i + 1]) if dates_list[i + 1] in all_dates else -1
|
|
||||||
curr_idx = all_dates.index(dates_list[i]) if dates_list[i] in all_dates else -1
|
|
||||||
if prev_idx - curr_idx <= 2: # 允许间隔1天
|
|
||||||
consecutive += 1
|
|
||||||
last_n.append(dates_list[i])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if consecutive >= min_days:
|
|
||||||
results.append({
|
results.append({
|
||||||
"stockCode": code,
|
"stockCode": code,
|
||||||
"stockName": s["stockName"],
|
"stockName": s["stockName"],
|
||||||
"consecutiveDays": consecutive,
|
"consecutiveDays": len(s["dates"]),
|
||||||
"recentDates": last_n,
|
"recentDates": sorted(s["dates"]),
|
||||||
"dailyGains": {d: s["dailyGains"].get(d) for d in last_n},
|
"dailyGains": s["dailyGains"],
|
||||||
})
|
})
|
||||||
|
|
||||||
# 按连续天数降序排列
|
|
||||||
results.sort(key=lambda x: -x["consecutiveDays"])
|
results.sort(key=lambda x: -x["consecutiveDays"])
|
||||||
|
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
"minDays": min_days,
|
"days": days,
|
||||||
"stocks": results[:30],
|
"recentDates": recent_dates,
|
||||||
|
"stocks": results,
|
||||||
}, ensure_ascii=False)
|
}, ensure_ascii=False)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user