前言: 想要拥有一个像华尔街专业分析师一样的 AI 助手吗?它能帮你全天候盯盘、分析财报、评估风险,甚至直接给出操作建议。 哪怕你没有任何编程基础,跟着这篇教程走,30 分钟内你也能拥有一套完全属于自己的 AI 投研系统!

🚀 为什么选择 TradingAgents-CN?

市面上的 AI 很多,但能深度整合金融数据的很少。TradingAgents-CN 是专为中国市场优化的版本,不仅能接入 GPT-5.2、Claude Opus 4.5 等顶尖大脑,还能直接读取 A 股、美股的实时行情和财报数据进行分析。

最重要的是:数据掌握在自己手里,安全、私密、可定制。

🛠️ 第一阶段:准备工作

1. 准备一台服务器 (VPS)

对于新手,我们推荐使用 Linux 服务器。由于我们要使用 LLM Hub 解决网络问题,所以你从阿里云、腾讯云购买的国内服务器完全可以使用!

  • 推荐配置
    • CPU: 2核 及以上
    • 内存: 4GB 及以上 (推荐 8GB,运行更流畅)
    • 系统: Ubuntu 22.04 LTS (最稳、坑最少)
    • 带宽: 3M 及以上

2. 准备 AI“大脑”密钥 (LLM Hub)

想要使用 GPT-5.2、Claude Opus 4.5、Gemini 3 Pro 这些顶尖模型,通常需要去各模型官网分别注册账号,不仅繁琐,还面临网络访问和封号难题。

本教程使用 LLM Hub 聚合平台来解决这些问题。它最大的优势是:

  • 一站式接入:一个账号就能同时使用 OpenAI、Claude、Gemini 以及国内 Qwen、DeepSeek 等主流大模型。
  • 免去繁琐注册:不需要你去申请 OpenAI 账号或绑国外信用卡,直接使用 LLM Hub 提供的密钥即可。
  • 稳定高速:专为国内开发者优化,无需魔法上网,支持支付宝支付。
  1. 访问 LLM Hub 官网 注册账号。
  2. 在后台创建令牌 (Token),复制 sk- 开头的密钥。

3. 安装服务器管家 (1Panel)

登录服务器终端(SSH),执行以下命令安装 1Panel:

# 1. 更新系统软件包
sudo apt update && sudo apt upgrade -y
# 2. 安装 1Panel 面板
bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"

安装完成后,登录 1Panel 面板,你就会拥有一个可视化的管理后台。

🏗️ 第二阶段:安装基础软件 (数据库)

为了让系统更稳定,我们通过 1Panel 的应用商店来独立部署数据库。

1. 安装 Redis

  1. 进入 1Panel 应用商店,搜索 Redis
  2. 点击安装。
  3. 关键设置
  • 版本:选最新的 8.x。
  • 容器名称:1panel-redis,稍后要用。
  • 密码:1Panel 自动生成密码,务必记下来

2. 安装 MongoDB

  1. 应用商店 搜索 MongoDB
  2. 点击安装。
  3. 关键设置
  • 版本:选最新的 8.x。
  • 容器名称:1panel-mongodb,稍后要用。
  • Root 用户名与密码:1Panel 自动生成用户名和密码,务必记下来

⚡️ 第三阶段:部署 TradingAgents-CN

1. 创建项目目录

在 1Panel 的【主机】->【文件】管理中,逐级进入目录:/opt/1panel/docker/compose。 【创建文件夹】,命名为 tradingagents-cn,并进入该目录。

2. 创建核心配置文件

tradingagents-cn 目录下,我们需要手动创建两个子文件夹和几个文件。

步骤 2.1:创建 Nginx 配置

  1. 新建文件夹:nginx
  2. 进入 nginx 文件夹,新建文件:nginx.conf
  3. 填入以下内容:
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://frontend:80;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /api/ {
            proxy_pass http://backend:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

步骤 2.2:创建初始化脚本

  1. 回到 tradingagents-cn 根目录。
  2. 新建文件夹:scripts
  3. 进入 scripts 文件夹,新建文件:import_config_and_create_user.py
  4. 填入以下内容:
#!/usr/bin/env python3
"""
导入配置数据并创建默认用户

功能:
1. 从导出的 JSON 文件导入配置数据到 MongoDB
2. 创建默认管理员用户(admin/admin123)
3. 支持选择性导入集合
4. 支持覆盖或跳过已存在的数据

使用方法:
    python scripts/import_config_and_create_user.py <export_file.json>
    python scripts/import_config_and_create_user.py <export_file.json> --overwrite
    python scripts/import_config_and_create_user.py <export_file.json> --collections system_configs users
"""

import json
import sys
import hashlib
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
import argparse
import os

# 添加项目根目录到路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

from pymongo import MongoClient
from bson import ObjectId


def load_env_config(script_dir: Path) -> dict:
    """从 .env 文件加载配置

    Args:
        script_dir: 脚本所在目录

    Returns:
        配置字典,包含 mongodb_port 等
    """
    # 查找 .env 文件(在项目根目录)
    env_file = script_dir.parent / '.env'

    # 优先从系统环境变量获取(适配 Docker 环境)
    config = {
        'mongodb_port': int(os.environ.get('MONGODB_PORT', 27017)),
        'mongodb_host': os.environ.get('MONGODB_HOST', 'localhost'),
        'mongodb_username': os.environ.get('MONGODB_USERNAME', 'admin'),
        'mongodb_password': os.environ.get('MONGODB_PASSWORD', 'tradingagents123'),
        'mongodb_database': os.environ.get('MONGODB_DATABASE', 'tradingagents')
    }

    # 如果系统环境变量中没有设置(例如本地开发),则尝试读取 .env 文件覆盖
    # 注意:这里逻辑改为“只有当环境变量未设置时才从文件读”或者“文件作为补充”
    # 但为了简单且安全,我们保持“系统环境变量优先”。
    # 如果脚本在容器外运行且没有设置环境变量,下面的 .env 读取逻辑会生效(覆盖默认值,但不覆盖已有的os.environ值)
    
    if env_file.exists():
        try:
            file_config = {}
            with open(env_file, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith('#'):
                        continue
                    if '=' in line:
                        key, value = line.split('=', 1)
                        key = key.strip()
                        value = value.strip()
                        file_config[key] = value

            # 仅当 config 中的值为默认值(或空)时,才使用文件中的值
            # 或者,更简单的逻辑:如果 os.environ 没取到(即使用了默认值),则尝试用文件值
            # 但由于 os.environ.get 已经给了默认值,这里我们做个判断:
            
            if 'MONGODB_PORT' not in os.environ and 'MONGODB_PORT' in file_config:
                config['mongodb_port'] = int(file_config['MONGODB_PORT'])
            
            # 对其他字段同理,只有环境变量未设置时才采纳文件配置
            if 'MONGODB_HOST' not in os.environ and 'MONGODB_HOST' in file_config:
                config['mongodb_host'] = file_config['MONGODB_HOST']
            if 'MONGODB_USERNAME' not in os.environ and 'MONGODB_USERNAME' in file_config:
                config['mongodb_username'] = file_config['MONGODB_USERNAME']
            if 'MONGODB_PASSWORD' not in os.environ and 'MONGODB_PASSWORD' in file_config:
                config['mongodb_password'] = file_config['MONGODB_PASSWORD']
             
        except Exception as e:
            print(f"⚠️  警告: 读取 .env 文件失败: {e}")
    else:
        # 仅在非Docker环境且无env文件时提示警告
        if not os.environ.get("DOCKER_CONTAINER"):
             print(f"⚠️  警告: .env 文件不存在: {env_file}")

    return config


# MongoDB 连接配置
# Docker 内部运行时使用服务名 "mongodb"
# 宿主机运行时使用 "localhost"
DB_NAME = "tradingagents"

# 默认管理员用户
DEFAULT_ADMIN = {
    "username": "admin",
    "password": "admin123",
    "email": "admin@tradingagents.cn"
}

# 配置集合列表
CONFIG_COLLECTIONS = [
    "system_configs",
    "users",
    "llm_providers",
    "market_categories",
    "user_tags",
    "datasource_groupings",
    "platform_configs",
    "user_configs",
    "model_catalog"
]


def hash_password(password: str) -> str:
    """使用 SHA256 哈希密码(与系统一致)"""
    return hashlib.sha256(password.encode()).hexdigest()


def convert_to_bson(data: Any) -> Any:
    """将 JSON 数据转换为 BSON 兼容格式"""
    if isinstance(data, dict):
        result = {}
        for key, value in data.items():
            # 处理 ObjectId
            if key == "_id" or key.endswith("_id"):
                if isinstance(value, str) and len(value) == 24:
                    try:
                        result[key] = ObjectId(value)
                        continue
                    except:
                        pass
            
            # 处理日期时间
            if key.endswith("_at") or key in ["created_at", "updated_at", "last_login", "added_at"]:
                if isinstance(value, str):
                    try:
                        result[key] = datetime.fromisoformat(value.replace('Z', '+00:00'))
                        continue
                    except:
                        pass
            
            result[key] = convert_to_bson(value)
        return result
    
    elif isinstance(data, list):
        return [convert_to_bson(item) for item in data]
    
    else:
        return data


def load_export_file(file_path: str) -> Dict[str, Any]:
    """加载导出的 JSON 文件"""
    print(f"\n📂 加载导出文件: {file_path}")
    
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        if "export_info" not in data or "data" not in data:
            print("❌ 错误: 文件格式不正确,缺少 export_info 或 data 字段")
            sys.exit(1)
        
        export_info = data["export_info"]
        print(f"✅ 文件加载成功")
        print(f"   导出时间: {export_info.get('created_at', 'Unknown')}")
        print(f"   导出格式: {export_info.get('format', 'Unknown')}")
        print(f"   集合数量: {len(export_info.get('collections', []))}")
        
        return data
    
    except FileNotFoundError:
        print(f"❌ 错误: 文件不存在: {file_path}")
        sys.exit(1)
    except json.JSONDecodeError as e:
        print(f"❌ 错误: JSON 解析失败: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"❌ 错误: 加载文件失败: {e}")
        sys.exit(1)


def connect_mongodb(use_docker: bool = True, config: dict = None) -> MongoClient:
    """连接到 MongoDB

    Args:
        use_docker: True=在 Docker 容器内运行(使用 mongodb 服务名)
                   False=在宿主机运行(使用 localhost)
        config: 配置字典,包含端口等信息
    """
    if config is None:
        config = {
            'mongodb_port': 27017,
            'mongodb_host': 'localhost',
            'mongodb_username': 'admin',
            'mongodb_password': 'tradingagents123',
            'mongodb_database': 'tradingagents'
        }

    # 构建 MongoDB URI
    host = 'mongodb' if use_docker else config['mongodb_host']
    port = config['mongodb_port']
    username = config['mongodb_username']
    password = config['mongodb_password']
    database = config['mongodb_database']

    mongo_uri = f"mongodb://{username}:{password}@{host}:{port}/{database}?authSource=admin"
    env_name = "Docker 容器内" if use_docker else "宿主机"

    print(f"\n🔌 连接到 MongoDB ({env_name})...")
    print(f"   URI: mongodb://{username}:***@{host}:{port}/{database}?authSource=admin")

    try:
        client = MongoClient(mongo_uri, serverSelectionTimeoutMS=5000)
        # 测试连接
        client.admin.command('ping')
        print(f"✅ MongoDB 连接成功")
        return client

    except Exception as e:
        print(f"❌ 错误: MongoDB 连接失败: {e}")
        if use_docker:
            print(f"   请确保在 Docker 容器内运行,或使用 --host 参数在宿主机运行")
            print(f"   检查容器: docker ps | grep mongodb")
        else:
            print(f"   请确保 MongoDB 正在运行并监听端口 {port}")
            print(f"   检查端口: netstat -an | findstr {port}")
        sys.exit(1)


def import_collection(
    db: Any,
    collection_name: str,
    documents: List[Dict[str, Any]],
    overwrite: bool = False
) -> Dict[str, int]:
    """导入单个集合"""
    collection = db[collection_name]
    
    # 转换文档格式
    converted_docs = [convert_to_bson(doc) for doc in documents]
    
    if overwrite:
        # 覆盖模式:删除现有数据
        result = collection.delete_many({})
        deleted_count = result.deleted_count
        
        if converted_docs:
            result = collection.insert_many(converted_docs)
            inserted_count = len(result.inserted_ids)
        else:
            inserted_count = 0
        
        return {
            "deleted": deleted_count,
            "inserted": inserted_count,
            "skipped": 0
        }
    else:
        # 增量模式:跳过已存在的文档
        inserted_count = 0
        skipped_count = 0
        
        for doc in converted_docs:
            # 检查是否已存在(根据 _id 或 username)
            query = {}
            if "_id" in doc:
                query["_id"] = doc["_id"]
            elif "username" in doc:
                query["username"] = doc["username"]
            elif "name" in doc:
                query["name"] = doc["name"]
            else:
                # 没有唯一标识,直接插入
                collection.insert_one(doc)
                inserted_count += 1
                continue
            
            existing = collection.find_one(query)
            if existing:
                skipped_count += 1
            else:
                collection.insert_one(doc)
                inserted_count += 1
        
        return {
            "deleted": 0,
            "inserted": inserted_count,
            "skipped": skipped_count
        }


def create_default_admin(db: Any, overwrite: bool = False) -> bool:
    """创建默认管理员用户"""
    print(f"\n👤 创建默认管理员用户...")
    
    users_collection = db.users
    
    # 检查用户是否已存在
    existing_user = users_collection.find_one({"username": DEFAULT_ADMIN["username"]})
    
    if existing_user:
        if not overwrite:
            print(f"⚠️  用户 '{DEFAULT_ADMIN['username']}' 已存在,跳过创建")
            return False
        else:
            print(f"⚠️  用户 '{DEFAULT_ADMIN['username']}' 已存在,将覆盖")
            users_collection.delete_one({"username": DEFAULT_ADMIN["username"]})
    
    # 创建用户文档
    user_doc = {
        "username": DEFAULT_ADMIN["username"],
        "email": DEFAULT_ADMIN["email"],
        "hashed_password": hash_password(DEFAULT_ADMIN["password"]),
        "is_active": True,
        "is_verified": True,
        "is_admin": True,
        "created_at": datetime.utcnow(),
        "updated_at": datetime.utcnow(),
        "last_login": None,
        "preferences": {
            "default_market": "A股",
            "default_depth": "深度",
            "ui_theme": "light",
            "language": "zh-CN",
            "notifications_enabled": True,
            "email_notifications": False
        },
        "daily_quota": 10000,
        "concurrent_limit": 10,
        "total_analyses": 0,
        "successful_analyses": 0,
        "failed_analyses": 0,
        "favorite_stocks": []
    }
    
    users_collection.insert_one(user_doc)
    
    print(f"✅ 默认管理员用户创建成功")
    print(f"   用户名: {DEFAULT_ADMIN['username']}")
    print(f"   密码: {DEFAULT_ADMIN['password']}")
    print(f"   邮箱: {DEFAULT_ADMIN['email']}")
    print(f"   角色: 管理员")
    
    return True


def ensure_new_providers(db: Any):
    """确保新加入的 Provider 存在于数据库中(兼容旧导出文件)"""
    print(f"\n✨ 检查并补充缺失的新 Provider...")
    
    providers_collection = db.llm_providers
    now = datetime.utcnow()
    
    # 新 Provider 定义列表
    new_providers = [
        {
            "name": "oneapi",
            "display_name": "LLM Hub",
            "description": "LLM Hub 一站式人工智能集成平台",
            "website": "https://www.llmhub.com.cn",
            "api_doc_url": "https://docs.llmhub.com.cn/",
            "default_base_url": "https://api.llmhub.com.cn/v1",
            "is_active": True,
            "supported_features": ["chat", "completion", "embedding", "image", "vision", "function_calling", "streaming"],
            "is_aggregator": True,
            "aggregator_type": "openai_compatible",
            "logo_url": "/assets/logos/oneapi.png"
        }
    ]
    
    added_count = 0
    
    for provider in new_providers:
        # 检查是否存在
        existing = providers_collection.find_one({"name": provider["name"]})
        
        if not existing:
            # 补全时间字段
            provider["created_at"] = now
            provider["updated_at"] = now
            provider["extra_config"] = {}
            
            # 插入
            providers_collection.insert_one(provider)
            print(f"   ➕ 已自动补全: {provider['display_name']} ({provider['name']})")
            added_count += 1
        else:
            # 可选:如果存在但不是聚合类型,更新它
            if not existing.get("is_aggregator"):
                providers_collection.update_one(
                    {"_id": existing["_id"]},
                    {"$set": {"is_aggregator": True, "aggregator_type": "openai_compatible"}}
                )
                print(f"   🔄 已更新以支持聚合模式: {provider['display_name']}")

    if added_count == 0:
        print("   ✅ 所有新 Provider 已存在,无需补充")
    else:
        print(f"   🎉 成功补全 {added_count} 个 Provider")

def main():
    """主函数"""
    parser = argparse.ArgumentParser(
        description="导入配置数据并创建默认用户",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  # 在 Docker 容器内运行(默认)
  python scripts/import_config_and_create_user.py

  # 在宿主机运行(连接到 localhost:27017)
  python scripts/import_config_and_create_user.py --host

  # 从指定文件导入(默认覆盖模式)
  python scripts/import_config_and_create_user.py export.json

  # 增量模式:跳过已存在的数据
  python scripts/import_config_and_create_user.py --incremental

  # 只导入指定的集合
  python scripts/import_config_and_create_user.py --collections system_configs users

  # 只创建默认用户,不导入数据
  python scripts/import_config_and_create_user.py --create-user-only
        """
    )

    parser.add_argument(
        "export_file",
        nargs="?",
        help="导出的 JSON 文件路径(默认:install/database_export_config_*.json)"
    )
    parser.add_argument(
        "--host",
        action="store_true",
        help="在宿主机运行(连接 localhost:27017),默认在 Docker 容器内运行(连接 mongodb:27017)"
    )
    parser.add_argument(
        "--overwrite",
        action="store_true",
        default=True,
        help="覆盖已存在的数据(默认:覆盖)"
    )
    parser.add_argument(
        "--incremental",
        action="store_true",
        help="增量模式:跳过已存在的数据"
    )
    parser.add_argument(
        "--collections",
        nargs="+",
        help="指定要导入的集合(默认:所有配置集合)"
    )
    parser.add_argument(
        "--create-user-only",
        action="store_true",
        help="只创建默认用户,不导入数据"
    )
    parser.add_argument(
        "--skip-user",
        action="store_true",
        help="跳过创建默认用户"
    )
    parser.add_argument(
        "--mongodb-port",
        type=int,
        help="MongoDB 端口(覆盖 .env 配置)"
    )
    parser.add_argument(
        "--mongodb-host",
        type=str,
        help="MongoDB 主机(覆盖 .env 配置)"
    )

    args = parser.parse_args()

    # 处理 incremental 参数(如果指定了 --incremental,则 overwrite 为 False)
    if args.incremental:
        args.overwrite = False

    # 如果没有指定文件,尝试从 install 目录查找
    if not args.create_user_only and not args.export_file:
        install_dir = project_root / "install"
        if install_dir.exists():
            # 查找 database_export_config_*.json 文件
            config_files = list(install_dir.glob("database_export_config_*.json"))
            if config_files:
                # 使用最新的文件
                args.export_file = str(sorted(config_files)[-1])
                print(f"💡 未指定文件,使用默认配置: {args.export_file}")
            else:
                parser.error("install 目录中未找到配置文件 (database_export_config_*.json)")
        else:
            parser.error("必须提供导出文件路径,或使用 --create-user-only")
    
    print("=" * 80)
    print("📦 导入配置数据并创建默认用户")
    print("=" * 80)

    # 加载 .env 配置
    script_dir = Path(__file__).parent
    env_config = load_env_config(script_dir)

    # 命令行参数覆盖 .env 配置
    if args.mongodb_port:
        env_config['mongodb_port'] = args.mongodb_port
        print(f"💡 使用命令行指定的 MongoDB 端口: {args.mongodb_port}")
    if args.mongodb_host:
        env_config['mongodb_host'] = args.mongodb_host
        print(f"💡 使用命令行指定的 MongoDB 主机: {args.mongodb_host}")

    # 连接数据库
    use_docker = not args.host  # 默认在 Docker 内运行,除非指定 --host
    client = connect_mongodb(use_docker=use_docker, config=env_config)
    db = client[DB_NAME]
    
    # 导入数据
    if not args.create_user_only:
        # 加载导出文件
        export_data = load_export_file(args.export_file)
        data = export_data["data"]
        
        # 确定要导入的集合
        if args.collections:
            collections_to_import = args.collections
        else:
            collections_to_import = [c for c in CONFIG_COLLECTIONS if c in data]
        
        print(f"\n📋 准备导入 {len(collections_to_import)} 个集合:")
        for col in collections_to_import:
            doc_count = len(data.get(col, []))
            print(f"   - {col}: {doc_count} 个文档")
        
        # 导入集合
        print(f"\n🚀 开始导入...")
        print(f"   模式: {'覆盖' if args.overwrite else '增量'}")
        
        total_stats = {
            "deleted": 0,
            "inserted": 0,
            "skipped": 0
        }
        
        for collection_name in collections_to_import:
            if collection_name not in data:
                print(f"⚠️  跳过 {collection_name}: 导出文件中不存在")
                continue
            
            documents = data[collection_name]
            print(f"\n   导入 {collection_name}...")
            
            try:
                stats = import_collection(db, collection_name, documents, args.overwrite)
                total_stats["deleted"] += stats["deleted"]
                total_stats["inserted"] += stats["inserted"]
                total_stats["skipped"] += stats["skipped"]
                
                if args.overwrite:
                    print(f"      ✅ 删除 {stats['deleted']} 个,插入 {stats['inserted']} 个")
                else:
                    print(f"      ✅ 插入 {stats['inserted']} 个,跳过 {stats['skipped']} 个")
            
            except Exception as e:
                print(f"      ❌ 失败: {e}")
        
        print(f"\n📊 导入统计:")
        if args.overwrite:
            print(f"   删除: {total_stats['deleted']} 个文档")
        print(f"   插入: {total_stats['inserted']} 个文档")
        if not args.overwrite:
            print(f"   跳过: {total_stats['skipped']} 个文档")
    
    # 创建默认用户
    if not args.skip_user:
        create_default_admin(db, args.overwrite)

    # 🟢 确保新加入的 Provider (OneAPI, NewAPI, 302.AI) 存在
    # 这是为了兼容旧的导出文件,防止新功能缺失
    ensure_new_providers(db)
    
    # 关闭连接
    client.close()
    
    print("\n" + "=" * 80)
    print("✅ 操作完成!")
    print("=" * 80)
    
    if not args.skip_user:
        print(f"\n🔐 登录信息:")
        print(f"   用户名: {DEFAULT_ADMIN['username']}")
        print(f"   密码: {DEFAULT_ADMIN['password']}")
    
    print(f"\n📝 后续步骤:")
    print(f"   1. 重启后端服务: docker restart tradingagents-backend")
    print(f"   2. 访问前端并使用默认账号登录")
    print(f"   3. 检查系统配置是否正确加载")


if __name__ == "__main__":
    main()

3. 配置连接 (最关键的一步!)

回到 tradingagents-cn 目录(即 /opt/1panel/docker/compose/tradingagents-cn),我们需要创建环境变量文件。

步骤 3.1:创建 .env 文件: 新建文件 .env,填入以下配置(请自行替换数据库信息):

# TradingAgents-CN 生产环境配置 (.env)
# 专为 1Panel 集成设计

# ==================== 1. 核心连接配置 ====================

# ⚠️ 注意:必须填写正确的容器名称 (1Panel 创建的容器通常有随机后缀)
# 请在 1Panel 容器列表或终端使用 `docker ps` 查看准确名称并填入下方

# MongoDB 配置 (外部)
MONGODB_ENABLED=true
# 填入 MongoDB 容器名称 (例如: 1panel-mongodb)
MONGODB_HOST=1Panel-mongodb
MONGODB_PORT=27017
MONGODB_USERNAME=mongo_zetJdx
MONGODB_PASSWORD=mongo_WHdXh8
MONGODB_DATABASE=tradingagents
MONGODB_AUTH_SOURCE=admin
# 连接字符串 (格式: mongodb://用户:密码@容器名:端口/数据库?authSource=admin)
# 请同步修改下方的容器名
MONGODB_CONNECTION_STRING=mongodb://mongo_zetJdx:mongo_WHdXh8@1Panel-mongodb:27017/tradingagents?authSource=admin

# Redis 配置 (外部)
REDIS_ENABLED=true
# 填入 Redis 容器名称 (例如: 1panel-redis)
REDIS_HOST=1Panel-redis
REDIS_PORT=6379
REDIS_PASSWORD=redis_3wPzP3
# 连接字符串 (格式: redis://:密码@容器名:6379/0)
REDIS_URL=redis://:redis_3wPzP3@1Panel-redis:6379/0

# ==================== 2. 应用端口 ====================

# 暴露给宿主机的端口,1Panel OpenResty 将反代此端口
APP_PORT=18000

# ==================== 3. 安全配置 (必须修改) ====================

# 生成新密钥: openssl rand -hex 32
JWT_SECRET=334a40eb30d222bee16de8ac34b6ad9af9f25cc3c7e1ed2df536401ea9f88446
CSRF_SECRET=a97e64c9678b6c3948dc0d18072753df55303592ca6aa65c6337dcc4d90638b1
ACCESS_TOKEN_EXPIRE_MINUTES=480

# ==================== 4. API 密钥 (按需填写) ====================

# DeepSeek (推荐)
DEEPSEEK_API_KEY=
DEEPSEEK_ENABLED=false

# DashScope (阿里百炼)
DASHSCOPE_API_KEY=
DASHSCOPE_ENABLED=false

# OpenAI
OPENAI_API_KEY=
OPENAI_ENABLED=false

# ==================== 5. 聚合渠道配置 (LLM Hub) ====================
ONEAPI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ONEAPI_BASE_URL=https://api.llmhub.com.cn/v1

# ==================== 6. 数据源配置 ====================
TUSHARE_TOKEN=
TUSHARE_ENABLED=false

# ==================== 6. 系统参数 ====================
TZ=Asia/Shanghai
TRADINGAGENTS_LOG_LEVEL=INFO
# 生产环境通常不需要这些
DEBUG=false
PYTHONDONTWRITEBYTECODE=1

步骤 3.2:创建.docker-compose.yml文件: 新建文件.docker-compose.yml,填入以下配置

services:
  # 后端服务
  backend:
    image: hsliup/tradingagents-backend:v1.0.0-preview
    container_name: tradingagents-backend
    restart: always
    expose:
      - "8000"
    volumes:
      - ./logs:/app/logs
      - ./data:/app/data
      # 挂载修改后的初始化脚本覆盖镜像内的版本
      - ./scripts/import_config_and_create_user.py:/app/scripts/import_config_and_create_user.py
    env_file:
      - .env
    networks:
      - 1panel-network
    deploy:
      resources:
        limits:
          memory: 2G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # 前端服务
  frontend:
    image: hsliup/tradingagents-frontend:v1.0.0-preview
    container_name: tradingagents-frontend
    restart: always
    expose:
      - "80"
    environment:
      # 前端只需知道它被代理在 /api 下访问后端,通常不需要改
      VITE_API_BASE_URL: "/api"
    networks:
      - 1panel-network
    healthcheck:
      test:
        [
          "CMD",
          "wget",
          "--quiet",
          "--tries=1",
          "--spider",
          "http://localhost:80",
        ]
      interval: 30s
      timeout: 10s
      retries: 3

  # 应用网关 (Nginx)
  nginx:
    image: nginx:alpine
    container_name: tradingagents-nginx
    restart: always
    ports:
      # 映射到宿主机端口,供 1Panel OpenResty 反代
      - "${APP_PORT:-18000}:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./logs/nginx:/var/log/nginx
    networks:
      - 1panel-network
    depends_on:
      - backend
      - frontend

networks:
  # 引用外部 1Panel 网络
  1panel-network:
    external: true

4. 启动应用

在终端执行:

# 进入 /opt/1panel/docker/compose/tradingagents-cn
cd /opt/1panel/docker/compose/tradingagents-cn
# 指定生产环境文件启动
docker compose --env-file .env up -d

(注意:容器会自动加入 1Panel 的内部网络,从而能连上刚才安装的 Redis 和 MongoDB)

等待日志显示 Done

5. 初始化数据

看到 ✅ 操作完成 即大功告成!

最后一步:防火墙放行端口

进入你的 VPS 防火墙设置放行 18000 端口。

访问地址为 http://111.222.333.444:18000,即可打开项目。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐