mirror of
https://github.com/simonlin1212/TradingAgents-astock.git
synced 2026-10-30 09:29:56 +00:00
- a_stock.py: mootdx TCP失败时自动切换akshare新浪源作为备用 (Closes #13) - .streamlit/config.toml: 固定暗色主题配色,解决文字不可见问题 (Closes #9) - web/app.py: 移除过度隐藏sidebar按钮的CSS规则 (Closes #11) - trader.py + research_manager.py: 添加get_language_instruction() 确保投资建议和风控评估输出中文 (Closes #5)
264 lines
9.1 KiB
Python
264 lines
9.1 KiB
Python
"""TradingAgents A股分析 — Streamlit Web UI."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import streamlit as st
|
|
from dotenv import load_dotenv
|
|
|
|
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
if str(_PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
|
|
load_dotenv(_PROJECT_ROOT / ".env")
|
|
|
|
from tradingagents.default_config import DEFAULT_CONFIG # noqa: E402
|
|
|
|
from web.components.progress_panel import render_progress # noqa: E402
|
|
from web.components.report_viewer import render_report # noqa: E402
|
|
from web.components.sidebar import render_sidebar # noqa: E402
|
|
from web.history import extract_signal, load_analysis # noqa: E402
|
|
from web.progress import ProgressTracker # noqa: E402
|
|
from web.runner import run_analysis_in_thread # noqa: E402
|
|
|
|
# ── Page config ──────────────────────────────────────────────────────────────
|
|
|
|
st.set_page_config(
|
|
page_title="TradingAgents-Astock A股分析",
|
|
page_icon="📈",
|
|
layout="wide",
|
|
initial_sidebar_state="expanded",
|
|
)
|
|
|
|
# ── Custom CSS ───────────────────────────────────────────────────────────────
|
|
|
|
st.markdown(
|
|
"""
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;900&display=swap');
|
|
|
|
/* Hide Streamlit chrome for clean video recording */
|
|
#MainMenu, header[data-testid="stHeader"],
|
|
footer, div[data-testid="stDecoration"],
|
|
div[data-testid="stToolbar"] { display: none !important; }
|
|
/* Ensure sidebar collapse/expand control is always visible */
|
|
button[data-testid="collapsedControl"] { display: flex !important; }
|
|
|
|
html, body, [class*="css"] {
|
|
font-family: 'Inter', -apple-system, sans-serif;
|
|
}
|
|
.stApp {
|
|
background: #0a0a0a;
|
|
}
|
|
section[data-testid="stSidebar"] {
|
|
background: #0f0f0f;
|
|
border-right: 1px solid #1a1a1a;
|
|
}
|
|
.stMetric label { color: #888 !important; font-size: 0.8rem !important; }
|
|
.stMetric [data-testid="stMetricValue"] {
|
|
color: #ff5a1f !important;
|
|
font-weight: 700 !important;
|
|
}
|
|
.stProgress > div > div > div {
|
|
background: linear-gradient(90deg, #ff5a1f, #ff8c42) !important;
|
|
}
|
|
button[kind="primary"] {
|
|
background: linear-gradient(135deg, #ff5a1f, #ff8c42) !important;
|
|
border: none !important;
|
|
font-weight: 700 !important;
|
|
letter-spacing: 0.05em !important;
|
|
box-shadow: 0 4px 15px rgba(255,90,31,0.3) !important;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
button[kind="primary"]:hover {
|
|
background: linear-gradient(135deg, #e04d15, #ff5a1f) !important;
|
|
box-shadow: 0 6px 20px rgba(255,90,31,0.4) !important;
|
|
transform: translateY(-1px) !important;
|
|
}
|
|
/* Secondary buttons (history items) */
|
|
button[kind="secondary"] {
|
|
background: #161616 !important;
|
|
border: 1px solid #2a2a2a !important;
|
|
color: #ccc !important;
|
|
transition: all 0.2s ease !important;
|
|
}
|
|
button[kind="secondary"]:hover {
|
|
background: #1e1e1e !important;
|
|
border-color: #ff5a1f !important;
|
|
color: #ff5a1f !important;
|
|
}
|
|
.stExpander {
|
|
border: 1px solid #222 !important;
|
|
border-radius: 8px !important;
|
|
}
|
|
.stTabs [data-baseweb="tab"] {
|
|
color: #888 !important;
|
|
}
|
|
.stTabs [aria-selected="true"] {
|
|
color: #ff5a1f !important;
|
|
border-bottom-color: #ff5a1f !important;
|
|
}
|
|
div[data-testid="stDownloadButton"] button {
|
|
background: #1a1a2e !important;
|
|
border: 1px solid #ff5a1f !important;
|
|
color: #ff5a1f !important;
|
|
}
|
|
/* Text input styling */
|
|
input[data-testid="stTextInputRootElement"] input,
|
|
.stTextInput input {
|
|
background: #161616 !important;
|
|
border-color: #2a2a2a !important;
|
|
color: #f5f1eb !important;
|
|
}
|
|
.stTextInput input:focus {
|
|
border-color: #ff5a1f !important;
|
|
box-shadow: 0 0 0 1px #ff5a1f !important;
|
|
}
|
|
/* Date input styling */
|
|
.stDateInput input {
|
|
background: #161616 !important;
|
|
border-color: #2a2a2a !important;
|
|
color: #f5f1eb !important;
|
|
}
|
|
</style>
|
|
""",
|
|
unsafe_allow_html=True,
|
|
)
|
|
|
|
|
|
# ── Build config ─────────────────────────────────────────────────────────────
|
|
|
|
def _build_config() -> dict:
|
|
config = DEFAULT_CONFIG.copy()
|
|
config["llm_provider"] = "minimax"
|
|
config["deep_think_llm"] = "MiniMax-M2.7"
|
|
config["quick_think_llm"] = "MiniMax-M2.7-highspeed"
|
|
config["data_vendors"] = {
|
|
"core_stock_apis": "a_stock",
|
|
"technical_indicators": "a_stock",
|
|
"fundamental_data": "a_stock",
|
|
"news_data": "a_stock",
|
|
"signal_data": "a_stock",
|
|
}
|
|
config["max_debate_rounds"] = 1
|
|
config["max_risk_discuss_rounds"] = 1
|
|
config["output_language"] = "Chinese"
|
|
return config
|
|
|
|
|
|
# ── Sidebar ──────────────────────────────────────────────────────────────────
|
|
|
|
with st.sidebar:
|
|
render_sidebar()
|
|
|
|
|
|
# ── Handle "Start Analysis" trigger ──────────────────────────────────────────
|
|
|
|
start_req = st.session_state.pop("start_analysis", None)
|
|
if start_req:
|
|
tracker = ProgressTracker(
|
|
ticker=start_req["ticker"],
|
|
trade_date=start_req["trade_date"],
|
|
)
|
|
st.session_state["tracker"] = tracker
|
|
run_analysis_in_thread(
|
|
ticker=start_req["ticker"],
|
|
trade_date=start_req["trade_date"],
|
|
config=_build_config(),
|
|
tracker=tracker,
|
|
)
|
|
|
|
|
|
# ── Main area state machine ─────────────────────────────────────────────────
|
|
|
|
tracker: ProgressTracker | None = st.session_state.get("tracker")
|
|
viewing_history: str | None = st.session_state.get("viewing_history")
|
|
|
|
# State 1: Viewing a historical analysis
|
|
if viewing_history:
|
|
try:
|
|
state = load_analysis(viewing_history)
|
|
signal = extract_signal(state)
|
|
ticker = Path(viewing_history).parent.parent.name
|
|
trade_date = Path(viewing_history).stem.replace("full_states_log_", "")
|
|
render_report(state, ticker, trade_date, signal)
|
|
except Exception as exc:
|
|
st.error(f"加载失败: {exc}")
|
|
|
|
# State 2: Analysis running
|
|
elif tracker and tracker.is_running:
|
|
render_progress(tracker)
|
|
time.sleep(2)
|
|
st.rerun()
|
|
|
|
# State 3: Analysis complete
|
|
elif tracker and tracker.is_complete:
|
|
render_report(
|
|
tracker.final_state,
|
|
tracker.ticker,
|
|
tracker.trade_date,
|
|
tracker.signal,
|
|
elapsed=tracker.elapsed,
|
|
)
|
|
|
|
# State 4: Analysis errored
|
|
elif tracker and tracker.error:
|
|
st.error(f"分析失败: {tracker.error}")
|
|
if st.button("重试"):
|
|
st.session_state.pop("tracker", None)
|
|
st.rerun()
|
|
|
|
# State 0: Idle — welcome screen
|
|
else:
|
|
st.markdown(
|
|
"""
|
|
<div style="
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 60vh;
|
|
text-align: center;
|
|
">
|
|
<div style="font-size: 4rem; margin-bottom: 1rem;">📈</div>
|
|
<div style="
|
|
font-size: 2.5rem;
|
|
font-weight: 900;
|
|
margin-bottom: 0.5rem;
|
|
">
|
|
<span style="color: #ff5a1f;">Trading</span><span style="color: #f5f1eb;">Agents</span><span style="color: #f5f1eb;">-</span><span style="color: #ff5a1f;">Astock</span>
|
|
</div>
|
|
<div style="color: #888; font-size: 1.1rem; max-width: 500px; line-height: 1.6;">
|
|
A股多Agent投研分析系统<br>
|
|
7位AI分析师 → 质量门控 → 多空辩论 → 风控评估 → 最终决策
|
|
</div>
|
|
<div style="
|
|
margin-top: 2rem;
|
|
padding: 1rem 2rem;
|
|
border: 1px solid #222;
|
|
border-radius: 12px;
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
">
|
|
← 在左侧输入股票代码,开始分析
|
|
</div>
|
|
<div style="
|
|
margin-top: 2.5rem;
|
|
padding: 0.8rem 1.5rem;
|
|
color: #555;
|
|
font-size: 0.75rem;
|
|
max-width: 500px;
|
|
line-height: 1.6;
|
|
border-top: 1px solid #1a1a1a;
|
|
">
|
|
⚠️ 本项目仅供学习研究与技术演示,不构成任何投资建议。<br>
|
|
投资决策请咨询持牌专业机构。作者不对使用本工具产生的任何损失承担责任。
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True,
|
|
)
|