Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copy to .env and fill in your keys:
# cp .env.example .env

# LLM API Keys — 变量名须与 config.yaml 中的 base_url 对应:
# DeepSeek -> DEEPSEEK_API_KEY
# Kimi Code -> KIMI_API_KEY, base_url=https://api.kimi.com/coding/v1
# Kimi Open Platform -> MOONSHOT_API_KEY, base_url=https://api.moonshot.ai/v1
# StepFun -> STEPFUN_API_KEY
# 通用(任意 OpenAI 兼容接口)-> LLM_API_KEY
DEEPSEEK_API_KEY=
KIMI_API_KEY=
MOONSHOT_API_KEY=
STEPFUN_API_KEY=
OPENAI_API_KEY=
LLM_API_KEY=
GEMINI_API_KEY=
GOOGLE_API_KEY=

# Optional LLM base URLs
# DEEPSEEK_BASE_URL=https://api.deepseek.com
# STEPFUN_BASE_URL=https://api.stepfun.com/v1
# OPENAI_BASE_URL=https://api.openai.com/v1

# ==================================================
# Data source keys (optional)
# ==================================================
# TUSHARE_KEY=
# BOCHA_KEY=
# SERP_KEY=
# FMP_KEY=
# FINNHUB_KEY=

# Market: CN-Stock | US-Stock
# CONTEST_TRADE_MARKET=CN-Stock
18 changes: 17 additions & 1 deletion cli/main.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
"""
ContestTrade: 基于内部竞赛机制的Multi-Agent交易系统
"""
from pathlib import Path


def _bootstrap_dotenv() -> None:
"""Load .env before any config/LLM imports."""
env_path = Path(__file__).resolve().parent.parent / ".env"
if not env_path.is_file():
return
try:
from dotenv import load_dotenv
load_dotenv(env_path, override=False)
except ImportError:
pass


_bootstrap_dotenv()

import asyncio
import sys
import json
import re
import os
from pathlib import Path
from typing import Optional, Dict
from datetime import datetime
from collections import deque
Expand Down
48 changes: 42 additions & 6 deletions cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,52 @@ def validate_tushare_connection():
def validate_llm_connection():
"""验证LLM连接"""
try:
# Import LLM model when needed
from contest_trade.models.llm_model import GLOBAL_LLM

from contest_trade.models.llm_model import GLOBAL_LLM, GLOBAL_LLM_CONFIG
from contest_trade.models.provider_auth import (
format_missing_key_error,
get_env_config_hints,
get_env_var_candidates,
resolve_api_key,
)

console.print("🔍 [cyan]正在验证LLM配置...[/cyan]")
console.print(
f" Provider: {GLOBAL_LLM_CONFIG.provider} | "
f"Model: Kimi K3 ({GLOBAL_LLM_CONFIG.model_name}) | "
f"Base URL: {GLOBAL_LLM_CONFIG.base_url or '—'}"
)

resolved_key = resolve_api_key(
GLOBAL_LLM_CONFIG.provider,
config_key=GLOBAL_LLM_CONFIG.api_key,
base_url=GLOBAL_LLM_CONFIG.base_url,
)
if not resolved_key:
env_vars = get_env_var_candidates(
GLOBAL_LLM_CONFIG.provider,
GLOBAL_LLM_CONFIG.base_url,
)
console.print(
f"❌ [red]{format_missing_key_error(GLOBAL_LLM_CONFIG.provider, GLOBAL_LLM_CONFIG.base_url)}[/red]"
)
console.print(f" 请在 .env 中设置: {', '.join(env_vars[:3])}")
for hint in get_env_config_hints(
GLOBAL_LLM_CONFIG.provider,
GLOBAL_LLM_CONFIG.base_url,
):
console.print(f" ⚠️ [yellow]{hint}[/yellow]")
return False

test_messages = [
{"role": "user", "content": "请回复'连接测试成功',不要添加任何其他内容。"}
]
result = GLOBAL_LLM.run(test_messages, max_tokens=1, temperature=0.1, max_retries=0)
if result and hasattr(result, 'content') and result.content:
console.print(f"✅ [green]LLM连接成功[/green] - 模型: {GLOBAL_LLM.model_name}")
result = GLOBAL_LLM.run(test_messages, max_tokens=32, temperature=1, max_retries=0)
has_content = result and (
(hasattr(result, 'content') and result.content)
or (hasattr(result, 'reasoning_content') and result.reasoning_content)
)
if has_content:
console.print(f"✅ [green]LLM连接成功[/green] - 模型: Kimi K3 ({GLOBAL_LLM.model_name})")
return True
else:
console.print("❌ [red]LLM连接失败 - 无响应内容[/red]")
Expand Down
31 changes: 22 additions & 9 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,28 @@ finnhub_key: ""
#
# 配置示例 / Configuration examples:
#
# 也可通过 .env 或环境变量设置 API Key(优先级低于 config.yaml):
# 1. 复制 .env.example 为 .env 并填入密钥
# 2. DeepSeek -> DEEPSEEK_API_KEY
# 3. StepFun -> STEPFUN_API_KEY
# 4. OpenAI -> OPENAI_API_KEY
# 5. 通用 -> LLM_API_KEY
#
# OpenAI:
# provider: "openai"
# base_url: "https://api.openai.com/v1"
# api_key: "your_openai_api_key"
# model_name: "gpt-4"
#
# Kimi Code (OpenAI compatible):
# provider: "openai"
# base_url: "https://api.kimi.com/coding/v1"
# api_key: null # 在 .env 中设置 KIMI_API_KEY(Kimi Code 控制台密钥)
# model_name: "kimi-k3" # API model id;显示名称: Kimi K3
#
# Kimi Open Platform:
# base_url: "https://api.moonshot.ai/v1"
# model_name: "kimi-k3"
# DeepSeek (OpenAI compatible):
# provider: "openai"
# base_url: "https://api.deepseek.com"
Expand Down Expand Up @@ -57,23 +73,20 @@ finnhub_key: ""
llm:
provider: "openai"
base_url: "https://api.deepseek.com"
#YOUR_DEEPSEEK_API_KEY
api_key: null
api_key: null
model_name: "deepseek-chat"

llm_thinking:
provider: "openai"
base_url: "https://api.deepseek.com"
#YOUR_DEEPSEEK_API_KEY
api_key: null
api_key: null
model_name: "deepseek-reasoner"

vlm:
provider: "openai"
base_url: "https://api.stepfun.com/v1"
#YOUR_STEPFUN_API_KEY
api_key: null
model_name: "step-1o-turbo-vision"
base_url: "https://api.kimi.com/coding/v1"
api_key: null
model_name: "kimi-k3"


data_agents_config:
Expand All @@ -92,7 +105,7 @@ data_agents_config:

research_agent_config:
belief_list_path: "config/belief_list.json"
max_react_step: 10
max_react_step: 5
output_language: "中文"
tools:
- "tools.stock_symbol_search_akshare.stock_symbol_search"
Expand Down
4 changes: 3 additions & 1 deletion config_us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ finnhub_key: ""
#YOUR_ALPHA_VANTAGE_KEY
alpha_vantage_key: ""

# YOUR_POLYGON_KEY
#YOUR_POLYGON_KEY
polygon_key: ""

# API keys: set in config.yaml, or copy .env.example to .env (see DEEPSEEK_API_KEY, STEPFUN_API_KEY, LLM_API_KEY)

llm:
provider: "openai"
base_url: "https://api.deepseek.com"
Expand Down
22 changes: 21 additions & 1 deletion contest_trade/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
import os

PROJECT_ROOT = Path(__file__).parent.parent.resolve()
REPO_ROOT = PROJECT_ROOT.parent


def _load_dotenv() -> None:
"""
Load .env from project root. Existing shell env vars take precedence.

@generated AI Assistant - 2026-08-04 11:42:00
"""
env_path = REPO_ROOT / ".env"
if not env_path.is_file():
return
try:
from dotenv import load_dotenv
load_dotenv(env_path, override=False)
except ImportError:
print(f"Warning: .env found at {env_path} but python-dotenv is not installed.")


_load_dotenv()


class ProjectConfig:
Expand All @@ -20,7 +40,7 @@ def __init__(self) -> None:
else:
config_filename = "config.yaml"

yaml_path = PROJECT_ROOT.parent / config_filename
yaml_path = REPO_ROOT / config_filename
print(f"Loading config from: {yaml_path} (Market: {market_type})")

with open(yaml_path, "r", encoding="utf-8") as fr:
Expand Down
14 changes: 11 additions & 3 deletions contest_trade/contest/researcher/research_contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
sys.path.append(str(PROJECT_ROOT))

from models.llm_model import GLOBAL_LLM
from models.provider_auth import resolve_api_key, resolve_base_url, resolve_chat_completions_url
from utils.market_manager import GLOBAL_MARKET_MANAGER
from config.config import cfg
from research_contest_types import SignalData, ResearchContestResult
Expand Down Expand Up @@ -316,10 +317,17 @@ async def _get_current_judge_scores(self, current_signals: Dict[str, SignalData]
"""获取当天信号的judge评分"""
logger.info(f"获取当天信号judge评分 - {len(current_signals)} 个信号")

llm_section = cfg.llm if isinstance(cfg.llm, dict) else vars(cfg.llm)
provider = llm_section.get("provider", "openai")
base_url = resolve_base_url(provider, llm_section.get("base_url"))
llm_config = {
"api_key": cfg.llm.api_key,
"api_base": cfg.llm.api_base,
"model_name": cfg.llm.model_name
"api_key": resolve_api_key(
provider,
config_key=llm_section.get("api_key"),
base_url=base_url,
),
"api_base": resolve_chat_completions_url(base_url),
"model_name": llm_section.get("model_name"),
}

judge_scores = await self.signal_judger.judge_signals(
Expand Down
4 changes: 2 additions & 2 deletions contest_trade/data_source/sina_news_crawl.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


class SinaNewsCrawl(DataSourceBase):
def __init__(self, start_page=1, end_page=50):
def __init__(self, start_page=1, end_page=20):
super().__init__("sina_news_crawl")
self.start_page = start_page
self.end_page = end_page
Expand All @@ -35,7 +35,7 @@ def __init__(self, start_page=1, end_page=50):
}
self.all_items = []
self.fetch_full_intro = True # 是否抓取文章页以补全 intro
self.article_concurrency = 2 # 控制抓取文章页的并发
self.article_concurrency = 10 # 控制抓取文章页的并发

async def fetch_page(self, session, page):
"""异步获取单个页面的数据"""
Expand Down
Loading