diff --git a/app.md b/app.md new file mode 100644 index 00000000..2e691e2f --- /dev/null +++ b/app.md @@ -0,0 +1,213 @@ +# TradingAgents - 多代理交易分析系統 + +> 基於 LangGraph 的智能股票交易分析平台,結合多個 AI 代理進行協作決策 + +- GitHub: [MarkLo127/TradingAgents](https://github.com/MarkLo127/TradingAgents) + +## 系統架構 + +### 後端架構 (FastAPI) + +``` +backend/ +├── __main__.py # 應用入口點 +├── requirements.txt # Python 依賴 +└── app/ + ├── main.py # FastAPI 應用主程式 + ├── api/ # API 路由層 + │ ├── routes.py # 分析、配置等 API 端點 + │ └── dependencies.py # 依賴注入 + ├── core/ # 核心配置 + │ ├── config.py # 環境變數設定 + │ └── cors.py # CORS 配置 + ├── models/ # 數據模型 + │ └── schemas.py # Pydantic 數據模式 + └── services/ # 業務邏輯 + ├── trading_service.py # TradingAgents 整合 + └── price_service.py # 股價數據處理 +``` + +**核心技術棧**: +- **FastAPI**: 現代化異步 Web 框架 +- **Pydantic**: 數據驗證與序列化 +- **LangGraph**: 多代理工作流編排 +- **LangChain**: LLM 整合框架 +- **Chromadb**: 向量數據庫(記憶系統) +- **yfinance**: 股票數據獲取 + +### 前端架構 (Next.js) + +``` +frontend/ +├── app/ # Next.js 應用路由 +│ ├── layout.tsx # 根佈局 +│ ├── page.tsx # 首頁 +│ └── analysis/ # 分析功能 +│ ├── page.tsx # 分析表單頁面 +│ └── results/ # 結果展示頁面 +├── components/ # React 組件 +│ ├── analysis/ # 分析相關組件 +│ │ ├── AnalysisForm.tsx # 分析參數表單 +│ │ ├── TradingDecision.tsx # 交易決策展示 +│ │ ├── AnalystReport.tsx # 分析師報告 +│ │ └── PriceChart.tsx # 股價圖表 +│ ├── layout/ # 佈局組件 +│ │ ├── Header.tsx # 導航欄 +│ │ └── Footer.tsx # 頁腳 +│ ├── shared/ # 共用組件 +│ └── ui/ # shadcn/ui 基礎組件 +├── context/ # React Context +│ └── AnalysisContext.tsx # 分析結果共享 +├── hooks/ # 自定義 Hooks +│ ├── useAnalysis.ts # 分析請求管理 +│ └── useConfig.ts # 配置獲取 +└── lib/ # 工具函數 + ├── api.ts # API 客戶端 + ├── types.ts # TypeScript 類型 + └── utils.ts # 輔助函數 +``` + +**核心技術棧**: +- **Next.js 16**: React 全棧框架 +- **TypeScript**: 靜態類型檢查 +- **Tailwind CSS**: 實用優先的 CSS 框架 +- **shadcn/ui**: 可定制的 UI 組件庫 +- **React Hook Form + Zod**: 表單驗證 +- **Recharts**: 數據可視化 +- **Axios**: HTTP 客戶端 +- **react-markdown**: Markdown 渲染 + +## 安裝步驟 + +### 前置要求 + +- **Python**: 3.10 或以上 +- **Node.js**: 18.x 或以上 +- **pnpm**: 最新版本 +- **Conda**: (推薦) 用於 Python 環境管理 +- **API 金鑰**: + - OpenAI API Key (必需) + - Alpha Vantage API Key (可選,用於更詳細數據) + +### 1. 克隆專案 + +```bash +git clone https://github.com/MarkLo127/TradingAgents.git +cd TradingAgents +``` + +### 2. 後端設置 + +#### 2.1 創建 Python 環境 + +```bash +# 使用 Conda (推薦) +conda create -n tradingagents python=3.13 +conda activate tradingagents +``` + +#### 2.2 安裝依賴 + +```bash +# 安裝 TradingAgents 核心 +pip install -e . + +# 安裝後端依賴 +pip install -r backend/requirements.txt +``` + +#### 2.3 環境配置 + +在專案根目錄創建 `.env` 文件: + +```bash +# API 金鑰 +OPENAI_API_KEY=sk-your-openai-api-key +OPENAI_BASE_URL=https://api.openai.com/v1 +ALPHA_VANTAGE_API_KEY=your-alpha-vantage-key # 可選 + +# 後端配置 +BACKEND_HOST=0.0.0.0 +BACKEND_PORT=8000 + +# CORS 配置 +CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 +``` + +#### 2.4 啟動後端 + +```bash +# 從專案根目錄運行 +python -m backend +``` + +後端將運行在 `http://localhost:8000` + +- API 文檔: http://localhost:8000/docs +- 健康檢查: http://localhost:8000/api/health + +### 3. 前端設置 + +#### 3.1 安裝依賴 + +```bash +pnpm -C frontend i +``` + +#### 3.2 啟動前端 + +```bash +pnpm -C frontend dev +``` + +前端將運行在 `http://localhost:3000` + +### Docker 部署 + +```bash +# 使用 Docker Compose 啟動 +docker-compose up -d + +# 查看日誌 +docker-compose logs -f + +# 停止服務 +docker-compose down -v +``` + +### 使用流程 + +1. **訪問首頁** - 查看功能介紹 +2. **進入分析頁面** - 點擊"開始分析" +3. **配置參數**: + - 選擇分析師團隊(市場、情緒、新聞、基本面) + - 輸入股票代碼(如 NVDA, AAPL, TSLA) + - 選擇分析日期 + - 設定研究深度(淺層/中等/深層) + - 選擇 LLM 模型 + - 輸入 API 金鑰 +4. **執行分析** - 點擊"執行分析"按鈕 +5. **查看結果** - 自動跳轉至結果頁面,查看: + - 交易決策(買入/賣出/持有) + - 股價走勢圖表 + - 各分析師詳細報告 + +## 核心功能 + +### 多代理協作系統 + +- **市場分析師**: 技術指標與價格走勢分析 +- **情緒分析師**: 社交媒體情緒分析 +- **新聞分析師**: 新聞事件影響評估 +- **基本面分析師**: 財務數據與估值分析 +- **研究團隊**: 看漲/看跌辯論機制 +- **交易員**: 投資計劃制定 +- **風險管理**: 風險評估與倉位管理 + +### 智能特性 + +- **動態研究深度**: 可調節分析詳細程度 +- **多模型支持**: GPT-4o, GPT-5.1 等 +- **記憶系統**: ChromaDB 向量存儲歷史決策 +- **Markdown 報告**: 格式化的分析輸出 +- **實時數據**: yfinance 股票數據整合 \ No newline at end of file diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 5de855d1..859f2921 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -4,6 +4,7 @@ import "./globals.css"; import { Header } from "@/components/layout/Header"; import { Footer } from "@/components/layout/Footer"; import { AnalysisProvider } from "@/context/AnalysisContext"; +import { ThemeProvider } from "@/components/theme/ThemeProvider"; const inter = Inter({ subsets: ["latin"] }); @@ -18,17 +19,19 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - -
-
-
- {children} -
-
-
+ + +
+
+
+ {children} +
+
+
+
); diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index b6c642cc..ed6d3da5 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -20,7 +20,7 @@ export default function HomePage() { diff --git a/frontend/components/layout/Footer.tsx b/frontend/components/layout/Footer.tsx index c3cfe142..2ac0c9c4 100644 --- a/frontend/components/layout/Footer.tsx +++ b/frontend/components/layout/Footer.tsx @@ -9,7 +9,7 @@ export function Footer() {
© 2025 TradingAgents. 技術支援:{" "}
-
diff --git a/frontend/components/theme/ThemeProvider.tsx b/frontend/components/theme/ThemeProvider.tsx new file mode 100644 index 00000000..7d92272d --- /dev/null +++ b/frontend/components/theme/ThemeProvider.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { type ReactNode } from "react"; + +export function ThemeProvider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/frontend/components/theme/ThemeToggle.tsx b/frontend/components/theme/ThemeToggle.tsx new file mode 100644 index 00000000..9ce2e9e6 --- /dev/null +++ b/frontend/components/theme/ThemeToggle.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { Moon, Sun, Monitor } from "lucide-react"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export function ThemeToggle() { + const { theme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + // Avoid hydration mismatch + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return ( + + ); + } + + return ( + + + + + + setTheme("light")}> + + 亮色 + + setTheme("dark")}> + + 暗色 + + setTheme("system")}> + + 系統 + + + + ); +} diff --git a/frontend/components/ui/dropdown-menu.tsx b/frontend/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..bbe6fb01 --- /dev/null +++ b/frontend/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/frontend/package.json b/frontend/package.json index 8bd51dd2..7230d5e0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@hookform/resolvers": "^5.2.2", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.4", @@ -22,6 +23,7 @@ "date-fns": "^4.1.0", "lucide-react": "^0.554.0", "next": "16.0.3", + "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", "react-hook-form": "^7.66.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a9a0f90e..e26636b9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@radix-ui/react-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-label': specifier: ^2.1.8 version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -47,6 +50,9 @@ importers: next: specifier: 16.0.3 version: 16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: specifier: 19.2.0 version: 19.2.0 @@ -586,6 +592,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.3': resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: @@ -630,6 +649,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -2354,6 +2386,12 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@16.0.3: resolution: {integrity: sha512-Ka0/iNBblPFcIubTA1Jjh6gvwqfjrGq1Y2MTI5lbjeLIAfmC+p5bQmojpRZqgHHVu5cG4+qdIiwXiBSm/8lZ3w==} engines: {node: '>=20.9.0'} @@ -3418,6 +3456,21 @@ snapshots: '@types/react': 19.2.6 '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.6)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.6 + '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.6)(react@19.2.0)': dependencies: react: 19.2.0 @@ -3451,6 +3504,32 @@ snapshots: '@types/react': 19.2.6 '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.6)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.6)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.6)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.6 + '@types/react-dom': 19.2.3(@types/react@19.2.6) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -5512,6 +5591,11 @@ snapshots: natural-compare@1.4.0: {} + next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + next@16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 16.0.3