fix: K线数据源fallback + 侧边栏/主题修复 + 强制中文输出

- 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)
This commit is contained in:
simon
2026-05-17 11:16:20 +08:00
parent d399e4e472
commit 9de2004fb2
5 changed files with 118 additions and 57 deletions
+7
View File
@@ -0,0 +1,7 @@
[theme]
base = "dark"
primaryColor = "#ff5a1f"
backgroundColor = "#0a0a0a"
secondaryBackgroundColor = "#0f0f0f"
textColor = "#f5f1eb"
font = "sans serif"
@@ -3,7 +3,7 @@
from __future__ import annotations
from tradingagents.agents.schemas import ResearchPlan, render_research_plan
from tradingagents.agents.utils.agent_utils import build_instrument_context
from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction
from tradingagents.agents.utils.structured import (
bind_structured,
invoke_structured_or_freetext,
@@ -39,7 +39,7 @@ Commit to a clear stance whenever the debate's strongest arguments warrant one;
---
**Debate History:**
{history}"""
{history}""" + get_language_instruction()
investment_plan = invoke_structured_or_freetext(
structured_llm,
+2 -1
View File
@@ -7,7 +7,7 @@ import functools
from langchain_core.messages import AIMessage
from tradingagents.agents.schemas import TraderProposal, render_trader_proposal
from tradingagents.agents.utils.agent_utils import build_instrument_context
from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction
from tradingagents.agents.utils.structured import (
bind_structured,
invoke_structured_or_freetext,
@@ -62,6 +62,7 @@ def create_trader(llm):
f"Proposed Investment Plan:\n{investment_plan}\n\n"
+ (f"Additional A-Stock Analyst Context:\n{astock_context}\n\n" if astock_context else "")
+ "Leverage these insights to craft a precise transaction proposal."
+ get_language_instruction()
),
},
]
+105 -53
View File
@@ -227,27 +227,55 @@ def _load_ohlcv_astock(symbol: str, curr_date: str) -> pd.DataFrame:
return data[data["Date"] <= cutoff]
# Fetch from mootdx — 800 daily bars (~3 years of trading days)
client = _get_mootdx_client()
df = client.bars(symbol=code, category=4, offset=800)
try:
client = _get_mootdx_client()
df = client.bars(symbol=code, category=4, offset=800)
if df is None or df.empty:
raise ValueError(f"No OHLCV data from mootdx for {code}")
if df is None or df.empty:
raise ValueError(f"No OHLCV data from mootdx for {code}")
# mootdx returns index named 'datetime' AND a column named 'datetime'
# (plus year/month/day/hour/minute/volume). Drop duplicates before reset.
df = df.drop(columns=["datetime", "year", "month", "day", "hour", "minute"], errors="ignore")
df = df.reset_index() # moves index 'datetime' → column 'datetime'
rename_map = {
"datetime": "Date",
"open": "Open",
"close": "Close",
"high": "High",
"low": "Low",
"volume": "Volume",
}
df = df.rename(columns=rename_map)
df = df[["Date", "Open", "High", "Low", "Close", "Volume"]]
df["Date"] = pd.to_datetime(df["Date"])
# mootdx returns index named 'datetime' AND a column named 'datetime'
# (plus year/month/day/hour/minute/volume). Drop duplicates before reset.
df = df.drop(columns=["datetime", "year", "month", "day", "hour", "minute"], errors="ignore")
df = df.reset_index() # moves index 'datetime' → column 'datetime'
rename_map = {
"datetime": "Date",
"open": "Open",
"close": "Close",
"high": "High",
"low": "Low",
"volume": "Volume",
}
df = df.rename(columns=rename_map)
df = df[["Date", "Open", "High", "Low", "Close", "Volume"]]
df["Date"] = pd.to_datetime(df["Date"])
except Exception as e:
logger.warning("mootdx OHLCV failed for %s: %s, trying akshare fallback", code, e)
# Fallback: akshare sina source
try:
import akshare as ak
prefix = "sh" if code.startswith("6") else "sz"
sina_symbol = f"{prefix}{code}"
# Calculate date range for akshare query (~3 years back)
end_dt = datetime.now()
start_dt = end_dt - relativedelta(years=3)
df_ak = ak.stock_zh_a_daily(
symbol=sina_symbol,
start_date=start_dt.strftime("%Y%m%d"),
end_date=end_dt.strftime("%Y%m%d"),
adjust="qfq",
)
if df_ak is None or df_ak.empty:
raise ValueError(f"No OHLCV data from akshare for {code}")
df_ak = df_ak.rename(columns={
"date": "Date", "open": "Open", "close": "Close",
"high": "High", "low": "Low", "volume": "Volume",
})
df_ak["Date"] = pd.to_datetime(df_ak["Date"])
df = df_ak[["Date", "Open", "High", "Low", "Close", "Volume"]].copy()
except Exception:
raise ValueError(f"No OHLCV data from mootdx for {code}")
# Cache to disk
df.to_csv(cache_file, index=False, encoding="utf-8")
@@ -273,15 +301,13 @@ def get_stock_data(
"""Get OHLCV stock price data via mootdx."""
code = _normalize_ticker(symbol)
data_source = "mootdx (TCP)"
try:
client = _get_mootdx_client()
df = client.bars(symbol=code, category=4, offset=800)
if df is None or df.empty:
return (
f"No data found for A-stock '{code}' "
f"between {start_date} and {end_date}"
)
raise ValueError(f"No data from mootdx for {code}")
# Drop duplicate datetime column + extra columns before reset_index
df = df.drop(
@@ -300,39 +326,65 @@ def get_stock_data(
"amount": "Amount",
}
)
# Filter by date range
df["Date"] = pd.to_datetime(df["Date"])
start_dt = pd.to_datetime(start_date)
end_dt = pd.to_datetime(end_date)
df = df[(df["Date"] >= start_dt) & (df["Date"] <= end_dt)]
if df.empty:
return (
f"No data found for A-stock '{code}' "
f"between {start_date} and {end_date}"
)
for col in ["Open", "High", "Low", "Close"]:
if col in df.columns:
df[col] = df[col].round(2)
df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
csv_out = df[["Date", "Open", "High", "Low", "Close", "Volume"]].to_csv(
index=False
)
header = f"# Stock data for {code} (A-stock) from {start_date} to {end_date}\n"
header += f"# Total records: {len(df)}\n"
header += "# Data source: mootdx (TCP)\n"
header += (
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
)
return header + csv_out
except Exception as e:
return f"Error retrieving A-stock data for {code}: {str(e)}"
logger.warning("mootdx K-line failed for %s: %s, trying akshare fallback", code, e)
# Fallback: akshare sina source
try:
import akshare as ak
prefix = "sh" if code.startswith("6") else "sz"
sina_symbol = f"{prefix}{code}"
start_str = start_date.replace("-", "")
end_str = end_date.replace("-", "")
df_ak = ak.stock_zh_a_daily(
symbol=sina_symbol,
start_date=start_str,
end_date=end_str,
adjust="qfq",
)
if df_ak is not None and not df_ak.empty:
df_ak = df_ak.rename(columns={
"date": "Date", "open": "Open", "close": "Close",
"high": "High", "low": "Low", "volume": "Volume",
})
df_ak["Date"] = pd.to_datetime(df_ak["Date"])
df = df_ak
data_source = "akshare (sina fallback)"
else:
return "K线数据获取失败:mootdx和akshare备用源均不可用,请检查网络连接"
except Exception:
return "K线数据获取失败:mootdx和akshare备用源均不可用,请检查网络连接"
# Filter by date range
start_dt = pd.to_datetime(start_date)
end_dt = pd.to_datetime(end_date)
df = df[(df["Date"] >= start_dt) & (df["Date"] <= end_dt)]
if df.empty:
return (
f"No data found for A-stock '{code}' "
f"between {start_date} and {end_date}"
)
for col in ["Open", "High", "Low", "Close"]:
if col in df.columns:
df[col] = df[col].round(2)
df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
csv_out = df[["Date", "Open", "High", "Low", "Close", "Volume"]].to_csv(
index=False
)
header = f"# Stock data for {code} (A-stock) from {start_date} to {end_date}\n"
header += f"# Total records: {len(df)}\n"
header += f"# Data source: {data_source}\n"
header += (
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
)
return header + csv_out
# ---- 2. get_indicators ----
+2 -1
View File
@@ -43,8 +43,9 @@ st.markdown(
/* Hide Streamlit chrome for clean video recording */
#MainMenu, header[data-testid="stHeader"],
footer, div[data-testid="stDecoration"],
button[data-testid="stBaseButton-header"],
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;