diff --git a/.omc/project-memory.json b/.omc/project-memory.json index 38b9f47..5669ed0 100644 --- a/.omc/project-memory.json +++ b/.omc/project-memory.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "lastScanned": 1774313111650, + "lastScanned": 1774515151036, "projectRoot": "/Users/cillin/workspeace/evotraders", "techStack": { "languages": [ @@ -54,7 +54,7 @@ "path": "backend", "purpose": null, "fileCount": 4, - "lastAccessed": 1774313111639, + "lastAccessed": 1774515151025, "keyFiles": [ "__init__.py", "cli.py", @@ -66,14 +66,14 @@ "path": "backtest", "purpose": null, "fileCount": 0, - "lastAccessed": 1774313111640, + "lastAccessed": 1774515151026, "keyFiles": [] }, "data": { "path": "data", "purpose": "Data files", "fileCount": 3, - "lastAccessed": 1774313111640, + "lastAccessed": 1774515151027, "keyFiles": [ "market_research.db", "market_research.db-shm", @@ -84,14 +84,14 @@ "path": "deploy", "purpose": null, "fileCount": 0, - "lastAccessed": 1774313111640, + "lastAccessed": 1774515151027, "keyFiles": [] }, "docs": { "path": "docs", "purpose": "Documentation", "fileCount": 1, - "lastAccessed": 1774313111641, + "lastAccessed": 1774515151027, "keyFiles": [ "compat-removal-plan.md" ] @@ -100,7 +100,7 @@ "path": "evotraders.egg-info", "purpose": null, "fileCount": 6, - "lastAccessed": 1774313111641, + "lastAccessed": 1774515151028, "keyFiles": [ "PKG-INFO", "SOURCES.txt", @@ -113,7 +113,7 @@ "path": "frontend", "purpose": null, "fileCount": 13, - "lastAccessed": 1774313111641, + "lastAccessed": 1774515151028, "keyFiles": [ "README.md", "components.json", @@ -126,41 +126,28 @@ "path": "live", "purpose": null, "fileCount": 0, - "lastAccessed": 1774313111642, + "lastAccessed": 1774515151028, "keyFiles": [] }, - "logs": { - "path": "logs", - "purpose": null, - "fileCount": 6, - "lastAccessed": 1774313111642, - "keyFiles": [ - "2026-03-16_00-48-03.log", - "2026-03-18_23-17-29.log", - "2026-03-18_23-17-30.log", - "2026-03-19_00-18-04.log", - "2026-03-19_00-34-21.log" - ] - }, "reference": { "path": "reference", "purpose": null, "fileCount": 0, - "lastAccessed": 1774313111643, + "lastAccessed": 1774515151028, "keyFiles": [] }, "runs": { "path": "runs", "purpose": null, "fileCount": 0, - "lastAccessed": 1774313111643, + "lastAccessed": 1774515151029, "keyFiles": [] }, "scripts": { "path": "scripts", "purpose": "Build/utility scripts", "fileCount": 1, - "lastAccessed": 1774313111644, + "lastAccessed": 1774515151030, "keyFiles": [ "run_prod.sh" ] @@ -169,7 +156,7 @@ "path": "services", "purpose": "Business logic services", "fileCount": 1, - "lastAccessed": 1774313111644, + "lastAccessed": 1774515151030, "keyFiles": [ "README.md" ] @@ -178,21 +165,14 @@ "path": "shared", "purpose": null, "fileCount": 0, - "lastAccessed": 1774313111644, - "keyFiles": [] - }, - "workspaces": { - "path": "workspaces", - "purpose": null, - "fileCount": 0, - "lastAccessed": 1774313111645, + "lastAccessed": 1774515151030, "keyFiles": [] }, "backend/api": { "path": "backend/api", "purpose": "API routes", "fileCount": 5, - "lastAccessed": 1774313111645, + "lastAccessed": 1774515151030, "keyFiles": [ "__init__.py", "agents.py", @@ -203,7 +183,7 @@ "path": "backend/config", "purpose": "Configuration files", "fileCount": 6, - "lastAccessed": 1774313111646, + "lastAccessed": 1774515151030, "keyFiles": [ "__init__.py", "agent_profiles.yaml", @@ -213,8 +193,8 @@ "backend/data": { "path": "backend/data", "purpose": "Data files", - "fileCount": 13, - "lastAccessed": 1774313111647, + "fileCount": 12, + "lastAccessed": 1774515151031, "keyFiles": [ "__init__.py", "cache.py", @@ -225,7 +205,7 @@ "path": "docs/assets", "purpose": "Static assets", "fileCount": 5, - "lastAccessed": 1774313111647, + "lastAccessed": 1774515151031, "keyFiles": [ "dashboard.jpg", "evotraders_demo.gif", @@ -236,7 +216,7 @@ "path": "frontend/dist", "purpose": "Distribution/build output", "fileCount": 2, - "lastAccessed": 1774313111647, + "lastAccessed": 1774515151031, "keyFiles": [ "index.html", "trading_logo.png" @@ -246,261 +226,309 @@ "path": "frontend/node_modules", "purpose": "Dependencies", "fileCount": 1, - "lastAccessed": 1774313111650, + "lastAccessed": 1774515151036, "keyFiles": [] } }, "hotPaths": [ { - "path": "CLAUDE.md", - "accessCount": 15, - "lastAccessed": 1774342728155, - "type": "directory" - }, - { - "path": "frontend/src/App.jsx", - "accessCount": 10, - "lastAccessed": 1774339397617, + "path": "frontend/src/hooks/useWebSocketConnection.js", + "accessCount": 100, + "lastAccessed": 1774550862686, "type": "file" }, - { - "path": "frontend/src/hooks/useWebsocketSessionSync.js", - "accessCount": 4, - "lastAccessed": 1774313470024, - "type": "file" - }, - { - "path": "", - "accessCount": 4, - "lastAccessed": 1774339108220, - "type": "directory" - }, { "path": "backend/services/gateway.py", - "accessCount": 3, - "lastAccessed": 1774339389171, + "accessCount": 98, + "lastAccessed": 1774550272354, + "type": "file" + }, + { + "path": "backend/services/gateway_openclaw_handlers.py", + "accessCount": 91, + "lastAccessed": 1774550256325, + "type": "file" + }, + { + "path": "backend/api/openclaw.py", + "accessCount": 48, + "lastAccessed": 1774545375555, + "type": "file" + }, + { + "path": "frontend/src/hooks/useOpenClawPanel.js", + "accessCount": 42, + "lastAccessed": 1774550688926, + "type": "file" + }, + { + "path": "shared/client/openclaw_client.py", + "accessCount": 39, + "lastAccessed": 1774545484770, + "type": "file" + }, + { + "path": "frontend/src", + "accessCount": 35, + "lastAccessed": 1774550715529, + "type": "directory" + }, + { + "path": "reference/openclaw/src", + "accessCount": 33, + "lastAccessed": 1774550840611, + "type": "directory" + }, + { + "path": "backend/services/openclaw_cli.py", + "accessCount": 31, + "lastAccessed": 1774545484887, + "type": "file" + }, + { + "path": "frontend/src/components/TraderView.jsx", + "accessCount": 23, + "lastAccessed": 1774543366574, + "type": "file" + }, + { + "path": "shared/models/openclaw.py", + "accessCount": 22, + "lastAccessed": 1774545419541, + "type": "file" + }, + { + "path": "frontend/src/store/openclawStore.js", + "accessCount": 20, + "lastAccessed": 1774550319533, + "type": "file" + }, + { + "path": "frontend/src/App.jsx", + "accessCount": 18, + "lastAccessed": 1774544542524, + "type": "file" + }, + { + "path": "frontend/src/services/websocket.js", + "accessCount": 18, + "lastAccessed": 1774549669596, + "type": "file" + }, + { + "path": "start-dev.sh", + "accessCount": 15, + "lastAccessed": 1774548224246, + "type": "file" + }, + { + "path": "frontend/src/components/RuntimeView.jsx", + "accessCount": 14, + "lastAccessed": 1774518525793, + "type": "file" + }, + { + "path": "frontend/src/components/AppShell.jsx", + "accessCount": 13, + "lastAccessed": 1774533781725, "type": "file" }, { "path": "backend/main.py", + "accessCount": 13, + "lastAccessed": 1774548236340, + "type": "directory" + }, + { + "path": "backend/apps/openclaw_service.py", + "accessCount": 10, + "lastAccessed": 1774547900186, + "type": "file" + }, + { + "path": "frontend/src/components/OpenClawStatusPanel.jsx", + "accessCount": 8, + "lastAccessed": 1774533622019, + "type": "file" + }, + { + "path": "reference/openclaw/src/commands", + "accessCount": 7, + "lastAccessed": 1774530402019, + "type": "directory" + }, + { + "path": "frontend/src/config/constants.js", + "accessCount": 7, + "lastAccessed": 1774544689658, + "type": "file" + }, + { + "path": "", + "accessCount": 6, + "lastAccessed": 1774550700047, + "type": "directory" + }, + { + "path": "backend/services", + "accessCount": 5, + "lastAccessed": 1774550692490, + "type": "directory" + }, + { + "path": "frontend/src/store/uiStore.js", + "accessCount": 4, + "lastAccessed": 1774533747700, + "type": "file" + }, + { + "path": "frontend/src/styles/GlobalStyles.jsx", + "accessCount": 4, + "lastAccessed": 1774533753657, + "type": "file" + }, + { + "path": "frontend/src/store/agentStore.js", "accessCount": 3, - "lastAccessed": 1774342613364, + "lastAccessed": 1774517930592, + "type": "file" + }, + { + "path": "reference/openclaw/src/cli/skills-cli.ts", + "accessCount": 3, + "lastAccessed": 1774527140107, + "type": "file" + }, + { + "path": "reference/openclaw/src/commands/agents.commands.list.ts", + "accessCount": 3, + "lastAccessed": 1774533427441, "type": "file" }, { "path": "frontend/src/store/runtimeStore.js", "accessCount": 2, - "lastAccessed": 1774317990919, + "lastAccessed": 1774517930660, "type": "file" }, { - "path": "frontend/src/services/websocket.js", + "path": "frontend/src/hooks/useAgentWorkspacePanel.js", "accessCount": 2, - "lastAccessed": 1774318009819, + "lastAccessed": 1774518021290, "type": "file" }, { - "path": "backend/core/pipeline_runner.py", + "path": "frontend/src/services/runtimeApi.js", "accessCount": 2, - "lastAccessed": 1774339367538, + "lastAccessed": 1774518025465, "type": "file" }, { - "path": "backend/runtime/manager.py", + "path": "reference/openclaw/src/commands/agents.commands.delete.ts", "accessCount": 2, - "lastAccessed": 1774339367572, + "lastAccessed": 1774530389553, + "type": "file" + }, + { + "path": "reference/openclaw/src/commands/agents.commands.add.ts", + "accessCount": 2, + "lastAccessed": 1774530389605, + "type": "file" + }, + { + "path": "backend/api/__init__.py", + "accessCount": 2, + "lastAccessed": 1774542416191, + "type": "file" + }, + { + "path": "frontend/vite.config.js", + "accessCount": 2, + "lastAccessed": 1774544772960, + "type": "file" + }, + { + "path": "frontend/src/store/index.js", + "accessCount": 1, + "lastAccessed": 1774515811752, "type": "file" }, { "path": "frontend/src/store/marketStore.js", "accessCount": 1, - "lastAccessed": 1774313140483, - "type": "file" - }, - { - "path": "frontend/src/hooks/useFeedProcessor.js", - "accessCount": 1, - "lastAccessed": 1774313148279, - "type": "file" - }, - { - "path": "frontend/src/components/Header.jsx", - "accessCount": 1, - "lastAccessed": 1774313156696, - "type": "file" - }, - { - "path": "frontend/src/components/TraderView.jsx", - "accessCount": 1, - "lastAccessed": 1774313156753, - "type": "file" - }, - { - "path": "frontend/src/store/uiStore.js", - "accessCount": 1, - "lastAccessed": 1774313187460, + "lastAccessed": 1774515838923, "type": "file" }, { "path": "frontend/src/store/portfolioStore.js", "accessCount": 1, - "lastAccessed": 1774313187511, + "lastAccessed": 1774515839687, "type": "file" }, { - "path": "frontend/src/store/agentStore.js", + "path": "frontend/src/index.css", "accessCount": 1, - "lastAccessed": 1774313187573, + "lastAccessed": 1774515988837, "type": "file" }, { - "path": "frontend/src/hooks/useWebSocketConnection.js", + "path": "frontend/src/App.css", "accessCount": 1, - "lastAccessed": 1774313279414, + "lastAccessed": 1774515998423, "type": "file" }, { - "path": "frontend/src/hooks/useStockDataRequests.js", + "path": "frontend/package.json", "accessCount": 1, - "lastAccessed": 1774313319716, + "lastAccessed": 1774516005569, "type": "file" }, { "path": "frontend/src/hooks/useAgentDataRequests.js", "accessCount": 1, - "lastAccessed": 1774313347455, + "lastAccessed": 1774517930219, "type": "file" }, { - "path": "frontend/src/components/AppShell.jsx", + "path": "backend/services/gateway_admin_handlers.py", "accessCount": 1, - "lastAccessed": 1774313396331, - "type": "file" - }, - { - "path": "start-dev.sh", - "accessCount": 1, - "lastAccessed": 1774317979859, + "lastAccessed": 1774517937966, "type": "file" }, { "path": "backend/apps/agent_service.py", "accessCount": 1, - "lastAccessed": 1774317984348, + "lastAccessed": 1774517946208, "type": "file" }, { - "path": "shared/client/trading_client.py", + "path": "frontend/src/hooks", "accessCount": 1, - "lastAccessed": 1774317984365, + "lastAccessed": 1774517946260, + "type": "directory" + }, + { + "path": "frontend/src/hooks/useFeedProcessor.js", + "accessCount": 1, + "lastAccessed": 1774517952115, "type": "file" }, { - "path": "backend/apps/trading_service.py", + "path": "reference/openclaw/src/commands/models/set.ts", "accessCount": 1, - "lastAccessed": 1774317984408, + "lastAccessed": 1774526963526, "type": "file" }, { - "path": "pyproject.toml", + "path": "reference/openclaw/src/commands/models/list.ts", "accessCount": 1, - "lastAccessed": 1774317990970, + "lastAccessed": 1774526963632, "type": "file" }, { - "path": "backend/agents/factory.py", + "path": "reference/openclaw/src/cli/skills-cli.format.ts", "accessCount": 1, - "lastAccessed": 1774318009867, - "type": "file" - }, - { - "path": "backend/config/constants.py", - "accessCount": 1, - "lastAccessed": 1774318009922, - "type": "file" - }, - { - "path": "backend/api/__init__.py", - "accessCount": 1, - "lastAccessed": 1774318009973, - "type": "file" - }, - { - "path": "README.md", - "accessCount": 1, - "lastAccessed": 1774339107381, - "type": "file" - }, - { - "path": "backend/runtime/registry.py", - "accessCount": 1, - "lastAccessed": 1774339380024, - "type": "file" - }, - { - "path": "backend/runtime/session.py", - "accessCount": 1, - "lastAccessed": 1774339380084, - "type": "file" - }, - { - "path": "backend/runtime/context.py", - "accessCount": 1, - "lastAccessed": 1774339380120, - "type": "file" - }, - { - "path": "backend/runtime/agent_runtime.py", - "accessCount": 1, - "lastAccessed": 1774339380185, - "type": "file" - }, - { - "path": "backend/process/supervisor.py", - "accessCount": 1, - "lastAccessed": 1774339389110, - "type": "file" - }, - { - "path": "backend/core/pipeline.py", - "accessCount": 1, - "lastAccessed": 1774339389187, - "type": "file" - }, - { - "path": "backend/process/models.py", - "accessCount": 1, - "lastAccessed": 1774339397557, - "type": "file" - }, - { - "path": "backend/process/registry.py", - "accessCount": 1, - "lastAccessed": 1774339397577, - "type": "file" - }, - { - "path": "backend/config/env_config.py", - "accessCount": 1, - "lastAccessed": 1774342678236, - "type": "file" - }, - { - "path": "backend/config/data_config.py", - "accessCount": 1, - "lastAccessed": 1774342678253, - "type": "file" - }, - { - "path": "frontend/env.template", - "accessCount": 1, - "lastAccessed": 1774342678290, - "type": "file" - }, - { - "path": "env.template", - "accessCount": 1, - "lastAccessed": 1774342678310, + "lastAccessed": 1774526963684, "type": "file" } ], diff --git a/.omc/state/hud-state.json b/.omc/state/hud-state.json index 1eedef0..8aae63d 100644 --- a/.omc/state/hud-state.json +++ b/.omc/state/hud-state.json @@ -1,6 +1,6 @@ { - "timestamp": "2026-03-24T07:58:12.123Z", + "timestamp": "2026-03-26T17:14:45.135Z", "backgroundTasks": [], - "sessionStartTimestamp": "2026-03-24T07:58:09.417Z", - "sessionId": "fda34772-7bd2-402e-86b2-d656296416f3" + "sessionStartTimestamp": "2026-03-26T17:13:16.686Z", + "sessionId": "83f172c1-eb0f-4418-87a5-b9d4b6ce5b61" } \ No newline at end of file diff --git a/.omc/state/hud-stdin-cache.json b/.omc/state/hud-stdin-cache.json index d6b2010..c44d454 100644 --- a/.omc/state/hud-stdin-cache.json +++ b/.omc/state/hud-stdin-cache.json @@ -1 +1 @@ -{"session_id":"fda34772-7bd2-402e-86b2-d656296416f3","transcript_path":"/Users/cillin/.claude/projects/-Users-cillin-workspeace-evotraders/fda34772-7bd2-402e-86b2-d656296416f3.jsonl","cwd":"/Users/cillin/workspeace/evotraders","model":{"id":"MiniMax-M2.7-highspeed","display_name":"MiniMax-M2.7-highspeed"},"workspace":{"current_dir":"/Users/cillin/workspeace/evotraders","project_dir":"/Users/cillin/workspeace/evotraders","added_dirs":[]},"version":"2.1.78","output_style":{"name":"default"},"cost":{"total_cost_usd":36.63980749999998,"total_duration_ms":69778027,"total_api_duration_ms":2925118,"total_lines_added":3056,"total_lines_removed":4537},"context_window":{"total_input_tokens":910503,"total_output_tokens":145207,"context_window_size":200000,"current_usage":{"input_tokens":507,"output_tokens":247,"cache_creation_input_tokens":4132,"cache_read_input_tokens":96553},"used_percentage":51,"remaining_percentage":49},"exceeds_200k_tokens":false} \ No newline at end of file +{"session_id":"83f172c1-eb0f-4418-87a5-b9d4b6ce5b61","transcript_path":"/Users/cillin/.claude/projects/-Users-cillin-workspeace-evotraders/83f172c1-eb0f-4418-87a5-b9d4b6ce5b61.jsonl","cwd":"/Users/cillin/workspeace/evotraders","model":{"id":"MiniMax-M2.7-highspeed","display_name":"MiniMax-M2.7-highspeed"},"workspace":{"current_dir":"/Users/cillin/workspeace/evotraders","project_dir":"/Users/cillin/workspeace/evotraders","added_dirs":[]},"version":"2.1.78","output_style":{"name":"default"},"cost":{"total_cost_usd":98.95595149999994,"total_duration_ms":43461876,"total_api_duration_ms":7482894,"total_lines_added":2289,"total_lines_removed":1132},"context_window":{"total_input_tokens":949049,"total_output_tokens":356074,"context_window_size":200000,"current_usage":{"input_tokens":507,"output_tokens":72,"cache_creation_input_tokens":346,"cache_read_input_tokens":82368},"used_percentage":42,"remaining_percentage":58},"exceeds_200k_tokens":false} \ No newline at end of file diff --git a/.omc/state/idle-notif-cooldown.json b/.omc/state/idle-notif-cooldown.json index 412c4a2..4b87c5f 100644 --- a/.omc/state/idle-notif-cooldown.json +++ b/.omc/state/idle-notif-cooldown.json @@ -1,3 +1,3 @@ { - "lastSentAt": "2026-03-24T08:58:57.965Z" + "lastSentAt": "2026-03-27T03:08:22.675Z" } \ No newline at end of file diff --git a/.omc/state/subagent-tracking.json b/.omc/state/subagent-tracking.json index d2ec92f..3f33a62 100644 --- a/.omc/state/subagent-tracking.json +++ b/.omc/state/subagent-tracking.json @@ -1,26 +1,17 @@ { "agents": [ { - "agent_id": "abeaf609b74a2b7ee", + "agent_id": "ace758bdbd117358d", "agent_type": "Explore", - "started_at": "2026-03-24T08:01:40.015Z", + "started_at": "2026-03-26T17:16:09.450Z", "parent_mode": "none", "status": "completed", - "completed_at": "2026-03-24T08:02:31.822Z", - "duration_ms": 51807 - }, - { - "agent_id": "afb6750eaae72bc72", - "agent_type": "Explore", - "started_at": "2026-03-24T08:56:21.471Z", - "parent_mode": "none", - "status": "completed", - "completed_at": "2026-03-24T08:57:27.856Z", - "duration_ms": 66385 + "completed_at": "2026-03-26T17:17:33.704Z", + "duration_ms": 84254 } ], - "total_spawned": 2, - "total_completed": 2, + "total_spawned": 1, + "total_completed": 1, "total_failed": 0, - "last_updated": "2026-03-24T08:59:06.380Z" + "last_updated": "2026-03-27T03:08:25.014Z" } \ No newline at end of file diff --git a/.playwright-mcp/page-2026-03-26T12-28-14-006Z.png b/.playwright-mcp/page-2026-03-26T12-28-14-006Z.png new file mode 100644 index 0000000..38fdb42 Binary files /dev/null and b/.playwright-mcp/page-2026-03-26T12-28-14-006Z.png differ diff --git a/README.md b/README.md index df8a281..54461aa 100644 --- a/README.md +++ b/README.md @@ -5,32 +5,28 @@

EvoTraders: A Self-Evolving Multi-Agent Trading System

- 📌 Visit us at EvoTraders website ! + 📌 Visit the EvoTraders website

![System Demo](./docs/assets/evotraders_demo.gif) -EvoTraders is an open-source financial trading agent framework that builds a trading system capable of continuous learning and evolution in real markets through multi-agent collaboration and memory systems. +EvoTraders is an open-source financial trading agent framework that combines multi-agent collaboration, run-scoped workspaces, and memory to support both backtests and live trading workflows. --- ## Core Features -**Multi-Agent Collaborative Trading** -A team of 6 members, including 4 specialized analyst roles (fundamentals, technical, sentiment, valuation) + portfolio manager + risk management, collaborating to make decisions like a real trading team. +**Multi-agent trading team** +Six roles collaborate like a real desk: four specialist analysts (fundamentals, technical, sentiment, valuation), one portfolio manager, and one risk manager. -You can customize your Agents here: [Custom Configuration](#custom-configuration) +**Continuous learning** +Agents can persist long-term memory with ReMe, reflect after each cycle, and evolve their decision patterns over time. -**Continuous Learning and Evolution** -Based on the ReMe memory framework, agents reflect and summarize after each trade, preserving experience across rounds, and forming unique investment methodologies. +**Backtest and live modes** +The same runtime model supports historical simulation and live execution with real-time market data. -Through this design, we hope that when AI Agents form a team and enter the real-time market, they will gradually develop their own trading styles and decision preferences, rather than one-time random inference. - -**Real-Time Market Trading** -Supports real-time market data integration, providing backtesting mode and live trading mode, allowing AI Agents to learn and make decisions in real market fluctuations. - -**Visualized Trading Information** -Observe agents' analysis processes, communication records, and decision evolution in real-time, with complete tracking of return curves and analyst performance. +**Operator-facing UI** +The frontend exposes the trading room, runtime controls, logs, approvals, agent workspaces, and explain/news views.

@@ -39,83 +35,158 @@ Observe agents' analysis processes, communication records, and decision evolutio --- +## Current Architecture + +The repository is currently in a transition from a modular monolith to split service surfaces. The split-service path is the default local development mode. + +Current app surfaces: + +- `backend.apps.agent_service` on `:8000`: control plane for workspaces, agents, skills, and guard/approval APIs +- `backend.apps.trading_service` on `:8001`: read-only trading data APIs +- `backend.apps.news_service` on `:8002`: read-only explain/news APIs +- `backend.apps.runtime_service` on `:8003`: runtime lifecycle APIs +- `backend.apps.openclaw_service` on `:8004`: read-only OpenClaw facade +- WebSocket gateway on `:8765`: live event/feed channel for the frontend + +The most important runtime path today is: + +`frontend -> runtime_service/control APIs -> gateway/runtime manager -> market service + pipeline + storage` + +Reference notes for the migration live in [services/README.md](./services/README.md). + +--- + ## Quick Start -### Installation +### 1. Install ```bash -# Clone repository -git clone https://github.com/agentscope-ai/agentscope-samples -cd agentscope-samples/EvoTraders +# clone this repository, then: +cd evotraders -# Install dependencies (Recommend uv!) +# recommended uv pip install -e . -# optional: pip install -e . +# optional +# uv pip install -e ".[dev]" +# pip install -e . +``` -# Configure environment variables +### 2. Configure environment + +```bash cp env.template .env -# Edit .env file and add your API Keys. The following config are required: +``` -# finance data API: At minimum, FINANCIAL_DATASETS_API_KEY is required, corresponding to FIN_DATA_SOURCE=financial_datasets; It is recommended to add FINNHUB_API_KEY, corresponding to FIN_DATA_SOURCE=finnhub; If using live mode, FINNHUB_API_KEY must be added -FIN_DATA_SOURCE = #finnhub or financial_datasets -FINANCIAL_DATASETS_API_KEY= #Required -FINNHUB_API_KEY= #Optional +The root `env.template` is the canonical local template. A `.env.example` is also kept in the repo for reference. -# LLM API for Agents +Minimum useful variables: + +```bash +# watchlist +TICKERS=AAPL,MSFT,GOOGL,NVDA,TSLA,META,AMZN + +# market data +FIN_DATA_SOURCE=finnhub +FINANCIAL_DATASETS_API_KEY= +FINNHUB_API_KEY= +POLYGON_API_KEY= + +# agent model OPENAI_API_KEY= OPENAI_BASE_URL= MODEL_NAME=qwen3-max-preview -# LLM & embedding API for Memory +# memory (optional unless --enable-memory is used) MEMORY_API_KEY= ``` -### Running +Notes: + +- `FINNHUB_API_KEY` is required for live mode. +- `POLYGON_API_KEY` enables long-lived market-store ingestion and refresh helpers. +- `MEMORY_API_KEY` is only required when long-term memory is enabled. + +### 3. Start the stack + +Recommended local development flow: -**Backtest Mode:** ```bash -evotraders backtest --start 2025-11-01 --end 2025-12-01 -evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory # Use Memory +./start-dev.sh ``` -If you do not have market data APIs and just want to try the backtest demo, download the offline data and unzip it into `backend/data`: +This starts: + +- `agent_service` at `http://localhost:8000` +- `trading_service` at `http://localhost:8001` +- `news_service` at `http://localhost:8002` +- `runtime_service` at `http://localhost:8003` +- gateway WebSocket at `ws://localhost:8765` + +Then start the frontend in another terminal: + +```bash +evotraders frontend +``` + +Open `http://localhost:5173`. + +You can also run services manually: + +```bash +python -m uvicorn backend.apps.agent_service:app --host 0.0.0.0 --port 8000 --reload +python -m uvicorn backend.apps.trading_service:app --host 0.0.0.0 --port 8001 --reload +python -m uvicorn backend.apps.news_service:app --host 0.0.0.0 --port 8002 --reload +python -m uvicorn backend.apps.runtime_service:app --host 0.0.0.0 --port 8003 --reload +python -m backend.main --mode live --host 0.0.0.0 --port 8765 +``` + +### 4. Run backtest or live mode from CLI + +Backtest: + +```bash +evotraders backtest --start 2025-11-01 --end 2025-12-01 +evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory +evotraders backtest --config-name smoke_fullstack --start 2025-11-01 --end 2025-12-01 +``` + +Live: + +```bash +evotraders live +evotraders live --enable-memory +evotraders live --schedule-mode intraday --interval-minutes 60 +evotraders live --trigger-time 22:30 +``` + +Help: + +```bash +evotraders --help +evotraders backtest --help +evotraders live --help +evotraders frontend --help +``` + +### Offline backtest data + +If you want a quick backtest demo without external market APIs, download the offline bundle and unzip it into `backend/data`: + ```bash wget "https://agentscope-open.oss-cn-beijing.aliyuncs.com/ret_data.zip" unzip ret_data.zip -d backend/data ``` -The zip includes basic stock price data so you can run the backtest demo out of the box. -**Live Trading:** -```bash -evotraders live # Run immediately (default) -evotraders live --enable-memory # Use memory -evotraders live --mock # Mock mode (testing) -evotraders live -t 22:30 # Run daily at 22:30 local time (auto-converts to NYSE timezone) -``` +--- -**Get Help:** -```bash -evotraders --help # View global CLI help -evotraders backtest --help # View backtest mode parameters -evotraders live --help # View live/mock run parameters -``` +## Runtime Data Layout -**Launch Visualization Interface:** -```bash -# Ensure npm is installed, otherwise install it: -# npm install -evotraders frontend # Default connects to port 8765, you can modify the address in ./frontend/env.local to change the port number -``` - -Visit `http://localhost:5173/` to view the trading room, select a date and click Run/Replay to observe the decision-making process. - -### Runtime Data Layout - -- Long-lived research data is stored in `data/market_research.db` -- Each task run writes run-scoped state under `runs//` -- `runs//team_dashboard/*.json` is an export/compatibility layer for dashboard views, not the authoritative runtime source of truth -- Runtime APIs prefer active runtime state, `server_state.json`, and `runtime.db` +- Long-lived research data lives in `data/market_research.db` +- Each run writes run-scoped state under `runs//` +- `runs//BOOTSTRAP.md` stores run-specific bootstrap values and prompt body +- `runs//state/runtime_state.json` stores runtime snapshot state +- `runs//team_dashboard/*.json` is a compatibility/export layer for dashboard consumers, not the primary runtime source of truth Optional retention control: @@ -123,129 +194,147 @@ Optional retention control: RUNS_RETENTION_COUNT=20 ``` -Only timestamped run folders like `YYYYMMDD_HHMMSS` are pruned automatically when starting a new runtime. Named runs such as `smoke_fullstack` or `test_*` are preserved. +Only timestamped run folders like `YYYYMMDD_HHMMSS` are pruned automatically. Named runs such as `live`, `smoke_fullstack`, or `reload_demo_*` are preserved. --- -## System Architecture +## Frontend Service Routing -![Architecture Diagram](docs/assets/evotraders_pipeline.jpg) +The frontend always uses the control plane and runtime APIs, and can optionally call split services directly for read-only data. -### Agent Design +Useful frontend env vars: -**Analyst Team:** -- **Fundamentals Analyst**: Financial health, profitability, growth quality -- **Technical Analyst**: Price trends, technical indicators, momentum analysis -- **Sentiment Analyst**: Market sentiment, news sentiment, insider trading -- **Valuation Analyst**: DCF, residual income, EV/EBITDA - -**Decision Layer:** -- **Portfolio Manager**: Integrates analysis signals from analysts, executes communication strategies, combines analyst and team historical performance, recent investment memories, and long-term investment experience to make final decisions -- **Risk Management**: Real-time price and volatility monitoring, position limits, multi-layer risk warnings - -### Decision Process - -``` -Real-time Market Data → Independent Analysis → Intelligent Communication (1v1/1vN/NvN) → Decision Execution → Performance Evaluation → Learning and Evolution (Memory Update) +```bash +VITE_CONTROL_API_BASE_URL=http://localhost:8000/api +VITE_RUNTIME_API_BASE_URL=http://localhost:8003/api/runtime +VITE_NEWS_SERVICE_URL=http://localhost:8002 +VITE_TRADING_SERVICE_URL=http://localhost:8001 +VITE_WS_URL=ws://localhost:8765 ``` -Each trading day goes through five stages: - -1. **Analysis Stage**: Each agent independently analyzes based on their respective tools and historical experience -2. **Communication Stage**: Exchange views through private chats, notifications, meetings, etc. -3. **Decision Stage**: Portfolio manager makes comprehensive judgments and provides final trades -4. **Evaluation Stage** - - **Performance Charts**: Track portfolio return curves vs. benchmark strategies (equal-weighted, market-cap weighted, momentum). Used to evaluate overall strategy effectiveness. - - - **Analyst Rankings**: Click on avatars in the Trading Room to view analyst performance (win rate, bull/bear market win rate). Used to understand which analysts provide the most valuable insights. - - - **Statistics**: Detailed position and trading history. Used for in-depth analysis of position management and execution quality. - -5. **Review Stage**: Agents reflect on decisions and summarize experiences based on actual returns of the day, and store them in the ReMe memory framework for continuous improvement +If these are not set, the frontend falls back to its local defaults and compatibility paths where available. --- -### Module Support +## Decision Flow -- **Agent Framework**: [AgentScope](https://github.com/agentscope-ai/agentscope) -- **Memory System**: [ReMe](https://github.com/agentscope-ai/reme) -- **LLM Support**: OpenAI, DeepSeek, Qwen, Moonshot, Zhipu AI, etc. +```text +Market data -> independent analyst work -> team communication -> portfolio decision -> +risk review -> execution/settlement -> reflection/memory update +``` + +The runtime manager also tracks: + +- agent registration and status +- pending approvals +- run events +- current session key --- ## Custom Configuration -### Custom Analyst Roles +### Add or change analyst roles -1. Register role information in [./backend/agents/prompts/analyst/personas.yaml](./backend/agents/prompts/analyst/personas.yaml), for example: +1. Define the analyst persona in [backend/agents/prompts/analyst/personas.yaml](./backend/agents/prompts/analyst/personas.yaml) +2. Register the role in [backend/config/constants.py](./backend/config/constants.py) +3. Optionally add/update the frontend seat metadata in [frontend/src/config/constants.js](./frontend/src/config/constants.js) + +Example persona entry: ```yaml comprehensive_analyst: name: "Comprehensive Analyst" focus: - - ... - preferred_tools: # Flexibly select based on situation + - multi-factor synthesis + preferred_tools: + - get_stock_price + - get_company_financials description: | - As a comprehensive analyst ... + A generalist analyst that combines multiple signals. ``` -2. Add role definition in [./backend/config/constants.py](./backend/config/constants.py) -```python -ANALYST_TYPES = { - # Add new analyst - "comprehensive_analyst": { - "display_name": "Comprehensive Analyst", - "agent_id": "comprehensive_analyst", - "description": "Uses LLM to intelligently select analysis tools, performs comprehensive analysis", - "order": 15 - } -} -``` +### Configure per-agent models -3. Introduce new role in frontend configuration [./frontend/src/config/constants.js](./frontend/src/config/constants.js) (optional) -```javascript -export const AGENTS = [ - // Override one of the agents - { - id: "comprehensive_analyst", - name: "Comprehensive Analyst", - role: "Comprehensive Analyst", - avatar: `${ASSET_BASE_URL}/...`, - colors: { bg: '#F9FDFF', text: '#1565C0', accent: '#1565C0' } - } -] -``` - -### Custom Models - -Configure models used by different agents in the [.env](.env) file: +Model overrides are configured in `.env`: ```bash -AGENT_SENTIMENT_ANALYST_MODEL_NAME=qwen3-max-preview -AGENT_FUNDAMENTALS_ANALYST_MODEL_NAME=deepseek-chat -AGENT_TECHNICAL_ANALYST_MODEL_NAME=glm-4-plus -AGENT_VALUATION_ANALYST_MODEL_NAME=moonshot-v1-32k +AGENT_SENTIMENT_ANALYST_MODEL_NAME=deepseek-v3.2-exp +AGENT_TECHNICAL_ANALYST_MODEL_NAME=glm-4.6 +AGENT_FUNDAMENTALS_ANALYST_MODEL_NAME=qwen3-max-preview +AGENT_VALUATION_ANALYST_MODEL_NAME=Moonshot-Kimi-K2-Instruct +AGENT_RISK_MANAGER_MODEL_NAME=qwen3-max-preview +AGENT_PORTFOLIO_MANAGER_MODEL_NAME=qwen3-max-preview ``` -### Project Structure +### Run-scoped bootstrap config +Each run can override defaults through `runs//BOOTSTRAP.md`. The front matter is parsed by [backend/config/bootstrap_config.py](./backend/config/bootstrap_config.py) and can define values such as: + +```yaml +tickers: + - AAPL + - MSFT +initial_cash: 100000 +margin_requirement: 0.5 +max_comm_cycles: 2 +schedule_mode: daily +trigger_time: "09:30" +enable_memory: false ``` -EvoTraders/ + +Initialize a run workspace with: + +```bash +evotraders init-workspace --config-name my_run +``` + +--- + +## Project Structure + +```text +evotraders/ ├── backend/ -│ ├── agents/ # Agent implementation -│ ├── communication/ # Communication system -│ ├── memory/ # Memory system (ReMe) -│ ├── tools/ # Analysis toolset -│ ├── servers/ # WebSocket services -│ └── cli.py # CLI entry point -├── frontend/ # React visualization interface -└── logs_and_memory/ # Logs and memory data +│ ├── agents/ # agent roles, prompts, skills, workspaces +│ ├── api/ # FastAPI routers +│ ├── apps/ # split service surfaces +│ ├── core/ # pipeline, scheduler, state sync +│ ├── runtime/ # runtime manager and agent runtime state +│ ├── services/ # gateway, market/storage/db services +│ └── cli.py # Typer CLI entrypoint +├── frontend/ # React + Vite UI +├── shared/ # shared clients and schemas for split services +├── runs/ # run-scoped state and dashboards +├── data/ # long-lived research artifacts +└── services/README.md +``` + +--- + +## Testing + +Backend tests live under `backend/tests` and cover service apps, shared clients, domains, routing, enrichment, gateway support, and runtime support. + +Typical commands: + +```bash +pytest +pytest backend/tests/test_runtime_service_app.py +pytest backend/tests/test_trading_service_app.py +``` + +Frontend tests: + +```bash +cd frontend +npm test ``` --- ## License and Disclaimer -EvoTraders is a research and educational project, open-sourced under the Apache 2.0 license. +EvoTraders is a research and educational project. Review the repository license before redistribution or commercial use. -**Risk Warning**: Before trading with real funds, please conduct thorough testing and risk assessment. Past performance does not guarantee future returns. Investment involves risks, and decisions should be made with caution. +**Risk warning**: this project is not investment advice. Test thoroughly before any real-money deployment. Past performance does not guarantee future returns. diff --git a/README_zh.md b/README_zh.md index 479b062..5c7237a 100644 --- a/README_zh.md +++ b/README_zh.md @@ -4,291 +4,337 @@

EvoTraders:自我进化的多智能体交易系统

-

- 📌 Visit us at EvoTraders website ! + 📌 访问 EvoTraders 官网

![系统演示](./docs/assets/evotraders_demo.gif) -EvoTraders是一个开源的金融交易智能体框架,通过多智能体协作和记忆系统,构建能够在真实市场中持续学习与进化的交易系统。 +EvoTraders 是一个开源的金融交易智能体框架,结合多智能体协作、run 级工作区和记忆机制,支持回测与实盘两类交易运行模式。 --- ## 核心特性 -**多智能体协作交易** -6名成员,包含4种专业分析师角色(基本面、技术面、情绪、估值)+ 投资组合经理 + 风险管理,像真实交易团队一样协作决策。 +**多智能体交易团队** +系统默认包含 6 个角色:4 个分析师(基本面、技术面、情绪、估值)+ 投资经理 + 风控经理。 -你可以在这里自定义你的Agents,支持配置不同大模型(如 Qwen、DeepSeek、GPT、Claude等)协同分析:[自定义配置](#自定义配置) +**持续学习** +可选接入 ReMe 长期记忆,智能体会在每轮结束后反思、复盘并沉淀经验。 -**持续学习与进化** -基于 ReMe 记忆框架,智能体在每次交易后反思总结,跨回合保留经验,形成独特的投资方法论。 - -通过这样的设计,我们希望当 AI Agents 组成团队进入实时市场,它们会逐渐形成自己的交易风格和决策偏好,而不是一次性的随机推理 - - -**实时市场交易** -支持实时行情接入,提供回测模式和实盘模式,让 AI Agents 在真实市场波动中学习和决策。 - -**可视化交易信息** -实时观察 Agents 的分析过程、沟通记录和决策演化,完整追踪收益曲线和分析师表现。 +**统一运行时** +同一套运行时模型支持历史回测和实时行情驱动的实盘流程。 +**可操作前端** +前端不只是展示层,还包含交易室、运行控制、日志、审批、Agent 工作区和 explain/news 视图。

+--- + +## 当前架构 + +仓库目前处于“模块化单体 -> 拆分服务”的迁移阶段,本地开发默认走 split-service 路径。 + +当前 app surface: + +- `backend.apps.agent_service`,端口 `8000`:控制面,负责 workspaces、agents、skills、审批接口 +- `backend.apps.trading_service`,端口 `8001`:只读交易数据接口 +- `backend.apps.news_service`,端口 `8002`:只读 explain/news 接口 +- `backend.apps.runtime_service`,端口 `8003`:运行时生命周期接口 +- `backend.apps.openclaw_service`,端口 `8004`:只读 OpenClaw facade +- WebSocket gateway,端口 `8765`:前端实时事件和 feed 通道 + +当前最关键的主链路是: + +`frontend -> runtime_service/control APIs -> gateway/runtime manager -> market service + pipeline + storage` + +迁移背景可参考 [services/README.md](./services/README.md)。 --- ## 快速开始 -### 安装 +### 1. 安装 ```bash -# 克隆仓库 -git clone https://github.com/agentscope-ai/agentscope-samples -cd agentscope-samples/EvoTraders +# 克隆仓库后进入项目目录 +cd evotraders -# 安装依赖(推荐使用uv) +# 推荐 uv pip install -e . -# (可选)pip install -e . -# 配置环境变量 +# 可选 +# uv pip install -e ".[dev]" +# pip install -e . +``` + +### 2. 配置环境变量 + +```bash cp env.template .env -# 编辑 .env 文件,添加你的 API Keys,以下的配置项为必填项 +``` -# finance data API:至少需要FINANCIAL_DATASETS_API_KEY,对应FIN_DATA_SOURCE=financial_datasets;推荐添加FINNHUB_API_KEY,对应至少需要FINANCIAL_DATASETS_API_KEY,对应FIN_DATA_SOURCE填为finnhub;如果使用live 模式必须添加FINNHUB_API_KEY -FIN_DATA_SOURCE= #finnhub or financial_datasets -FINANCIAL_DATASETS_API_KEY= #必需 -FINNHUB_API_KEY= #可选 +根目录 `env.template` 是当前本地开发的主模板,仓库里也保留了 `.env.example` 作为参考。 -# LLM API for Agents +最常用的配置项: + +```bash +# 自选股 +TICKERS=AAPL,MSFT,GOOGL,NVDA,TSLA,META,AMZN + +# 行情数据 +FIN_DATA_SOURCE=finnhub +FINANCIAL_DATASETS_API_KEY= +FINNHUB_API_KEY= +POLYGON_API_KEY= + +# Agent 模型 OPENAI_API_KEY= OPENAI_BASE_URL= MODEL_NAME=qwen3-max-preview -# LLM & embedding API for Memory +# 长期记忆(只有启用 --enable-memory 才需要) MEMORY_API_KEY= ``` -### 运行 +说明: + +- live 模式必须配置 `FINNHUB_API_KEY` +- `POLYGON_API_KEY` 用于长期 market store 的补数和刷新 +- `MEMORY_API_KEY` 仅在启用长期记忆时需要 + +### 3. 启动服务栈 + +本地开发推荐直接使用: -**回测模式:** ```bash -evotraders backtest --start 2025-11-01 --end 2025-12-01 -evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory # 使用记忆 - +./start-dev.sh ``` -如果没有可用的行情 API,想快速体验回测 demo,可直接下载离线数据并解压到 `backend/data`: +该脚本会启动: + +- `agent_service`:`http://localhost:8000` +- `trading_service`:`http://localhost:8001` +- `news_service`:`http://localhost:8002` +- `runtime_service`:`http://localhost:8003` +- gateway WebSocket:`ws://localhost:8765` + +然后在另一个终端启动前端: + +```bash +evotraders frontend +``` + +访问 `http://localhost:5173`。 + +也可以手动分别启动: + +```bash +python -m uvicorn backend.apps.agent_service:app --host 0.0.0.0 --port 8000 --reload +python -m uvicorn backend.apps.trading_service:app --host 0.0.0.0 --port 8001 --reload +python -m uvicorn backend.apps.news_service:app --host 0.0.0.0 --port 8002 --reload +python -m uvicorn backend.apps.runtime_service:app --host 0.0.0.0 --port 8003 --reload +python -m backend.main --mode live --host 0.0.0.0 --port 8765 +``` + +### 4. 使用 CLI 运行回测或实盘 + +回测: + +```bash +evotraders backtest --start 2025-11-01 --end 2025-12-01 +evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory +evotraders backtest --config-name smoke_fullstack --start 2025-11-01 --end 2025-12-01 +``` + +实盘: + +```bash +evotraders live +evotraders live --enable-memory +evotraders live --schedule-mode intraday --interval-minutes 60 +evotraders live --trigger-time 22:30 +``` + +帮助: + +```bash +evotraders --help +evotraders backtest --help +evotraders live --help +evotraders frontend --help +``` + +### 离线回测数据 + +如果只是想快速体验回测,不依赖外部行情 API,可以下载离线数据包并解压到 `backend/data`: + ```bash wget "https://agentscope-open.oss-cn-beijing.aliyuncs.com/ret_data.zip" unzip ret_data.zip -d backend/data ``` -该压缩包提供基础的股票行情数据,解压后即可直接用于回测演示。 -**实盘交易:** +--- + +## 运行时数据布局 + +- 长期研究数据保存在 `data/market_research.db` +- 每次 run 的状态写入 `runs//` +- `runs//BOOTSTRAP.md` 保存该 run 的 bootstrap 值和 prompt body +- `runs//state/runtime_state.json` 保存运行时快照 +- `runs//team_dashboard/*.json` 主要是给 dashboard 用的兼容导出层,不是唯一真相源 + +可选保留策略: + ```bash -evotraders live # 立即运行(默认) -evotraders live --enable-memory # 使用记忆 -evotraders live --mock # Mock 模式(测试) -evotraders live -t 22:30 # 每天本地时间 22:30 运行(自动转换为 NYSE 时区) -evotraders live --schedule-mode intraday --interval-minutes 60 # 每隔 1 小时触发一次;仅交易时段执行交易,其他时段只分析 +RUNS_RETENTION_COUNT=20 ``` -前端的“运行设置”面板也支持热更新 `schedule_mode`、`interval_minutes`、`max_comm_cycles`;其中 daily 模式时间当前按 NYSE/ET 配置。 +只有形如 `YYYYMMDD_HHMMSS` 的时间戳目录会被自动清理;`live`、`smoke_fullstack`、`reload_demo_*` 这类命名 run 会保留。 -**获取帮助:** -```bash -evotraders --help # 查看整体命令行帮助 -evotraders backtest --help # 查看回测模式的参数说明 -evotraders live --help # 查看实盘/Mock 运行的参数说明 -``` +--- -**启动可视化界面:** -```bash -# 确保已安装 npm, 否则请安装: -# npm install -evotraders frontend # 默认连接 8765 端口, 你可以修改 ./frontend/env.local 中的地址从而修改端口号 -``` +## 前端服务路由 -访问 `http://localhost:5173/` 查看交易大厅,选择日期并点击 Run/Replay 观察决策过程。 +前端始终会使用 control plane 和 runtime API,同时可以选择直连拆分服务读取只读数据。 -### 迁移期服务边界说明 - -当前仓库正处于从模块化单体向独立服务迁移的阶段,当前默认开发路径已经切到独立 app surface: - -- `backend.apps.agent_service` -- `backend.apps.runtime_service` -- `backend.apps.trading_service` -- `backend.apps.news_service` - -当前本地开发默认推荐直接运行拆分后的服务: +常用前端环境变量: ```bash -./start-dev.sh split - -# 或分别手动启动 -python -m uvicorn backend.apps.agent_service:app --port 8000 --reload -python -m uvicorn backend.apps.runtime_service:app --port 8003 --reload -python -m uvicorn backend.apps.trading_service:app --port 8001 --reload -python -m uvicorn backend.apps.news_service:app --port 8002 --reload -``` - -迁移期关键环境变量: - -```bash -# 后端 Gateway 优先走独立服务读取 -NEWS_SERVICE_URL=http://localhost:8002 -TRADING_SERVICE_URL=http://localhost:8001 - -# 前端浏览器直连控制面 / 运行时面 VITE_CONTROL_API_BASE_URL=http://localhost:8000/api VITE_RUNTIME_API_BASE_URL=http://localhost:8003/api/runtime - -# 前端浏览器优先直连独立服务 VITE_NEWS_SERVICE_URL=http://localhost:8002 VITE_TRADING_SERVICE_URL=http://localhost:8001 +VITE_WS_URL=ws://localhost:8765 ``` -目前前端已支持直连 `news-service` 的 explain 只读路径包括: - -- runtime panel / gateway port 查询已可独立指向 `runtime-service` -- story -- similar days -- range explain -- news for date -- news categories - -如果没有配置这些变量,系统会继续走当前保留的本地回退逻辑。 +如果不配置,前端会按本地默认值和兼容回退逻辑运行。 --- -## 系统架构 +## 决策流程 -![架构图](docs/assets/evotraders_pipeline.jpg) - -### 智能体设计 - -**分析师团队:** -- **基本面分析师**:财务健康度、盈利能力、增长质量 -- **技术分析师**:价格趋势、技术指标、动量分析 -- **情绪分析师**:市场情绪、新闻舆情、内部人交易 -- **估值分析师**:DCF、剩余收益、EV/EBITDA - -**决策层:** -- **投资组合经理**:整合来自分析师的分析信号,执行沟通策略,结合分析师和团队历史表现、近期投资记忆和长期投资经验,进行最终决策 -- **风险管理**:实时价格与波动率监控、头寸限制,多层风险预警 - -### 决策流程 - -``` -实时行情 → 独立分析 → 智能沟通 (1v1/1vN/NvN) → 决策执行 → 收益评估 → 学习与进化(记忆更新) +```text +市场数据 -> 分析师独立分析 -> 团队沟通 -> 投资决策 -> +风控审核 -> 执行/结算 -> 复盘/记忆更新 ``` -每个交易日经历五个阶段: - -1. **分析阶段**:各智能体基于各自工具和历史经验独立分析 -2. **沟通阶段**:通过私聊、通知、会议等方式交换观点 -3. **决策阶段**:投资组合经理综合判断,给出最终交易 -4. **评估阶段** - - **业绩图表**: 追踪组合收益曲线 vs. 基准策略(等权、市值加权、动量)。用于评估整体策略有效性。 - - - **分析师排名**: 在 Trading Room 点击头像查看分析师表现(胜率、牛/熊市胜率)。用于了解哪些分析师提供最有价值的洞察。 - - - **统计数据**: 详细的持仓和交易历史。用于深入分析仓位管理和执行质量。 - -4. **复盘阶段**:Agents 根据当日实际收益反思决策、总结经验,并存入 ReMe 记忆框架以持续改进 - ---- - -### 模块支持 - -- **智能体框架**:[AgentScope](https://github.com/agentscope-ai/agentscope) -- **记忆系统**:[ReMe](https://github.com/agentscope-ai/reme) -- **LLM 支持**:OpenAI、DeepSeek、Qwen、Moonshot、Zhipu AI 等 +运行时管理器还会跟踪: +- agent 注册和状态 +- 待审批项 +- run 事件 +- 当前 session key --- ## 自定义配置 -### 自定义分析师角色 +### 新增或修改分析师角色 -1. 在 [./backend/agents/prompts/analyst/personas.yaml](./backend/agents/prompts/analyst/personas.yaml) 中注册角色信息,例如: +1. 在 [backend/agents/prompts/analyst/personas.yaml](./backend/agents/prompts/analyst/personas.yaml) 中定义 persona +2. 在 [backend/config/constants.py](./backend/config/constants.py) 中注册角色 +3. 如有需要,在 [frontend/src/config/constants.js](./frontend/src/config/constants.js) 中补充前端展示元数据 + +示例: ```yaml comprehensive_analyst: name: "Comprehensive Analyst" focus: - - ... - preferred_tools: # Flexibly select based on situation + - multi-factor synthesis + preferred_tools: + - get_stock_price + - get_company_financials description: | - As a comprehensive analyst ... + A generalist analyst that combines multiple signals. ``` -2. 在 [./backend/config/constants.py](./backend/config/constants.py) 添加角色定义 -```python -ANALYST_TYPES = { - # 增加新的分析师 - "comprehensive_analyst": { - "display_name": "Comprehensive Analyst", - "agent_id": "comprehensive_analyst", - "description": "Uses LLM to intelligently select analysis tools, performs comprehensive analysis", - "order": 15 - } -} -``` +### 配置各 Agent 使用的模型 -3. 在前端配置 [./frontend/src/config/constants.js](./frontend/src/config/constants.js) 中引入新角色(可选) -```javascript -export const AGENTS = [ - // 覆盖掉其中某一个agent - { - id: "comprehensive_analyst", - name: "Comprehensive Analyst", - role: "Comprehensive Analyst", - avatar: `${ASSET_BASE_URL}/...`, - colors: { bg: '#F9FDFF', text: '#1565C0', accent: '#1565C0' } - } - ] -``` - - - -### 自定义模型 - -在 [.env](.env) 文件中配置不同智能体使用的模型: +模型覆盖在 `.env` 中配置: ```bash -AGENT_SENTIMENT_ANALYST_MODEL_NAME=qwen3-max-preview -AGENT_FUNDAMENTAL_ANALYST_MODEL_NAME=deepseek-chat -AGENT_TECHNICAL_ANALYST_MODEL_NAME=glm-4-plus -AGENT_VALUATION_ANALYST_MODEL_NAME=moonshot-v1-32k +AGENT_SENTIMENT_ANALYST_MODEL_NAME=deepseek-v3.2-exp +AGENT_TECHNICAL_ANALYST_MODEL_NAME=glm-4.6 +AGENT_FUNDAMENTALS_ANALYST_MODEL_NAME=qwen3-max-preview +AGENT_VALUATION_ANALYST_MODEL_NAME=Moonshot-Kimi-K2-Instruct +AGENT_RISK_MANAGER_MODEL_NAME=qwen3-max-preview +AGENT_PORTFOLIO_MANAGER_MODEL_NAME=qwen3-max-preview ``` -### 项目结构 +### run 级 BOOTSTRAP 配置 +每个 run 都可以通过 `runs//BOOTSTRAP.md` 覆盖默认值。该文件由 [backend/config/bootstrap_config.py](./backend/config/bootstrap_config.py) 解析,front matter 可配置: + +```yaml +tickers: + - AAPL + - MSFT +initial_cash: 100000 +margin_requirement: 0.5 +max_comm_cycles: 2 +schedule_mode: daily +trigger_time: "09:30" +enable_memory: false ``` -EvoTraders/ + +初始化一个 run 工作区: + +```bash +evotraders init-workspace --config-name my_run +``` + +--- + +## 项目结构 + +```text +evotraders/ ├── backend/ -│ ├── agents/ # 智能体实现 -│ ├── communication/ # 通信系统 -│ ├── memory/ # 记忆系统 (ReMe) -│ ├── tools/ # 分析工具集 -│ ├── servers/ # WebSocket 服务 -│ └── cli.py # CLI 入口 -├── frontend/ # React 可视化界面 -└── logs_and_memory/ # 日志和记忆数据 +│ ├── agents/ # agent 角色、prompts、skills、workspaces +│ ├── api/ # FastAPI 路由层 +│ ├── apps/ # 拆分服务 app surface +│ ├── core/ # pipeline、scheduler、state sync +│ ├── runtime/ # runtime manager 和 agent runtime state +│ ├── services/ # gateway、market/storage/db 服务 +│ └── cli.py # Typer CLI 入口 +├── frontend/ # React + Vite 前端 +├── shared/ # 拆分服务共用 client 和 schema +├── runs/ # run 级状态和 dashboard 导出 +├── data/ # 长期研究数据 +└── services/README.md +``` + +--- + +## 测试 + +后端测试位于 `backend/tests`,覆盖 service app、shared client、domain、路由、enrichment、gateway 支撑模块和 runtime 支撑模块。 + +常用命令: + +```bash +pytest +pytest backend/tests/test_runtime_service_app.py +pytest backend/tests/test_trading_service_app.py +``` + +前端测试: + +```bash +cd frontend +npm test ``` --- ## 许可与免责 -EvoTraders 是一个研究和教育项目,采用 Apache 2.0 许可协议开源。 +EvoTraders 是研究和教育用途项目。再次分发或商用前,请先核对仓库中的实际 license 文件。 -**风险提示**:在实际资金交易前,请务必进行充分的测试和风险评估。历史表现不代表未来收益,投资有风险,决策需谨慎。 +**风险提示**:本项目不构成投资建议。任何实盘部署前都应进行充分测试和风险评估,历史表现不代表未来收益。 diff --git a/backend/api/__init__.py b/backend/api/__init__.py index 9e8c0c5..535587e 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -11,11 +11,13 @@ Provides REST API endpoints for: from .agents import router as agents_router from .workspaces import router as workspaces_router from .guard import router as guard_router +from .openclaw import router as openclaw_router from .runtime import router as runtime_router __all__ = [ "agents_router", "workspaces_router", "guard_router", + "openclaw_router", "runtime_router", ] diff --git a/backend/api/runtime.py b/backend/api/runtime.py index 24d3a9f..2a2438d 100644 --- a/backend/api/runtime.py +++ b/backend/api/runtime.py @@ -389,11 +389,21 @@ def _find_available_port(start_port: int = 8765, max_port: int = 9000) -> int: def _is_gateway_running() -> bool: - """Check if Gateway process is running.""" + """Check if Gateway process is running. + + Checks both the internally-managed gateway process and falls back to + port availability (for externally-managed gateway processes). + """ process = _runtime_state.gateway_process - if process is None: + if process is not None and process.poll() is None: + return True + # Fallback: check if the gateway port is in use (for externally started gateway) + import socket + try: + with socket.create_connection(("127.0.0.1", _runtime_state.gateway_port), timeout=1): + return True + except OSError: return False - return process.poll() is None def _stop_gateway() -> bool: diff --git a/backend/apps/__init__.py b/backend/apps/__init__.py index 7c71854..7084136 100644 --- a/backend/apps/__init__.py +++ b/backend/apps/__init__.py @@ -5,6 +5,8 @@ from .agent_service import app as agent_app from .agent_service import create_app as create_agent_app from .news_service import app as news_app from .news_service import create_app as create_news_app +from .openclaw_service import app as openclaw_app +from .openclaw_service import create_app as create_openclaw_app from .runtime_service import app as runtime_app from .runtime_service import create_app as create_runtime_app from .trading_service import app as trading_app @@ -21,6 +23,8 @@ __all__ = [ "create_agent_app", "news_app", "create_news_app", + "openclaw_app", + "create_openclaw_app", "runtime_app", "create_runtime_app", "trading_app", diff --git a/backend/services/gateway_cycle_support.py b/backend/services/gateway_cycle_support.py index a04cf36..313073b 100644 --- a/backend/services/gateway_cycle_support.py +++ b/backend/services/gateway_cycle_support.py @@ -388,4 +388,15 @@ def stop_gateway(gateway: Any) -> None: gateway._market_status_task.cancel() if gateway._watchlist_ingest_task: gateway._watchlist_ingest_task.cancel() + # Close OpenClaw WebSocket connection + if gateway._openclaw_ws: + import asyncio + try: + loop = asyncio.get_event_loop() + if loop.is_running(): + loop.create_task(gateway._openclaw_ws.disconnect()) + else: + loop.run_until_complete(gateway._openclaw_ws.disconnect()) + except Exception: + pass gateway._dashboard.stop() diff --git a/backend/tests/test_service_clients.py b/backend/tests/test_service_clients.py index 19cc677..877d697 100644 --- a/backend/tests/test_service_clients.py +++ b/backend/tests/test_service_clients.py @@ -4,6 +4,7 @@ import pytest from shared.client.control_client import ControlPlaneClient +from shared.client.openclaw_client import OpenClawServiceClient from shared.client.runtime_client import RuntimeServiceClient @@ -105,3 +106,25 @@ async def test_runtime_service_client_hits_current_runtime_routes(): ("get", "/config", None), ("put", "/config", {"schedule_mode": "intraday"}), ] + + +@pytest.mark.asyncio +async def test_openclaw_service_client_hits_current_openclaw_routes(): + client = OpenClawServiceClient() + client._client = _DummyAsyncClient() + + await client.fetch_status() + await client.list_sessions() + await client.get_session("main/session-1") + await client.get_session_history("main/session-1", limit=5) + await client.list_cron_jobs() + await client.list_approvals() + + assert client._client.calls == [ + ("get", "/status", None), + ("get", "/sessions", None), + ("get", "/sessions/main/session-1", None), + ("get", "/sessions/main/session-1/history", {"limit": 5}), + ("get", "/cron", None), + ("get", "/approvals", None), + ] diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..2a1a9b3 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,121 @@ +# Deployment Notes + +This directory contains the current production-oriented deployment artifacts for +the EvoTraders frontend site and the live gateway process. + +## Contents + +- [deploy/systemd/evotraders.service](./systemd/evotraders.service) + - systemd unit for the long-running EvoTraders gateway process +- [scripts/run_prod.sh](../scripts/run_prod.sh) + - production launch script used by the systemd unit +- [deploy/nginx/evotraders.cillinn.com.conf](./nginx/evotraders.cillinn.com.conf) + - HTTPS nginx config with WebSocket proxying +- [deploy/nginx/evotraders.cillinn.com.http.conf](./nginx/evotraders.cillinn.com.http.conf) + - plain HTTP/static-site variant + +## Current Production Shape + +The checked-in production path is intentionally minimal: + +- nginx serves the built frontend from `/var/www/evotraders/current` +- nginx proxies `/ws` to `127.0.0.1:8765` +- systemd runs `scripts/run_prod.sh` +- `scripts/run_prod.sh` starts `python3 -m backend.main` in live mode on `127.0.0.1:8765` + +This means the checked-in production example is centered on the gateway and +frontend, not on exposing the split FastAPI services directly. + +## Important Paths And Ports + +- frontend root: `/var/www/evotraders/current` +- gateway bind: `127.0.0.1:8765` +- public WebSocket path: `/ws` +- working directory expected by systemd: `/root/code/evotraders` + +## systemd + +The current systemd unit: + +- uses `WorkingDirectory=/root/code/evotraders` +- executes [scripts/run_prod.sh](../scripts/run_prod.sh) +- restarts automatically on failure + +Enable and start: + +```bash +sudo cp deploy/systemd/evotraders.service /etc/systemd/system/evotraders.service +sudo systemctl daemon-reload +sudo systemctl enable evotraders +sudo systemctl start evotraders +``` + +Check status and logs: + +```bash +sudo systemctl status evotraders +journalctl -u evotraders -f +``` + +## nginx + +The HTTPS nginx config does two things: + +- redirects `http://evotraders.cillinn.com` to HTTPS +- proxies `/ws` to the local gateway process with WebSocket upgrade headers + +Typical install flow: + +```bash +sudo cp deploy/nginx/evotraders.cillinn.com.conf /etc/nginx/sites-available/evotraders.cillinn.com.conf +sudo ln -s /etc/nginx/sites-available/evotraders.cillinn.com.conf /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +``` + +The checked-in TLS config expects Let's Encrypt assets at: + +- `/etc/letsencrypt/live/evotraders.cillinn.com/fullchain.pem` +- `/etc/letsencrypt/live/evotraders.cillinn.com/privkey.pem` + +## Environment Expectations + +Before using the production scripts, ensure the runtime environment has: + +- a usable Python environment +- repo dependencies installed +- required market/model API keys +- any desired `TICKERS` override + +The production script currently sets: + +```bash +PYTHONPATH=/root/code/evotraders/.pydeps:. +TICKERS=${TICKERS:-AAPL,MSFT,GOOGL,AMZN,NVDA,META,TSLA,AMD,NFLX,AVGO,PLTR,COIN} +``` + +It then launches: + +```bash +python3 -m backend.main \ + --mode live \ + --config-name production \ + --host 127.0.0.1 \ + --port 8765 \ + --trigger-time now \ + --poll-interval 15 +``` + +## What This Deployment Does Not Yet Cover + +The checked-in deployment artifacts do not currently document or automate: + +- split FastAPI service deployment on `8000` to `8004` +- OpenClaw gateway deployment on `18789` +- database backup/retention workflows +- frontend build/publish steps +- secret management + +If you move production fully to split-service mode, update this directory so it +documents the new service topology explicitly instead of relying on the gateway- +only path. diff --git a/docs/compat-removal-plan.md b/docs/compat-removal-plan.md index f4f960f..c86e5b9 100644 --- a/docs/compat-removal-plan.md +++ b/docs/compat-removal-plan.md @@ -1,28 +1,116 @@ -# Compatibility Removal Plan +# Compatibility And Migration Status -This document tracks the remaining migration-only surfaces that still exist -after the move to split-first development. +This document tracks the remaining migration-related boundaries after the +repository switched to split-first development. -## Migration-only Surfaces +## Current Status -None currently remain as dedicated compatibility wrappers. +The repo no longer depends on a combined FastAPI compatibility wrapper for +normal local development. The default path is now: -## Completed Removals +`agent_service + trading_service + news_service + runtime_service + gateway` + +That means compatibility is no longer a separate startup mode. What remains is +mostly protocol-level and routing-level compatibility while the codebase +continues to move responsibilities into clearer service surfaces. + +## What Was Removed ### `backend.app` -- Removed after compatibility startup switched to - `backend.apps.combined_service:app` directly. +- Removed after startup paths switched away from the legacy app wrapper. + +### `backend.apps.combined_service` + +- Removed after split-service startup became the only supported local dev mode. ### `shared.client.AgentServiceClient` - Removed after split-aware clients became the default import surface. -- Replacement: +- Replaced by: - `ControlPlaneClient` - `RuntimeServiceClient` - `TradingServiceClient` - `NewsServiceClient` -### `backend.apps.combined_service` +## What Still Exists For Compatibility -- Removed after split-service mode became the only supported dev startup path. +These are not legacy wrappers in the old sense, but they still preserve +backward-compatible behavior while migration settles. + +### Gateway-mediated flows + +- The WebSocket gateway still carries a mix of: + - live runtime feed transport + - orchestration + - selected read flows that have not been moved to direct browser service calls +- This is intentional for now because the frontend still depends on the gateway + for event streaming and some compatibility reads. + +### In-process fallbacks + +- Some read paths still support local-module fallback when split-service URLs + are not configured. +- Relevant variables include: + - `TRADING_SERVICE_URL` + - `NEWS_SERVICE_URL` +- This keeps the app resilient during migration, but it also means behavior can + differ depending on env configuration. + +### Dual OpenClaw integration surfaces + +- OpenClaw currently appears through two different shapes: + - WebSocket gateway integration on `:18789` + - optional REST surface at `backend.apps.openclaw_service` on `:8004` +- These are both valid, but they are not the same surface and should not be + documented as interchangeable. + +## Remaining Migration Risks + +### Split service deployment is not yet the checked-in production default + +- The repo documents split-service local development clearly. +- The checked-in production example still centers on `backend.main` and nginx + WebSocket proxying. +- This is a topology mismatch to keep in mind when changing deploy docs or prod + automation. + +### Environment-dependent routing + +- The frontend and gateway can switch behavior based on configured service URLs. +- This is helpful operationally, but it makes debugging more configuration- + sensitive than a fully fixed service topology. + +### Runtime/control-plane separation is logical, not fully operationally isolated + +- `runtime_service` owns lifecycle APIs. +- `agent_service` owns control-plane APIs. +- The gateway still hosts the live runtime orchestration path, so the split is + clean at the API level but not yet a completely independent service mesh. + +## Exit Criteria For Declaring Migration Complete + +Migration can be considered effectively complete when all of the following are +true: + +1. Production deployment docs and scripts explicitly run the same split-service + topology used in development, or intentionally document a different stable + production topology. +2. Critical read paths no longer require ambiguous fallback behavior to local + module implementations. +3. OpenClaw integration is documented as a stable contract with clear guidance + on when to use the WebSocket gateway versus the REST surface. +4. The frontend-service routing model is stable enough that direct-service and + gateway-mediated paths are deliberate design choices rather than migration + leftovers. + +## Practical Read Of The Current State + +The migration away from combined-service startup is done. + +What remains is not “legacy startup debt”, but: + +- topology clarification +- deployment consistency +- reduction of env-dependent fallback behavior +- sharper documentation around gateway and OpenClaw boundaries diff --git a/env.template b/env.template index 569945e..ac2e31f 100644 --- a/env.template +++ b/env.template @@ -20,6 +20,9 @@ MARKET_DB_PATH= #optional path for long-lived market_research.db | 长期市场 OPENAI_API_KEY= OPENAI_BASE_URL= MODEL_NAME=qwen3-max-preview +OPENCLAW_CMD= +OPENCLAW_CWD= +OPENCLAW_TIMEOUT_SECONDS=15 EXPLAIN_ENRICH_USE_LLM=false EXPLAIN_ENRICH_MODEL_PROVIDER= EXPLAIN_ENRICH_MODEL_NAME= diff --git a/frontend/README.md b/frontend/README.md index fa7fa51..abe9c87 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,31 +1,56 @@ -## QuickStart +## Frontend Quick Start + ```bash cd frontend npm install npm run dev ``` -## Optional Direct Service Calls +Default dev URL: `http://localhost:5173` -The frontend still works with the compatibility backend entrypoint by default. -In the current test-stage setup, split services are the recommended default. -Point the frontend directly at those standalone services: +The frontend expects the EvoTraders gateway WebSocket on `ws://localhost:8765` unless overridden. + +## Recommended Local Backend Stack + +Start the split backend services from the project root: + +```bash +./start-dev.sh +``` + +That gives you: + +- control plane at `http://localhost:8000/api` +- trading service at `http://localhost:8001` +- news service at `http://localhost:8002` +- runtime service at `http://localhost:8003/api/runtime` +- gateway WebSocket at `ws://localhost:8765` + +## Frontend Environment Variables + +You can point the frontend directly at those services with: ```bash VITE_CONTROL_API_BASE_URL=http://localhost:8000/api VITE_RUNTIME_API_BASE_URL=http://localhost:8003/api/runtime VITE_NEWS_SERVICE_URL=http://localhost:8002 VITE_TRADING_SERVICE_URL=http://localhost:8001 +VITE_WS_URL=ws://localhost:8765 ``` -Current direct-call coverage: +There is also a starter template at [frontend/env.template](./env.template). -- runtime panel + gateway port discovery +## Direct-Service Coverage + +Current direct-call coverage includes: + +- runtime panel data loading +- gateway port/runtime discovery - `story` - `similar days` - `range explain` - `news for date` - `news categories` +- selected trading reads such as price history and insider trades -If these variables are not set, the frontend falls back to the existing -WebSocket-driven compatibility flow. +If these variables are not set, the frontend falls back to local defaults and compatibility paths where they still exist. diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index cfa7717..7363493 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,6 +13,7 @@ import { useAgentStore } from './store/agentStore'; import { useMarketStore } from './store/marketStore'; import { usePortfolioStore } from './store/portfolioStore'; import { useRuntimeStore } from './store/runtimeStore'; +import { useOpenClawStore } from './store/openclawStore'; import { useUIStore } from './store/uiStore'; const EDITABLE_AGENT_WORKSPACE_FILES = [ @@ -141,6 +142,11 @@ export default function LiveTradingApp() { addSystemMessage, }); + // Make clientRef available to OpenClaw panel via store + useEffect(() => { + useOpenClawStore.getState().setClientRef(clientRef); + }, [clientRef]); + const runtimeControls = useRuntimeControls({ clientRef, currentTickers: tickers, diff --git a/frontend/src/components/AppShell.jsx b/frontend/src/components/AppShell.jsx index cd7b399..bae13db 100644 --- a/frontend/src/components/AppShell.jsx +++ b/frontend/src/components/AppShell.jsx @@ -14,6 +14,7 @@ const AgentFeed = lazy(() => import('./AgentFeed')); const StatisticsView = lazy(() => import('./StatisticsView')); const StockExplainView = lazy(() => import('./StockExplainView.jsx')); const TraderView = lazy(() => import('./TraderView.jsx')); +const OpenClawView = lazy(() => import('./OpenClawView.jsx')); function ViewLoadingFallback({ label = '加载中...' }) { return ( @@ -171,7 +172,8 @@ export default function AppShell({ const base = `view-slider-five ${currentView === 'traders' ? 'show-traders' : currentView === 'room' ? 'show-room' : currentView === 'explain' ? 'show-explain' : - currentView === 'statistics' ? 'show-statistics' : 'show-chart'}`; + currentView === 'chart' ? 'show-chart' : + currentView === 'statistics' ? 'show-statistics' : 'show-openclaw'}`; return base; }, [currentView]); @@ -382,6 +384,12 @@ export default function AppShell({ > 统计 +
@@ -485,6 +493,13 @@ export default function AppShell({ />
+ + {/* OpenClaw View Panel */} +
+ }> + + +
diff --git a/frontend/src/components/TraderView.jsx b/frontend/src/components/TraderView.jsx index 7dd403c..a3c2f49 100644 --- a/frontend/src/components/TraderView.jsx +++ b/frontend/src/components/TraderView.jsx @@ -127,7 +127,7 @@ export default function TraderView({ padding: '18px', background: 'linear-gradient(180deg, #ffffff 0%, #f4f7fb 100%)', display: 'grid', - gridTemplateRows: 'auto minmax(0, 1fr)', + gridTemplateRows: 'auto auto 1fr', gap: 18 }}>
@@ -138,82 +138,86 @@ export default function TraderView({ 聚焦查看每个 Agent 的模型、工具组、技能编排和工作区记忆,不展示交易表现数据
+
-
- {agents.map((agent) => { - const isSelected = agent.id === selectedAgentId; - return ( - - ); - })} -
+ /> +
+ {agent.name} +
+ + ); + })} +
-
+ {/* Right: agent detail content */} +
-
+
{isSkillPickerOpen && createPortal((
( */ export const useUIStore = create((set) => ({ // Current view - currentView: 'traders', // 'traders' | 'room' | 'explain' | 'chart' | 'statistics' | 'runtime' + currentView: 'traders', // 'traders' | 'room' | 'explain' | 'chart' | 'statistics' | 'openclaw' | 'runtime' setCurrentView: (currentView) => set((state) => ({ currentView: resolveValue(currentView, state.currentView) })), // Chart tab diff --git a/frontend/src/styles/GlobalStyles.jsx b/frontend/src/styles/GlobalStyles.jsx index acdfd06..f560c00 100644 --- a/frontend/src/styles/GlobalStyles.jsx +++ b/frontend/src/styles/GlobalStyles.jsx @@ -1098,6 +1098,10 @@ export default function GlobalStyles() { transform: translateX(-80%); } + .view-slider-five.show-openclaw { + transform: translateX(-100%); + } + .view-panel { flex: 0 0 33.333%; width: 33.333%; diff --git a/frontend/trader-full.png b/frontend/trader-full.png new file mode 100644 index 0000000..3542a67 Binary files /dev/null and b/frontend/trader-full.png differ diff --git a/frontend/trader-view.png b/frontend/trader-view.png new file mode 100644 index 0000000..0e77444 Binary files /dev/null and b/frontend/trader-view.png differ diff --git a/reference/CoPaw b/reference/CoPaw deleted file mode 160000 index 934cfce..0000000 --- a/reference/CoPaw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 934cfce0a7b2981cb6f40bb0cafe7153e365504e diff --git a/reference/Hyper-Alpha-Arena b/reference/Hyper-Alpha-Arena deleted file mode 160000 index f137cff..0000000 --- a/reference/Hyper-Alpha-Arena +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f137cff4766d0fafa86ba44d4826d7ca3ea52504 diff --git a/reference/PokieTicker b/reference/PokieTicker deleted file mode 160000 index 4fed775..0000000 --- a/reference/PokieTicker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4fed7755e5de106b51788cbd327989d745d725e6 diff --git a/reference/openclaw b/reference/openclaw index 7b151af..f92c925 160000 --- a/reference/openclaw +++ b/reference/openclaw @@ -1 +1 @@ -Subproject commit 7b151afeeb36d48f3edf495a675166e8c6fd1abb +Subproject commit f92c92515bd439a71bd03eb1bc969c1964f17acf diff --git a/reference/openclaw-control-center b/reference/openclaw-control-center new file mode 160000 index 0000000..473f42b --- /dev/null +++ b/reference/openclaw-control-center @@ -0,0 +1 @@ +Subproject commit 473f42bb412f3dc70ac6c6f33f59a8b53fa6cf8b diff --git a/services/README.md b/services/README.md index 1893da0..78d778f 100644 --- a/services/README.md +++ b/services/README.md @@ -1,73 +1,160 @@ -# EvoTraders Services Architecture +# EvoTraders Service Surfaces -This repo is currently in a **migration state** between a modular monolith and -fully split services. Service boundaries now exist as dedicated FastAPI app -surfaces, and local development now runs those split services directly. +This repository is in a split-first state: local development now assumes +separate app surfaces and a dedicated WebSocket gateway instead of a single +combined backend entrypoint. -## Current App Surfaces +## Service Map -| App surface | Default port | Responsibility | +| Surface | Default port | Role | | --- | --- | --- | -| `backend.apps.agent_service` | 8000 | Control-plane only: workspaces, agents, guard. | -| `backend.apps.runtime_service` | 8003 | Runtime lifecycle only: `/api/runtime/*`. | -| `backend.apps.trading_service` | 8001 | Read-only trading data: prices, financials, insider trades, market status, market cap. | -| `backend.apps.news_service` | 8002 | Read-only explain/news data: enriched news, categories, story, similar days, range explain. | +| `backend.apps.agent_service` | `8000` | Control plane for workspaces, agents, skills, guard/approvals | +| `backend.apps.trading_service` | `8001` | Read-only trading data APIs such as prices, financials, insider trades | +| `backend.apps.news_service` | `8002` | Read-only explain/news APIs such as story, similar days, range explain | +| `backend.apps.runtime_service` | `8003` | Runtime lifecycle APIs under `/api/runtime/*` | +| `backend.apps.openclaw_service` | `8004` | Read-only OpenClaw REST facade | +| Gateway (`backend.main`) | `8765` | WebSocket feed, runtime event stream, legacy/compat orchestration path | +| OpenClaw Gateway | `18789` | External OpenClaw WebSocket endpoint consumed by EvoTraders gateway | -## Local Development Modes +## What Runs By Default In Dev -### 1. Split-service mode - -This is now the default development mode. +The supported local dev path is: ```bash ./start-dev.sh - -# explicit -./start-dev.sh split ``` -Run dedicated service surfaces explicitly: +That script starts: + +- `agent_service` on `8000` +- `trading_service` on `8001` +- `news_service` on `8002` +- `runtime_service` on `8003` +- EvoTraders gateway on `8765` + +It does **not** start `openclaw_service` on `8004`. + +Instead, the gateway expects an OpenClaw WebSocket server to already be +available at `ws://localhost:18789` unless you override the OpenClaw gateway +configuration outside the script. + +## Manual Startup + +Run split service surfaces explicitly: ```bash -python -m uvicorn backend.apps.agent_service:app --port 8000 --reload -python -m uvicorn backend.apps.runtime_service:app --port 8003 --reload -python -m uvicorn backend.apps.trading_service:app --port 8001 --reload -python -m uvicorn backend.apps.news_service:app --port 8002 --reload +python -m uvicorn backend.apps.agent_service:app --host 0.0.0.0 --port 8000 --reload +python -m uvicorn backend.apps.trading_service:app --host 0.0.0.0 --port 8001 --reload +python -m uvicorn backend.apps.news_service:app --host 0.0.0.0 --port 8002 --reload +python -m uvicorn backend.apps.runtime_service:app --host 0.0.0.0 --port 8003 --reload +python -m backend.main --mode live --host 0.0.0.0 --port 8765 ``` -## Migration Variables +Optional OpenClaw REST surface: -These env vars control whether the app still uses local-module fallbacks or -prefers service boundaries: +```bash +python -m uvicorn backend.apps.openclaw_service:app --host 0.0.0.0 --port 8004 --reload +``` -| Variable | Used by | Purpose | +## Runtime Responsibilities + +The runtime path is intentionally split: + +- `runtime_service` handles start, stop, restart, current runtime info, logs, and runtime state APIs +- `agent_service` handles control-plane reads and writes for agents, workspaces, files, and approvals +- `backend.main` / gateway hosts the live WebSocket channel and coordinates market service, scheduler, and pipeline execution + +The practical request path looks like: + +`frontend -> runtime_service/control APIs -> gateway/runtime manager -> market service + pipeline + storage` + +## Environment Variables + +### Backend routing preferences + +These variables let the gateway or tools prefer split services over in-process fallbacks: + +| Variable | Used by | Meaning | | --- | --- | --- | -| `NEWS_SERVICE_URL` | backend Gateway | Prefer `news-service` for explain/news read paths | -| `TRADING_SERVICE_URL` | backend Gateway | Prefer `trading-service` for trading read paths | -| `RUNTIME_SERVICE_URL` | reserved | Future runtime/control-plane split follow-up | -| `VITE_NEWS_SERVICE_URL` | frontend | Direct browser calls to `news-service` for selected explain paths | -| `VITE_TRADING_SERVICE_URL` | frontend | Reserved for future direct trading reads | +| `TRADING_SERVICE_URL` | gateway, data tools | Prefer `trading_service` for trading reads | +| `NEWS_SERVICE_URL` | gateway, data tools | Prefer `news_service` for explain/news reads | +| `RUNTIME_SERVICE_URL` | dev scripts / future follow-up | Reserved for runtime-service-aware flows | +| `OPENCLAW_SERVICE_URL` | dev scripts / future follow-up | Points at the OpenClaw gateway origin in current dev setup | -If these are empty, the repo keeps using local module fallbacks where they still exist. +Current `start-dev.sh` defaults: -## Current Internal Direction +```bash +TRADING_SERVICE_URL=http://localhost:8001 +NEWS_SERVICE_URL=http://localhost:8002 +RUNTIME_SERVICE_URL=http://localhost:8003 +OPENCLAW_SERVICE_URL=http://localhost:18789 +``` -The repository is now organized around split service surfaces: +Note that `OPENCLAW_SERVICE_URL` currently points at the OpenClaw gateway origin used by the live WebSocket bridge, not the optional REST app on `:8004`. + +### Frontend service targets + +The frontend can directly call split services with: + +```bash +VITE_CONTROL_API_BASE_URL=http://localhost:8000/api +VITE_RUNTIME_API_BASE_URL=http://localhost:8003/api/runtime +VITE_NEWS_SERVICE_URL=http://localhost:8002 +VITE_TRADING_SERVICE_URL=http://localhost:8001 +VITE_WS_URL=ws://localhost:8765 +``` + +## Current Frontend Direct-Call Coverage + +Direct browser calls currently cover: + +- runtime panel loading and runtime discovery +- story +- similar days +- range explain +- news for date +- news categories +- selected trading reads such as stock history and insider trades + +Other flows still depend on the gateway WebSocket and control plane APIs. + +## OpenClaw Integration Notes + +There are two separate OpenClaw integration surfaces in this repo: + +- OpenClaw WebSocket gateway on `:18789` + - used directly by `backend/services/gateway.py` + - this is what `start-dev.sh` assumes exists +- `backend.apps.openclaw_service` on `:8004` + - optional REST facade over OpenClaw CLI-backed reads + - useful for typed client access and service-level testing + +Do not treat those as interchangeable in docs or deployment config. + +## Internal Module Direction + +The codebase is now organized around these boundaries: ```text frontend - ├─ runtime/control/news/trading split endpoints - └─ selective per-request fallbacks where still retained + ├─ runtime/control/news/trading API clients + └─ WebSocket runtime feed backend.apps.agent_service └─ control-plane routes backend.apps.runtime_service - └─ runtime lifecycle + gateway discovery + └─ runtime lifecycle routes backend.apps.trading_service └─ read-only trading contract backend.apps.news_service └─ read-only explain/news contract + +backend.apps.openclaw_service + └─ optional OpenClaw REST facade + +backend.main / backend.services.gateway + └─ live orchestration, feed transport, scheduler, runtime coordination ``` diff --git a/shared/client/__init__.py b/shared/client/__init__.py index ae4f920..4836fa8 100644 --- a/shared/client/__init__.py +++ b/shared/client/__init__.py @@ -2,13 +2,15 @@ """Shared client package.""" from shared.client.control_client import ControlPlaneClient -from shared.client.trading_client import TradingServiceClient from shared.client.news_client import NewsServiceClient +from shared.client.openclaw_client import OpenClawServiceClient from shared.client.runtime_client import RuntimeServiceClient +from shared.client.trading_client import TradingServiceClient __all__ = [ "ControlPlaneClient", "RuntimeServiceClient", "TradingServiceClient", "NewsServiceClient", + "OpenClawServiceClient", ] diff --git a/start-dev.sh b/start-dev.sh index 5123e3a..7043d01 100755 --- a/start-dev.sh +++ b/start-dev.sh @@ -1,8 +1,8 @@ -#!/bin/bash +#!/usr/bin/env bash # EvoTraders Development Startup Script # Split-service mode only -set -e +set -euo pipefail echo "==========================================" echo "EvoTraders Development Environment" @@ -14,24 +14,84 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color -# Check virtual environment -if [ -z "$VIRTUAL_ENV" ]; then - echo -e "${YELLOW}Warning: Virtual environment not activated${NC}" - echo "Activating .venv..." - source .venv/bin/activate -fi +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" -# Load environment variables -if [ -f .env ]; then - echo -e "${GREEN}Loading environment from .env${NC}" - export $(grep -v '^#' .env | xargs) -else - echo -e "${YELLOW}Warning: .env file not found${NC}" -fi - -cd /Users/cillin/workspeace/evotraders PIDS=() +require_command() { + local command_name="$1" + if ! command -v "${command_name}" >/dev/null 2>&1; then + echo -e "${RED}Missing required command: ${command_name}${NC}" + exit 1 + fi +} + +check_python_module() { + local module_name="$1" + if ! python -c "import ${module_name}" >/dev/null 2>&1; then + echo -e "${RED}Missing required Python module: ${module_name}${NC}" + echo "Install dependencies with one of:" + echo " pip install -r requirements.txt" + echo " pip install -r requirements-dev.txt" + echo " uv pip install -e '.[dev]'" + exit 1 + fi +} + +load_env_file() { + if [ -f .env ]; then + echo -e "${GREEN}Loading environment from .env${NC}" + set -a + source .env + set +a + else + echo -e "${YELLOW}Warning: .env file not found. Copy env.template to .env first if you need live credentials.${NC}" + fi +} + +check_env_var() { + local var_name="$1" + local severity="${2:-warn}" + local value="${!var_name:-}" + if [ -z "${value}" ]; then + if [ "${severity}" = "error" ]; then + echo -e "${RED}Missing required environment variable: ${var_name}${NC}" + exit 1 + fi + echo -e "${YELLOW}Warning: ${var_name} is not set${NC}" + fi +} + +check_openclaw_gateway() { + local target_host="127.0.0.1" + local target_port="18789" + if python - </dev/null 2>&1 +import socket +sock = socket.socket() +sock.settimeout(1.0) +sock.connect(("${target_host}", ${target_port})) +sock.close() +PY + then + echo -e "${GREEN}OpenClaw gateway reachable at ws://${target_host}:${target_port}${NC}" + else + echo -e "${YELLOW}Warning: OpenClaw gateway is not reachable at ws://${target_host}:${target_port}${NC}" + echo " OpenClaw panel features may be unavailable until it is started." + fi +} + +print_prereq_help() { + echo "Environment checks:" + echo " - repo root: ${SCRIPT_DIR}" + echo " - python: $(command -v python)" + if [ -n "${VIRTUAL_ENV:-}" ]; then + echo " - virtualenv: ${VIRTUAL_ENV}" + else + echo " - virtualenv: not activated" + fi +} + start_service() { local name="$1" local app_path="$2" @@ -74,16 +134,56 @@ if [ $# -gt 0 ]; then echo "Split-service mode is now the only supported development mode." fi +require_command python +require_command lsof + +if [ -z "${VIRTUAL_ENV:-}" ]; then + if [ -f ".venv/bin/activate" ]; then + echo -e "${YELLOW}Virtual environment not activated; auto-activating .venv${NC}" + # shellcheck disable=SC1091 + source .venv/bin/activate + else + echo -e "${YELLOW}Warning: no active virtual environment and .venv not found${NC}" + fi +fi + +load_env_file + +print_prereq_help + +python - <<'PY' +import sys +if sys.version_info < (3, 9): + raise SystemExit("Python 3.9+ is required") +print(f"Python version OK: {sys.version.split()[0]}") +PY + +check_python_module fastapi +check_python_module uvicorn +check_python_module websockets +check_python_module yaml +check_python_module dotenv + +check_env_var OPENAI_API_KEY +check_env_var FINNHUB_API_KEY +check_env_var FIN_DATA_SOURCE + +if ! command -v npm >/dev/null 2>&1; then + echo -e "${YELLOW}Warning: npm is not installed. Frontend startup via 'evotraders frontend' will not work.${NC}" +fi + export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:8001}" export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:8002}" export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:8003}" export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:18789}" +check_openclaw_gateway + echo "" echo -e "${GREEN}Starting EvoTraders split services (default mode)...${NC}" echo " agent_service: http://localhost:8000" echo " runtime_service: http://localhost:8003" -echo " openclaw_gateway: ws://localhost:18789" +echo " openclaw_gateway: ws://localhost:18789" echo " trading_service: http://localhost:8001" echo " news_service: http://localhost:8002" echo "" diff --git a/test_openclaw_ws.py b/test_openclaw_ws.py new file mode 100644 index 0000000..a33e638 --- /dev/null +++ b/test_openclaw_ws.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Quick test script for OpenClaw WebSocket client.""" + +import asyncio +import sys +sys.path.insert(0, '.') + +from shared.client.openclaw_websocket_client import ( + OpenClawWebSocketClient, + DEFAULT_GATEWAY_URL, +) + + +async def test_connection(): + """Test basic connection to OpenClaw Gateway.""" + print(f"Connecting to {DEFAULT_GATEWAY_URL}...") + + client = OpenClawWebSocketClient( + url=DEFAULT_GATEWAY_URL, + client_name="cli", + client_version="1.0.0", + gateway_token="d4b2d831b8a177b5cac07e781f438af3840bd0dfaca630ee", + ) + + try: + hello = await client.connect() + print(f"✓ Connected!") + print(f" Protocol version: {hello.protocol}") + print(f" Server version: {hello.server_version}") + print(f" Connection ID: {hello.conn_id}") + + # List sessions + print("\nListing sessions...") + sessions = await client.list_sessions(limit=5) + print(f" Found {len(sessions)} sessions") + for session in sessions[:3]: + print(f" - {session.get('key', 'unknown')}") + + # List agents + print("\nListing agents...") + agents = await client.list_agents() + print(f" Found {len(agents)} agents") + for agent in agents[:3]: + print(f" - {agent.get('id', 'unknown')}: {agent.get('name', 'unknown')}") + + # Send a message to agent + if sessions: + session_key = sessions[0].get('key') + if session_key: + print(f"\nSending message to session: {session_key}") + result = await client.send_message(session_key, "Hello! This is a test message from Python.") + print(f" Message sent: {result}") + + await client.disconnect() + print("\n✓ All tests passed!") + return True + + except Exception as e: + print(f"✗ Error: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + success = asyncio.run(test_connection()) + sys.exit(0 if success else 1)