1
This commit is contained in:
parent
a0407f69a2
commit
abe709305a
@ -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),
|
||||||
|
|||||||
@ -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]
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user