diff --git a/README.md b/README.md
index 4cfeb4e5..74672934 100644
--- a/README.md
+++ b/README.md
@@ -138,6 +138,27 @@ Alternatively, copy `.env.example` to `.env` and fill in your keys:
cp .env.example .env
```
+#### No OpenAI API key?
+
+You can still run TradingAgents in two ways:
+
+1. Use another cloud provider key (Gemini / Claude / xAI / OpenRouter), then set:
+```python
+config["llm_provider"] = "google" # or anthropic / xai / openrouter
+```
+
+2. Use local Ollama models (no cloud API key required):
+```bash
+ollama pull qwen3:8b
+```
+
+```python
+config["llm_provider"] = "ollama"
+config["backend_url"] = "http://localhost:11434/v1"
+config["deep_think_llm"] = "qwen3:8b"
+config["quick_think_llm"] = "qwen3:8b"
+```
+
### CLI Usage
Launch the interactive CLI:
@@ -147,6 +168,19 @@ python -m cli.main # alternative: run directly from source
```
You will see a screen where you can select your desired tickers, analysis date, LLM provider, research depth, and more.
+When prompted with:
+
+```text
+Select Output Language
+```
+
+choose `Chinese (中文)` if you want Chinese analyst reports/final decision text.
+You can also force it in config:
+
+```python
+config["output_language"] = "Chinese"
+```
+
@@ -199,6 +233,56 @@ _, decision = ta.propagate("NVDA", "2026-01-15")
print(decision)
```
+### A-share (China) with AKShare
+
+If you want to run TradingAgents on A-share symbols with AKShare data:
+
+1. Install dependencies:
+```bash
+pip install .
+pip install akshare
+```
+2. Switch data vendors to AKShare in your config:
+```python
+config["data_vendors"] = {
+ "core_stock_apis": "akshare",
+ "technical_indicators": "akshare",
+ "fundamental_data": "akshare",
+ "news_data": "akshare",
+}
+```
+3. Use A-share symbols like `600519` or `sh600519`:
+```python
+_, decision = ta.propagate("600519", "2026-01-15")
+```
+
+Notes:
+- The current AKShare adapter provides production-ready price + basic technical indicator access.
+- Fundamental/news/insider methods are scaffolded as placeholders so you can bind your preferred AKShare endpoints quickly.
+
+#### Run in GitHub Codespaces (recommended quick start)
+
+You can run this project directly in Codespaces (no local setup required):
+
+```bash
+# 1) open terminal in Codespaces
+python -m venv .venv
+source .venv/bin/activate
+
+# 2) install project and AKShare
+pip install -U pip
+pip install .
+pip install akshare
+
+# 3) set your LLM key (pick one provider)
+export OPENAI_API_KEY=your_key_here
+
+# 4) run CLI
+python -m cli.main
+```
+
+For A-share symbols, input values such as `600519`, `000001`, `sh600519`, or `sz000001`.
+
See `tradingagents/default_config.py` for all configuration options.
## Contributing
diff --git a/main.py b/main.py
index c94fde32..293015a6 100644
--- a/main.py
+++ b/main.py
@@ -14,10 +14,10 @@ config["max_debate_rounds"] = 1 # Increase debate rounds
# Configure data vendors (default uses yfinance, no extra API keys needed)
config["data_vendors"] = {
- "core_stock_apis": "yfinance", # Options: alpha_vantage, yfinance
- "technical_indicators": "yfinance", # Options: alpha_vantage, yfinance
- "fundamental_data": "yfinance", # Options: alpha_vantage, yfinance
- "news_data": "yfinance", # Options: alpha_vantage, yfinance
+ "core_stock_apis": "yfinance", # Options: alpha_vantage, yfinance, akshare
+ "technical_indicators": "yfinance", # Options: alpha_vantage, yfinance, akshare
+ "fundamental_data": "yfinance", # Options: alpha_vantage, yfinance, akshare
+ "news_data": "yfinance", # Options: alpha_vantage, yfinance, akshare
}
# Initialize with custom config
diff --git a/pyproject.toml b/pyproject.toml
index 0decedb0..cbeaa9f9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,6 +30,7 @@ dependencies = [
"tqdm>=4.67.1",
"typing-extensions>=4.14.0",
"yfinance>=0.2.63",
+ "akshare>=1.17.62",
]
[project.scripts]
diff --git a/tradingagents/dataflows/akshare_cn.py b/tradingagents/dataflows/akshare_cn.py
new file mode 100644
index 00000000..b9bfec0a
--- /dev/null
+++ b/tradingagents/dataflows/akshare_cn.py
@@ -0,0 +1,193 @@
+from __future__ import annotations
+
+from datetime import datetime, timedelta
+from typing import Annotated
+
+import pandas as pd
+
+
+def _load_akshare():
+ try:
+ import akshare as ak
+ except ImportError as exc:
+ raise RuntimeError(
+ "AKShare is not installed. Run `pip install akshare` first."
+ ) from exc
+ return ak
+
+
+def _normalize_a_share_symbol(symbol: str) -> str:
+ s = symbol.strip().lower()
+ if s.startswith(("sh", "sz", "bj")) and len(s) >= 8:
+ return s
+ if s.isdigit() and len(s) == 6:
+ if s.startswith(("6", "9")):
+ return f"sh{s}"
+ if s.startswith(("0", "2", "3")):
+ return f"sz{s}"
+ if s.startswith(("4", "8")):
+ return f"bj{s}"
+ return s
+
+
+def _extract_code(symbol: str) -> str:
+ norm = _normalize_a_share_symbol(symbol)
+ if len(norm) >= 8 and norm[:2] in {"sh", "sz", "bj"}:
+ return norm[2:]
+ return norm
+
+
+def get_stock_data_akshare(
+ symbol: Annotated[str, "A-share symbol, e.g. 600519 or sh600519"],
+ start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
+ end_date: Annotated[str, "End date in yyyy-mm-dd format"],
+):
+ ak = _load_akshare()
+ datetime.strptime(start_date, "%Y-%m-%d")
+ datetime.strptime(end_date, "%Y-%m-%d")
+
+ code = _extract_code(symbol)
+ start = start_date.replace("-", "")
+ end = end_date.replace("-", "")
+
+ try:
+ df = ak.stock_zh_a_hist(
+ symbol=code,
+ period="daily",
+ start_date=start,
+ end_date=end,
+ adjust="qfq",
+ )
+ except Exception as exc:
+ return f"Error fetching A-share stock data for {symbol}: {exc}"
+
+ if df is None or df.empty:
+ return f"No A-share data found for symbol '{symbol}' between {start_date} and {end_date}"
+
+ rename_map = {
+ "日期": "Date",
+ "开盘": "Open",
+ "收盘": "Close",
+ "最高": "High",
+ "最低": "Low",
+ "成交量": "Volume",
+ "成交额": "Amount",
+ "振幅": "Amplitude",
+ "涨跌幅": "ChangePct",
+ "涨跌额": "Change",
+ "换手率": "Turnover",
+ }
+ out = df.rename(columns=rename_map)
+ header = (
+ f"# A-share stock data for {symbol} ({code}) from {start_date} to {end_date}\n"
+ f"# Total records: {len(out)}\n"
+ f"# Data source: AKShare stock_zh_a_hist\n\n"
+ )
+ return header + out.to_csv(index=False)
+
+
+def get_indicator_akshare(
+ symbol: Annotated[str, "A-share symbol"],
+ indicator: Annotated[str, "technical indicator"],
+ curr_date: Annotated[str, "The current trading date, YYYY-mm-dd"],
+ look_back_days: Annotated[int, "how many days to look back"],
+) -> str:
+ end_date = curr_date
+ start_date = (datetime.strptime(curr_date, "%Y-%m-%d") - timedelta(days=look_back_days * 3)).strftime("%Y-%m-%d")
+ raw = get_stock_data_akshare(symbol, start_date, end_date)
+ if raw.startswith("No A-share") or raw.startswith("Error"):
+ return raw
+
+ csv_part = "\n".join(raw.splitlines()[4:])
+ df = pd.read_csv(pd.io.common.StringIO(csv_part))
+ if df.empty or "Close" not in df.columns:
+ return f"Unable to compute indicator {indicator} for {symbol}"
+
+ close = pd.to_numeric(df["Close"], errors="coerce")
+
+ indicator_lower = indicator.lower()
+ if indicator_lower == "rsi":
+ delta = close.diff()
+ gain = delta.clip(lower=0).rolling(14).mean()
+ loss = (-delta.clip(upper=0)).rolling(14).mean()
+ rs = gain / loss.replace(0, pd.NA)
+ series = 100 - (100 / (1 + rs))
+ elif indicator_lower in {"close_10_ema", "ema10"}:
+ series = close.ewm(span=10, adjust=False).mean()
+ elif indicator_lower in {"close_50_sma", "sma50"}:
+ series = close.rolling(50).mean()
+ elif indicator_lower == "macd":
+ ema12 = close.ewm(span=12, adjust=False).mean()
+ ema26 = close.ewm(span=26, adjust=False).mean()
+ series = ema12 - ema26
+ else:
+ return (
+ f"AKShare adapter currently supports indicators: rsi, close_10_ema, close_50_sma, macd. "
+ f"Requested: {indicator}"
+ )
+
+ df["Date"] = pd.to_datetime(df["Date"], errors="coerce").dt.strftime("%Y-%m-%d")
+ df["indicator"] = series
+ window_df = df.dropna(subset=["Date"]).tail(look_back_days)
+ lines = [f"{d}: {v}" for d, v in zip(window_df["Date"], window_df["indicator"])]
+ return f"## {indicator} values for {symbol} up to {curr_date}:\n\n" + "\n".join(lines)
+
+
+def get_fundamentals_akshare(
+ ticker: Annotated[str, "A-share symbol"],
+ curr_date: Annotated[str, "current date"] = None,
+):
+ return (
+ "AKShare fundamentals vary by endpoint and exchange. "
+ "Recommended: wire dedicated endpoints for balance sheet/cashflow/income statement per your data policy. "
+ f"Current symbol: {ticker}."
+ )
+
+
+def get_balance_sheet_akshare(
+ ticker: Annotated[str, "A-share symbol"],
+ freq: Annotated[str, "quarterly/annual"] = "quarterly",
+ curr_date: Annotated[str, "current date"] = None,
+):
+ return f"AKShare balance-sheet adapter placeholder for {ticker} ({freq})."
+
+
+def get_cashflow_akshare(
+ ticker: Annotated[str, "A-share symbol"],
+ freq: Annotated[str, "quarterly/annual"] = "quarterly",
+ curr_date: Annotated[str, "current date"] = None,
+):
+ return f"AKShare cashflow adapter placeholder for {ticker} ({freq})."
+
+
+def get_income_statement_akshare(
+ ticker: Annotated[str, "A-share symbol"],
+ freq: Annotated[str, "quarterly/annual"] = "quarterly",
+ curr_date: Annotated[str, "current date"] = None,
+):
+ return f"AKShare income-statement adapter placeholder for {ticker} ({freq})."
+
+
+def get_news_akshare(
+ ticker: Annotated[str, "A-share symbol"],
+ start_date: Annotated[str, "Start date"] = None,
+ end_date: Annotated[str, "End date"] = None,
+):
+ return f"AKShare news adapter placeholder for {ticker} from {start_date} to {end_date}."
+
+
+def get_global_news_akshare(
+ curr_date: Annotated[str, "Current date in yyyy-mm-dd format"] = None,
+ look_back_days: Annotated[int, "Number of days to look back"] = 7,
+ limit: Annotated[int, "Maximum number of articles to return"] = 5,
+):
+ return (
+ "AKShare global news adapter placeholder. "
+ f"curr_date={curr_date}, look_back_days={look_back_days}, limit={limit}."
+ )
+
+
+def get_insider_transactions_akshare(
+ ticker: Annotated[str, "A-share symbol"],
+):
+ return f"A-share insider transactions adapter placeholder for {ticker}."
diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py
index 0caf4b68..a4eac7e1 100644
--- a/tradingagents/dataflows/interface.py
+++ b/tradingagents/dataflows/interface.py
@@ -23,6 +23,17 @@ from .alpha_vantage import (
get_global_news as get_alpha_vantage_global_news,
)
from .alpha_vantage_common import AlphaVantageRateLimitError
+from .akshare_cn import (
+ get_stock_data_akshare,
+ get_indicator_akshare,
+ get_fundamentals_akshare,
+ get_balance_sheet_akshare,
+ get_cashflow_akshare,
+ get_income_statement_akshare,
+ get_news_akshare,
+ get_global_news_akshare,
+ get_insider_transactions_akshare,
+)
# Configuration and routing logic
from .config import get_config
@@ -63,6 +74,7 @@ TOOLS_CATEGORIES = {
VENDOR_LIST = [
"yfinance",
"alpha_vantage",
+ "akshare",
]
# Mapping of methods to their vendor-specific implementations
@@ -71,41 +83,50 @@ VENDOR_METHODS = {
"get_stock_data": {
"alpha_vantage": get_alpha_vantage_stock,
"yfinance": get_YFin_data_online,
+ "akshare": get_stock_data_akshare,
},
# technical_indicators
"get_indicators": {
"alpha_vantage": get_alpha_vantage_indicator,
"yfinance": get_stock_stats_indicators_window,
+ "akshare": get_indicator_akshare,
},
# fundamental_data
"get_fundamentals": {
"alpha_vantage": get_alpha_vantage_fundamentals,
"yfinance": get_yfinance_fundamentals,
+ "akshare": get_fundamentals_akshare,
},
"get_balance_sheet": {
"alpha_vantage": get_alpha_vantage_balance_sheet,
"yfinance": get_yfinance_balance_sheet,
+ "akshare": get_balance_sheet_akshare,
},
"get_cashflow": {
"alpha_vantage": get_alpha_vantage_cashflow,
"yfinance": get_yfinance_cashflow,
+ "akshare": get_cashflow_akshare,
},
"get_income_statement": {
"alpha_vantage": get_alpha_vantage_income_statement,
"yfinance": get_yfinance_income_statement,
+ "akshare": get_income_statement_akshare,
},
# news_data
"get_news": {
"alpha_vantage": get_alpha_vantage_news,
"yfinance": get_news_yfinance,
+ "akshare": get_news_akshare,
},
"get_global_news": {
"yfinance": get_global_news_yfinance,
"alpha_vantage": get_alpha_vantage_global_news,
+ "akshare": get_global_news_akshare,
},
"get_insider_transactions": {
"alpha_vantage": get_alpha_vantage_insider_transactions,
"yfinance": get_yfinance_insider_transactions,
+ "akshare": get_insider_transactions_akshare,
},
}
@@ -159,4 +180,4 @@ def route_to_vendor(method: str, *args, **kwargs):
except AlphaVantageRateLimitError:
continue # Only rate limits trigger fallback
- raise RuntimeError(f"No available vendor for '{method}'")
\ No newline at end of file
+ raise RuntimeError(f"No available vendor for '{method}'")
diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py
index 26a4e4d2..38faf437 100644
--- a/tradingagents/default_config.py
+++ b/tradingagents/default_config.py
@@ -26,10 +26,10 @@ DEFAULT_CONFIG = {
# Data vendor configuration
# Category-level configuration (default for all tools in category)
"data_vendors": {
- "core_stock_apis": "yfinance", # Options: alpha_vantage, yfinance
- "technical_indicators": "yfinance", # Options: alpha_vantage, yfinance
- "fundamental_data": "yfinance", # Options: alpha_vantage, yfinance
- "news_data": "yfinance", # Options: alpha_vantage, yfinance
+ "core_stock_apis": "yfinance", # Options: alpha_vantage, yfinance, akshare
+ "technical_indicators": "yfinance", # Options: alpha_vantage, yfinance, akshare
+ "fundamental_data": "yfinance", # Options: alpha_vantage, yfinance, akshare
+ "news_data": "yfinance", # Options: alpha_vantage, yfinance, akshare
},
# Tool-level configuration (takes precedence over category-level)
"tool_vendors": {