Date: Fri, 9 Jan 2026 17:07:40 +0800
Subject: [PATCH 7/9] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E6=A8=A1=E5=9E=8B=E5=8F=AA=E8=83=BD=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?deepseek=E5=AE=98=E6=96=B9=E7=9A=84=EF=BC=8C=E4=B8=94=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E5=90=8D=E7=A7=B0=E5=86=99=E6=AD=BB=E9=97=AE=E9=A2=98?=
=?UTF-8?q?=E3=80=82env=E4=B8=AD=E5=A2=9E=E5=8A=A0=20DEEPSEEK=5FMODEL=5FNA?=
=?UTF-8?q?ME=20=E5=8F=82=E6=95=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
2、历史数据存储没有放入data目录下,导致容器容器后数据丢失
---
.env.example | 2 +
ai_agents.py | 23 ++++++++--
app.py | 85 ++++++++++++++++++++++++++++++++---
config.py | 70 ++++++++++++++++++++++++++++-
config_manager.py | 7 +++
database.py | 21 +++++++--
deepseek_client.py | 87 ++++++++++++++++++++++++++++++++++--
env_example.txt | 1 +
longhubang_agents.py | 19 ++++++--
longhubang_db.py | 15 +++++--
longhubang_engine.py | 15 +++++--
longhubang_ui.py | 11 +++--
low_price_bull_monitor.py | 15 +++++--
main_force_analysis.py | 19 ++++++--
main_force_batch_db.py | 20 +++++++--
main_force_ui.py | 3 +-
monitor_db.py | 20 ++++++++-
portfolio_db.py | 8 +++-
portfolio_manager.py | 19 ++++++--
profit_growth_monitor.py | 15 +++++--
restart_service.sh | 35 +++++++++++++++
sector_strategy_agents.py | 19 ++++++--
sector_strategy_db.py | 15 +++++--
sector_strategy_engine.py | 18 +++++---
sector_strategy_scheduler.py | 3 +-
sector_strategy_ui.py | 7 ++-
smart_monitor_db.py | 15 +++++--
smart_monitor_deepseek.py | 77 ++++++++++++++++++++++++++++---
smart_monitor_engine.py | 18 +++++---
update_env_example.py | 4 ++
30 files changed, 607 insertions(+), 79 deletions(-)
create mode 100755 restart_service.sh
diff --git a/.env.example b/.env.example
index 5529c41..bfeeb47 100644
--- a/.env.example
+++ b/.env.example
@@ -15,6 +15,8 @@ DEEPSEEK_API_KEY=your_actual_deepseek_api_key_here
# DeepSeek API基础URL(可选,使用默认值即可)
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
+# DeepSeek 模型名称,为空时取默认值 deepseek-chat
+DEEPSEEK_MODEL_NAME=deepseek-chat
# ========== Tushare数据接口(可选)==========
# Tushare Token(可选,用于获取更多金融数据)
diff --git a/ai_agents.py b/ai_agents.py
index 75ea06e..c6efe57 100644
--- a/ai_agents.py
+++ b/ai_agents.py
@@ -1,13 +1,30 @@
from deepseek_client import DeepSeekClient
from typing import Dict, Any
import time
+import config
class StockAnalysisAgents:
"""股票分析AI智能体集合"""
- def __init__(self, model="deepseek-chat"):
- self.model = model
- self.deepseek_client = DeepSeekClient(model=model)
+ def __init__(self, model=None):
+ # 调试:打印传入的参数和配置
+ print(f"[StockAnalysisAgents.__init__] 传入参数: model={model}")
+ print(f"[StockAnalysisAgents.__init__] 配置文件中的 DEEPSEEK_MODEL_NAME: {config.DEEPSEEK_MODEL_NAME}")
+
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[StockAnalysisAgents.__init__] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ print(f"[StockAnalysisAgents.__init__] 使用配置文件中的默认模型: {self.model}")
+ else:
+ self.model = model
+ print(f"[StockAnalysisAgents.__init__] 使用传入的模型参数: {self.model}")
+
+ print(f"[StockAnalysisAgents.__init__] ✅ 最终使用的模型: {self.model}")
+ self.deepseek_client = DeepSeekClient(model=self.model)
def technical_analyst_agent(self, stock_info: Dict, stock_data: Any, indicators: Dict) -> Dict[str, Any]:
"""技术面分析智能体"""
diff --git a/app.py b/app.py
index f030097..24dd612 100644
--- a/app.py
+++ b/app.py
@@ -18,6 +18,7 @@
from monitor_service import monitor_service
from notification_service import notification_service
from config_manager import config_manager
+import config
from main_force_ui import display_main_force_selector
from sector_strategy_ui import display_sector_strategy
from longhubang_ui import display_longhubang
@@ -37,11 +38,22 @@ def model_selector():
st.sidebar.markdown("---")
st.sidebar.subheader("🤖 AI模型选择")
-
+ # 获取配置文件中的默认模型
+ default_model = config.DEEPSEEK_MODEL_NAME
+
+ # 如果配置的模型不在选项中,使用列表第一个
+ if default_model not in model_options:
+ default_model = list(model_options.keys())[0]
+
+ # 如果 session_state 中已有值,且该值在选项中,使用它;否则使用配置的默认值
+ current_model = st.session_state.get('selected_model', default_model)
+ if current_model not in model_options:
+ current_model = default_model
selected_model = st.sidebar.selectbox(
"选择AI模型",
options=list(model_options.keys()),
+ index=list(model_options.keys()).index(current_model) if current_model in model_options else 0,
format_func=lambda x: model_options[x],
help="DeepSeek Reasoner提供更强的推理能力,但响应时间可能更长"
)
@@ -277,6 +289,10 @@ def model_selector():
""", unsafe_allow_html=True)
def main():
+ # 初始化:检查配置文件中的默认模型,如果session_state中没有或值无效,使用配置文件的值
+ if 'selected_model' not in st.session_state or st.session_state.get('selected_model') not in model_options:
+ st.session_state.selected_model = config.DEEPSEEK_MODEL_NAME if config.DEEPSEEK_MODEL_NAME in model_options else list(model_options.keys())[0]
+
# 顶部标题栏
st.markdown("""
@@ -410,7 +426,19 @@ def main():
# 模型选择器
selected_model = model_selector()
- st.session_state.selected_model = selected_model
+
+ # 如果配置文件中的默认模型发生了变化,且用户没有主动选择其他模型,更新为配置文件的值
+ config_default_model = config.DEEPSEEK_MODEL_NAME
+ if config_default_model in model_options:
+ # 如果 session_state 中的值不在有效选项中,或者配置文件的值与当前选择不同且用户未手动选择过,使用配置文件的值
+ if (selected_model not in model_options) or \
+ (st.session_state.get('selected_model') not in model_options):
+ selected_model = config_default_model
+ st.session_state.selected_model = selected_model
+ else:
+ st.session_state.selected_model = selected_model
+ else:
+ st.session_state.selected_model = selected_model
st.markdown("---")
@@ -812,7 +840,20 @@ def parse_stock_list(stock_input):
return unique_list
-def analyze_single_stock_for_batch(symbol, period, enabled_analysts_config=None, selected_model='deepseek-chat'):
+def analyze_single_stock_for_batch(symbol, period, enabled_analysts_config=None, selected_model=None):
+ # 强制使用配置文件中的默认模型(忽略传入的旧值)
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ config_model = config.DEEPSEEK_MODEL_NAME
+ if selected_model is None or selected_model == "" or selected_model == "deepseek-chat":
+ selected_model = config_model
+ print(f"[analyze_single_stock_for_batch] 强制使用配置文件中的模型: {selected_model} (传入值被忽略)")
+ else:
+ # 如果传入的模型在选项中,使用传入的值;否则使用配置文件的值
+ if selected_model not in model_options:
+ selected_model = config_model
+ print(f"[analyze_single_stock_for_batch] 传入的模型不在选项中,使用配置文件中的模型: {selected_model}")
+ else:
+ print(f"[analyze_single_stock_for_batch] 使用传入的模型: {selected_model}")
"""单个股票分析(用于批量分析)
Args:
@@ -970,7 +1011,14 @@ def run_batch_analysis(stock_list, period, batch_mode="顺序分析"):
'sentiment': st.session_state.get('enable_sentiment', False),
'news': st.session_state.get('enable_news', False)
}
- selected_model = st.session_state.get('selected_model', 'deepseek-chat')
+ # 强制使用配置文件中的默认模型(忽略 session_state 中的旧值)
+ config_model = config.DEEPSEEK_MODEL_NAME
+ if config_model in model_options:
+ selected_model = config_model
+ else:
+ selected_model = list(model_options.keys())[0]
+
+ print(f"[run_batch_analysis] 使用模型: {selected_model} (来自配置文件: {config_model})")
# 创建进度显示
st.subheader(f"📊 批量分析进行中 ({batch_mode})")
@@ -1242,8 +1290,17 @@ def run_stock_analysis(symbol, period):
# 6. 初始化AI分析系统
status_text.text("🤖 正在初始化AI分析系统...")
- # 使用选择的模型
- selected_model = st.session_state.get('selected_model', 'deepseek-chat')
+ # 获取模型,强制使用配置文件中的默认模型(忽略 session_state 中的旧值)
+ # 如果配置文件中的模型不在选项中,则使用第一个选项
+ config_model = config.DEEPSEEK_MODEL_NAME
+ if config_model in model_options:
+ selected_model = config_model
+ else:
+ selected_model = list(model_options.keys())[0]
+
+ print(f"[run_stock_analysis] 使用模型: {selected_model} (来自配置文件: {config_model})")
+ print(f"[run_stock_analysis] session_state中的selected_model: {st.session_state.get('selected_model', 'None')}")
+
agents = StockAnalysisAgents(model=selected_model)
progress_bar.progress(55)
@@ -2141,6 +2198,21 @@ def display_config_manager():
)
st.session_state.temp_config["DEEPSEEK_BASE_URL"] = new_base_url
+ st.markdown("---")
+
+ # DeepSeek Model Name
+ default_model_name = config.DEEPSEEK_MODEL_NAME
+ model_name_info = config_info.get("DEEPSEEK_MODEL_NAME", {"value": default_model_name, "description": "DeepSeek模型名称", "required": False, "type": "text"})
+ current_model_name = st.session_state.temp_config.get("DEEPSEEK_MODEL_NAME", model_name_info.get("value", default_model_name))
+
+ new_model_name = st.text_input(
+ f"🤖 {model_name_info['description']}",
+ value=current_model_name,
+ help="配置要使用的模型名称,如:deepseek-chat、deepseek-reasoner等",
+ key="input_deepseek_model_name"
+ )
+ st.session_state.temp_config["DEEPSEEK_MODEL_NAME"] = new_model_name
+
st.info("💡 如何获取DeepSeek API密钥?\n\n1. 访问 https://platform.deepseek.com\n2. 注册/登录账号\n3. 进入API密钥管理页面\n4. 创建新的API密钥\n5. 复制密钥并粘贴到上方输入框")
with tab2:
@@ -2480,6 +2552,7 @@ def display_config_manager():
# ========== DeepSeek API配置 ==========
DEEPSEEK_API_KEY="{current_config.get('DEEPSEEK_API_KEY', '')}"
DEEPSEEK_BASE_URL="{current_config.get('DEEPSEEK_BASE_URL', '')}"
+DEEPSEEK_MODEL_NAME="{current_config.get('DEEPSEEK_MODEL_NAME', config.DEEPSEEK_MODEL_NAME)}"
# ========== Tushare数据接口(可选)==========
TUSHARE_TOKEN="{current_config.get('TUSHARE_TOKEN', '')}"
diff --git a/config.py b/config.py
index 5d3830b..b88c892 100644
--- a/config.py
+++ b/config.py
@@ -1,12 +1,78 @@
import os
+from pathlib import Path
from dotenv import load_dotenv
-# 加载环境变量(override=True 强制覆盖已存在的环境变量)
-load_dotenv(override=True)
+# 获取当前文件的目录
+current_dir = Path(__file__).parent.absolute()
+
+# 尝试多个可能的 .env 文件路径
+env_paths = [
+ current_dir / '.env', # 当前目录
+ Path('.env'), # 工作目录
+ Path('/app/.env'), # Docker 容器中的路径
+]
+
+env_loaded = False
+env_file_path = None
+
+# 尝试加载 .env 文件
+for env_path in env_paths:
+ if env_path.exists():
+ env_loaded = load_dotenv(env_path, override=True)
+ if env_loaded:
+ env_file_path = env_path
+ print(f"[config.py] ✅ 成功加载 .env 文件: {env_path}")
+ break
+ else:
+ print(f"[config.py] .env 文件不存在: {env_path}")
+
+# 如果都没有找到,尝试从当前目录加载(load_dotenv 的默认行为)
+if not env_loaded:
+ env_loaded = load_dotenv(override=True)
+ print(f"[config.py] 尝试默认路径加载 .env: {'成功' if env_loaded else '失败'}")
# DeepSeek API配置
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")
+DEEPSEEK_MODEL_NAME = os.getenv("DEEPSEEK_MODEL_NAME", "deepseek-chat")
+
+# 调试:打印配置值(隐藏敏感信息)
+print(f"[config.py] =========================================")
+print(f"[config.py] 配置加载结果:")
+print(f"[config.py] DEEPSEEK_MODEL_NAME: {DEEPSEEK_MODEL_NAME}")
+print(f"[config.py] DEEPSEEK_BASE_URL: {DEEPSEEK_BASE_URL}")
+print(f"[config.py] DEEPSEEK_API_KEY: {'已设置' if DEEPSEEK_API_KEY else '未设置'}")
+print(f"[config.py] 环境变量 DEEPSEEK_MODEL_NAME: {os.getenv('DEEPSEEK_MODEL_NAME', '未设置')}")
+
+# 如果 DEEPSEEK_MODEL_NAME 仍然是默认值,尝试从 .env 文件直接读取
+if DEEPSEEK_MODEL_NAME == "deepseek-chat":
+ print("[config.py] ⚠️ 警告: DEEPSEEK_MODEL_NAME 仍然是默认值 'deepseek-chat'")
+ print("[config.py] 尝试直接读取 .env 文件...")
+
+ # 尝试所有可能的路径
+ for env_path in env_paths:
+ if env_path.exists():
+ print(f"[config.py] 检查 .env 文件: {env_path}")
+ try:
+ with open(env_path, 'r', encoding='utf-8') as f:
+ for line_num, line in enumerate(f, 1):
+ line = line.strip()
+ # 跳过空行和注释
+ if not line or line.startswith('#'):
+ continue
+ if 'DEEPSEEK_MODEL_NAME' in line and '=' in line:
+ value = line.split('=', 1)[1].strip().strip('"').strip("'")
+ if value and value != "deepseek-chat":
+ DEEPSEEK_MODEL_NAME = value
+ # 更新环境变量
+ os.environ['DEEPSEEK_MODEL_NAME'] = value
+ print(f"[config.py] ✅ 从 .env 文件直接读取到 DEEPSEEK_MODEL_NAME: {DEEPSEEK_MODEL_NAME} (第 {line_num} 行)")
+ break
+ except Exception as e:
+ print(f"[config.py] ❌ 读取 .env 文件失败 ({env_path}): {e}")
+
+print(f"[config.py] 最终使用的 DEEPSEEK_MODEL_NAME: {DEEPSEEK_MODEL_NAME}")
+print(f"[config.py] =========================================")
# 其他配置
TUSHARE_TOKEN = os.getenv("TUSHARE_TOKEN", "")
diff --git a/config_manager.py b/config_manager.py
index 3881b50..94be8ce 100644
--- a/config_manager.py
+++ b/config_manager.py
@@ -26,6 +26,12 @@ def __init__(self, env_file: str = ".env"):
"required": False,
"type": "text"
},
+ "DEEPSEEK_MODEL_NAME": {
+ "value": "deepseek-chat",
+ "description": "DeepSeek模型名称",
+ "required": False,
+ "type": "text"
+ },
"TUSHARE_TOKEN": {
"value": "",
"description": "Tushare数据接口Token(可选)",
@@ -172,6 +178,7 @@ def write_env(self, config: Dict[str, str]) -> bool:
lines.append("# ========== DeepSeek API配置 ==========")
lines.append(f'DEEPSEEK_API_KEY="{config.get("DEEPSEEK_API_KEY", "")}"')
lines.append(f'DEEPSEEK_BASE_URL="{config.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")}"')
+ lines.append(f'DEEPSEEK_MODEL_NAME="{config.get("DEEPSEEK_MODEL_NAME", "deepseek-chat")}"')
lines.append("")
# Tushare配置
diff --git a/database.py b/database.py
index 07dd0db..6d72fa9 100644
--- a/database.py
+++ b/database.py
@@ -4,13 +4,28 @@
import os
class StockAnalysisDatabase:
- def __init__(self, db_path="stock_analysis.db"):
- """初始化数据库连接"""
- self.db_path = db_path
+ def __init__(self, db_path=None):
+ """初始化数据库连接
+
+ Args:
+ db_path: 数据库文件路径,如果为None,则使用data目录下的stock_analysis.db(确保持久化)
+ """
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ # 确保data目录存在
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'stock_analysis.db')
+ else:
+ self.db_path = db_path
+
# 确保数据库所在目录存在
db_dir = os.path.dirname(self.db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
+
+ print(f"[StockAnalysisDatabase] 数据库文件路径: {self.db_path}")
self.init_database()
def init_database(self):
diff --git a/deepseek_client.py b/deepseek_client.py
index 17db710..f90e952 100644
--- a/deepseek_client.py
+++ b/deepseek_client.py
@@ -2,16 +2,55 @@
import json
from typing import Dict, List, Any, Optional
import config
+import logging
+import os
+
+# 配置日志
+logger = logging.getLogger(__name__)
+# 如果没有配置handler,则添加默认配置
+if not logger.handlers:
+ logging.basicConfig(
+ level=logging.INFO,
+ format='[%(asctime)s] %(levelname)s %(name)s: %(message)s',
+ datefmt='%Y-%m-%d %H:%M:%S'
+ )
class DeepSeekClient:
"""DeepSeek API客户端"""
- def __init__(self, model="deepseek-chat"):
- self.model = model
+ def __init__(self, model=None):
+ # 调试:打印传入的参数和配置
+ logger.info(f"[DeepSeekClient.__init__] 传入参数: model={model}")
+ logger.info(f"[DeepSeekClient.__init__] 配置文件中的 DEEPSEEK_MODEL_NAME: {config.DEEPSEEK_MODEL_NAME}")
+ print(f"[DeepSeekClient.__init__] 传入参数: model={model}")
+ print(f"[DeepSeekClient.__init__] 配置文件中的 DEEPSEEK_MODEL_NAME: {config.DEEPSEEK_MODEL_NAME}")
+ print(f"[DeepSeekClient.__init__] 环境变量 DEEPSEEK_MODEL_NAME: {os.getenv('DEEPSEEK_MODEL_NAME', '未设置')}")
+
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ logger.warning(f"[DeepSeekClient.__init__] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ print(f"[DeepSeekClient.__init__] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ logger.info(f"[DeepSeekClient.__init__] 使用配置文件中的默认模型: {self.model}")
+ print(f"[DeepSeekClient.__init__] ✅ 使用配置文件中的默认模型: {self.model}")
+ # 如果配置文件的模型仍然是默认值,发出警告
+ if self.model == "deepseek-chat":
+ print(f"[DeepSeekClient.__init__] ⚠️ 警告: 模型仍然是默认值 'deepseek-chat',请检查 .env 文件中的 DEEPSEEK_MODEL_NAME 配置")
+ else:
+ self.model = model
+ logger.info(f"[DeepSeekClient.__init__] 使用传入的模型参数: {self.model}")
+ print(f"[DeepSeekClient.__init__] 使用传入的模型参数: {self.model}")
+
+ self.base_url = config.DEEPSEEK_BASE_URL
self.client = openai.OpenAI(
api_key=config.DEEPSEEK_API_KEY,
- base_url=config.DEEPSEEK_BASE_URL
+ base_url=self.base_url
)
+ logger.info(f"[DeepSeekClient] 初始化完成 - 最终使用的模型: {self.model}, API地址: {self.base_url}")
+ print(f"[DeepSeekClient] ✅ 初始化完成 - 最终使用的模型: {self.model}, API地址: {self.base_url}")
def call_api(self, messages: List[Dict[str, str]], model: Optional[str] = None,
temperature: float = 0.7, max_tokens: int = 2000) -> str:
@@ -20,9 +59,38 @@ def call_api(self, messages: List[Dict[str, str]], model: Optional[str] = None,
model_to_use = model or self.model
# 对于 reasoner 模型,自动增加 max_tokens
+ original_max_tokens = max_tokens
if "reasoner" in model_to_use.lower() and max_tokens <= 2000:
max_tokens = 8000 # reasoner 模型需要更多 tokens 来输出推理过程
+ # 计算输入消息的token估算(简单估算:字符数/4)
+ total_chars = sum(len(str(msg.get('content', ''))) for msg in messages)
+ estimated_tokens = total_chars // 4
+
+ # 准备消息摘要(安全处理)
+ message_summaries = []
+ for msg in messages[:3]:
+ role = msg.get('role', 'unknown')
+ content = str(msg.get('content', ''))
+ if len(content) > 50:
+ content = content[:50] + '...'
+ message_summaries.append(f"{role}:{content}")
+
+ # 输出模型调用信息(同时输出到日志和控制台)
+ log_msg = f"""
+{'=' * 60}
+[DeepSeekClient] 准备调用API
+ 模型名称: {model_to_use}
+ API地址: {self.base_url}
+ 消息数量: {len(messages)}
+ 估算输入Token: ~{estimated_tokens}
+ 温度参数: {temperature}
+ 最大输出Token: {max_tokens}{f' (已从{original_max_tokens}自动调整)' if max_tokens != original_max_tokens else ''}
+ 消息摘要: {message_summaries}
+{'=' * 60}"""
+ logger.info(log_msg)
+ print(log_msg) # 同时输出到控制台,方便调试
+
try:
response = self.client.chat.completions.create(
model=model_to_use,
@@ -31,6 +99,17 @@ def call_api(self, messages: List[Dict[str, str]], model: Optional[str] = None,
max_tokens=max_tokens
)
+ # 输出响应信息
+ if response and hasattr(response, 'usage'):
+ usage = response.usage
+ usage_info = f"""
+[DeepSeekClient] API调用成功
+ 实际输入Token: {usage.prompt_tokens if hasattr(usage, 'prompt_tokens') else 'N/A'}
+ 实际输出Token: {usage.completion_tokens if hasattr(usage, 'completion_tokens') else 'N/A'}
+ 总Token: {usage.total_tokens if hasattr(usage, 'total_tokens') else 'N/A'}"""
+ logger.info(usage_info)
+ print(usage_info) # 同时输出到控制台
+
# 处理 reasoner 模型的响应
message = response.choices[0].message
@@ -49,6 +128,8 @@ def call_api(self, messages: List[Dict[str, str]], model: Optional[str] = None,
return result if result else "API返回空响应"
except Exception as e:
+ logger.error(f"[DeepSeekClient] API调用失败: {str(e)}")
+ logger.error(f" 模型: {model_to_use}, API地址: {self.base_url}")
return f"API调用失败: {str(e)}"
def technical_analysis(self, stock_info: Dict, stock_data: Any, indicators: Dict) -> str:
diff --git a/env_example.txt b/env_example.txt
index 2549f5b..5cb109f 100644
--- a/env_example.txt
+++ b/env_example.txt
@@ -6,6 +6,7 @@
# DeepSeek API配置
DEEPSEEK_API_KEY=your_deepseek_api_key_here
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
+DEEPSEEK_MODEL_NAME=deepseek-chat
# Tushare数据源配置(备用数据源)
TUSHARE_TOKEN=your_tushare_token_here
diff --git a/longhubang_agents.py b/longhubang_agents.py
index fba5a39..34ceab6 100644
--- a/longhubang_agents.py
+++ b/longhubang_agents.py
@@ -6,15 +6,26 @@
from deepseek_client import DeepSeekClient
from typing import Dict, Any, List
import time
+import config
class LonghubangAgents:
"""龙虎榜AI分析师集合"""
- def __init__(self, model="deepseek-chat"):
- self.model = model
- self.deepseek_client = DeepSeekClient(model=model)
- print(f"[智瞰龙虎] AI分析师系统初始化 (模型: {model})")
+ def __init__(self, model=None):
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[智瞰龙虎] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ print(f"[智瞰龙虎] AI分析师系统初始化 - 使用配置文件中的默认模型: {self.model}")
+ else:
+ self.model = model
+ print(f"[智瞰龙虎] AI分析师系统初始化 - 使用传入的模型参数: {self.model}")
+ self.deepseek_client = DeepSeekClient(model=self.model)
+ print(f"[智瞰龙虎] AI分析师系统初始化完成 - 最终使用的模型: {self.model}")
def youzi_behavior_analyst(self, longhubang_data: str, summary: Dict) -> Dict[str, Any]:
"""
diff --git a/longhubang_db.py b/longhubang_db.py
index e9db828..4991a93 100644
--- a/longhubang_db.py
+++ b/longhubang_db.py
@@ -13,14 +13,23 @@
class LonghubangDatabase:
"""龙虎榜数据库管理类"""
- def __init__(self, db_path='longhubang.db'):
+ def __init__(self, db_path=None):
"""
初始化数据库
Args:
- db_path: 数据库文件路径
+ db_path: 数据库文件路径,如果为None,则使用data目录下的longhubang.db(确保持久化)
"""
- self.db_path = db_path
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ import os
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'longhubang.db')
+ else:
+ self.db_path = db_path
+ print(f"[LonghubangDatabase] 数据库文件路径: {self.db_path}")
# 初始化日志
self.logger = logging.getLogger(__name__)
if not self.logger.handlers:
diff --git a/longhubang_engine.py b/longhubang_engine.py
index 69ecd06..d5b825f 100644
--- a/longhubang_engine.py
+++ b/longhubang_engine.py
@@ -16,17 +16,26 @@
class LonghubangEngine:
"""龙虎榜综合分析引擎"""
- def __init__(self, model="deepseek-chat", db_path='longhubang.db'):
+ def __init__(self, model=None, db_path=None):
"""
初始化分析引擎
Args:
- model: AI模型名称
+ model: AI模型名称(如果不传入,则使用配置文件中的默认模型)
db_path: 数据库路径
"""
+ import config
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[智瞰龙虎引擎] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ self.model = model
self.data_fetcher = LonghubangDataFetcher()
self.database = LonghubangDatabase(db_path)
- self.agents = LonghubangAgents(model=model)
+ self.agents = LonghubangAgents(model=self.model)
self.scoring = LonghubangScoring()
# 初始化日志
self.logger = logging.getLogger(__name__)
diff --git a/longhubang_ui.py b/longhubang_ui.py
index e10dc60..87162c1 100644
--- a/longhubang_ui.py
+++ b/longhubang_ui.py
@@ -13,6 +13,7 @@
from longhubang_engine import LonghubangEngine
from longhubang_pdf import LonghubangPDFGenerator
+import config
def display_longhubang():
@@ -183,9 +184,13 @@ def display_analysis_tab():
st.error(f"❌ 分析失败: {result.get('error', '未知错误')}")
-def run_longhubang_analysis(model="deepseek-chat", date=None, days=1):
+def run_longhubang_analysis(model=None, date=None, days=1):
"""运行龙虎榜分析"""
+ # 如果没有传入model,则使用配置文件中的默认模型
+ if model is None:
+ model = config.DEEPSEEK_MODEL_NAME
+
# 进度显示
progress_bar = st.progress(0)
status_text = st.empty()
@@ -1397,7 +1402,7 @@ def run_longhubang_batch_analysis():
'sentiment': False,
'news': False
},
- selected_model='deepseek-chat'
+ selected_model=config.DEEPSEEK_MODEL_NAME
)
results.append({
@@ -1428,7 +1433,7 @@ def analyze_one(code):
'sentiment': False,
'news': False
},
- selected_model='deepseek-chat'
+ selected_model=config.DEEPSEEK_MODEL_NAME
)
return {"code": code, "result": result}
except Exception as e:
diff --git a/low_price_bull_monitor.py b/low_price_bull_monitor.py
index 4963eb9..61dec68 100644
--- a/low_price_bull_monitor.py
+++ b/low_price_bull_monitor.py
@@ -16,15 +16,24 @@
class LowPriceBullMonitor:
"""低价擒牛策略监控器"""
- def __init__(self, db_path: str = "low_price_bull_monitor.db"):
+ def __init__(self, db_path: str = None):
"""
初始化监控器
Args:
- db_path: 数据库文件路径
+ db_path: 数据库文件路径,如果为None,则使用data目录下的low_price_bull_monitor.db(确保持久化)
"""
self.logger = logging.getLogger(__name__)
- self.db_path = db_path
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ import os
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'low_price_bull_monitor.db')
+ else:
+ self.db_path = db_path
+ print(f"[LowPriceBullMonitor] 数据库文件路径: {self.db_path}")
self._init_database()
def _init_database(self):
diff --git a/main_force_analysis.py b/main_force_analysis.py
index f7de82a..902a9d5 100644
--- a/main_force_analysis.py
+++ b/main_force_analysis.py
@@ -14,14 +14,27 @@
import time
import json
+import config
+
class MainForceAnalyzer:
"""主力选股分析器 - 批量整体分析"""
- def __init__(self, model='deepseek-chat'):
+ def __init__(self, model=None):
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[MainForceAnalyzer] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ print(f"[MainForceAnalyzer] 使用配置文件中的默认模型: {self.model}")
+ else:
+ self.model = model
+ print(f"[MainForceAnalyzer] 使用传入的模型参数: {self.model}")
+ print(f"[MainForceAnalyzer] ✅ 最终使用的模型: {self.model}")
self.selector = main_force_selector
self.fetcher = StockDataFetcher()
- self.model = model
- self.agents = StockAnalysisAgents(model=model)
+ self.agents = StockAnalysisAgents(model=self.model)
self.deepseek_client = self.agents.deepseek_client
self.raw_stocks = None
self.final_recommendations = []
diff --git a/main_force_batch_db.py b/main_force_batch_db.py
index 55093c7..716dbed 100644
--- a/main_force_batch_db.py
+++ b/main_force_batch_db.py
@@ -13,9 +13,23 @@
class MainForceBatchDatabase:
"""主力选股批量分析历史数据库管理类"""
- def __init__(self, db_path: str = "main_force_batch.db"):
- """初始化数据库连接"""
- self.db_path = db_path
+ def __init__(self, db_path: str = None):
+ """
+ 初始化数据库连接
+
+ Args:
+ db_path: 数据库文件路径,如果为None,则使用data目录下的main_force_batch.db(确保持久化)
+ """
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ import os
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'main_force_batch.db')
+ else:
+ self.db_path = db_path
+ print(f"[MainForceBatchDatabase] 数据库文件路径: {self.db_path}")
self._init_database()
def _init_database(self):
diff --git a/main_force_ui.py b/main_force_ui.py
index 5477f48..75a542d 100644
--- a/main_force_ui.py
+++ b/main_force_ui.py
@@ -10,6 +10,7 @@
from main_force_pdf_generator import display_report_download_section
from main_force_history_ui import display_batch_history
import pandas as pd
+import config
def display_main_force_selector():
"""显示主力选股界面"""
@@ -613,7 +614,7 @@ def run_main_force_batch_analysis():
'sentiment': False, # 禁用以提升速度
'news': False # 禁用以提升速度
}
- selected_model = 'deepseek-chat'
+ selected_model = config.DEEPSEEK_MODEL_NAME
period = '1y'
# 创建进度显示
diff --git a/monitor_db.py b/monitor_db.py
index 0d9ca56..bbfaf4e 100644
--- a/monitor_db.py
+++ b/monitor_db.py
@@ -7,12 +7,28 @@
class StockMonitorDatabase:
"""股票监测数据库管理类"""
- def __init__(self, db_path: str = "stock_monitor.db"):
- self.db_path = db_path
+ def __init__(self, db_path: str = None):
+ """
+ 初始化数据库
+
+ Args:
+ db_path: 数据库文件路径,如果为None,则使用data目录下的stock_monitor.db(确保持久化)
+ """
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'stock_monitor.db')
+ else:
+ self.db_path = db_path
+
# 确保数据库所在目录存在
db_dir = os.path.dirname(self.db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
+
+ print(f"[StockMonitorDatabase] 数据库文件路径: {self.db_path}")
self.init_database()
def init_database(self):
diff --git a/portfolio_db.py b/portfolio_db.py
index 6b0f595..591b9e5 100644
--- a/portfolio_db.py
+++ b/portfolio_db.py
@@ -9,8 +9,12 @@
from typing import List, Dict, Optional, Tuple
import os
-# 数据库文件路径
-DB_PATH = "portfolio_stocks.db"
+# 数据库文件路径(使用data目录确保持久化)
+_data_dir = os.path.join(os.path.dirname(__file__), 'data')
+if not os.path.exists(_data_dir):
+ os.makedirs(_data_dir, exist_ok=True)
+DB_PATH = os.path.join(_data_dir, "portfolio_stocks.db")
+print(f"[PortfolioDB] 数据库文件路径: {DB_PATH}")
class PortfolioDB:
diff --git a/portfolio_manager.py b/portfolio_manager.py
index e23e85f..3ad8cb6 100644
--- a/portfolio_manager.py
+++ b/portfolio_manager.py
@@ -13,17 +13,30 @@
from portfolio_db import portfolio_db
+import config
+
class PortfolioManager:
"""持仓管理器类"""
- def __init__(self, model="deepseek-chat"):
+ def __init__(self, model=None):
"""
初始化持仓管理器
Args:
- model: AI模型(deepseek-chat 或 deepseek-reasoner)
+ model: AI模型(如果不传入,则使用配置文件中的默认模型)
"""
- self.model = model
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[PortfolioManager] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ print(f"[PortfolioManager] 使用配置文件中的默认模型: {self.model}")
+ else:
+ self.model = model
+ print(f"[PortfolioManager] 使用传入的模型参数: {self.model}")
+ print(f"[PortfolioManager] ✅ 最终使用的模型: {self.model}")
self.db = portfolio_db
# ==================== 持仓股票管理 ====================
diff --git a/profit_growth_monitor.py b/profit_growth_monitor.py
index 869963a..061ac08 100644
--- a/profit_growth_monitor.py
+++ b/profit_growth_monitor.py
@@ -15,14 +15,23 @@
class ProfitGrowthMonitor:
"""净利增长策略监控数据库管理"""
- def __init__(self, db_path: str = "profit_growth_monitor.db"):
+ def __init__(self, db_path: str = None):
"""
初始化监控数据库
Args:
- db_path: 数据库文件路径
+ db_path: 数据库文件路径,如果为None,则使用data目录下的profit_growth_monitor.db(确保持久化)
"""
- self.db_path = db_path
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ import os
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'profit_growth_monitor.db')
+ else:
+ self.db_path = db_path
+ print(f"[ProfitGrowthMonitor] 数据库文件路径: {self.db_path}")
self.logger = logging.getLogger(__name__)
self._init_database()
diff --git a/restart_service.sh b/restart_service.sh
new file mode 100755
index 0000000..909799e
--- /dev/null
+++ b/restart_service.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# 停止并重启 Docker 服务脚本
+
+cd "$(dirname "$0")"
+
+echo "=========================================="
+echo "停止并重启 Docker 服务"
+echo "=========================================="
+echo ""
+
+# 1. 停止服务
+echo "1. 停止现有服务..."
+docker-compose down
+echo ""
+
+# 2. 重新构建(使用 --no-cache 确保使用最新代码)
+echo "2. 重新构建 Docker 镜像(这可能需要几分钟)..."
+docker-compose build --no-cache
+echo ""
+
+# 3. 启动服务
+echo "3. 启动服务..."
+docker-compose up -d
+echo ""
+
+# 4. 查看服务状态
+echo "4. 查看服务状态..."
+sleep 3
+docker-compose ps
+echo ""
+
+# 5. 查看日志
+echo "5. 查看最新日志(按 Ctrl+C 退出)..."
+echo "=========================================="
+docker-compose logs -f --tail=50
diff --git a/sector_strategy_agents.py b/sector_strategy_agents.py
index 6383ba9..7b477e9 100644
--- a/sector_strategy_agents.py
+++ b/sector_strategy_agents.py
@@ -6,15 +6,26 @@
from deepseek_client import DeepSeekClient
from typing import Dict, Any
import time
+import config
class SectorStrategyAgents:
"""板块策略AI智能体集合"""
- def __init__(self, model="deepseek-chat"):
- self.model = model
- self.deepseek_client = DeepSeekClient(model=model)
- print(f"[智策] AI智能体系统初始化 (模型: {model})")
+ def __init__(self, model=None):
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[智策] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ print(f"[智策] AI智能体系统初始化 - 使用配置文件中的默认模型: {self.model}")
+ else:
+ self.model = model
+ print(f"[智策] AI智能体系统初始化 - 使用传入的模型参数: {self.model}")
+ self.deepseek_client = DeepSeekClient(model=self.model)
+ print(f"[智策] AI智能体系统初始化完成 - 最终使用的模型: {self.model}")
def macro_strategist_agent(self, market_data: Dict, news_data: list) -> Dict[str, Any]:
"""
diff --git a/sector_strategy_db.py b/sector_strategy_db.py
index 38b8253..8209d5b 100644
--- a/sector_strategy_db.py
+++ b/sector_strategy_db.py
@@ -13,14 +13,23 @@
class SectorStrategyDatabase:
"""智策板块数据库管理类"""
- def __init__(self, db_path='sector_strategy.db'):
+ def __init__(self, db_path=None):
"""
初始化数据库
Args:
- db_path: 数据库文件路径
+ db_path: 数据库文件路径,如果为None,则使用data目录下的sector_strategy.db(确保持久化)
"""
- self.db_path = db_path
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_path is None:
+ import os
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_path = os.path.join(data_dir, 'sector_strategy.db')
+ else:
+ self.db_path = db_path
+ print(f"[SectorStrategyDatabase] 数据库文件路径: {self.db_path}")
# 初始化日志
self.logger = logging.getLogger(__name__)
if not self.logger.handlers:
diff --git a/sector_strategy_engine.py b/sector_strategy_engine.py
index 5e39364..81668be 100644
--- a/sector_strategy_engine.py
+++ b/sector_strategy_engine.py
@@ -11,20 +11,28 @@
import json
import pandas as pd
import logging
+import config
class SectorStrategyEngine:
"""板块策略综合研判引擎"""
- def __init__(self, model="deepseek-chat"):
- self.model = model
- self.agents = SectorStrategyAgents(model=model)
- self.deepseek_client = DeepSeekClient(model=model)
+ def __init__(self, model=None):
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ print(f"[智策引擎] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ self.model = model
+ self.agents = SectorStrategyAgents(model=self.model)
+ self.deepseek_client = DeepSeekClient(model=self.model)
self.database = SectorStrategyDatabase()
self.logger = logging.getLogger(__name__)
if not self.logger.handlers:
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s %(name)s: %(message)s')
- print(f"[智策引擎] 初始化完成 (模型: {model})")
+ print(f"[智策引擎] 初始化完成 - 最终使用的模型: {self.model} (传入参数: {model}, 配置默认: {config.DEEPSEEK_MODEL_NAME})")
def save_raw_data_with_fallback(self, data_type, data_df, data_date=None):
"""
diff --git a/sector_strategy_scheduler.py b/sector_strategy_scheduler.py
index 60436e9..440914e 100644
--- a/sector_strategy_scheduler.py
+++ b/sector_strategy_scheduler.py
@@ -11,6 +11,7 @@
from sector_strategy_engine import SectorStrategyEngine
from notification_service import notification_service
import json
+import config
class SectorStrategyScheduler:
@@ -128,7 +129,7 @@ def _run_analysis(self):
# 2. 运行AI分析
print("[智策定时] [2/3] AI智能体分析中...")
- engine = SectorStrategyEngine(model="deepseek-chat")
+ engine = SectorStrategyEngine(model=config.DEEPSEEK_MODEL_NAME)
result = engine.run_comprehensive_analysis(data)
if not result.get("success"):
diff --git a/sector_strategy_ui.py b/sector_strategy_ui.py
index dc5f13a..126c3cb 100644
--- a/sector_strategy_ui.py
+++ b/sector_strategy_ui.py
@@ -18,6 +18,7 @@
from sector_strategy_pdf import SectorStrategyPDFGenerator
from sector_strategy_db import SectorStrategyDatabase
from sector_strategy_scheduler import sector_strategy_scheduler
+import config
def _parse_json_field(value, default):
@@ -261,9 +262,13 @@ def display_report_detail(report_id):
st.info("当前版本仅提供报告摘要,详细页面已移除。")
-def run_sector_strategy_analysis(model="deepseek-chat"):
+def run_sector_strategy_analysis(model=None):
"""运行智策分析"""
+ # 如果没有传入model,则使用配置文件中的默认模型
+ if model is None:
+ model = config.DEEPSEEK_MODEL_NAME
+
# 进度显示
progress_bar = st.progress(0)
status_text = st.empty()
diff --git a/smart_monitor_db.py b/smart_monitor_db.py
index 7a22fd5..afee645 100644
--- a/smart_monitor_db.py
+++ b/smart_monitor_db.py
@@ -13,14 +13,23 @@
class SmartMonitorDB:
"""智能盯盘数据库"""
- def __init__(self, db_file: str = 'smart_monitor.db'):
+ def __init__(self, db_file: str = None):
"""
初始化数据库
Args:
- db_file: 数据库文件路径
+ db_file: 数据库文件路径,如果为None,则使用data目录下的smart_monitor.db(确保持久化)
"""
- self.db_file = db_file
+ # 如果没有指定路径,使用data目录下的数据库文件(确保容器重启后数据不丢失)
+ if db_file is None:
+ import os
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir, exist_ok=True)
+ self.db_file = os.path.join(data_dir, 'smart_monitor.db')
+ else:
+ self.db_file = db_file
+ print(f"[SmartMonitorDB] 数据库文件路径: {self.db_file}")
self.logger = logging.getLogger(__name__)
self._init_database()
diff --git a/smart_monitor_deepseek.py b/smart_monitor_deepseek.py
index 622fe99..db27a6e 100644
--- a/smart_monitor_deepseek.py
+++ b/smart_monitor_deepseek.py
@@ -7,25 +7,43 @@
from typing import Dict, List, Optional
from datetime import datetime, time
import pytz
+import config
class SmartMonitorDeepSeek:
"""A股智能盯盘 - DeepSeek AI决策引擎"""
- def __init__(self, api_key: str):
+ def __init__(self, api_key: str, base_url: str = None, model: str = None):
"""
初始化DeepSeek客户端
Args:
api_key: DeepSeek API密钥
+ base_url: API基础URL(可选,默认使用配置文件的值)
+ model: 模型名称(可选,默认使用配置文件的值)
"""
self.api_key = api_key
- self.base_url = "https://api.deepseek.com/v1"
+ # 如果没有传入base_url,则使用配置文件中的默认值
+ if base_url is None or base_url == "":
+ self.base_url = config.DEEPSEEK_BASE_URL
+ else:
+ self.base_url = base_url
+ # 强制使用配置文件中的默认模型
+ # 如果传入的是 None、空字符串或旧的默认值 "deepseek-chat",都使用配置文件的值
+ if model is None or model == "" or model == "deepseek-chat":
+ self.model = config.DEEPSEEK_MODEL_NAME
+ if model == "deepseek-chat":
+ self.logger.warning(f"[SmartMonitorDeepSeek] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ print(f"[SmartMonitorDeepSeek] ⚠️ 检测到传入的模型是旧的默认值 'deepseek-chat',强制使用配置文件中的模型: {self.model}")
+ else:
+ self.model = model
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
self.logger = logging.getLogger(__name__)
+ self.logger.info(f"[SmartMonitorDeepSeek] 初始化完成 - 模型: {self.model}, API地址: {self.base_url}")
+ print(f"[SmartMonitorDeepSeek] 初始化完成 - 模型: {self.model}, API地址: {self.base_url}")
def is_trading_time(self) -> bool:
"""
@@ -138,14 +156,14 @@ def get_trading_session(self) -> Dict:
'can_trade': False
}
- def chat_completion(self, messages: List[Dict], model: str = "deepseek-chat",
+ def chat_completion(self, messages: List[Dict], model: str = None,
temperature: float = 0.7, max_tokens: int = 2000) -> Dict:
"""
调用DeepSeek API
Args:
messages: 对话消息列表
- model: 模型名称
+ model: 模型名称(如果不传入,则使用配置文件中的默认模型)
temperature: 温度参数
max_tokens: 最大token数
@@ -154,8 +172,39 @@ def chat_completion(self, messages: List[Dict], model: str = "deepseek-chat",
"""
import requests
+ # 如果没有传入model,则使用配置文件中的默认模型
+ model_to_use = model or self.model
+
+ # 计算输入消息的token估算(简单估算:字符数/4)
+ total_chars = sum(len(str(msg.get('content', ''))) for msg in messages)
+ estimated_tokens = total_chars // 4
+
+ # 准备消息摘要(安全处理)
+ message_summaries = []
+ for msg in messages[:3]:
+ role = msg.get('role', 'unknown')
+ content = str(msg.get('content', ''))
+ if len(content) > 50:
+ content = content[:50] + '...'
+ message_summaries.append(f"{role}:{content}")
+
+ # 输出模型调用信息(同时输出到日志和控制台)
+ log_msg = f"""
+{'=' * 60}
+[SmartMonitorDeepSeek] 准备调用API
+ 模型名称: {model_to_use}
+ API地址: {self.base_url}
+ 消息数量: {len(messages)}
+ 估算输入Token: ~{estimated_tokens}
+ 温度参数: {temperature}
+ 最大输出Token: {max_tokens}
+ 消息摘要: {message_summaries}
+{'=' * 60}"""
+ self.logger.info(log_msg)
+ print(log_msg) # 同时输出到控制台,方便调试
+
payload = {
- "model": model,
+ "model": model_to_use,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
@@ -169,9 +218,23 @@ def chat_completion(self, messages: List[Dict], model: str = "deepseek-chat",
timeout=60
)
response.raise_for_status()
- return response.json()
+ result = response.json()
+
+ # 输出响应信息
+ if result and 'usage' in result:
+ usage = result['usage']
+ usage_info = f"""
+[SmartMonitorDeepSeek] API调用成功
+ 实际输入Token: {usage.get('prompt_tokens', 'N/A')}
+ 实际输出Token: {usage.get('completion_tokens', 'N/A')}
+ 总Token: {usage.get('total_tokens', 'N/A')}"""
+ self.logger.info(usage_info)
+ print(usage_info) # 同时输出到控制台
+
+ return result
except Exception as e:
- self.logger.error(f"DeepSeek API调用失败: {e}")
+ self.logger.error(f"[SmartMonitorDeepSeek] API调用失败: {e}")
+ self.logger.error(f" 模型: {model_to_use}, API地址: {self.base_url}")
raise
def analyze_stock_and_decide(self, stock_code: str, market_data: Dict,
diff --git a/smart_monitor_engine.py b/smart_monitor_engine.py
index 0c35798..a1d0468 100644
--- a/smart_monitor_engine.py
+++ b/smart_monitor_engine.py
@@ -15,6 +15,7 @@
from smart_monitor_db import SmartMonitorDB
from notification_service import notification_service # 复用主程序的通知服务
from config_manager import config_manager # 复用主程序的配置管理器
+import config # 导入config模块以使用DEEPSEEK_BASE_URL和DEEPSEEK_MODEL_NAME
class SmartMonitorEngine:
@@ -33,23 +34,30 @@ def __init__(self, deepseek_api_key: str = None, qmt_account_id: str = None,
self.logger = logging.getLogger(__name__)
# 从配置管理器读取配置
- config = config_manager.read_env()
+ env_config = config_manager.read_env()
# DeepSeek API
if deepseek_api_key is None:
- deepseek_api_key = config.get('DEEPSEEK_API_KEY', '')
+ deepseek_api_key = env_config.get('DEEPSEEK_API_KEY', '')
# MiniQMT配置
if qmt_account_id is None:
- qmt_account_id = config.get('MINIQMT_ACCOUNT_ID', '')
+ qmt_account_id = env_config.get('MINIQMT_ACCOUNT_ID', '')
if use_simulator is None:
# 如果MINIQMT_ENABLED=false,则使用模拟器
- miniqmt_enabled = config.get('MINIQMT_ENABLED', 'false').lower() == 'true'
+ miniqmt_enabled = env_config.get('MINIQMT_ENABLED', 'false').lower() == 'true'
use_simulator = not miniqmt_enabled
# 初始化各个模块
- self.deepseek = SmartMonitorDeepSeek(deepseek_api_key)
+ # 从config模块读取 base_url 和 model(从.env文件加载的配置)
+ deepseek_base_url = config.DEEPSEEK_BASE_URL
+ deepseek_model = config.DEEPSEEK_MODEL_NAME
+ self.deepseek = SmartMonitorDeepSeek(
+ api_key=deepseek_api_key,
+ base_url=deepseek_base_url,
+ model=deepseek_model
+ )
self.data_fetcher = SmartMonitorDataFetcher()
self.db = SmartMonitorDB()
self.notification = notification_service # 使用主程序的通知服务
diff --git a/update_env_example.py b/update_env_example.py
index 9a054dc..94b5597 100644
--- a/update_env_example.py
+++ b/update_env_example.py
@@ -18,6 +18,10 @@
# DeepSeek API基础URL(可选,使用默认值即可)
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
+# DeepSeek模型名称(可选,默认:deepseek-chat)
+# 可选模型:deepseek-chat、deepseek-reasoner等
+DEEPSEEK_MODEL_NAME=deepseek-chat
+
# ========== Tushare数据接口(可选)==========
# Tushare Token(可选,用于获取更多金融数据)
From 7fffe31d24a08e6bf6fe3148e93b1bf7d7286113 Mon Sep 17 00:00:00 2001
From: jlzhao6
Date: Tue, 20 Jan 2026 14:14:12 +0800
Subject: [PATCH 8/9] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E6=A8=A1=E5=9E=8B=E5=8F=AA=E8=83=BD=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?deepseek=E5=AE=98=E6=96=B9=E7=9A=84=EF=BC=8C=E4=B8=94=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E5=90=8D=E7=A7=B0=E5=86=99=E6=AD=BB=E9=97=AE=E9=A2=98?=
=?UTF-8?q?=E3=80=82env=E4=B8=AD=E5=A2=9E=E5=8A=A0=20DEEPSEEK=5FMODEL=5FNA?=
=?UTF-8?q?ME=20=E5=8F=82=E6=95=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
2、历史数据存储没有放入data目录下,导致容器容器后数据丢失
---
.gitignore | 20 +-
...11\346\213\251\350\257\264\346\230\216.md" | 258 ++++++++
app.py | 101 ++--
check_qmt_status.py | 211 +++++++
cleanup_root_databases.sh | 40 ++
database_check_report.md | 110 ++++
database_fix_recommendations.md | 147 +++++
database_migration_summary.md | 183 ++++++
env/__init__.py | 0
...46\265\213path\344\277\235\345\255\230.py" | 266 +++++++++
...42\344\277\235\345\255\230\347\211\210.py" | 173 ++++++
longhubang.db | Bin 991232 -> 0 bytes
longhubang_ui.py | 47 +-
low_price_bull_monitor.db | Bin 20480 -> 0 bytes
main_force_batch.db | Bin 7094272 -> 0 bytes
main_force_batch_db.py | 248 +++++---
main_force_history_ui.py | 65 +++
main_force_ui.py | 551 +++++++++++-------
migrate_all_databases_to_data.py | 185 ++++++
portfolio_manager.py | 23 +-
portfolio_stocks.db | Bin 28672 -> 0 bytes
portfolio_ui.py | 16 +-
profit_growth_monitor.db | Bin 20480 -> 0 bytes
sector_strategy.db | Bin 294912 -> 0 bytes
sector_strategy_agents.py | 33 +-
sector_strategy_ui.py | 39 +-
smart_monitor.db | Bin 315392 -> 0 bytes
stock_analysis.db | Bin 22032384 -> 0 bytes
stock_monitor.db | Bin 20480 -> 0 bytes
29 files changed, 2330 insertions(+), 386 deletions(-)
create mode 100644 "AI\346\250\241\345\236\213\351\200\211\346\213\251\350\257\264\346\230\216.md"
create mode 100755 check_qmt_status.py
create mode 100755 cleanup_root_databases.sh
create mode 100644 database_check_report.md
create mode 100644 database_fix_recommendations.md
create mode 100644 database_migration_summary.md
create mode 100644 env/__init__.py
create mode 100644 "env/miniQMT(1\347\216\257\345\242\203)\343\200\220\345\205\210\347\224\250\350\277\231\344\270\252\343\200\221-\347\273\210\347\253\257\347\216\257\345\242\203\346\243\200\346\265\213path\344\277\235\345\255\230.py"
create mode 100644 "env/miniQMT(2\350\264\246\346\210\267)\346\237\245\350\257\242\344\277\235\345\255\230\347\211\210.py"
delete mode 100644 longhubang.db
delete mode 100644 low_price_bull_monitor.db
delete mode 100644 main_force_batch.db
create mode 100644 migrate_all_databases_to_data.py
delete mode 100644 portfolio_stocks.db
delete mode 100644 profit_growth_monitor.db
delete mode 100644 sector_strategy.db
delete mode 100644 smart_monitor.db
delete mode 100644 stock_analysis.db
delete mode 100644 stock_monitor.db
diff --git a/.gitignore b/.gitignore
index 1dfa6a0..98fb92c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,22 @@
.env
/.cursor
/openspec
-TradEnv/
\ No newline at end of file
+TradEnv/
+/.idea/inspectionProfiles/profiles_settings.xml
+/.idea/inspectionProfiles/Project_Default.xml
+/.idea/.gitignore
+/.idea/aiagents-stock.iml
+/.idea/misc.xml
+/.idea/modules.xml
+/.idea/vcs.xml
+/data/longhubang.db
+/data/low_price_bull_monitor.db
+/data/main_force_batch.db
+/data/portfolio_stocks.db
+/data/profit_growth_monitor.db
+/data/sector_strategy.db
+/data/smart_monitor.db
+/data/stock_analysis.db
+/data/stock_monitor.db
+/migrate_databases.sh
+/migrate_databases_by_size.sh
diff --git "a/AI\346\250\241\345\236\213\351\200\211\346\213\251\350\257\264\346\230\216.md" "b/AI\346\250\241\345\236\213\351\200\211\346\213\251\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..ccd0e32
--- /dev/null
+++ "b/AI\346\250\241\345\236\213\351\200\211\346\213\251\350\257\264\346\230\216.md"
@@ -0,0 +1,258 @@
+# AI模型选择机制说明
+
+## 📋 问题回答
+
+### 1. 左侧的AI模型选择会全局默认生效吗?
+
+**答案:部分生效,但不完全全局**
+
+**当前状态**:
+- ✅ 模型选择器会保存到 `st.session_state.selected_model`
+- ❌ 但很多功能模块**强制使用** `config.DEEPSEEK_MODEL_NAME`(从 `.env` 文件读取),**忽略**了 `session_state` 中的选择
+- ✅ 只有部分页面(如"主力选股")会使用页面内的模型选择
+
+**具体表现**:
+- **股票分析页面**:强制使用 `config.DEEPSEEK_MODEL_NAME`,忽略侧边栏选择
+- **批量分析功能**:强制使用 `config.DEEPSEEK_MODEL_NAME`,忽略侧边栏选择
+- **主力选股页面**:使用页面内的模型选择(不是侧边栏的)
+- **智策板块页面**:强制使用 `config.DEEPSEEK_MODEL_NAME`
+
+### 2. 如果选择其他模型,模型信息在哪里配置?
+
+**答案:两个地方**
+
+#### 位置1:模型选项列表 - `model_config.py`
+
+**文件路径**:`model_config.py`
+
+**作用**:定义所有可用的AI模型选项
+
+**内容示例**:
+```python
+model_options = {
+ "deepseek-chat": "DeepSeek Chat (默认)",
+ "deepseek-reasoner": "DeepSeek Reasoner (推理增强)",
+ "qwen-plus": "qwen-plus (阿里百炼)",
+ # ... 更多模型
+}
+```
+
+**如何添加新模型**:
+1. 在 `model_config.py` 的 `model_options` 字典中添加新项
+2. 格式:`"模型ID": "显示名称"`
+
+#### 位置2:默认模型配置 - `.env` 文件
+
+**文件路径**:`.env`(项目根目录)
+
+**作用**:设置系统默认使用的模型
+
+**配置项**:
+```bash
+DEEPSEEK_MODEL_NAME=deepseek-chat
+```
+
+**如何修改默认模型**:
+1. 编辑 `.env` 文件
+2. 修改 `DEEPSEEK_MODEL_NAME` 的值
+3. 值必须是 `model_config.py` 中 `model_options` 的键之一
+
+---
+
+## 🔍 代码实现分析
+
+### 模型选择器(侧边栏)
+
+**位置**:`app.py` 的 `model_selector()` 函数
+
+**代码**:
+```python
+def model_selector():
+ """模型选择器"""
+ st.sidebar.subheader("🤖 AI模型选择")
+
+ # 获取配置文件中的默认模型
+ default_model = config.DEEPSEEK_MODEL_NAME
+
+ # 从 session_state 获取当前选择
+ current_model = st.session_state.get('selected_model', default_model)
+
+ # 显示选择框
+ selected_model = st.sidebar.selectbox(
+ "选择AI模型",
+ options=list(model_options.keys()),
+ ...
+ )
+
+ return selected_model
+```
+
+**问题**:虽然保存到 `session_state`,但很多地方不使用它
+
+### 实际使用情况
+
+#### ❌ 不生效的地方(强制使用配置文件)
+
+**1. 股票分析页面** (`app.py` 的 `run_stock_analysis`)
+```python
+# 强制使用配置文件中的默认模型(忽略 session_state 中的旧值)
+config_model = config.DEEPSEEK_MODEL_NAME
+if config_model in model_options:
+ selected_model = config_model
+else:
+ selected_model = list(model_options.keys())[0]
+
+agents = StockAnalysisAgents(model=selected_model) # 使用配置文件的值
+```
+
+**2. 批量分析功能** (`app.py` 的 `run_batch_analysis`)
+```python
+# 强制使用配置文件中的默认模型
+config_model = config.DEEPSEEK_MODEL_NAME
+if config_model in model_options:
+ selected_model = config_model
+else:
+ selected_model = list(model_options.keys())[0]
+```
+
+**3. 批量分析中的单个股票分析** (`app.py` 的 `analyze_single_stock_for_batch`)
+```python
+# 强制使用配置文件中的默认模型(忽略传入的旧值)
+config_model = config.DEEPSEEK_MODEL_NAME
+if selected_model is None or selected_model == "" or selected_model == "deepseek-chat":
+ selected_model = config_model
+```
+
+#### ✅ 生效的地方
+
+**1. 主力选股页面** (`main_force_ui.py`)
+```python
+# 页面内有自己的模型选择
+model = st.selectbox(
+ "选择AI模型",
+ list(app_model_options.keys()),
+ ...
+)
+
+analyzer = MainForceAnalyzer(model=model) # 使用页面选择的值
+```
+
+---
+
+## 🔧 如何让侧边栏选择全局生效?
+
+### 方案1:修改代码使用 session_state(推荐)
+
+修改所有强制使用 `config.DEEPSEEK_MODEL_NAME` 的地方,改为优先使用 `session_state.selected_model`:
+
+```python
+# 修改前
+config_model = config.DEEPSEEK_MODEL_NAME
+selected_model = config_model
+
+# 修改后
+selected_model = st.session_state.get('selected_model', config.DEEPSEEK_MODEL_NAME)
+if selected_model not in model_options:
+ selected_model = config.DEEPSEEK_MODEL_NAME
+```
+
+**需要修改的文件**:
+1. `app.py` - `run_stock_analysis()` 函数
+2. `app.py` - `run_batch_analysis()` 函数
+3. `app.py` - `analyze_single_stock_for_batch()` 函数
+
+### 方案2:保持现状,使用配置文件
+
+**优点**:
+- 配置持久化(重启后仍然有效)
+- 不依赖 Streamlit session
+
+**缺点**:
+- 需要修改 `.env` 文件才能改变模型
+- 不能通过UI快速切换
+
+---
+
+## 📝 模型配置详细说明
+
+### model_config.py 结构
+
+```python
+model_options = {
+ # 键:模型ID(用于API调用)
+ # 值:显示名称(在UI中显示)
+ "deepseek-chat": "DeepSeek Chat (默认)",
+ "deepseek-reasoner": "DeepSeek Reasoner (推理增强)",
+ # ...
+}
+```
+
+### 添加新模型的步骤
+
+1. **确定模型ID**:从API文档获取模型标识符
+2. **添加到 model_config.py**:
+ ```python
+ model_options = {
+ # ... 现有模型
+ "new-model-id": "新模型显示名称",
+ }
+ ```
+3. **(可选)设置为默认**:在 `.env` 文件中设置
+ ```bash
+ DEEPSEEK_MODEL_NAME=new-model-id
+ ```
+
+### 当前可用的模型
+
+根据 `model_config.py`,当前支持以下模型:
+
+| 模型ID | 显示名称 | 说明 |
+|--------|----------|------|
+| `deepseek-chat` | DeepSeek Chat (默认) | 默认模型 |
+| `deepseek-reasoner` | DeepSeek Reasoner (推理增强) | 推理能力更强 |
+| `qwen-plus` | qwen-plus (阿里百炼) | 阿里云模型 |
+| `qwen-plus-latest` | qwen-plus-latest (阿里百炼) | 最新版本 |
+| `qwen-flash` | qwen-flash (阿里百炼) | 快速版本 |
+| `qwen-turbo` | qwen-turbo (阿里百炼) | 加速版本 |
+| `qwen3-max` | qwen-max (阿里百炼) | 最大版本 |
+| `qwen-long` | qwen-long (阿里百炼) | 长文本版本 |
+| `deepseek-ai/DeepSeek-R1-0528-Qwen3-8B` | DeepSeek-R1 免费(硅基流动) | 免费模型 |
+| `Qwen/Qwen2.5-7B-Instruct` | Qwen 免费(硅基流动) | 免费模型 |
+| `Pro/deepseek-ai/DeepSeek-V3.1-Terminus` | DeepSeek-V3.1-Terminus (硅基流动) | 专业版 |
+| `deepseek-ai/DeepSeek-R1` | DeepSeek-R1 (硅基流动) | R1版本 |
+| `Qwen/Qwen3-235B-A22B-Thinking-2507` | Qwen3-235B (硅基流动) | 大模型 |
+| `zai-org/GLM-4.6` | 智谱(硅基流动) | 智谱模型 |
+| `moonshotai/Kimi-K2-Instruct-0905` | Kimi (硅基流动) | Kimi模型 |
+| `Ring-1T` | 蚂蚁百灵 (硅基流动) | 蚂蚁模型 |
+| `step3` | 阶跃星辰(硅基流动) | 阶跃模型 |
+
+---
+
+## 🎯 建议
+
+### 短期方案(保持现状)
+
+1. **修改默认模型**:编辑 `.env` 文件,修改 `DEEPSEEK_MODEL_NAME`
+2. **主力选股页面**:使用页面内的模型选择(已生效)
+
+### 长期方案(推荐)
+
+修改代码,让侧边栏的模型选择真正全局生效:
+
+1. 修改 `app.py` 中所有使用模型的地方
+2. 优先使用 `st.session_state.selected_model`
+3. 如果不存在或无效,回退到 `config.DEEPSEEK_MODEL_NAME`
+
+这样可以:
+- ✅ 通过UI快速切换模型
+- ✅ 配置持久化(通过 `.env`)
+- ✅ 用户体验更好
+
+---
+
+## 📚 相关文件
+
+- **模型选项定义**:`model_config.py`
+- **默认模型配置**:`.env` 文件中的 `DEEPSEEK_MODEL_NAME`
+- **模型选择器**:`app.py` 的 `model_selector()` 函数
+- **配置文件加载**:`config.py`
diff --git a/app.py b/app.py
index 24dd612..609fec7 100644
--- a/app.py
+++ b/app.py
@@ -60,6 +60,30 @@ def model_selector():
return selected_model
+
+def get_selected_model():
+ """
+ 获取当前选择的AI模型
+ 优先使用session_state中的选择,如果不存在或无效,则使用配置文件中的默认值
+
+ Returns:
+ str: 模型ID
+ """
+ # 优先使用session_state中的选择
+ selected_model = st.session_state.get('selected_model')
+
+ # 如果session_state中有值且在有效选项中,使用它
+ if selected_model and selected_model in model_options:
+ return selected_model
+
+ # 否则使用配置文件中的默认值
+ default_model = config.DEEPSEEK_MODEL_NAME
+ if default_model in model_options:
+ return default_model
+
+ # 如果配置文件的值也不在选项中,使用第一个选项
+ return list(model_options.keys())[0]
+
# 自定义CSS样式 - 专业版
st.markdown("""