This commit is contained in:
aaron 2026-04-28 13:15:11 +08:00
parent a0407f69a2
commit abe709305a
4 changed files with 29 additions and 2 deletions

View File

@ -24,6 +24,7 @@ logger = logging.getLogger(__name__)
ZT_POOL_URL = "https://push2ex.eastmoney.com/getTopicZTPool" ZT_POOL_URL = "https://push2ex.eastmoney.com/getTopicZTPool"
DT_POOL_URL = "https://push2ex.eastmoney.com/getTopicDTPool" DT_POOL_URL = "https://push2ex.eastmoney.com/getTopicDTPool"
MIN_RELIABLE_SAMPLE_COUNT = 4500
async def get_market_breadth() -> MarketBreadth: async def get_market_breadth() -> MarketBreadth:
@ -34,7 +35,7 @@ async def get_market_breadth() -> MarketBreadth:
return cached return cached
quotes = await get_a_share_realtime_ranking(page_size=6000) quotes = await get_a_share_realtime_ranking(page_size=6000)
if quotes and len(quotes) >= 3000: if quotes and len(quotes) >= MIN_RELIABLE_SAMPLE_COUNT:
up_count = sum(1 for q in quotes if q.get("pct_chg", 0) > 0) up_count = sum(1 for q in quotes if q.get("pct_chg", 0) > 0)
down_count = sum(1 for q in quotes if q.get("pct_chg", 0) < 0) down_count = sum(1 for q in quotes if q.get("pct_chg", 0) < 0)
flat_count = sum(1 for q in quotes if q.get("pct_chg", 0) == 0) flat_count = sum(1 for q in quotes if q.get("pct_chg", 0) == 0)
@ -55,7 +56,11 @@ async def get_market_breadth() -> MarketBreadth:
cache.set(cache_key, breadth, ttl=60) cache.set(cache_key, breadth, ttl=60)
return breadth return breadth
logger.warning("市场广度实时样本不足quotes=%s", len(quotes)) logger.warning(
"市场广度实时样本不足quotes=%s,小于可靠阈值 %s,回退到基线口径",
len(quotes),
MIN_RELIABLE_SAMPLE_COUNT,
)
breadth = MarketBreadth( breadth = MarketBreadth(
trade_date=today_trade_date(), trade_date=today_trade_date(),
total_count=len(quotes), total_count=len(quotes),

View File

@ -31,6 +31,8 @@ class StrategyProfile(BaseModel):
market_stance: str = "" market_stance: str = ""
decision_note: str = "" decision_note: str = ""
notes: list[str] = [] notes: list[str] = []
feedback_applied: bool = False
feedback_notes: list[str] = []
generated_by: str = "rules" generated_by: str = "rules"
@ -165,6 +167,7 @@ async def _apply_strategy_feedback(profile: StrategyProfile) -> StrategyProfile:
return profile return profile
updated = profile.model_copy(deep=True) updated = profile.model_copy(deep=True)
updated.feedback_applied = True
if controls.get("force_defensive"): if controls.get("force_defensive"):
updated.allow_trading = False updated.allow_trading = False
@ -180,6 +183,7 @@ async def _apply_strategy_feedback(profile: StrategyProfile) -> StrategyProfile:
notes = controls.get("notes") or [] notes = controls.get("notes") or []
if notes: if notes:
updated.feedback_notes = notes[:3]
updated.notes.extend(notes[:2]) updated.notes.extend(notes[:2])
updated.decision_note = notes[0] updated.decision_note = notes[0]

View File

@ -268,6 +268,7 @@ export default function DashboardPage() {
<AdminPanel <AdminPanel
isAdmin={user?.role === "admin"} isAdmin={user?.role === "admin"}
opsStatus={opsStatus} opsStatus={opsStatus}
strategyProfile={data?.strategy_profile ?? null}
refreshing={refreshing} refreshing={refreshing}
opsRunning={opsRunning} opsRunning={opsRunning}
onRefresh={handleRefresh} onRefresh={handleRefresh}
@ -472,6 +473,7 @@ function MarketSnapshot({
function AdminPanel({ function AdminPanel({
isAdmin, isAdmin,
opsStatus, opsStatus,
strategyProfile,
refreshing, refreshing,
opsRunning, opsRunning,
onRefresh, onRefresh,
@ -479,6 +481,7 @@ function AdminPanel({
}: { }: {
isAdmin?: boolean; isAdmin?: boolean;
opsStatus: OpsStatusResponse | null; opsStatus: OpsStatusResponse | null;
strategyProfile: LatestResult["strategy_profile"];
refreshing: boolean; refreshing: boolean;
opsRunning: string | null; opsRunning: string | null;
onRefresh: () => void; onRefresh: () => void;
@ -505,6 +508,19 @@ function AdminPanel({
<FreshnessPill label="推荐" value={opsStatus.data_freshness.last_recommendation_created_at || "暂无"} /> <FreshnessPill label="推荐" value={opsStatus.data_freshness.last_recommendation_created_at || "暂无"} />
</div> </div>
{strategyProfile?.feedback_applied ? (
<div className="mt-3 rounded-2xl border border-cyan-500/15 bg-cyan-500/[0.05] p-3">
<div className="text-[10px] uppercase tracking-wider text-cyan-400 font-semibold"></div>
<div className="mt-2 space-y-1.5">
{(strategyProfile.feedback_notes?.length ? strategyProfile.feedback_notes : strategyProfile.notes || []).slice(0, 3).map((note, index) => (
<div key={`${note}-${index}`} className="text-xs leading-5 text-cyan-400/85">
{note}
</div>
))}
</div>
</div>
) : null}
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
<button <button
onClick={onRefresh} onClick={onRefresh}

View File

@ -236,6 +236,8 @@ export interface LatestResult {
market_stance?: string; market_stance?: string;
decision_note?: string; decision_note?: string;
notes?: string[]; notes?: string[];
feedback_applied?: boolean;
feedback_notes?: string[];
generated_by?: string; generated_by?: string;
} | null; } | null;
} }