From ad5865d3b427dacfb29788a726eca3eb0393b51f Mon Sep 17 00:00:00 2001 From: oficcejo Date: Fri, 12 Dec 2025 19:46:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=80=89=E8=82=A1=E7=AD=96?= =?UTF-8?q?=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 29 ++ app.py | 31 +- docs/TDX_API配置快速指南.md | 246 +++++++++++ docs/三重滤网策略保存.py | 416 +++++++++++++++++++ docs/低价擒牛功能说明.md | 240 +++++++++++ docs/低价擒牛实现总结.md | 260 ++++++++++++ docs/低价擒牛快速开始.md | 196 +++++++++ docs/低价擒牛策略监控功能实现总结.md | 365 +++++++++++++++++ docs/低价擒牛策略监控配置说明.md | 239 +++++++++++ docs/均线回归策略保存.py | 460 +++++++++++++++++++++ longhubang.db | Bin 778240 -> 991232 bytes longhubang_data.py | 5 +- low_price_bull_monitor.db | Bin 0 -> 20480 bytes low_price_bull_monitor.py | 375 +++++++++++++++++ low_price_bull_monitor_ui.py | 293 +++++++++++++ low_price_bull_selector.py | 157 +++++++ low_price_bull_service.py | 430 +++++++++++++++++++ low_price_bull_strategy.py | 277 +++++++++++++ low_price_bull_ui.py | 589 +++++++++++++++++++++++++++ notification_service.py | 42 +- smart_monitor.db | Bin 151552 -> 315392 bytes smart_monitor_engine.py | 14 +- stock_analysis.db | Bin 22032384 -> 22032384 bytes stock_monitor.db | Bin 20480 -> 20480 bytes test_tdx_api.py | 174 ++++++++ 25 files changed, 4818 insertions(+), 20 deletions(-) create mode 100644 docs/TDX_API配置快速指南.md create mode 100644 docs/三重滤网策略保存.py create mode 100644 docs/低价擒牛功能说明.md create mode 100644 docs/低价擒牛实现总结.md create mode 100644 docs/低价擒牛快速开始.md create mode 100644 docs/低价擒牛策略监控功能实现总结.md create mode 100644 docs/低价擒牛策略监控配置说明.md create mode 100644 docs/均线回归策略保存.py create mode 100644 low_price_bull_monitor.db create mode 100644 low_price_bull_monitor.py create mode 100644 low_price_bull_monitor_ui.py create mode 100644 low_price_bull_selector.py create mode 100644 low_price_bull_service.py create mode 100644 low_price_bull_strategy.py create mode 100644 low_price_bull_ui.py create mode 100644 test_tdx_api.py diff --git a/.env.example b/.env.example index 19d9873..5529c41 100644 --- a/.env.example +++ b/.env.example @@ -93,3 +93,32 @@ WEBHOOK_KEYWORD= # ========== 时区配置 ========== # 系统时区设置(可选) TZ=Asia/Shanghai + + +# ========== TDX数据源API配置(策略监控必须)========== +# TDX API服务地址 +# 说明: +# - 用于获取股票K线数据和技术指标 +# - 默认本地服务地址:http://127.0.0.1:5000 +# - 如果使用远程服务,请修改为实际地址 +# API接口: +# - /api/kline?code=000001&type=day # 获取日K线数据 +# - /api/quote?code=000001 # 获取实时行情 +# - /api/health # 健康检查 +TDX_BASE_URL=http://127.0.0.1:5000 + + +# ========== 低价擒牛策略监控配置 ========== +# 扫描间隔(秒) +# 说明: +# - 监控服务扫描股票的时间间隔 +# - 建议范围:30-300秒 +# - 默认值:60秒(每分钟扫描1次) +LOW_PRICE_BULL_SCAN_INTERVAL=60 + +# 持股天数限制 +# 说明: +# - 持股达到该天数后自动提醒卖出 +# - 按自然日计算,不区分交易日 +# - 默认值:5天 +LOW_PRICE_BULL_HOLDING_DAYS=5 diff --git a/app.py b/app.py index d3eed2e..aeaac58 100644 --- a/app.py +++ b/app.py @@ -294,7 +294,7 @@ def main(): if st.button("🏠 股票分析", width='stretch', key="nav_home", help="返回首页,进行单只股票的深度分析"): # 清除所有功能页面标志 for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force', - 'show_sector_strategy', 'show_longhubang', 'show_portfolio']: + 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] @@ -307,7 +307,14 @@ def main(): if st.button("💰 主力选股", width='stretch', key="nav_main_force", help="基于主力资金流向的选股策略"): st.session_state.show_main_force = True for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy', - 'show_longhubang', 'show_portfolio']: + 'show_longhubang', 'show_portfolio', 'show_low_price_bull']: + if key in st.session_state: + del st.session_state[key] + + if st.button("🐂 低价擒牛", width='stretch', key="nav_low_price_bull", help="低价高成长股票筛选策略"): + st.session_state.show_low_price_bull = True + for key in ['show_history', 'show_monitor', 'show_config', 'show_sector_strategy', + 'show_longhubang', 'show_portfolio', 'show_main_force']: if key in st.session_state: del st.session_state[key] @@ -318,14 +325,14 @@ def main(): if st.button("🎯 智策板块", width='stretch', key="nav_sector_strategy", help="AI板块策略分析"): st.session_state.show_sector_strategy = True for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force', - 'show_longhubang', 'show_portfolio', 'show_smart_monitor']: + 'show_longhubang', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] if st.button("🐉 智瞰龙虎", width='stretch', key="nav_longhubang", help="龙虎榜深度分析"): st.session_state.show_longhubang = True for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force', - 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor']: + 'show_sector_strategy', 'show_portfolio', 'show_smart_monitor', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] @@ -336,21 +343,21 @@ def main(): if st.button("📊 持仓分析", width='stretch', key="nav_portfolio", help="投资组合分析与定时跟踪"): st.session_state.show_portfolio = True for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force', - 'show_sector_strategy', 'show_longhubang', 'show_smart_monitor']: + 'show_sector_strategy', 'show_longhubang', 'show_smart_monitor', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] if st.button("🤖 AI盯盘", width='stretch', key="nav_smart_monitor", help="DeepSeek AI自动盯盘决策交易(支持A股T+1)"): st.session_state.show_smart_monitor = True for key in ['show_history', 'show_monitor', 'show_config', 'show_main_force', - 'show_sector_strategy', 'show_longhubang', 'show_portfolio']: + 'show_sector_strategy', 'show_longhubang', 'show_portfolio', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] if st.button("📡 实时监测", width='stretch', key="nav_monitor", help="价格监控与预警提醒"): st.session_state.show_monitor = True for key in ['show_history', 'show_main_force', 'show_longhubang', 'show_portfolio', - 'show_config', 'show_sector_strategy', 'show_smart_monitor']: + 'show_config', 'show_sector_strategy', 'show_smart_monitor', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] @@ -360,7 +367,7 @@ def main(): if st.button("📖 历史记录", width='stretch', key="nav_history", help="查看历史分析记录"): st.session_state.show_history = True for key in ['show_monitor', 'show_longhubang', 'show_portfolio', 'show_config', - 'show_main_force', 'show_sector_strategy']: + 'show_main_force', 'show_sector_strategy', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] @@ -368,7 +375,7 @@ def main(): if st.button("⚙️ 环境配置", width='stretch', key="nav_config", help="系统设置与API配置"): st.session_state.show_config = True for key in ['show_history', 'show_monitor', 'show_main_force', 'show_sector_strategy', - 'show_longhubang', 'show_portfolio']: + 'show_longhubang', 'show_portfolio', 'show_low_price_bull']: if key in st.session_state: del st.session_state[key] @@ -460,6 +467,12 @@ def main(): if 'show_main_force' in st.session_state and st.session_state.show_main_force: display_main_force_selector() return + + # 检查是否显示低价擒牛 + if 'show_low_price_bull' in st.session_state and st.session_state.show_low_price_bull: + from low_price_bull_ui import display_low_price_bull + display_low_price_bull() + return # 检查是否显示智策板块 if 'show_sector_strategy' in st.session_state and st.session_state.show_sector_strategy: diff --git a/docs/TDX_API配置快速指南.md b/docs/TDX_API配置快速指南.md new file mode 100644 index 0000000..46c1c8e --- /dev/null +++ b/docs/TDX_API配置快速指南.md @@ -0,0 +1,246 @@ +# TDX API配置快速指南 + +## 问题说明 + +如果您看到以下警告: + +``` +WARNING:low_price_bull_service:TDX数据源未配置,无法获取股票数据 +``` + +说明系统无法连接到TDX API服务,需要进行配置。 + +## 解决方案 + +### 步骤1:配置TDX API地址 + +在 `.env` 文件中添加以下配置: + +```bash +# TDX数据源API配置 +TDX_BASE_URL=http://127.0.0.1:5000 +``` + +### 步骤2:确保TDX API服务已启动 + +TDX API服务需要单独启动,请检查: + +1. **检查服务是否运行** + ```bash + # 在浏览器中访问 + http://127.0.0.1:5000/api/health + + # 或使用curl命令 + curl http://127.0.0.1:5000/api/health + ``` + +2. **如果服务未启动** + - 请启动您的TDX API服务 + - 默认端口:5000 + - 如使用其他端口,请修改.env中的URL + +### 步骤3:测试配置 + +运行测试脚本验证配置: + +```bash +python test_tdx_api.py +``` + +**期望输出**: + +``` +============================================================ +TDX API配置测试 +============================================================ + +1. TDX API地址: http://127.0.0.1:5000 + +2. 测试健康检查接口... + ✅ 健康检查成功 + 响应: OK + +3. 测试K线数据接口... + 测试股票: 000001 + ✅ K线数据获取成功 + 数据条数: 250 + + 最新K线数据: + - 日期: 2024-12-12 + - 开盘: 10.50 + - 收盘: 10.60 + - 最高: 10.80 + - 最低: 10.30 + - 成交量: 1000000 + + ✅ 数据量充足,可以计算MA20(需要至少20条) + +4. 测试均线计算... + ✅ 均线计算成功 + - 收盘价: 10.60 + - MA5: 10.55 + - MA20: 10.45 + - 趋势: 🟢 MA5 > MA20 (多头) + +============================================================ +✅ 所有测试通过!TDX API配置正常 +============================================================ +``` + +## TDX API接口说明 + +### 核心接口 + +| 接口 | 说明 | 示例 | +|------|------|------| +| /api/health | 健康检查 | - | +| /api/kline | K线数据 | ?code=000001&type=day | +| /api/quote | 五档行情 | ?code=000001 | +| /api/stock-info | 综合信息 | ?code=000001 | + +### K线数据接口详情 + +**URL**: `/api/kline` + +**参数**: +- `code`: 股票代码(必填) + - 支持格式: + - 纯数字:`000001`、`600000` + - 市场前缀:`SZ000001`、`SH600000` + - 示例:000001(平安银行) + - 示例:600000(浦发银行) +- `type`: K线类型(必填) + - `day`: 日K线 + - `week`: 周K线 + - `month`: 月K线 + +**代码格式说明**: +- 系统会自动处理不同格式的股票代码 +- 如果使用`SZ`/`SH`前缀失败,会自动重试纯数字代码 +- 支持带后缀的代码(如`002259.SZ`),系统会自动去除后缀 + +**返回格式**: +```json +[ + { + "date": "2024-12-12", + "open": 10.50, + "high": 10.80, + "low": 10.30, + "close": 10.60, + "volume": 1000000 + }, + ... +] +``` + +**使用示例**: +```bash +# 获取平安银行日K线 +curl "http://127.0.0.1:5000/api/kline?code=000001&type=day" +``` + +## 常见问题 + +### Q1: 连接失败,显示连接超时? + +**原因**: +- TDX API服务未启动 +- 防火墙阻止了连接 +- 端口被占用 + +**解决方案**: +1. 检查TDX API服务是否启动 +2. 检查端口5000是否被占用 +3. 尝试使用其他端口并修改.env配置 + +### Q2: 健康检查失败,HTTP 404? + +**原因**: +- TDX API服务版本不支持`/api/health`接口 +- URL配置错误 + +**解决方案**: +1. 尝试访问其他接口,如`/api/kline` +2. 检查.env中的TDX_BASE_URL是否正确 +3. 确认服务地址和端口 + +### Q3: K线数据获取失败? + +**原因**: +- 股票代码不存在 +- 接口参数错误 +- 数据源未就绪 + +**解决方案**: +1. 使用已知存在的股票代码测试(如000001) +2. 检查参数格式是否正确 +3. 查看TDX API服务日志 + +### Q4: 数据量不足,无法计算MA20? + +**原因**: +- TDX API返回的K线数据少于20条 +- 新上市股票数据不足 + +**解决方案**: +1. 这是正常情况,系统会自动跳过 +2. 等待股票积累更多交易日数据 +3. 监控服务会在下次扫描时重试 + +### Q5: 如何使用远程TDX API服务? + +如果TDX API服务部署在其他服务器上: + +```bash +# .env配置 +TDX_BASE_URL=http://192.168.1.100:5000 + +# 或使用域名 +TDX_BASE_URL=http://tdx-api.example.com:5000 +``` + +### Q6: 如何修改扫描间隔? + +在.env文件中修改: + +```bash +# 扫描间隔(秒) +LOW_PRICE_BULL_SCAN_INTERVAL=60 # 默认60秒,可改为30-300秒 +``` + +或在监控面板的"⚙️ 监控配置"中动态修改。 + +## 配置检查清单 + +- [ ] ✅ .env文件中已配置TDX_BASE_URL +- [ ] ✅ TDX API服务已启动 +- [ ] ✅ 可以访问http://127.0.0.1:5000/api/health +- [ ] ✅ 运行test_tdx_api.py测试通过 +- [ ] ✅ 在监控面板启动监控服务 +- [ ] ✅ 配置了Webhook通知(可选) + +## 下一步 + +配置完成后: + +1. 进入"低价擒牛"选股板块 +2. 执行选股,查看结果 +3. 点击"➕ 加入策略监控" +4. 进入"📊 策略监控"面板 +5. 点击"▶️ 启动监控服务" +6. 监控服务将每60秒扫描一次 + +## 需要帮助? + +如果仍然遇到问题,请: + +1. 查看控制台错误日志 +2. 运行`python test_tdx_api.py`获取详细错误信息 +3. 检查TDX API服务日志 +4. 确认网络连接正常 + +--- + +**文档版本**: v1.0 +**更新日期**: 2024-12-12 diff --git a/docs/三重滤网策略保存.py b/docs/三重滤网策略保存.py new file mode 100644 index 0000000..a64c8d1 --- /dev/null +++ b/docs/三重滤网策略保存.py @@ -0,0 +1,416 @@ +''' +三重滤网交易系统 V2.0 +by Alexander Elder (优化版) + +核心改进: +1. 第一重滤网:月线MACD金叉/多头(更稳定的趋势判断) +2. 第二重滤网:日线RSI/KDJ超卖回调(放宽条件) +3. 第三重滤网:价格企稳或突破(更灵活的入场) +''' + +import jqdata +import numpy as np + +## 初始化函数 +def initialize(context): + set_benchmark('000300.XSHG') + set_option('use_real_price', True) + set_option('order_volume_ratio', 1) + set_order_cost(OrderCost(open_tax=0, close_tax=0.001, + open_commission=0.0003, close_commission=0.0003, + close_today_commission=0, min_commission=5), type='stock') + + # ========== 策略参数 ========== + g.stocknum = 10 # 增加持仓数 + g.max_position_ratio = 0.98 + + # 第一重滤网参数(月线MACD) + g.macd_fast = 12 + g.macd_slow = 26 + g.macd_signal = 9 + + # 第二重滤网参数(日线震荡 - 放宽) + g.rsi_period = 14 + g.rsi_oversold = 50 # 放宽到50 + g.rsi_low = 35 # 极度超卖 + g.kdj_oversold = 40 # 放宽到40 + + # 第三重滤网参数(入场确认 - 放宽) + g.ma_short = 5 # 5日均线 + g.ma_mid = 20 # 20日均线 + + # 止盈止损参数 + g.stop_loss = 0.06 + g.take_profit = 0.20 + g.trailing_stop = 0.08 + + # 市值筛选(亿) + g.min_market_cap = 20 + g.max_market_cap = 1500 + + # 记录 + g.highest_profit = {} + g.hold_days = {} + + # 定时任务 + run_daily(morning_screen, '09:35') + run_daily(check_positions, '14:00') + run_daily(afternoon_trade, '14:50') + +## 早盘筛选 +def morning_screen(context): + g.buy_list = triple_screen_filter(context) + log.info("【三重滤网】筛选出 %d 只股票" % len(g.buy_list)) + +## 三重滤网筛选 +def triple_screen_filter(context): + # 基础选股 + q = query( + valuation.code, + valuation.market_cap + ).filter( + valuation.market_cap.between(g.min_market_cap, g.max_market_cap) + ).order_by( + valuation.market_cap.asc() + ).limit(500) + + df = get_fundamentals(q) + if df.empty: + return [] + + stock_list = list(df['code']) + stock_list = filter_basic(stock_list) + + # 三重滤网筛选 + candidates = [] + for stock in stock_list[:150]: + result = check_triple_screen(stock) + if result['pass']: + candidates.append((stock, result['score'])) + + # 按评分排序 + candidates.sort(key=lambda x: x[1], reverse=True) + return [c[0] for c in candidates[:g.stocknum * 3]] + +## 检查三重滤网信号 +def check_triple_screen(stock): + """ + 三重滤网检查 V2.0: + 1. 第一重:月线MACD金叉/多头 + 2. 第二重:日线RSI/KDJ回调 + 3. 第三重:价格企稳或突破 + """ + result = {'pass': False, 'score': 0} + + try: + # 获取更多日线数据(计算月线MACD需要约130天) + df = attribute_history(stock, 150, '1d', ['close', 'high', 'low'], skip_paused=True) + if len(df) < 130: + return result + + close = df['close'].values + high = df['high'].values + low = df['low'].values + + # ========== 第一重滤网:月线MACD ========== + # 模拟月线:每20天取收盘价(或用月末价格) + monthly_close = [] + for i in range(19, len(close), 20): + monthly_close.append(close[i]) + + if len(monthly_close) < 6: + # 数据不足,改用日线MACD判断大趋势 + dif, dea, macd = calculate_macd(close) + if dif[-1] <= dea[-1]: # MACD死叉 + return result + if dif[-1] <= 0: # DIF在零轴下方 + return result + else: + # 计算月线MACD + m_dif, m_dea, m_macd = calculate_macd(np.array(monthly_close)) + + # 月线MACD条件(放宽): + # 1. MACD金叉(DIF上穿DEA)或 + # 2. MACD柱由绿变红 或 + # 3. DIF > 0 且 DIF > DEA(多头) + macd_golden = m_dif[-1] > m_dea[-1] and m_dif[-2] <= m_dea[-2] + macd_turn_red = m_macd[-1] > 0 and m_macd[-2] <= 0 + macd_bullish = m_dif[-1] > 0 and m_dif[-1] > m_dea[-1] + + if not (macd_golden or macd_turn_red or macd_bullish): + return result + + result['score'] += 30 # 趋势分 + + # ========== 第二重滤网:日线震荡指标 ========== + rsi = calculate_rsi(close, g.rsi_period) + k, d, j = calculate_kdj(high, low, close) + + # RSI条件(放宽): + # 1. RSI < 50(相对低位)或 + # 2. RSI从低位回升 + rsi_current = rsi[-1] if len(rsi) > 0 else 50 + rsi_prev = rsi[-2] if len(rsi) > 1 else 50 + + rsi_signal = (rsi_current < g.rsi_oversold) or \ + (rsi_prev < g.rsi_low and rsi_current > rsi_prev) or \ + (rsi_current < 60 and rsi_current > rsi_prev) + + # KDJ条件(放宽): + # 1. K < D 后金叉 或 + # 2. J < 40 或 + # 3. K/D都在低位回升 + kdj_golden = k[-1] > d[-1] and k[-2] <= d[-2] + kdj_oversold = j[-1] < g.kdj_oversold or k[-1] < g.kdj_oversold + kdj_rising = k[-1] > k[-2] and d[-1] > d[-2] and k[-1] < 60 + + kdj_signal = kdj_golden or kdj_oversold or kdj_rising + + if not (rsi_signal or kdj_signal): + return result + + result['score'] += 30 + + # ========== 第三重滤网:入场确认(放宽)========== + current_price = close[-1] + ma5 = np.mean(close[-g.ma_short:]) + ma20 = np.mean(close[-g.ma_mid:]) + + # 入场条件(满足任一): + # 1. 价格站上5日均线 + # 2. 价格接近20日均线(±3%) + # 3. 价格突破近5日高点 + recent_high = np.max(high[-6:-1]) + + price_above_ma5 = current_price > ma5 + price_near_ma20 = abs(current_price - ma20) / ma20 < 0.03 + price_breakout = current_price > recent_high * 0.99 + + if not (price_above_ma5 or price_near_ma20 or price_breakout): + return result + + result['score'] += 40 + + # 额外加分 + if rsi_current < 35: + result['score'] += 15 + if kdj_golden: + result['score'] += 10 + if current_price > ma5 > ma20: + result['score'] += 10 + + result['pass'] = True + return result + + except: + return result + +## 计算MACD +def calculate_macd(close, fast=12, slow=26, signal=9): + ema_fast = calculate_ema(close, fast) + ema_slow = calculate_ema(close, slow) + dif = ema_fast - ema_slow + dea = calculate_ema(dif, signal) + macd = (dif - dea) * 2 + return dif, dea, macd + +## 计算EMA +def calculate_ema(data, period): + ema = np.zeros(len(data)) + ema[0] = data[0] + multiplier = 2 / (period + 1) + for i in range(1, len(data)): + ema[i] = (data[i] - ema[i-1]) * multiplier + ema[i-1] + return ema + +## 计算RSI +def calculate_rsi(close, period=14): + delta = np.diff(close) + gain = np.where(delta > 0, delta, 0) + loss = np.where(delta < 0, -delta, 0) + + avg_gain = np.zeros(len(delta)) + avg_loss = np.zeros(len(delta)) + + avg_gain[period-1] = np.mean(gain[:period]) + avg_loss[period-1] = np.mean(loss[:period]) + + for i in range(period, len(delta)): + avg_gain[i] = (avg_gain[i-1] * (period-1) + gain[i]) / period + avg_loss[i] = (avg_loss[i-1] * (period-1) + loss[i]) / period + + rs = avg_gain / (avg_loss + 1e-10) + rsi = 100 - 100 / (1 + rs) + return rsi + +## 计算KDJ +def calculate_kdj(high, low, close, n=9, m1=3, m2=3): + length = len(close) + rsv = np.zeros(length) + k = np.zeros(length) + d = np.zeros(length) + j = np.zeros(length) + + for i in range(n-1, length): + hn = np.max(high[i-n+1:i+1]) + ln = np.min(low[i-n+1:i+1]) + rsv[i] = (close[i] - ln) / (hn - ln + 1e-10) * 100 + + k[n-1] = 50 + d[n-1] = 50 + + for i in range(n, length): + k[i] = (m1-1)/m1 * k[i-1] + 1/m1 * rsv[i] + d[i] = (m2-1)/m2 * d[i-1] + 1/m2 * k[i] + j[i] = 3 * k[i] - 2 * d[i] + + return k, d, j + +## 检查持仓 +def check_positions(context): + if len(context.portfolio.positions) == 0: + return + + for stock in list(context.portfolio.positions.keys()): + position = context.portfolio.positions[stock] + if position.closeable_amount <= 0: + continue + + cost = position.avg_cost + current_price = position.price + if cost <= 0: + continue + + profit_ratio = (current_price - cost) / cost + g.hold_days[stock] = g.hold_days.get(stock, 0) + 1 + + if stock not in g.highest_profit: + g.highest_profit[stock] = profit_ratio + else: + g.highest_profit[stock] = max(g.highest_profit[stock], profit_ratio) + + highest = g.highest_profit[stock] + + # === 止损 === + if profit_ratio < -g.stop_loss: + log.info("【止损】%s 亏损 %.2f%%" % (stock, profit_ratio * 100)) + order_target(stock, 0) + clean_stock_data(stock) + continue + + # === 趋势反转卖出 === + if check_trend_reversal(stock): + log.info("【趋势反转】%s 周线趋势转弱" % stock) + order_target(stock, 0) + clean_stock_data(stock) + continue + + # === 移动止盈 === + if highest >= g.take_profit: + drawdown = highest - profit_ratio + allowed_drawdown = min(g.trailing_stop + highest * 0.4, 0.18) + if drawdown >= allowed_drawdown: + log.info("【移动止盈】%s 最高%.1f%% 回撤%.1f%%" % + (stock, highest*100, drawdown*100)) + order_target(stock, 0) + clean_stock_data(stock) + continue + + # === 分批止盈 === + if profit_ratio >= g.take_profit * 2: + sell_amount = int(position.closeable_amount * 0.3 / 100) * 100 + if sell_amount >= 100: + log.info("【止盈】%s +%.1f%%" % (stock, profit_ratio*100)) + order(stock, -sell_amount) + +## 检查趋势反转(使用MACD) +def check_trend_reversal(stock): + try: + df = attribute_history(stock, 60, '1d', ['close'], skip_paused=True) + if len(df) < 50: + return False + + close = df['close'].values + dif, dea, macd = calculate_macd(close) + + # MACD死叉且DIF < 0 + if dif[-1] < dea[-1] and dif[-1] < 0: + return True + + # MACD连续3天下降且为负 + if macd[-1] < 0 and macd[-2] < 0 and macd[-3] < 0: + if macd[-1] < macd[-2] < macd[-3]: + return True + + return False + except: + return False + +## 清理数据 +def clean_stock_data(stock): + for d in [g.highest_profit, g.hold_days]: + if stock in d: + del d[stock] + +## 尾盘交易 +def afternoon_trade(context): + buy_stocks(context) + +## 买入函数 +def buy_stocks(context): + if not hasattr(g, 'buy_list') or not g.buy_list: + return + + position_count = len(context.portfolio.positions) + if position_count >= g.stocknum: + return + + available_cash = context.portfolio.available_cash * g.max_position_ratio + buy_count = min(g.stocknum - position_count, len(g.buy_list)) + if buy_count <= 0 or available_cash < 10000: + return + + cash_per_stock = available_cash / buy_count + + bought = 0 + for stock in g.buy_list: + if bought >= buy_count: + break + if stock in context.portfolio.positions: + continue + + # 再次确认三重滤网信号 + result = check_triple_screen(stock) + if result['pass']: + order_value(stock, cash_per_stock) + log.info("【买入】%s 评分:%.0f" % (stock, result['score'])) + g.highest_profit[stock] = 0 + g.hold_days[stock] = 0 + bought += 1 + +## 基础过滤 +def filter_basic(stock_list): + if not stock_list: + return [] + + current_data = get_current_data() + filtered = [] + + for stock in stock_list: + if current_data[stock].paused: + continue + if current_data[stock].is_st: + continue + if 'ST' in current_data[stock].name or '*' in current_data[stock].name: + continue + if stock.startswith('688') or stock.startswith('8') or stock.startswith('4'): + continue + if current_data[stock].last_price >= current_data[stock].high_limit: + continue + if current_data[stock].last_price <= current_data[stock].low_limit: + continue + + filtered.append(stock) + + return filtered + diff --git a/docs/低价擒牛功能说明.md b/docs/低价擒牛功能说明.md new file mode 100644 index 0000000..0e57502 --- /dev/null +++ b/docs/低价擒牛功能说明.md @@ -0,0 +1,240 @@ +# 低价擒牛功能说明 + +## 功能概述 + +**低价擒牛**是一个专注于筛选低价高成长股票的选股策略板块,结合量化交易策略,帮助投资者发现潜在的价值洼地。 + +## 选股策略 + +### 筛选条件 + +| 条件 | 说明 | +|------|------| +| 股价 | < 10元 | +| 净利润增长率 | ≥ 100%(净利润同比增长率) | +| 市场板块 | 深圳A股 | +| 排除项 | ST股票、科创板、创业板 | +| 排序方式 | 按成交额由小至大排名 | + +### 策略理念 + +1. **低价优势**:股价低于10元,易于散户参与,具有更大的上涨空间 +2. **高成长性**:净利润增长率≥100%,说明公司业绩高速增长 +3. **流动性考虑**:优先选择成交额较小的股票,避免过度热门 +4. **风险控制**:排除ST、科创板、创业板,降低风险 + +## 量化交易策略 + +### 资金与仓位管理 + +| 参数 | 配置 | 说明 | +|------|------|------| +| 初始资金 | 100万元 | 策略启动资金 | +| 持股周期 | 5天 | 单只股票最长持有时间 | +| 交易仓位 | 满仓 | 使用所有可用资金 | +| 个股最大持仓 | 40% | 单只股票最多占用40%资金 | +| 账户最大持股数 | 4只 | 同时最多持有4只股票 | +| 单日最大买入数 | 2只 | 每天最多买入2只新股票 | + +### 买卖择时规则 + +#### 买入时机 +- **时间点**:开盘买入 +- **优先级**:按成交额由小到大排序 +- **执行条件**: + - 未达到最大持股数(4只) + - 当日买入数量未达上限(2只) + - 有足够的可用资金 + +#### 卖出时机 +满足以下**任一**条件即卖出: +1. **技术信号**:MA5日线下穿MA20日线 +2. **时间到期**:持股满5天 + +### 策略优势 + +1. ✅ **严格的仓位控制**:避免单一股票风险过大 +2. ✅ **轮动机制**:卖出后释放资金,继续买入新标的 +3. ✅ **纪律性强**:明确的买卖规则,避免情绪化交易 +4. ✅ **风险分散**:最多持有4只股票,分散风险 + +## 使用步骤 + +### 1. 进入功能 +在左侧菜单栏点击:**🎯 选股板块** → **🐂 低价擒牛** + +### 2. 设置参数 +- **筛选数量**:选择展示的股票数量(默认5只) + +### 3. 开始选股 +点击 **🚀 开始低价擒牛选股** 按钮 + +系统将: +- 调用问财接口获取符合条件的股票 +- 按成交额排序 +- 展示前N只股票 + +### 4. 查看结果 + +#### 股票列表 +- 展示每只股票的基本信息 +- 包含股价、净利润增长率、成交额等关键指标 +- 可下载完整数据CSV文件 + +#### 钉钉通知 +如果配置了Webhook,系统会自动发送钉钉消息,包含: +- 筛选策略说明 +- 精选股票列表 +- 关键财务指标 + +### 5. 量化交易模拟 + +#### 策略模拟 +点击 **🎮 开始策略模拟** 可以查看: +- 模拟买入信号 +- 当前持仓情况 +- 账户资金状态 + +#### 实盘交易(可选) +如需使用MiniQMT进行实盘交易: +1. 配置环境变量(详见环境配置) +2. 点击 **🔗 连接MiniQMT实盘** +3. 系统将自动执行买卖操作 + +## 配置说明 + +### Webhook通知配置 + +在 `.env` 文件中配置: + +```bash +# Webhook通知配置 +WEBHOOK_ENABLED=true +WEBHOOK_URL=your_dingtalk_webhook_url +WEBHOOK_TYPE=dingtalk +WEBHOOK_KEYWORD=aiagents通知 +``` + +### MiniQMT交易配置 + +在 `.env` 文件中配置: + +```bash +# MiniQMT配置 +MINIQMT_ENABLED=true +MINIQMT_ACCOUNT_ID=your_account_id +``` + +## 风险提示 + +⚠️ **重要提醒**: + +1. **历史业绩不代表未来收益** + - 净利润高增长可能不可持续 + - 低价股波动性较大 + +2. **交易成本** + - 实际交易存在滑点 + - 需要考虑手续费和印花税 + +3. **市场风险** + - 深圳A股市场波动较大 + - 小盘股流动性风险 + +4. **技术风险** + - MA均线信号可能滞后 + - 5天持股周期可能错过更好的卖点 + +5. **策略局限** + - 本策略为机械化交易策略 + - 无法应对突发事件(如重大利空) + +## 策略改进建议 + +### 可优化方向 + +1. **动态仓位管理** + - 根据市场情绪调整仓位 + - 引入风险评分机制 + +2. **止损机制** + - 添加固定比例止损(如-5%) + - 避免单只股票亏损过大 + +3. **择时优化** + - 结合更多技术指标(MACD、RSI等) + - 考虑大盘走势 + +4. **基本面过滤** + - 添加财务指标筛选 + - 排除财务造假风险股 + +## 常见问题 + +### Q1: 为什么只选深圳A股? +A: 深圳A股集中了大量中小盘股票,符合"低价高成长"的特征。上海主板以大盘蓝筹为主,不太符合本策略定位。 + +### Q2: 成交额排序的意义是什么? +A: 成交额小的股票往往还未被市场充分发现,具有更大的潜在空间。但也要注意流动性风险。 + +### Q3: 持股5天的依据是什么? +A: 5天是一个交易周(一周)的时间,可以让股票有足够的时间展现其趋势,同时避免长期持有的风险。 + +### Q4: 能否调整策略参数? +A: 当前版本参数固定,未来版本将支持自定义参数配置。 + +### Q5: 模拟交易和实盘有什么区别? +A: 模拟交易不考虑滑点、手续费等实际成本,仅供策略验证。实盘交易需要接入MiniQMT,会有实际的资金变动。 + +## 技术实现 + +### 核心模块 + +1. **low_price_bull_selector.py** + - 使用pywencai获取数据 + - 数据清洗和格式化 + +2. **low_price_bull_strategy.py** + - 量化交易策略实现 + - 仓位管理和买卖信号 + +3. **low_price_bull_ui.py** + - Streamlit界面展示 + - 用户交互逻辑 + +### 数据流程 + +``` +用户输入参数 + ↓ +调用pywencai接口 + ↓ +获取符合条件的股票 + ↓ +按成交额排序 + ↓ +展示股票列表 + ↓ +发送钉钉通知(可选) + ↓ +执行量化策略(可选) +``` + +## 更新日志 + +### v1.0 (2024-12-12) +- ✅ 初始版本发布 +- ✅ 实现基础选股功能 +- ✅ 集成钉钉通知 +- ✅ 量化策略模拟 +- ✅ MiniQMT接口支持 + +## 联系与反馈 + +如有问题或建议,请通过以下方式反馈: +- GitHub Issues +- 系统内置反馈功能 + +--- + +**免责声明**:本功能仅供学习和研究使用,不构成任何投资建议。股市有风险,投资需谨慎。 diff --git a/docs/低价擒牛实现总结.md b/docs/低价擒牛实现总结.md new file mode 100644 index 0000000..13606ae --- /dev/null +++ b/docs/低价擒牛实现总结.md @@ -0,0 +1,260 @@ +# 低价擒牛选股板块 - 实现总结 + +## 实现日期 +2024-12-12 + +## 功能概述 +在左侧菜单栏"选股板块"下新增"低价擒牛"选股策略,支持低价高成长股票筛选和量化交易模拟。 + +## 核心功能 + +### 1. 选股策略 +- ✅ 股价 < 10元 +- ✅ 净利润增长率 ≥ 100% +- ✅ 非ST、非科创板、非创业板 +- ✅ 深圳A股 +- ✅ 按成交额由小至大排名 +- ✅ 筛选前5名(可配置3-10只) + +### 2. 钉钉通知 +- ✅ 读取.env中的Webhook配置 +- ✅ 自动发送选股结果到钉钉群 +- ✅ 包含股票代码、价格、净利增长率等信息 + +### 3. 量化交易策略 +- ✅ 初始资金:100万元 +- ✅ 持股周期:5天 +- ✅ 仓位控制:满仓 +- ✅ 个股最大持仓:40% +- ✅ 最大持股数:4只 +- ✅ 单日最大买入:2只 +- ✅ 买入时机:开盘买入 +- ✅ 卖出信号:MA5下穿MA20 或 持股满5天 + +### 4. 交易模拟 +- ✅ 支持策略模拟(无需配置) +- ✅ 支持MiniQMT实盘对接(需配置) +- ✅ 显示买入信号、持仓、账户状态 + +## 新增文件 + +### Python模块 +1. **low_price_bull_selector.py** (158行) + - 使用pywencai获取符合条件的股票 + - 数据清洗和格式化 + - 股票代码列表生成 + +2. **low_price_bull_strategy.py** (278行) + - 量化交易策略实现 + - 买卖信号判断 + - 仓位管理 + - 交易历史记录 + +3. **low_price_bull_ui.py** (382行) + - Streamlit界面展示 + - 用户交互逻辑 + - 钉钉通知集成 + - 策略模拟展示 + +### 文档 +1. **docs/低价擒牛功能说明.md** (241行) + - 完整功能文档 + - 策略详解 + - 配置说明 + - 风险提示 + +2. **docs/低价擒牛快速开始.md** (197行) + - 快速上手指南 + - 典型使用场景 + - 常见问题解答 + +## 修改文件 + +### app.py +修改位置:第293-380行(菜单导航区域) + +**修改内容**: +1. 在"选股板块"下添加"🐂 低价擒牛"按钮 +2. 更新所有按钮的状态清除列表,增加`show_low_price_bull` +3. 添加路由检查,显示低价擒牛界面 + +**代码变更**: +```python +# 添加导航按钮 +if st.button("🐂 低价擒牛", width='stretch', key="nav_low_price_bull", help="低价高成长股票筛选策略"): + st.session_state.show_low_price_bull = True + # 清除其他页面标志 + +# 添加路由 +if 'show_low_price_bull' in st.session_state and st.session_state.show_low_price_bull: + from low_price_bull_ui import display_low_price_bull + display_low_price_bull() + return +``` + +## 技术实现 + +### 数据获取 +- 使用pywencai模块调用问财接口 +- 查询语句:`"股价<10元,净利润增长率(净利润同比增长率)≥100%,非st,非科创板,非创业板,深圳A股,成交额由小至大排名"` +- 智能解析返回结果,兼容DataFrame/dict/list格式 + +### 通知集成 +- 复用notification_service模块 +- 支持钉钉/飞书Webhook +- 自动包含关键词"aiagents通知" +- Markdown格式消息展示 + +### 策略实现 +- 基于面向对象设计 +- 严格的仓位管理 +- 清晰的买卖信号 +- 完整的交易记录 + +### UI展示 +- Streamlit expander组件展示详情 +- DataFrame展示完整数据 +- Metric组件展示关键指标 +- CSV下载功能 + +## 环境变量配置 + +### Webhook通知(可选) +```bash +WEBHOOK_ENABLED=true +WEBHOOK_URL=your_dingtalk_webhook_url +WEBHOOK_TYPE=dingtalk +WEBHOOK_KEYWORD=aiagents通知 +``` + +### MiniQMT实盘(可选) +```bash +MINIQMT_ENABLED=true +MINIQMT_ACCOUNT_ID=your_account_id +``` + +## 使用流程 + +1. **启动系统** + ```bash + streamlit run app.py + ``` + +2. **进入功能** + - 左侧菜单 → 🎯 选股板块 → 🐂 低价擒牛 + +3. **开始选股** + - 调整筛选数量 + - 点击"🚀 开始低价擒牛选股" + +4. **查看结果** + - 股票列表 + - 统计信息 + - 钉钉通知(如已配置) + +5. **策略模拟**(可选) + - 点击"🎮 开始策略模拟" + - 查看买入信号和持仓 + +6. **实盘交易**(可选) + - 配置MiniQMT + - 点击"🔗 连接MiniQMT实盘" + +## 特色亮点 + +### 1. 智能筛选 +- 多维度条件过滤 +- 成交额排序优先低热度股票 +- 自动排除风险股(ST、科创板) + +### 2. 完整策略 +- 清晰的买卖规则 +- 严格的风险控制 +- 可复现的交易逻辑 + +### 3. 即时通知 +- 自动发送钉钉消息 +- 无需人工查看 +- 移动端即时接收 + +### 4. 灵活配置 +- 筛选数量可调 +- 策略参数可改 +- 支持模拟和实盘 + +## 注意事项 + +### 使用限制 +1. ⚠️ 问财接口有频率限制,建议每次间隔30秒以上 +2. ⚠️ 净利润增长率≥100%的股票可能较少 +3. ⚠️ 策略模拟不包含手续费和滑点 + +### 风险提示 +1. ⚠️ 本功能仅供学习研究使用 +2. ⚠️ 不构成任何投资建议 +3. ⚠️ 股市有风险,投资需谨慎 +4. ⚠️ 历史业绩不代表未来收益 + +### 技术限制 +1. ⚠️ 需要稳定的网络连接 +2. ⚠️ pywencai模块需要正确安装 +3. ⚠️ MiniQMT需要额外配置 + +## 后续优化方向 + +### 短期优化 +- [ ] 添加历史选股记录 +- [ ] 支持自定义筛选条件 +- [ ] 增加更多技术指标 + +### 中期优化 +- [ ] 回测功能 +- [ ] 策略参数优化 +- [ ] 多策略对比 + +### 长期优化 +- [ ] AI选股建议 +- [ ] 实时监控 +- [ ] 自动化交易 + +## 测试建议 + +### 功能测试 +1. ✅ 测试选股功能是否正常 +2. ✅ 验证数据格式是否正确 +3. ✅ 检查钉钉通知是否发送 +4. ✅ 确认策略模拟逻辑 + +### 边界测试 +1. ✅ 筛选不到股票时的处理 +2. ✅ 网络异常时的错误处理 +3. ✅ Webhook未配置时的提示 + +### 性能测试 +1. ✅ 数据获取速度 +2. ✅ UI渲染性能 +3. ✅ 并发访问支持 + +## 相关文档 + +- [低价擒牛功能说明](./docs/低价擒牛功能说明.md) +- [低价擒牛快速开始](./docs/低价擒牛快速开始.md) +- [Webhook通知配置指南](./docs/Webhook通知配置指南.md) +- [MiniQMT集成指南](./docs/MINIQMT_INTEGRATION_GUIDE.md) + +## 总结 + +本次实现完整地添加了"低价擒牛"选股板块,包含: +- ✅ 完整的选股策略 +- ✅ 钉钉通知集成 +- ✅ 量化交易策略 +- ✅ 策略模拟功能 +- ✅ 详细的文档说明 + +所有功能均已集成到现有系统中,与主力选股等其他板块保持一致的交互体验。 + +--- + +**实现完成时间**:2024-12-12 +**版本**:v1.0 +**状态**:✅ 已完成并可使用 diff --git a/docs/低价擒牛快速开始.md b/docs/低价擒牛快速开始.md new file mode 100644 index 0000000..fa28e48 --- /dev/null +++ b/docs/低价擒牛快速开始.md @@ -0,0 +1,196 @@ +# 低价擒牛 - 快速开始指南 + +## 🚀 5分钟上手 + +### 第一步:进入功能 + +1. 启动系统:`streamlit run app.py` +2. 在左侧菜单栏找到 **🎯 选股板块** +3. 点击 **🐂 低价擒牛** + +### 第二步:开始选股 + +1. 调整筛选数量(默认5只,可调整3-10只) +2. 点击 **🚀 开始低价擒牛选股** +3. 等待30-60秒,系统自动获取数据 + +### 第三步:查看结果 + +系统会展示: +- ✅ 筛选数量和统计信息 +- ✅ 精选股票详细列表 +- ✅ 完整数据表格(可下载CSV) + +### 第四步:接收通知(可选) + +如果配置了钉钉Webhook,会自动收到消息通知 + +## 📊 选股策略一览 + +| 条件 | 值 | +|------|-----| +| 股价 | < 10元 | +| 净利润增长率 | ≥ 100% | +| 市场 | 深圳A股 | +| 排除 | ST、科创板、创业板 | +| 排序 | 成交额由小到大 | + +## 🎯 量化策略参数 + +| 参数 | 值 | +|------|-----| +| 初始资金 | 100万元 | +| 持股周期 | 5天 | +| 最大持股数 | 4只 | +| 个股最大仓位 | 40% | +| 单日最大买入 | 2只 | +| 买入时机 | 开盘 | +| 卖出信号 | MA5下穿MA20 或 持股5天 | + +## 🔧 配置Webhook通知(可选) + +### 1. 获取钉钉机器人Webhook URL + +1. 打开钉钉群 → 群设置 → 智能群助手 +2. 添加机器人 → 自定义 +3. 添加安全设置 → 自定义关键词:`aiagents通知` +4. 复制Webhook URL + +### 2. 配置环境变量 + +编辑项目根目录的 `.env` 文件: + +```bash +# Webhook通知配置 +WEBHOOK_ENABLED=true +WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=xxxxx +WEBHOOK_TYPE=dingtalk +WEBHOOK_KEYWORD=aiagents通知 +``` + +### 3. 重启系统 + +配置完成后重启Streamlit即可 + +## 🎮 使用量化策略模拟 + +### 策略模拟(无需配置) + +1. 完成选股后,在页面底部找到 **量化交易策略模拟** +2. 点击 **🎮 开始策略模拟** +3. 系统会展示: + - 模拟买入信号 + - 当前持仓情况 + - 账户资金状态 + +### MiniQMT实盘(需要配置) + +**前提条件**: +- 已安装MiniQMT客户端 +- 已开通量化交易账户 + +**配置步骤**: +1. 编辑 `.env` 文件: + ```bash + MINIQMT_ENABLED=true + MINIQMT_ACCOUNT_ID=your_account_id + ``` +2. 重启系统 +3. 点击 **🔗 连接MiniQMT实盘** + +## 📋 典型使用场景 + +### 场景1:每日早盘选股 + +**时间**:每天早上9:15-9:25 + +**操作**: +1. 打开低价擒牛功能 +2. 开始选股,获取最新数据 +3. 查看筛选结果 +4. 开盘后根据买入信号下单 + +### 场景2:周末策略回测 + +**时间**:周末 + +**操作**: +1. 选股获取数据 +2. 导出CSV文件 +3. 使用Excel或Python进行历史数据分析 +4. 优化策略参数 + +### 场景3:自动通知监控 + +**时间**:工作日定时 + +**操作**: +1. 配置Webhook +2. 设置定时任务(cron或Task Scheduler) +3. 每日自动选股并推送钉钉消息 + +## 💡 使用技巧 + +### 技巧1:成交额排序的意义 +- 成交额小 = 市场关注度低 = 可能被低估 +- 优先买入排名靠前的股票(成交额最小) + +### 技巧2:持股周期管理 +- 5天是一个交易周期 +- 可在第3-4天关注卖出信号 +- 避免持股到期临近时盲目持有 + +### 技巧3:仓位控制 +- 建议新手先用小资金测试 +- 逐步增加至策略设定的资金量 +- 严格执行单股40%的仓位限制 + +### 技巧4:结合大盘走势 +- 大盘上涨时:可适当激进 +- 大盘下跌时:减少买入或观望 +- 策略不能替代市场判断 + +## ⚠️ 常见问题 + +### Q: 为什么有时候筛选不到股票? +A: 可能原因: +- 市场环境不符合条件(如整体估值偏高) +- 净利润增长率≥100%的股票较少 +- 建议适当放宽筛选条件 + +### Q: 钉钉通知没收到? +A: 检查清单: +- ✅ WEBHOOK_ENABLED 设置为 true +- ✅ WEBHOOK_URL 正确 +- ✅ 钉钉机器人关键词包含"aiagents通知" +- ✅ 系统已重启 + +### Q: 策略模拟和实盘有什么区别? +A: 主要区别: +- 模拟:不实际下单,无手续费 +- 实盘:真实下单,有滑点和手续费 +- 建议先用模拟验证策略 + +### Q: 如何调整策略参数? +A: 当前版本参数固定在代码中,如需调整: +1. 编辑 `low_price_bull_strategy.py` +2. 修改 `__init__` 方法中的参数 +3. 重启系统 + +## 📚 进阶阅读 + +- [低价擒牛功能说明](./低价擒牛功能说明.md) - 完整功能文档 +- [Webhook配置指南](./Webhook通知配置指南.md) - 详细配置步骤 +- [MiniQMT集成指南](./MINIQMT_INTEGRATION_GUIDE.md) - 量化交易配置 + +## 🎯 下一步 + +1. ✅ 熟悉基础选股功能 +2. ✅ 配置钉钉通知 +3. ✅ 使用策略模拟 +4. ✅ 尝试MiniQMT实盘(谨慎) +5. ✅ 根据实际情况优化策略 + +--- + +**祝您投资顺利!** 🚀📈💰 diff --git a/docs/低价擒牛策略监控功能实现总结.md b/docs/低价擒牛策略监控功能实现总结.md new file mode 100644 index 0000000..c4eb726 --- /dev/null +++ b/docs/低价擒牛策略监控功能实现总结.md @@ -0,0 +1,365 @@ +# 低价擒牛策略监控功能实现总结 + +## 实现日期 +2024-12-12 + +## 功能概述 +为"低价擒牛"选股板块添加策略监控功能,实现自动监控持仓股票并在满足卖出条件时发送提醒。 + +## 核心功能 + +### 1. 策略监控 +- ✅ 加入监控:在选出的股票列表中点击"加入策略监控"按钮 +- ✅ 自动扫描:每分钟扫描1次(可配置) +- ✅ 持股天数监控:持股满5天第6天开盘提醒卖出 +- ✅ 均线监控:MA5下穿MA20提醒卖出 +- ✅ 自动移除:提醒卖出后自动移出监控列表 + +### 2. 数据源集成 +- ✅ TDX数据源:读取股票K线数据 +- ✅ 均线计算:自动计算MA5和MA20 +- ✅ 实时价格:获取最新股票价格 + +### 3. 通知系统 +- ✅ 钉钉通知:自动发送卖出提醒到钉钉群 +- ✅ 面板提醒:在监控面板显示待处理提醒 +- ✅ 历史记录:保存所有提醒记录 + +## 新增文件 + +### 核心模块(3个Python文件) + +1. **low_price_bull_monitor.py** (363行) + - 监控数据库管理 + - 股票添加/移除 + - 提醒生成和管理 + - 持有天数更新 + +2. **low_price_bull_service.py** (310行) + - 后台监控服务 + - 定时扫描循环 + - TDX数据获取 + - MA均线检测 + - 钉钉通知发送 + +3. **low_price_bull_monitor_ui.py** (273行) + - 监控面板UI + - 服务控制(启动/停止) + - 监控列表展示 + - 卖出提醒展示 + - 历史记录查询 + +### 文档(2个Markdown文件) + +1. **低价擒牛策略监控配置说明.md** + - 环境变量配置 + - TDX数据源配置 + - 使用流程说明 + - 常见问题解答 + +2. **低价擒牛策略监控功能实现总结.md** + - 功能概述 + - 技术实现 + - 使用说明 + +## 修改文件 + +### low_price_bull_ui.py +- ✅ 添加导入语句 +- ✅ 添加"策略监控"按钮(右上角) +- ✅ 在股票详情中添加"加入策略监控"按钮 +- ✅ 集成监控面板切换 +- ✅ 更新策略说明 + +## 数据库设计 + +### 表1: monitored_stocks(监控列表) +```sql +CREATE TABLE monitored_stocks ( + id INTEGER PRIMARY KEY, + stock_code TEXT NOT NULL, -- 股票代码 + stock_name TEXT NOT NULL, -- 股票名称 + buy_price REAL NOT NULL, -- 买入价格 + buy_date TEXT NOT NULL, -- 买入日期 + holding_days INTEGER DEFAULT 0, -- 持有天数 + status TEXT DEFAULT 'holding', -- 状态(holding/removed) + add_time TEXT NOT NULL, -- 加入时间 + remove_time TEXT, -- 移除时间 + remove_reason TEXT, -- 移除原因 + UNIQUE(stock_code, status) +) +``` + +### 表2: sell_alerts(卖出提醒) +```sql +CREATE TABLE sell_alerts ( + id INTEGER PRIMARY KEY, + stock_code TEXT NOT NULL, -- 股票代码 + stock_name TEXT NOT NULL, -- 股票名称 + alert_type TEXT NOT NULL, -- 提醒类型(holding_days/ma_cross) + alert_reason TEXT NOT NULL, -- 提醒原因 + current_price REAL, -- 当前价格 + ma5 REAL, -- MA5值 + ma20 REAL, -- MA20值 + holding_days INTEGER, -- 持有天数 + alert_time TEXT NOT NULL, -- 提醒时间 + is_sent INTEGER DEFAULT 0 -- 是否已发送 +) +``` + +## 技术实现 + +### 1. 监控服务架构 +``` +┌─────────────────────────────────────┐ +│ LowPriceBullService │ +│ (后台监控服务) │ +└──────────────┬──────────────────────┘ + │ + ┌──────▼──────┐ + │ 监控循环 │ + │ (每60秒) │ + └──────┬──────┘ + │ + ┌──────────▼─────────────┐ + │ 扫描监控列表 │ + │ - 更新持有天数 │ + │ - 检查卖出条件 │ + └──────────┬──────────────┘ + │ + ┌──────────▼────────────────┐ + │ 检测卖出信号 │ + │ 1. 持股天数≥5 │ + │ 2. MA5 < MA20 │ + └──────────┬─────────────────┘ + │ + ┌──────────▼──────────────┐ + │ 生成卖出提醒 │ + │ - 保存到数据库 │ + │ - 发送钉钉通知 │ + │ - 自动移除股票 │ + └───────────────────────────┘ +``` + +### 2. TDX数据获取流程 +```python +def _get_stock_data(stock_code): + # 1. 获取K线数据(30天) + df = get_stock_kline_data(stock_code, period='daily', count=30) + + # 2. 计算均线 + df['MA5'] = df['close'].rolling(window=5).mean() + df['MA20'] = df['close'].rolling(window=20).mean() + + # 3. 返回最新数据 + latest = df.iloc[-1] + return latest['close'], latest['MA5'], latest['MA20'] +``` + +### 3. 卖出信号检测 +```python +def _check_stock(stock): + # 检查1: 持股天数 + if holding_days >= 5: + add_sell_alert(type='holding_days') + return + + # 检查2: MA5下穿MA20 + current_price, ma5, ma20 = _get_stock_data(stock_code) + if ma5 < ma20: + add_sell_alert(type='ma_cross') +``` + +## 环境变量配置 + +### 新增配置项 +```bash +# 低价擒牛策略监控 +LOW_PRICE_BULL_SCAN_INTERVAL=60 # 扫描间隔(秒) +LOW_PRICE_BULL_HOLDING_DAYS=5 # 持股天数限制 +``` + +### 依赖的配置 +```bash +# Webhook通知(建议配置) +WEBHOOK_ENABLED=true +WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=xxxxx +WEBHOOK_TYPE=dingtalk +WEBHOOK_KEYWORD=aiagents通知 +``` + +## 使用流程 + +### 1. 启动流程 +``` +1. 低价擒牛选股 + ↓ +2. 查看股票列表 + ↓ +3. 点击"➕ 加入策略监控" + ↓ +4. 点击右上角"📊 策略监控" + ↓ +5. 点击"▶️ 启动监控服务" + ↓ +6. 监控服务开始运行 +``` + +### 2. 监控流程 +``` +监控服务 → 每60秒扫描 → 检测条件 → 生成提醒 → 发送通知 → 自动移除 +``` + +### 3. 提醒流程 +``` +满足卖出条件 + ↓ +生成卖出提醒 + ↓ +┌─────────┴─────────┐ +│ │ +发送钉钉通知 显示在面板 +│ │ +└─────────┬─────────┘ + ↓ + 自动移出监控列表 + ↓ + 记录到历史 +``` + +## 功能特点 + +### 1. 自动化监控 +- ✅ 无需人工盯盘 +- ✅ 定时自动扫描 +- ✅ 智能卖出提醒 + +### 2. 双重条件 +- ✅ 时间条件:持股5天 +- ✅ 技术条件:MA5下穿MA20 +- ✅ 满足任一即提醒 + +### 3. 灵活配置 +- ✅ 可调整扫描间隔 +- ✅ 可调整持股天数 +- ✅ 可启动/停止服务 + +### 4. 完整记录 +- ✅ 监控历史 +- ✅ 提醒记录 +- ✅ 操作日志 + +## 监控面板功能 + +### 📋 监控列表 +- 显示所有监控中的股票 +- 实时更新持有天数 +- 支持批量移除 +- 显示买入价格和日期 + +### 🔔 卖出提醒 +- 显示待处理提醒 +- 查看MA5/MA20数据 +- 操作选项: + - ✅ 已处理(移除股票) + - ❌ 忽略(保留股票) + +### 📜 历史记录 +- 查看历史提醒 +- 筛选已发送/未发送 +- 清理旧记录 + +### ⚙️ 服务控制 +- ▶️ 启动服务 +- ⏸️ 停止服务 +- 配置扫描间隔 +- 查看服务状态 + +## 钉钉通知格式 + +```markdown +### aiagents通知 - 低价擒牛卖出提醒 + +**股票代码**: 002259 + +**股票名称**: 升达林业 + +**提醒类型**: MA均线死叉 + +**提醒原因**: MA5下穿MA20,技术信号卖出 + +**当前价格**: 8.52元 + +**MA5**: 8.45 + +**MA20**: 8.67 + +**持有天数**: 3天 + +**提醒时间**: 2024-12-12 14:30:00 + +--- + +**建议**: 开盘时卖出该股票 + +_此消息由AI股票分析系统自动发送_ +``` + +## 注意事项 + +### 1. 服务运行 +- ⚠️ 监控服务需要手动启动 +- ⚠️ 关闭浏览器后服务停止 +- ⚠️ 建议在服务器上运行 + +### 2. 数据源 +- ⚠️ 需要配置TDX数据源 +- ⚠️ 建议使用pytdx(无需配置) +- ⚠️ K线数据可能有延迟 + +### 3. 扫描频率 +- ⚠️ 默认60秒扫描1次 +- ⚠️ 不建议低于30秒 +- ⚠️ 避免API限流 + +### 4. 持股天数 +- ⚠️ 按自然日计算 +- ⚠️ 不区分交易日 +- ⚠️ 建议预留余地 + +## 后续优化方向 + +### 短期 +- [ ] 支持后台常驻服务 +- [ ] 增加更多技术指标 +- [ ] 支持自定义卖出条件 + +### 中期 +- [ ] 集成MiniQMT自动交易 +- [ ] 支持止损止盈设置 +- [ ] 增加回测功能 + +### 长期 +- [ ] AI智能卖出建议 +- [ ] 多策略组合监控 +- [ ] 移动端APP推送 + +## 总结 + +本次实现为"低价擒牛"选股板块添加了完整的策略监控功能,包括: + +- ✅ **3个核心模块**:监控管理、后台服务、UI界面 +- ✅ **完整的数据库设计**:监控列表、提醒记录 +- ✅ **自动化监控**:定时扫描、智能提醒、自动移除 +- ✅ **双重卖出条件**:持股天数 + MA均线 +- ✅ **钉钉通知集成**:实时接收卖出提醒 +- ✅ **灵活的配置**:可调整扫描间隔和持股天数 +- ✅ **完善的文档**:配置说明、使用指南 + +整个功能已集成到系统中,用户可以立即使用! + +--- + +**实现完成时间**:2024-12-12 +**版本**:v1.0 +**状态**:✅ 已完成并可使用 diff --git a/docs/低价擒牛策略监控配置说明.md b/docs/低价擒牛策略监控配置说明.md new file mode 100644 index 0000000..59e522b --- /dev/null +++ b/docs/低价擒牛策略监控配置说明.md @@ -0,0 +1,239 @@ +# 低价擒牛策略监控配置说明 + +## 环境变量配置 + +在 `.env` 文件中添加以下配置: + +```bash +# 低价擒牛策略监控配置 +LOW_PRICE_BULL_SCAN_INTERVAL=60 # 扫描间隔(秒),默认60秒 +LOW_PRICE_BULL_HOLDING_DAYS=5 # 持股天数限制,默认5天 + +# TDX数据源API配置(必须) +TDX_BASE_URL=http://127.0.0.1:5000 # TDX API服务地址 +``` + +## TDX数据源配置 + +策略监控需要使用TDX数据API获取股票K线数据和均线指标。 + +### 配置方法 + +在 `.env` 文件中添加TDX API URL: + +```bash +# TDX数据源API配置 +TDX_BASE_URL=http://127.0.0.1:5000 # TDX API服务地址 +``` + +### API接口说明 + +#### 核心接口 + +| 接口 | 说明 | 示例 | +|------|------|------| +| /api/quote | 五档行情 | ?code=000001 | +| /api/kline | K线数据 | ?code=000001&type=day | +| /api/minute | 分时数据 | ?code=000001 | +| /api/trade | 分时成交 | ?code=000001 | +| /api/search | 搜索股票 | ?keyword=平安 | +| /api/stock-info | 综合信息 | ?code=000001 | + +#### K线数据接口 + +策略监控使用的接口:**`/api/kline`** + +**请求参数**: +- `code`: 股票代码(例如:000001) +- `type`: K线类型(day=日K线) + +**返回格式**: +```json +[ + { + "date": "2024-12-12", + "open": 10.50, + "high": 10.80, + "low": 10.30, + "close": 10.60, + "volume": 1000000 + }, + ... +] +``` + +**使用示例**: +```bash +curl "http://127.0.0.1:5000/api/kline?code=000001&type=day" +``` + +### 启动TDX API服务 + +如果您还没有TDX API服务,需要先启动服务: + +1. 确保已安装相关依赖 +2. 启动TDX API服务器(默认端口5000) +3. 在.env中配置服务地址 + +### 数据说明 + +- **更新频率**:K线数据实时更新 +- **数据量**:策略需要至少20天的K线数据 +- **均线计算**:系统自动计算MA5和MA20 + +## Webhook通知配置 + +建议配置钉钉Webhook以接收卖出提醒: + +```bash +# Webhook通知配置 +WEBHOOK_ENABLED=true +WEBHOOK_URL=https://oapi.dingtalk.com/robot/send?access_token=xxxxx +WEBHOOK_TYPE=dingtalk +WEBHOOK_KEYWORD=aiagents通知 +``` + +## 功能说明 + +### 1. 加入策略监控 + +在"低价擒牛"选股结果中,点击任意股票详情中的"➕ 加入策略监控"按钮。 + +### 2. 监控条件 + +#### 条件1:持股天数到期 +- 持股满5天(可配置) +- 第6天开盘时发送卖出提醒 +- 自动从监控列表移除 + +#### 条件2:MA5下穿MA20 +- 每分钟扫描1次(可配置) +- 检测MA5是否小于MA20 +- 满足条件时发送卖出提醒 +- 自动从监控列表移除 + +### 3. 卖出提醒 + +提醒会通过以下方式发送: +- 钉钉Webhook通知(需配置) +- 监控面板中的"卖出提醒"标签页 + +### 4. 自动移除 + +当满足卖出条件时: +1. 生成卖出提醒 +2. 发送钉钉通知(如已配置) +3. 自动从监控列表移除 +4. 记录到历史提醒 + +## 使用流程 + +``` +1. 低价擒牛选股 + ↓ +2. 查看选股结果 + ↓ +3. 点击"➕ 加入策略监控" + ↓ +4. 进入"📊 策略监控"面板 + ↓ +5. 启动监控服务 + ↓ +6. 等待卖出提醒 + ↓ +7. 收到提醒后卖出 +``` + +## 监控面板功能 + +### 服务控制 +- ▶️ 启动监控服务 +- ⏸️ 停止监控服务 +- ⚙️ 配置扫描间隔 + +### 监控列表 +- 查看所有监控中的股票 +- 显示持有天数 +- 手动移除股票 + +### 卖出提醒 +- 查看待处理的提醒 +- 查看MA5/MA20数据 +- 标记已处理/忽略 + +### 历史记录 +- 查看历史提醒 +- 清理旧记录 + +## 注意事项 + +1. ⚠️ **监控服务需要手动启动** + - 首次使用需在监控面板启动服务 + - 服务启动后会持续运行 + - 关闭浏览器后服务会停止 + +2. ⚠️ **TDX数据源** + - 必须配置TDX_BASE_URL + - 确俜TDX API服务已启动 + - 使用`/api/kline`接口获取日K线数据 + - 数据需要至少20天才能计算MA20 + +3. ⚠️ **扫描间隔** + - 默认60秒扫描1次 + - 可在监控面板调整 + - 建议不要低于30秒 + +4. ⚠️ **持股天数计算** + - 按自然日计算 + - 不区分交易日/非交易日 + - 建议留有余地 + +## 常见问题 + +### Q1: 监控服务无法启动? +A: 检查: +- Python环境是否正常 +- 依赖包是否安装完整 +- 查看控制台错误日志 + +### Q2: 没有收到卖出提醒? +A: 检查: +- Webhook是否配置正确 +- 监控服务是否在运行 +- TDX API服务是否启动 +- 访问`http://127.0.0.1:5000/api/health`检查API状态 +- 查看控制台日志是否有错误信息 + +### Q3: 如何修改扫描间隔? +A: 两种方式: +- 在监控面板的"⚙️ 监控配置"中修改 +- 在.env文件中修改`LOW_PRICE_BULL_SCAN_INTERVAL` + +### Q4: 持股天数如何调整? +A: 在.env文件中修改: +```bash +LOW_PRICE_BULL_HOLDING_DAYS=7 # 改为7天 +``` + +## 技术实现 + +### 数据库 +- SQLite本地数据库 +- 文件:`low_price_bull_monitor.db` +- 包含监控列表和提醒记录 + +### 后台服务 +- Python线程实现 +- 定时扫描循环 +- 异常自动恢复 + +### 数据获取 +- 使用TDX API +- 调用`/api/kline`接口 +- 获取日K线数据 +- 计算MA5/MA20均线 + +--- + +**版本**: v1.0 +**更新日期**: 2024-12-12 diff --git a/docs/均线回归策略保存.py b/docs/均线回归策略保存.py new file mode 100644 index 0000000..d21007d --- /dev/null +++ b/docs/均线回归策略保存.py @@ -0,0 +1,460 @@ +''' +均线回归策略 V3.0 进攻版 +优化重点:提高Beta,增强进攻性,减少过度保守 + +核心改进: +1. 放宽市场择时,只在极端熊市降仓 +2. 增加持仓数量,提高资金利用率 +3. 放宽买入条件,增加交易机会 +4. 优化止盈,让利润奔跑 +5. 增加突破买入模式 +''' + +import jqdata +import numpy as np + +## 初始化函数 +def initialize(context): + # 设定沪深300作为基准 + set_benchmark('000300.XSHG') + # 开启动态复权模式 + set_option('use_real_price', True) + # 设定成交量比例 + set_option('order_volume_ratio', 1) + # 设置交易手续费 + set_order_cost(OrderCost(open_tax=0, close_tax=0.001, + open_commission=0.0003, close_commission=0.0003, + close_today_commission=0, min_commission=5), type='stock') + + # ========== 策略参数(进攻版)========== + g.stocknum = 8 # 持仓数量增加到8只 + g.max_position_ratio = 0.98 # 最大仓位98% + + # 均线参数 + g.ma_short = 20 # 短期均线 + g.ma_mid = 60 # 中期均线 + g.ma_long = 120 # 长期均线 + + # 回调买入参数(放宽条件) + g.pullback_ratio = 0.035 # 放宽到3.5% + g.break_tolerance = 0.015 # 允许跌破1.5% + + # 止盈止损参数(让利润奔跑) + g.base_take_profit = 0.20 # 止盈提高到20% + g.base_stop_loss = 0.06 # 止损放宽到6% + g.trailing_stop = 0.08 # 回撤容忍8% + + # 市值筛选(亿)- 扩大范围 + g.min_market_cap = 20 + g.max_market_cap = 1000 + + # 行业分散参数 + g.max_industry_stocks = 3 # 同行业最多3只 + + # 市场状态 + g.market_state = 'NORMAL' + g.position_ratio = 1.0 + + # 记录 + g.highest_profit = {} + g.stock_volatility = {} + g.hold_days = {} # 持仓天数 + + # 定时任务 + run_daily(market_analysis, '09:31') + run_daily(morning_check, '09:35') + run_daily(check_positions, '14:00') # 只检查一次 + run_daily(afternoon_trade, '14:50') + +## 市场环境分析(放宽版) +def market_analysis(context): + """ + 只在极端情况下降低仓位,大部分时间保持高仓位 + """ + index_code = '000300.XSHG' + + try: + df = attribute_history(index_code, 130, '1d', ['close'], skip_paused=True) + if len(df) < 120: + g.market_state = 'NORMAL' + g.position_ratio = 1.0 + return + + close = df['close'].values + current = close[-1] + + ma20 = np.mean(close[-20:]) + ma60 = np.mean(close[-60:]) + ma120 = np.mean(close[-120:]) + + change_5d = (close[-1] - close[-6]) / close[-6] + change_10d = (close[-1] - close[-11]) / close[-11] + + # 只在极端熊市才降仓(条件更严格) + if current < ma120 and ma20 < ma60 < ma120 and change_10d < -0.08: + # 极端熊市:指数跌破120日均线,均线空头,10日跌幅超8% + g.market_state = 'BEAR' + g.position_ratio = 0.5 + log.info("【市场】极端熊市,仓位50%%") + elif change_5d < -0.08: + # 短期暴跌超8%:暂时减仓 + g.market_state = 'BEAR' + g.position_ratio = 0.6 + log.info("【市场】短期暴跌,仓位60%%") + elif current > ma20 and ma20 > ma60: + # 趋势向上:满仓 + g.market_state = 'BULL' + g.position_ratio = 1.0 + log.info("【市场】趋势向上,满仓") + else: + # 默认高仓位运行 + g.market_state = 'NORMAL' + g.position_ratio = 0.9 + log.info("【市场】正常状态,仓位90%%") + + except Exception as e: + g.market_state = 'NORMAL' + g.position_ratio = 0.9 + +## 早盘检查 +def morning_check(context): + g.buy_list = check_stocks(context) + log.info("【选股】%d 只,市场: %s,仓位: %.0f%%" % + (len(g.buy_list), g.market_state, g.position_ratio * 100)) + +## 检查持仓 - 优化版(让利润奔跑) +def check_positions(context): + if len(context.portfolio.positions) == 0: + return + + for stock in list(context.portfolio.positions.keys()): + position = context.portfolio.positions[stock] + if position.closeable_amount <= 0: + continue + + cost = position.avg_cost + current_price = position.price + if cost <= 0: + continue + + profit_ratio = (current_price - cost) / cost + + # 更新持仓天数 + g.hold_days[stock] = g.hold_days.get(stock, 0) + 1 + + # 更新最高盈利 + if stock not in g.highest_profit: + g.highest_profit[stock] = profit_ratio + else: + g.highest_profit[stock] = max(g.highest_profit[stock], profit_ratio) + + highest = g.highest_profit[stock] + hold_days = g.hold_days.get(stock, 0) + + # 动态止损(根据波动率和持仓时间) + volatility = g.stock_volatility.get(stock, 0.025) + # 持仓时间越长,止损越宽松 + time_factor = min(1 + hold_days * 0.01, 1.5) + dynamic_stop_loss = min(g.base_stop_loss * time_factor, 0.12) + + # === 止损逻辑 === + if profit_ratio < -dynamic_stop_loss: + log.info("【止损】%s 亏损 %.2f%%" % (stock, profit_ratio * 100)) + order_target(stock, 0) + clean_stock_data(stock) + continue + + # === 均线破位(只在亏损时卖)=== + if profit_ratio < 0 and check_sell_signal(stock): + log.info("【均线破位】%s 破位且亏损,卖出" % stock) + order_target(stock, 0) + clean_stock_data(stock) + continue + + # === 移动止盈(让利润奔跑)=== + if highest >= g.base_take_profit: + drawdown = highest - profit_ratio + # 盈利越多,允许回撤越大(最多允许回撤15%) + allowed_drawdown = min(g.trailing_stop + highest * 0.4, 0.18) + if drawdown >= allowed_drawdown: + log.info("【移动止盈】%s 最高 %.2f%%,回撤 %.2f%%" % + (stock, highest * 100, drawdown * 100)) + order_target(stock, 0) + clean_stock_data(stock) + continue + + # === 分批止盈(保守止盈,只卖小部分)=== + if profit_ratio >= g.base_take_profit * 2: + # 盈利超40%,卖出30%锁定利润 + sell_amount = int(position.closeable_amount * 0.3 / 100) * 100 + if sell_amount >= 100: + log.info("【大幅止盈】%s 盈利 %.2f%%,卖出30%%" % (stock, profit_ratio * 100)) + order(stock, -sell_amount) + +## 清理股票数据 +def clean_stock_data(stock): + for d in [g.highest_profit, g.stock_volatility, g.hold_days]: + if stock in d: + del d[stock] + +## 尾盘交易 +def afternoon_trade(context): + buy_stocks(context) + +## 买入函数(积极版) +def buy_stocks(context): + if not hasattr(g, 'buy_list') or not g.buy_list: + return + + position_count = len(context.portfolio.positions) + adjusted_stocknum = int(g.stocknum * g.position_ratio) + if adjusted_stocknum <= 0 or position_count >= adjusted_stocknum: + return + + # 计算资金分配 + available_cash = context.portfolio.available_cash * g.max_position_ratio + buy_count = min(adjusted_stocknum - position_count, len(g.buy_list)) + if buy_count <= 0 or available_cash < 10000: + return + + cash_per_stock = available_cash / buy_count + held_industries = get_held_industries(context) + + bought = 0 + for stock in g.buy_list: + if bought >= buy_count: + break + if stock in context.portfolio.positions: + continue + + # 行业分散 + stock_industry = get_stock_industry(stock) + if stock_industry and held_industries.get(stock_industry, 0) >= g.max_industry_stocks: + continue + + # 简化买入条件检查 + if check_buy_signal(stock): + order_value(stock, cash_per_stock) + log.info("【买入】%s" % stock) + g.highest_profit[stock] = 0 + g.stock_volatility[stock] = 0.025 + g.hold_days[stock] = 0 + if stock_industry: + held_industries[stock_industry] = held_industries.get(stock_industry, 0) + 1 + bought += 1 + +## 获取已持仓的行业分布 +def get_held_industries(context): + industries = {} + for stock in context.portfolio.positions.keys(): + ind = get_stock_industry(stock) + if ind: + industries[ind] = industries.get(ind, 0) + 1 + return industries + +## 获取股票所属行业 +def get_stock_industry(stock): + try: + ind_dict = get_industry(stock) + if ind_dict and stock in ind_dict: + # 获取申万一级行业 + for ind_code, ind_info in ind_dict[stock].items(): + if ind_code.startswith('sw_l1'): + return ind_info.get('industry_name', None) + return None + except: + return None + +## 选股函数(进攻版) +def check_stocks(context): + # 第一步:基础筛选(放宽条件) + q = query( + valuation.code, + valuation.market_cap + ).filter( + valuation.market_cap.between(g.min_market_cap, g.max_market_cap) + ).order_by( + valuation.market_cap.asc() + ).limit(500) # 扩大候选池 + + df = get_fundamentals(q) + if df.empty: + return [] + + stock_list = list(df['code']) + stock_list = filter_basic(stock_list) + + # 均线信号筛选 + candidates = [] + for stock in stock_list[:200]: # 检查前200只 + if check_buy_signal(stock): + score = calculate_trend_score(stock) + candidates.append((stock, score)) + + # 按评分排序 + candidates.sort(key=lambda x: x[1], reverse=True) + buy_list = [c[0] for c in candidates[:g.stocknum * 3]] + + return buy_list + +## 计算趋势强度评分 +def calculate_trend_score(stock): + """ + 评分因素: + 1. 均线发散程度 + 2. 价格距离MA20的位置 + 3. 成交量配合 + """ + try: + df = attribute_history(stock, 130, '1d', ['close', 'volume'], skip_paused=True) + if len(df) < 120: + return 0 + + close = df['close'].values + volume = df['volume'].values + + ma20 = np.mean(close[-20:]) + ma60 = np.mean(close[-60:]) + ma120 = np.mean(close[-120:]) + current = close[-1] + + score = 0 + + # 均线发散程度(20分) + spread = (ma20 - ma120) / ma120 + score += min(spread * 100, 20) + + # 价格位置(30分)- 越接近MA20越好 + distance = abs(current - ma20) / ma20 + score += max(0, 30 - distance * 500) + + # 成交量(20分)- 回调缩量为佳 + vol_ma5 = np.mean(volume[-5:]) + vol_ma20 = np.mean(volume[-20:]) + if vol_ma5 < vol_ma20 * 0.8: + score += 20 # 缩量回调 + elif vol_ma5 < vol_ma20: + score += 10 + + # 趋势持续性(30分) + ma20_slope = (ma20 - np.mean(close[-25:-5])) / np.mean(close[-25:-5]) + if ma20_slope > 0: + score += min(ma20_slope * 300, 30) + + return score + + except: + return 0 + +## 基础过滤 +def filter_basic(stock_list): + if not stock_list: + return [] + + current_data = get_current_data() + filtered = [] + + for stock in stock_list: + # 过滤停牌 + if current_data[stock].paused: + continue + # 过滤ST + if current_data[stock].is_st: + continue + if 'ST' in current_data[stock].name or '*' in current_data[stock].name: + continue + # 过滤科创板、北交所 + if stock.startswith('688') or stock.startswith('8') or stock.startswith('4'): + continue + # 过滤涨跌停 + if current_data[stock].last_price >= current_data[stock].high_limit: + continue + if current_data[stock].last_price <= current_data[stock].low_limit: + continue + + filtered.append(stock) + + return filtered + +## 检查买入信号(放宽版) +def check_buy_signal(stock): + """ + 放宽买入条件,增加交易机会 + """ + try: + df = attribute_history(stock, g.ma_long + 10, '1d', ['close'], skip_paused=True) + if len(df) < g.ma_long: + return False + + close = df['close'].values + current_price = close[-1] + + # 计算均线 + ma20 = np.mean(close[-g.ma_short:]) + ma60 = np.mean(close[-g.ma_mid:]) + ma120 = np.mean(close[-g.ma_long:]) + + # 条件1:均线多头排列(核心条件) + if not (ma20 > ma60 > ma120): + return False + + # 条件2:MA20向上 + ma20_5d_ago = np.mean(close[-g.ma_short-5:-5]) + if ma20 <= ma20_5d_ago: + return False + + # 条件3:价格在MA20附近(放宽范围) + distance = (current_price - ma20) / ma20 + # 允许在MA20上方5%以内,或跌破2%以内 + if distance > 0.05 or distance < -0.02: + return False + + # 条件4:价格不能离MA60太远 + if current_price > ma60 * 1.30: + return False + + return True + + except: + return False + +## 检查卖出信号(均线破位) +def check_sell_signal(stock): + """ + 卖出条件: + 1. 价格跌破MA60 + 2. MA20下穿MA60(死叉) + 3. 价格跌破MA20且持续3天 + """ + try: + df = attribute_history(stock, g.ma_mid + 5, '1d', ['close'], skip_paused=True) + if len(df) < g.ma_mid: + return False + + close = df['close'].values + current_price = close[-1] + + ma20 = np.mean(close[-g.ma_short:]) + ma60 = np.mean(close[-g.ma_mid:]) + + # 价格跌破MA60超过2% + if current_price < ma60 * 0.98: + return True + + # MA20下穿MA60(死叉) + ma20_yesterday = np.mean(close[-g.ma_short-1:-1]) + ma60_yesterday = np.mean(close[-g.ma_mid-1:-1]) + if ma20_yesterday > ma60_yesterday and ma20 < ma60: + return True + + # 连续3天收盘在MA20下方 + ma20_3d = [np.mean(close[-g.ma_short-i:-i]) if i > 0 else ma20 for i in range(3)] + below_ma20_count = sum(1 for i in range(3) if close[-1-i] < ma20_3d[i] * 0.99) + if below_ma20_count >= 3: + return True + + return False + + except: + return False + diff --git a/longhubang.db b/longhubang.db index 87034861cea4910ff49dc3d79d7e37516c79c026..f1c273bd112bfefc57c8966c87d6050fb93bc754 100644 GIT binary patch delta 138224 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 delta 33327 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

vT5m}h$A8maf}7U_ZUfYE^4 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 AUH||9 diff --git a/longhubang_data.py b/longhubang_data.py index 2ebcc7e..501d5c3 100644 --- a/longhubang_data.py +++ b/longhubang_data.py @@ -23,7 +23,9 @@ class LonghubangDataFetcher: api_key: StockAPI的API密钥(可选,普通请求每日免费1000次) """ print("[智瞰龙虎] 龙虎榜数据获取器初始化...") - self.base_url = "https://www.stockapi.com.cn/v1" + # self.base_url = "https://api-lhb.zhongdu.net" + self.base_url = "http://lhb-api.ws4.cn/v1" + # self.base_url = "https://www.stockapi.com.cn/v1" self.api_key = api_key self.max_retries = 3 # 最大重试次数 self.retry_delay = 2 # 重试延迟(秒) @@ -79,6 +81,7 @@ class LonghubangDataFetcher: """ print(f"[智瞰龙虎] 获取 {date} 的龙虎榜数据...") + # url = f"{self.base_url}" url = f"{self.base_url}/youzi/all" params = {'date': date} diff --git a/low_price_bull_monitor.db b/low_price_bull_monitor.db new file mode 100644 index 0000000000000000000000000000000000000000..1f5cbc1b67a0d270c2456ddafb6e4a9db7c4cdd5 GIT binary patch literal 20480 zcmeI3(Qn&C7{JetvxaWTv8qxvjiN(kTB|ja6UT#w2bL8xRixS4B@=xzUTjK45^u2+ zQ6A7F24U?c1P@G1)1*#Fu$S!#tpc>Z@)sbXy(TH;0bY6J96LC6nk6qt>-3%CVbEzp5JKbh3eu~SSeoHFH}se3%J<8Tqm#9b zF#ZQ|2NNh<$B)7y%|HSIKmZ5;0U!VbfB+Bx0{e)-r=M^m6H`-6ecn_q6*WUE7G3%iKN@-xtb~j{m?edT{E1F{H|V8{k_w4;i=CB2=> zrL%3Lqnku&k+3?@99sl#{02M|+mo{?2$K@J;r> z#Dyq>3T0LM$O$>6YU=HK*&h&c%r6Ph*KNpp<%T0?5f|Z5WF52kZp6w$0s=q)2mk>f z00e*l5C8%|00;nq=afJycn}>q7J4OgR6vO51zvcqv30%q^^Kheo2||JaXv0aV{!U% zX34IA#O9L1>q&kNe~jk4RK(-rT=QoAkH?Q&cW*VeZo4h|syJ0+|MNmvdYTvZJX&>i zT3*D&q!9Ot1TmT57gJ(mYh&mB_F^i||NYC2*80`forjI>&E}Vnnm4~`)_kss*9|~c)V}`zQ?)JV?5s<>z=+@BXi2@ zTS82VV(a=wbNz>%?|f00e-*GfiNOJ93n<*F&7elb|EAmIIuLKj(<7)eA=y=xXSQtR4JgFWBn{jwnn; zEdCwgKj{}FAOHk_01yBIKmZ5;0U!VbfB+Bx0zhDY5x{I Tuple[bool, str]: + """ + 添加股票到监控列表 + + Args: + stock_code: 股票代码(不含后缀) + stock_name: 股票名称 + buy_price: 买入价格 + buy_date: 买入日期(格式:YYYY-MM-DD) + + Returns: + (是否成功, 消息) + """ + try: + if buy_date is None: + buy_date = datetime.now().strftime("%Y-%m-%d") + + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 检查是否已存在 + cursor.execute(""" + SELECT id FROM monitored_stocks + WHERE stock_code = ? AND status = 'holding' + """, (stock_code,)) + + if cursor.fetchone(): + conn.close() + return False, f"股票 {stock_code} 已在监控列表中" + + # 添加到监控列表 + cursor.execute(""" + INSERT INTO monitored_stocks + (stock_code, stock_name, buy_price, buy_date, add_time) + VALUES (?, ?, ?, ?, ?) + """, (stock_code, stock_name, buy_price, buy_date, + datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + + conn.commit() + conn.close() + + self.logger.info(f"添加股票到监控: {stock_code} {stock_name}") + return True, f"成功添加 {stock_code} {stock_name} 到监控列表" + + except Exception as e: + self.logger.error(f"添加股票失败: {e}") + return False, f"添加失败: {str(e)}" + + def remove_stock(self, stock_code: str, reason: str = "手动移除") -> Tuple[bool, str]: + """ + 从监控列表移除股票 + + Args: + stock_code: 股票代码 + reason: 移除原因 + + Returns: + (是否成功, 消息) + """ + try: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 先检查是否存在持仓中的股票 + cursor.execute(""" + SELECT id FROM monitored_stocks + WHERE stock_code = ? AND status = 'holding' + """, (stock_code,)) + + if not cursor.fetchone(): + conn.close() + return False, f"股票 {stock_code} 不在监控列表中" + + # 先删除该股票的所有'removed'记录(避免UNIQUE约束冲突) + cursor.execute(""" + DELETE FROM monitored_stocks + WHERE stock_code = ? AND status = 'removed' + """, (stock_code,)) + + # 然后更新持仓中的记录为'removed' + cursor.execute(""" + UPDATE monitored_stocks + SET status = 'removed', + remove_time = ?, + remove_reason = ? + WHERE stock_code = ? AND status = 'holding' + """, (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), reason, stock_code)) + + conn.commit() + conn.close() + + self.logger.info(f"移除股票: {stock_code}, 原因: {reason}") + return True, f"成功移除 {stock_code}" + + except Exception as e: + self.logger.error(f"移除股票失败: {e}") + return False, f"移除失败: {str(e)}" + + def get_monitored_stocks(self) -> List[Dict]: + """ + 获取所有监控中的股票 + + Returns: + 股票列表 + """ + try: + conn = sqlite3.connect(self.db_path) + df = pd.read_sql_query(""" + SELECT * FROM monitored_stocks + WHERE status = 'holding' + ORDER BY add_time DESC + """, conn) + conn.close() + + return df.to_dict('records') if not df.empty else [] + + except Exception as e: + self.logger.error(f"获取监控列表失败: {e}") + return [] + + def update_holding_days(self): + """更新所有股票的持有天数""" + try: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 获取所有持仓股票 + cursor.execute(""" + SELECT stock_code, buy_date FROM monitored_stocks + WHERE status = 'holding' + """) + + stocks = cursor.fetchall() + today = datetime.now().date() + + for stock_code, buy_date in stocks: + buy_date_obj = datetime.strptime(buy_date, "%Y-%m-%d").date() + holding_days = (today - buy_date_obj).days + + cursor.execute(""" + UPDATE monitored_stocks + SET holding_days = ? + WHERE stock_code = ? AND status = 'holding' + """, (holding_days, stock_code)) + + conn.commit() + conn.close() + + self.logger.info("持有天数更新完成") + + except Exception as e: + self.logger.error(f"更新持有天数失败: {e}") + + def add_sell_alert(self, stock_code: str, stock_name: str, alert_type: str, + alert_reason: str, current_price: float = None, + ma5: float = None, ma20: float = None, + holding_days: int = None) -> bool: + """ + 添加卖出提醒 + + Args: + stock_code: 股票代码 + stock_name: 股票名称 + alert_type: 提醒类型(holding_days/ma_cross) + alert_reason: 提醒原因 + current_price: 当前价格 + ma5: MA5值 + ma20: MA20值 + holding_days: 持有天数 + + Returns: + 是否成功 + """ + try: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + # 检查是否已存在相同的提醒 + cursor.execute(""" + SELECT id FROM sell_alerts + WHERE stock_code = ? AND alert_type = ? AND is_sent = 0 + """, (stock_code, alert_type)) + + if cursor.fetchone(): + conn.close() + return False + + cursor.execute(""" + INSERT INTO sell_alerts + (stock_code, stock_name, alert_type, alert_reason, + current_price, ma5, ma20, holding_days, alert_time) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, (stock_code, stock_name, alert_type, alert_reason, + current_price, ma5, ma20, holding_days, + datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + + conn.commit() + conn.close() + + self.logger.info(f"添加卖出提醒: {stock_code} - {alert_reason}") + return True + + except Exception as e: + self.logger.error(f"添加卖出提醒失败: {e}") + return False + + def get_pending_alerts(self) -> List[Dict]: + """ + 获取待发送的提醒 + + Returns: + 提醒列表 + """ + try: + conn = sqlite3.connect(self.db_path) + df = pd.read_sql_query(""" + SELECT * FROM sell_alerts + WHERE is_sent = 0 + ORDER BY alert_time DESC + """, conn) + conn.close() + + return df.to_dict('records') if not df.empty else [] + + except Exception as e: + self.logger.error(f"获取提醒失败: {e}") + return [] + + def mark_alert_sent(self, alert_id: int): + """标记提醒已发送""" + try: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cursor.execute(""" + UPDATE sell_alerts + SET is_sent = 1 + WHERE id = ? + """, (alert_id,)) + + conn.commit() + conn.close() + + except Exception as e: + self.logger.error(f"标记提醒失败: {e}") + + def get_history_alerts(self, limit: int = 50) -> List[Dict]: + """ + 获取历史提醒记录 + + Args: + limit: 返回记录数 + + Returns: + 提醒列表 + """ + try: + conn = sqlite3.connect(self.db_path) + df = pd.read_sql_query(f""" + SELECT * FROM sell_alerts + ORDER BY alert_time DESC + LIMIT {limit} + """, conn) + conn.close() + + return df.to_dict('records') if not df.empty else [] + + except Exception as e: + self.logger.error(f"获取历史提醒失败: {e}") + return [] + + def clear_old_alerts(self, days: int = 30): + """ + 清理旧的提醒记录 + + Args: + days: 保留天数 + """ + try: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + cutoff_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d") + + cursor.execute(""" + DELETE FROM sell_alerts + WHERE alert_time < ? AND is_sent = 1 + """, (cutoff_date,)) + + deleted = cursor.rowcount + conn.commit() + conn.close() + + self.logger.info(f"清理了 {deleted} 条旧提醒记录") + + except Exception as e: + self.logger.error(f"清理旧提醒失败: {e}") + + +# 全局监控器实例 +low_price_bull_monitor = LowPriceBullMonitor() diff --git a/low_price_bull_monitor_ui.py b/low_price_bull_monitor_ui.py new file mode 100644 index 0000000..6eb34ef --- /dev/null +++ b/low_price_bull_monitor_ui.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +低价擒牛策略监控UI模块 +""" + +import streamlit as st +import pandas as pd +from datetime import datetime +from low_price_bull_monitor import low_price_bull_monitor +from low_price_bull_service import low_price_bull_service + + +def display_monitor_panel(): + """显示策略监控面板""" + + st.markdown("## 📊 策略监控中心") + st.markdown("---") + + # 服务状态 + status = low_price_bull_service.get_status() + + col1, col2, col3, col4 = st.columns(4) + + with col1: + service_status = "🟢 运行中" if status['running'] else "🔴 已停止" + st.metric("服务状态", service_status) + + with col2: + st.metric("监控股票", f"{status['monitored_count']} 只") + + with col3: + st.metric("待处理提醒", f"{status['pending_alerts']} 条") + + with col4: + st.metric("扫描间隔", f"{status['scan_interval']} 秒") + + st.markdown("---") + + # 服务控制 + col_start, col_stop, col_config = st.columns(3) + + with col_start: + if st.button("▶️ 启动监控服务", type="primary", disabled=status['running']): + if low_price_bull_service.start(): + st.success("✅ 监控服务已启动") + st.rerun() + else: + st.error("❌ 启动失败") + + with col_stop: + if st.button("⏸️ 停止监控服务", type="secondary", disabled=not status['running']): + if low_price_bull_service.stop(): + st.success("✅ 监控服务已停止") + st.rerun() + else: + st.error("❌ 停止失败") + + with col_config: + with st.popover("⚙️ 监控配置"): + st.markdown("**扫描间隔**") + new_interval = st.number_input( + "扫描间隔(秒)", + min_value=10, + max_value=600, + value=status['scan_interval'], + step=10, + label_visibility="collapsed" + ) + + if st.button("保存配置"): + low_price_bull_service.scan_interval = new_interval + st.success("✅ 配置已保存") + + st.markdown("---") + + # 标签页 + tab1, tab2, tab3 = st.tabs(["📋 监控列表", "🔔 卖出提醒", "📜 历史记录"]) + + with tab1: + display_monitored_stocks() + + with tab2: + display_sell_alerts() + + with tab3: + display_alert_history() + + +def display_monitored_stocks(): + """显示监控中的股票列表""" + + stocks = low_price_bull_monitor.get_monitored_stocks() + + if not stocks: + st.info("暂无监控中的股票") + return + + st.markdown(f"### 📋 监控列表(共{len(stocks)}只)") + + # 转换为DataFrame + df = pd.DataFrame(stocks) + + # 显示表格 + display_df = df[['stock_code', 'stock_name', 'buy_price', 'buy_date', 'holding_days', 'add_time']].copy() + display_df.columns = ['股票代码', '股票名称', '买入价格', '买入日期', '持有天数', '加入时间'] + + st.dataframe(display_df, width='content', height=400) + + # 批量移除 + st.markdown("---") + st.markdown("### 🗑️ 批量管理") + + selected_codes = st.multiselect( + "选择要移除的股票", + options=[f"{s['stock_code']} {s['stock_name']}" for s in stocks], + format_func=lambda x: x + ) + + if selected_codes and st.button("🗑️ 移除选中股票", type="secondary"): + for item in selected_codes: + code = item.split()[0] + success, msg = low_price_bull_monitor.remove_stock(code, "手动移除") + if success: + st.success(msg) + else: + st.error(msg) + st.rerun() + + +def display_sell_alerts(): + """显示待处理的卖出提醒""" + + alerts = low_price_bull_monitor.get_pending_alerts() + + if not alerts: + st.info("暂无待处理的卖出提醒") + return + + st.markdown(f"### 🔔 卖出提醒(共{len(alerts)}条)") + + for alert in alerts: + with st.expander( + f"🔔 {alert['stock_code']} {alert['stock_name']} - {alert['alert_reason']}", + expanded=True + ): + col1, col2 = st.columns(2) + + with col1: + st.markdown("#### 📊 提醒信息") + st.markdown(f"**股票代码**: {alert['stock_code']}") + st.markdown(f"**股票名称**: {alert['stock_name']}") + st.markdown(f"**提醒类型**: {_get_alert_type_name(alert['alert_type'])}") + st.markdown(f"**提醒原因**: {alert['alert_reason']}") + st.markdown(f"**提醒时间**: {alert['alert_time']}") + + with col2: + st.markdown("#### 💰 市场数据") + + # 处理当前价格(避免bytes类型错误) + current_price = alert.get('current_price') + if current_price is not None: + try: + price_val = float(current_price) + st.markdown(f"**当前价格**: {price_val:.2f}元") + except (ValueError, TypeError): + st.markdown(f"**当前价格**: {current_price}") + + # 处理MA5和MA20(避免bytes类型错误) + ma5 = alert.get('ma5') + ma20 = alert.get('ma20') + if ma5 is not None and ma20 is not None: + try: + ma5_val = float(ma5) + ma20_val = float(ma20) + st.markdown(f"**MA5**: {ma5_val:.2f}") + st.markdown(f"**MA20**: {ma20_val:.2f}") + st.markdown(f"**均线差**: {(ma5_val - ma20_val):.2f}") + except (ValueError, TypeError): + st.markdown(f"**MA5**: {ma5}") + st.markdown(f"**MA20**: {ma20}") + + # 处理持有天数 + holding_days = alert.get('holding_days') + if holding_days is not None: + try: + days_val = int(holding_days) + st.markdown(f"**持有天数**: {days_val}天") + except (ValueError, TypeError): + st.markdown(f"**持有天数**: {holding_days}") + + # 操作按钮 + st.markdown("---") + col_action1, col_action2 = st.columns(2) + + with col_action1: + if st.button(f"✅ 已处理 - {alert['stock_code']}", key=f"done_{alert['id']}"): + low_price_bull_monitor.mark_alert_sent(alert['id']) + low_price_bull_monitor.remove_stock(alert['stock_code'], "已处理提醒") + st.success(f"✅ 已标记为已处理并移除 {alert['stock_code']}") + st.rerun() + + with col_action2: + if st.button(f"❌ 忽略 - {alert['stock_code']}", key=f"ignore_{alert['id']}"): + low_price_bull_monitor.mark_alert_sent(alert['id']) + st.success(f"✅ 已忽略提醒(股票保留在监控列表)") + st.rerun() + + +def display_alert_history(): + """显示历史提醒记录""" + + alerts = low_price_bull_monitor.get_history_alerts(limit=50) + + if not alerts: + st.info("暂无历史提醒记录") + return + + st.markdown("### 📜 历史提醒记录") + + # 转换为DataFrame + df = pd.DataFrame(alerts) + + # 选择显示列 + display_cols = ['stock_code', 'stock_name', 'alert_type', 'alert_reason', 'current_price', 'holding_days', 'alert_time', 'is_sent'] + display_df = df[display_cols].copy() + + # 重命名列 + display_df.columns = ['股票代码', '股票名称', '提醒类型', '提醒原因', '当前价格', '持有天数', '提醒时间', '已发送'] + + # 格式化 + display_df['提醒类型'] = display_df['提醒类型'].apply(_get_alert_type_name) + display_df['已发送'] = display_df['已发送'].apply(lambda x: '✅' if x == 1 else '❌') + + st.dataframe(display_df, width='content', height=400) + + # 清理按钮 + st.markdown("---") + if st.button("🗑️ 清理30天前的记录"): + low_price_bull_monitor.clear_old_alerts(days=30) + st.success("✅ 已清理旧记录") + st.rerun() + + +def _get_alert_type_name(alert_type: str) -> str: + """获取提醒类型名称""" + type_map = { + 'holding_days': '持股到期', + 'ma_cross': 'MA均线死叉' + } + return type_map.get(alert_type, alert_type) + + +def add_stock_to_monitor_button(stock_code: str, stock_name: str, price: float = None): + """ + 添加股票到监控按钮(在股票详情中使用) + + Args: + stock_code: 股票代码 + stock_name: 股票名称 + price: 买入价格 + """ + # 检查是否已在监控 + stocks = low_price_bull_monitor.get_monitored_stocks() + is_monitored = any(s['stock_code'] == stock_code for s in stocks) + + if is_monitored: + st.info(f"✅ {stock_code} 已在监控列表中") + if st.button(f"🗑️ 移出监控 - {stock_code}", key=f"remove_{stock_code}"): + success, msg = low_price_bull_monitor.remove_stock(stock_code, "手动移除") + if success: + st.success(msg) + st.rerun() + else: + st.error(msg) + else: + if st.button(f"➕ 加入策略监控 - {stock_code}", type="primary", key=f"add_{stock_code}"): + if price is None: + price = 0.0 # 如果没有价格,使用0 + + success, msg = low_price_bull_monitor.add_stock( + stock_code=stock_code, + stock_name=stock_name, + buy_price=price, + buy_date=datetime.now().strftime("%Y-%m-%d") + ) + + if success: + st.success(msg) + st.info("💡 提示:请在左侧菜单进入'策略监控'查看监控状态") + st.rerun() + else: + st.error(msg) diff --git a/low_price_bull_selector.py b/low_price_bull_selector.py new file mode 100644 index 0000000..798b060 --- /dev/null +++ b/low_price_bull_selector.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +低价擒牛选股模块 +使用pywencai获取低价高成长股票 +""" + +import pandas as pd +import pywencai +from datetime import datetime +from typing import Tuple, Optional +import time + + +class LowPriceBullSelector: + """低价擒牛选股类""" + + def __init__(self): + self.raw_data = None + self.selected_stocks = None + + def get_low_price_stocks(self, top_n: int = 5) -> Tuple[bool, Optional[pd.DataFrame], str]: + """ + 获取低价高成长股票 + + 选股策略: + - 股价<10元 + - 净利润增长率≥100% + - 非ST + - 非科创板 + - 非创业板 + - 深圳A股 + - 成交额由小至大排名 + + Args: + top_n: 返回前N只股票 + + Returns: + (success, dataframe, message) + """ + try: + print(f"\n{'='*60}") + print(f"🐂 低价擒牛选股 - 数据获取中") + print(f"{'='*60}") + print(f"策略: 股价<10元 + 净利润增长率≥100% + 深圳A股") + print(f"目标: 筛选前{top_n}只股票") + + # 构建查询语句(按成交额由小至大排名) + query = ( + "股价<10元," + "净利润增长率(净利润同比增长率)≥100%," + "非st," + "非科创板," + "非创业板," + "深圳A股," + "成交额由小至大排名" + ) + + print(f"\n查询语句: {query}") + print(f"正在调用问财接口...") + + # 调用pywencai + result = pywencai.get(query=query, loop=True) + + if result is None: + return False, None, "问财接口返回None,请检查网络或稍后重试" + + # 转换为DataFrame + df_result = self._convert_to_dataframe(result) + + if df_result is None or df_result.empty: + return False, None, "未获取到符合条件的股票数据" + + print(f"✅ 成功获取 {len(df_result)} 只股票") + + # 显示获取到的列名 + print(f"\n获取到的数据字段:") + for col in df_result.columns[:15]: + print(f" - {col}") + if len(df_result.columns) > 15: + print(f" ... 还有 {len(df_result.columns) - 15} 个字段") + + # 保存原始数据 + self.raw_data = df_result + + # 取前N只 + if len(df_result) > top_n: + selected = df_result.head(top_n) + print(f"\n从 {len(df_result)} 只股票中选出前 {top_n} 只") + else: + selected = df_result + print(f"\n共 {len(df_result)} 只符合条件的股票") + + self.selected_stocks = selected + + # 显示选中的股票 + print(f"\n✅ 选中的股票:") + for idx, row in selected.iterrows(): + code = row.get('股票代码', 'N/A') + name = row.get('股票简称', 'N/A') + price = row.get('股价', row.get('最新价', 'N/A')) + growth = row.get('净利润增长率', row.get('净利润同比增长率', 'N/A')) + turnover = row.get('成交额', 'N/A') + print(f" {idx+1}. {code} {name} - 股价:{price} 净利增长:{growth}% 成交额:{turnover}") + + print(f"{'='*60}\n") + + return True, selected, f"成功筛选出{len(selected)}只低价高成长股票" + + except Exception as e: + error_msg = f"获取数据失败: {str(e)}" + print(f"❌ {error_msg}") + import traceback + traceback.print_exc() + return False, None, error_msg + + def _convert_to_dataframe(self, result) -> Optional[pd.DataFrame]: + """将pywencai返回结果转换为DataFrame""" + try: + if isinstance(result, pd.DataFrame): + return result + elif isinstance(result, dict): + if 'data' in result: + return pd.DataFrame(result['data']) + elif 'result' in result: + return pd.DataFrame(result['result']) + else: + return pd.DataFrame(result) + elif isinstance(result, list): + return pd.DataFrame(result) + else: + print(f"⚠️ 未知的数据格式: {type(result)}") + return None + except Exception as e: + print(f"转换DataFrame失败: {e}") + return None + + def get_stock_codes(self) -> list: + """ + 获取选中股票的代码列表(去掉市场后缀) + + Returns: + 股票代码列表 + """ + if self.selected_stocks is None or self.selected_stocks.empty: + return [] + + codes = [] + for code in self.selected_stocks['股票代码'].tolist(): + if isinstance(code, str): + # 去掉 .SZ 等后缀 + clean_code = code.split('.')[0] if '.' in code else code + codes.append(clean_code) + else: + codes.append(str(code)) + + return codes diff --git a/low_price_bull_service.py b/low_price_bull_service.py new file mode 100644 index 0000000..e8586a0 --- /dev/null +++ b/low_price_bull_service.py @@ -0,0 +1,430 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +低价擒牛策略监控服务 +定时扫描股票,检测卖出信号 +""" + +import time +import threading +import logging +from datetime import datetime +from typing import Optional +import os + +from low_price_bull_monitor import low_price_bull_monitor +from notification_service import notification_service + + +class LowPriceBullService: + """低价擒牛策略监控服务""" + + def __init__(self): + self.logger = logging.getLogger(__name__) + self.running = False + self.thread: Optional[threading.Thread] = None + self.scan_interval = 60 # 默认扫描间隔(秒) + self.holding_days_limit = 5 # 持股天数限制 + + # 从环境变量读取配置 + self._load_config() + + def _load_config(self): + """从环境变量加载配置""" + try: + from dotenv import load_dotenv + load_dotenv() + + # 扫描间隔 + interval = os.getenv('LOW_PRICE_BULL_SCAN_INTERVAL', '60') + self.scan_interval = int(interval) + + # 持股天数限制 + days = os.getenv('LOW_PRICE_BULL_HOLDING_DAYS', '5') + self.holding_days_limit = int(days) + + # TDX API配置 + self.tdx_api_url = os.getenv('TDX_BASE_URL', 'http://127.0.0.1:5000') + + self.logger.info(f"监控配置: 扫描间隔={self.scan_interval}秒, 持股天数限制={self.holding_days_limit}天") + self.logger.info(f"TDX API: {self.tdx_api_url}") + + except Exception as e: + self.logger.warning(f"加载配置失败,使用默认值: {e}") + + def start(self): + """启动监控服务""" + if self.running: + self.logger.warning("监控服务已在运行") + return False + + self.running = True + self.thread = threading.Thread(target=self._monitor_loop, daemon=True) + self.thread.start() + + self.logger.info("低价擒牛监控服务已启动") + return True + + def stop(self): + """停止监控服务""" + if not self.running: + return False + + self.running = False + if self.thread: + self.thread.join(timeout=5) + + self.logger.info("低价擒牛监控服务已停止") + return True + + def _monitor_loop(self): + """监控循环""" + while self.running: + try: + self._scan_stocks() + time.sleep(self.scan_interval) + except Exception as e: + self.logger.error(f"监控循环错误: {e}") + time.sleep(self.scan_interval) + + def _scan_stocks(self): + """扫描所有监控的股票""" + try: + # 更新持有天数 + low_price_bull_monitor.update_holding_days() + + # 获取监控列表 + stocks = low_price_bull_monitor.get_monitored_stocks() + + if not stocks: + return + + self.logger.info(f"开始扫描 {len(stocks)} 只股票") + + for stock in stocks: + try: + self._check_stock(stock) + except Exception as e: + self.logger.error(f"检查股票 {stock['stock_code']} 失败: {e}") + + # 处理提醒 + self._process_alerts() + + except Exception as e: + self.logger.error(f"扫描股票失败: {e}") + + def _check_stock(self, stock: dict): + """ + 检查单只股票的卖出信号 + + Args: + stock: 股票信息字典 + """ + stock_code = stock['stock_code'] + stock_name = stock['stock_name'] + holding_days = stock['holding_days'] + + # 检查1: 持股天数 + if holding_days >= self.holding_days_limit: + # 添加提醒 + low_price_bull_monitor.add_sell_alert( + stock_code=stock_code, + stock_name=stock_name, + alert_type='holding_days', + alert_reason=f'持股满{self.holding_days_limit}天,建议卖出', + holding_days=holding_days + ) + self.logger.info(f"{stock_code} 持股满{self.holding_days_limit}天,生成卖出提醒") + return + + # 检查2: MA5下穿MA20 + current_price, ma5, ma20 = self._get_stock_data(stock_code) + + if current_price and ma5 and ma20: + if ma5 < ma20: + # MA5下穿MA20,添加提醒 + low_price_bull_monitor.add_sell_alert( + stock_code=stock_code, + stock_name=stock_name, + alert_type='ma_cross', + alert_reason='MA5下穿MA20,技术信号卖出', + current_price=current_price, + ma5=ma5, + ma20=ma20, + holding_days=holding_days + ) + self.logger.info(f"{stock_code} MA5下穿MA20,生成卖出提醒") + + def _get_stock_data(self, stock_code: str) -> tuple: + """ + 获取股票数据(价格和均线) + + Args: + stock_code: 股票代码(可能带后缀,如002259.SZ) + + Returns: + (当前价格, MA5, MA20) + """ + try: + import requests + import pandas as pd + + # 处理股票代码格式:去掉后缀,保留纯数字代码 + # 例如:002259.SZ -> 002259 + clean_code = stock_code.split('.')[0] if '.' in stock_code else stock_code + + # 判断市场并添加前缀(TDX API可能需要) + # 深圳:0开头、3开头 -> SZ前缀 + # 上海:6开头 -> SH前缀 + if clean_code.startswith(('0', '3')): + api_code = f"SZ{clean_code}" + elif clean_code.startswith('6'): + api_code = f"SH{clean_code}" + else: + api_code = clean_code + + # 调用TDX API获取K线数据 + url = f"{self.tdx_api_url}/api/kline" + params = { + 'code': api_code, + 'type': 'day' # 日K线 + } + + self.logger.debug(f"请求TDX API: code={stock_code} -> api_code={api_code}") + + response = requests.get(url, params=params, timeout=10) + + if response.status_code != 200: + self.logger.warning(f"获取 {stock_code} K线数据失败: HTTP {response.status_code}") + self.logger.warning(f"请求URL: {url}?code={api_code}&type=day") + + # 尝试使用纯数字代码重试 + if api_code != clean_code: + self.logger.info(f"尝试使用纯数字代码重试: {clean_code}") + params['code'] = clean_code + response = requests.get(url, params=params, timeout=10) + + if response.status_code != 200: + self.logger.warning(f"重试失败: HTTP {response.status_code}") + return None, None, None + else: + return None, None, None + + data = response.json() + + # 检查数据格式 + # 支持两种格式: + # 1. 直接返回数组: [{date, open, high, low, close, volume}, ...] + # 2. 嵌套格式: {code: 0, message: "success", data: {List: [...]}} + if isinstance(data, dict) and 'data' in data: + # 嵌套格式 + if data.get('code') != 0: + self.logger.warning(f"{stock_code} API返回错误: {data.get('message')}") + return None, None, None + + data_obj = data.get('data', {}) + kline_list = data_obj.get('List', []) + + if not kline_list or len(kline_list) < 20: + self.logger.warning(f"{stock_code} K线数据不足,需要至少20天,当前{len(kline_list)}天") + return None, None, None + + # 转换为DataFrame,字段名需要映射 + # API返回:{Time, Open, High, Low, Close, Volume, Amount} + # 需要:{date, open, high, low, close, volume} + df = pd.DataFrame(kline_list) + + # 重命名字段(大小写转换) + if 'Time' in df.columns: + df['date'] = df['Time'] + if 'Open' in df.columns: + df['open'] = df['Open'] + if 'High' in df.columns: + df['high'] = df['High'] + if 'Low' in df.columns: + df['low'] = df['Low'] + if 'Close' in df.columns: + df['close'] = df['Close'] + if 'Volume' in df.columns: + df['volume'] = df['Volume'] + + elif isinstance(data, list): + # 直接数组格式 + if len(data) < 20: + self.logger.warning(f"{stock_code} K线数据不足,需要至少20天") + return None, None, None + + df = pd.DataFrame(data) + else: + self.logger.warning(f"{stock_code} K线数据格式错误") + return None, None, None + + # 确保有close列 + if 'close' not in df.columns: + self.logger.warning(f"{stock_code} K线数据缺少close字段") + return None, None, None + + # 转换为浮点数 + df['close'] = pd.to_numeric(df['close'], errors='coerce') + + # 计算MA5和MA20 + df['MA5'] = df['close'].rolling(window=5).mean() + df['MA20'] = df['close'].rolling(window=20).mean() + + # 获取最新数据 + latest = df.iloc[-1] + current_price = latest['close'] + ma5 = latest['MA5'] + ma20 = latest['MA20'] + + # 检查是否有效 + if pd.isna(current_price) or pd.isna(ma5) or pd.isna(ma20): + self.logger.warning(f"{stock_code} 数据包含NaN值") + return None, None, None + + self.logger.info(f"{stock_code} 数据: 价格={current_price:.2f}, MA5={ma5:.2f}, MA20={ma20:.2f}") + return current_price, ma5, ma20 + + except requests.exceptions.RequestException as e: + self.logger.error(f"请求TDX API失败 {stock_code}: {e}") + self.logger.error(f"请检查.env中的TDX_BASE_URL配置: {self.tdx_api_url}") + return None, None, None + except Exception as e: + self.logger.error(f"获取股票数据失败 {stock_code}: {e}") + import traceback + traceback.print_exc() + return None, None, None + + def _process_alerts(self): + """处理待发送的提醒""" + try: + alerts = low_price_bull_monitor.get_pending_alerts() + + if not alerts: + return + + self.logger.info(f"处理 {len(alerts)} 条卖出提醒") + + for alert in alerts: + try: + # 发送通知 + self._send_alert_notification(alert) + + # 标记已发送 + low_price_bull_monitor.mark_alert_sent(alert['id']) + + # 自动移除股票 + low_price_bull_monitor.remove_stock( + alert['stock_code'], + reason=alert['alert_reason'] + ) + + self.logger.info(f"已处理提醒并移除股票: {alert['stock_code']}") + + except Exception as e: + self.logger.error(f"处理提醒失败: {e}") + + except Exception as e: + self.logger.error(f"处理提醒失败: {e}") + + def _send_alert_notification(self, alert: dict): + """ + 发送卖出提醒通知 + + Args: + alert: 提醒信息字典 + """ + try: + # 构建消息 + keyword = notification_service.config.get('webhook_keyword', 'aiagents通知') + + message_text = f"### {keyword} - 低价擒牛卖出提醒\n\n" + message_text += f"**股票代码**: {alert['stock_code']}\n\n" + message_text += f"**股票名称**: {alert['stock_name']}\n\n" + message_text += f"**提醒类型**: {self._get_alert_type_name(alert['alert_type'])}\n\n" + message_text += f"**提醒原因**: {alert['alert_reason']}\n\n" + + # 添加详细信息(确保数据类型正确) + current_price = alert.get('current_price') + if current_price is not None: + try: + price_val = float(current_price) + message_text += f"**当前价格**: {price_val:.2f}元\n\n" + except (ValueError, TypeError): + pass + + ma5 = alert.get('ma5') + ma20 = alert.get('ma20') + if ma5 is not None and ma20 is not None: + try: + ma5_val = float(ma5) + ma20_val = float(ma20) + message_text += f"**MA5**: {ma5_val:.2f}\n\n" + message_text += f"**MA20**: {ma20_val:.2f}\n\n" + except (ValueError, TypeError): + pass + + holding_days = alert.get('holding_days') + if holding_days is not None: + try: + days_val = int(holding_days) + message_text += f"**持有天数**: {days_val}天\n\n" + except (ValueError, TypeError): + pass + + message_text += f"**提醒时间**: {alert['alert_time']}\n\n" + message_text += "---\n\n" + message_text += "**建议**: 开盘时卖出该股票\n\n" + message_text += "_此消息由AI股票分析系统自动发送_" + + # 发送钉钉通知 + if notification_service.config['webhook_enabled']: + import requests + + data = { + "msgtype": "markdown", + "markdown": { + "title": f"{keyword} - 卖出提醒", + "text": message_text + } + } + + response = requests.post( + notification_service.config['webhook_url'], + json=data, + headers={'Content-Type': 'application/json'}, + timeout=10 + ) + + if response.status_code == 200: + self.logger.info(f"卖出提醒已发送: {alert['stock_code']}") + else: + self.logger.error(f"发送提醒失败: HTTP {response.status_code}") + + except Exception as e: + self.logger.error(f"发送通知失败: {e}") + + def _get_alert_type_name(self, alert_type: str) -> str: + """获取提醒类型名称""" + type_map = { + 'holding_days': '持股到期', + 'ma_cross': 'MA均线死叉' + } + return type_map.get(alert_type, alert_type) + + def get_status(self) -> dict: + """获取服务状态""" + stocks = low_price_bull_monitor.get_monitored_stocks() + alerts = low_price_bull_monitor.get_pending_alerts() + + return { + 'running': self.running, + 'scan_interval': self.scan_interval, + 'holding_days_limit': self.holding_days_limit, + 'monitored_count': len(stocks), + 'pending_alerts': len(alerts) + } + + +# 全局服务实例 +low_price_bull_service = LowPriceBullService() diff --git a/low_price_bull_strategy.py b/low_price_bull_strategy.py new file mode 100644 index 0000000..4426344 --- /dev/null +++ b/low_price_bull_strategy.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +低价擒牛量化交易策略 +实现基于MA均线的买卖择时策略 +""" + +import pandas as pd +from datetime import datetime, timedelta +from typing import Dict, List, Optional +import logging + + +class LowPriceBullStrategy: + """低价擒牛量化交易策略""" + + def __init__(self, initial_capital: float = 1000000.0): + """ + 初始化策略 + + Args: + initial_capital: 初始资金(默认100万) + """ + self.logger = logging.getLogger(__name__) + + # 策略参数 + self.initial_capital = initial_capital + self.available_cash = initial_capital + self.max_stocks = 4 # 账户最大持股数 + self.max_position_per_stock = 0.4 # 个股最大持仓比例(4成) + self.max_daily_buy = 2 # 单日最大买入数 + self.holding_period = 5 # 持股周期(天) + + # 持仓信息 + self.positions: Dict[str, Dict] = {} # {股票代码: {买入价, 数量, 买入日期, 持有天数}} + self.trade_history: List[Dict] = [] # 交易历史 + + # 当日交易计数 + self.daily_buy_count = 0 + self.current_date = None + + def reset_daily_counter(self, date): + """重置当日计数器""" + if self.current_date != date: + self.current_date = date + self.daily_buy_count = 0 + + def can_buy(self, stock_code: str) -> tuple[bool, str]: + """ + 检查是否可以买入 + + Returns: + (是否可买, 原因) + """ + # 检查是否已持有 + if stock_code in self.positions: + return False, "已持有该股票" + + # 检查持股数量 + if len(self.positions) >= self.max_stocks: + return False, f"已达最大持股数限制({self.max_stocks}只)" + + # 检查当日买入数量 + if self.daily_buy_count >= self.max_daily_buy: + return False, f"今日已达最大买入数限制({self.max_daily_buy}只)" + + # 检查资金 + if self.available_cash <= 0: + return False, "可用资金不足" + + return True, "可以买入" + + def calculate_buy_amount(self, stock_price: float) -> tuple[int, float]: + """ + 计算买入数量(满仓策略) + + Args: + stock_price: 股票价格 + + Returns: + (买入股数, 买入金额) + """ + # 满仓策略:使用所有可用资金 + max_amount = self.available_cash + + # 但不能超过个股最大持仓 + max_per_stock = self.initial_capital * self.max_position_per_stock + target_amount = min(max_amount, max_per_stock) + + # 计算股数(A股100股为1手) + shares = int(target_amount / stock_price / 100) * 100 + + if shares < 100: + return 0, 0 + + actual_amount = shares * stock_price + return shares, actual_amount + + def buy(self, stock_code: str, stock_name: str, price: float, date: str) -> tuple[bool, str, Optional[Dict]]: + """ + 执行买入操作 + + Returns: + (是否成功, 消息, 交易详情) + """ + # 重置当日计数 + self.reset_daily_counter(date) + + # 检查是否可买入 + can_buy, reason = self.can_buy(stock_code) + if not can_buy: + return False, reason, None + + # 计算买入数量 + shares, amount = self.calculate_buy_amount(price) + + if shares == 0: + return False, "资金不足,无法买入100股", None + + # 执行买入 + self.positions[stock_code] = { + 'name': stock_name, + 'shares': shares, + 'buy_price': price, + 'buy_date': date, + 'holding_days': 0 + } + + self.available_cash -= amount + self.daily_buy_count += 1 + + # 记录交易 + trade = { + 'type': 'BUY', + 'code': stock_code, + 'name': stock_name, + 'price': price, + 'shares': shares, + 'amount': amount, + 'date': date, + 'cash_after': self.available_cash + } + self.trade_history.append(trade) + + message = f"✅ 买入成功: {stock_code} {stock_name} | 价格:{price:.2f} | 数量:{shares}股 | 金额:{amount:.2f}元" + self.logger.info(message) + + return True, message, trade + + def should_sell(self, stock_code: str, ma5: float, ma20: float, current_date: str) -> tuple[bool, str]: + """ + 判断是否应该卖出 + + 策略: + 1. MA5下穿MA20时卖出 + 2. 持股满5天强制卖出 + + Returns: + (是否卖出, 原因) + """ + if stock_code not in self.positions: + return False, "未持有该股票" + + position = self.positions[stock_code] + + # 更新持有天数 + # 简化处理,按交易日计算 + position['holding_days'] += 1 + + # 检查持股周期 + if position['holding_days'] >= self.holding_period: + return True, f"持股满{self.holding_period}天,到期卖出" + + # 检查MA5下穿MA20 + if ma5 is not None and ma20 is not None: + if ma5 < ma20: + return True, "MA5下穿MA20,技术信号卖出" + + return False, "持有" + + def sell(self, stock_code: str, price: float, date: str, reason: str = "") -> tuple[bool, str, Optional[Dict]]: + """ + 执行卖出操作 + + Returns: + (是否成功, 消息, 交易详情) + """ + if stock_code not in self.positions: + return False, "未持有该股票", None + + position = self.positions[stock_code] + shares = position['shares'] + buy_price = position['buy_price'] + + # 计算盈亏 + amount = shares * price + cost = shares * buy_price + profit = amount - cost + profit_pct = (profit / cost) * 100 if cost > 0 else 0 + + # 归还资金 + self.available_cash += amount + + # 移除持仓 + del self.positions[stock_code] + + # 记录交易 + trade = { + 'type': 'SELL', + 'code': stock_code, + 'name': position['name'], + 'price': price, + 'shares': shares, + 'amount': amount, + 'date': date, + 'reason': reason, + 'buy_price': buy_price, + 'profit': profit, + 'profit_pct': profit_pct, + 'cash_after': self.available_cash + } + self.trade_history.append(trade) + + profit_str = f"+{profit:.2f}" if profit >= 0 else f"{profit:.2f}" + message = f"✅ 卖出成功: {stock_code} {position['name']} | 价格:{price:.2f} | 数量:{shares}股 | 盈亏:{profit_str}元({profit_pct:+.2f}%) | 原因:{reason}" + self.logger.info(message) + + return True, message, trade + + def get_portfolio_summary(self) -> Dict: + """ + 获取投资组合摘要 + + Returns: + 组合摘要信息 + """ + # 计算持仓市值(需要当前价格,这里用买入价估算) + position_value = sum( + pos['shares'] * pos['buy_price'] + for pos in self.positions.values() + ) + + total_value = self.available_cash + position_value + + # 计算收益 + total_profit = total_value - self.initial_capital + total_profit_pct = (total_profit / self.initial_capital) * 100 + + return { + 'initial_capital': self.initial_capital, + 'available_cash': self.available_cash, + 'position_value': position_value, + 'total_value': total_value, + 'total_profit': total_profit, + 'total_profit_pct': total_profit_pct, + 'positions_count': len(self.positions), + 'max_stocks': self.max_stocks, + 'trade_count': len(self.trade_history) + } + + def get_positions(self) -> List[Dict]: + """获取当前持仓列表""" + return [ + { + 'code': code, + 'name': pos['name'], + 'shares': pos['shares'], + 'buy_price': pos['buy_price'], + 'buy_date': pos['buy_date'], + 'holding_days': pos['holding_days'] + } + for code, pos in self.positions.items() + ] + + def get_trade_history(self) -> List[Dict]: + """获取交易历史""" + return self.trade_history.copy() diff --git a/low_price_bull_ui.py b/low_price_bull_ui.py new file mode 100644 index 0000000..8decb10 --- /dev/null +++ b/low_price_bull_ui.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +低价擒牛UI模块 +""" + +import streamlit as st +import pandas as pd +from datetime import datetime +from low_price_bull_selector import LowPriceBullSelector +from low_price_bull_strategy import LowPriceBullStrategy +from notification_service import notification_service +from low_price_bull_monitor import low_price_bull_monitor +from low_price_bull_service import low_price_bull_service + + +def display_low_price_bull(): + """显示低价擒牛选股界面""" + + # 检查是否显示监控面板 + if st.session_state.get('show_low_price_monitor'): + from low_price_bull_monitor_ui import display_monitor_panel + display_monitor_panel() + + # 返回按钮 + if st.button("🔙 返回选股", type="secondary"): + del st.session_state.show_low_price_monitor + st.rerun() + return + + st.markdown("顶部按钮区") + col_select, col_monitor = st.columns([3, 1]) + + with col_select: + st.markdown("## 🐂 低价擒牛 - 低价高成长股票筛选") + + with col_monitor: + st.write("") # 占位 + if st.button("📊 策略监控", type="primary", width='content'): + st.session_state.show_low_price_monitor = True + st.rerun() + + st.markdown("---") + + st.markdown(""" + ### 📋 选股策略说明 + + **筛选条件**: + - ✅ 股价 < 10元 + - ✅ 净利润增长率 ≥ 100%(净利润同比增长率) + - ✅ 非ST股票 + - ✅ 非科创板 + - ✅ 非创业板 + - ✅ 深圳A股 + - ✅ 按成交额由小至大排名 + + **量化交易策略**: + - 💰 资金量:100万元 + - 📅 持股周期:5天 + - 💼 仓位控制:满仓 + - 📊 个股最大持仓:4成(40%) + - 🎯 账户最大持股数:4只 + - 🛒 单日最大买入数:2只 + - 📈 买入时机:开盘买入 + - 📉 卖出时机:MA5下穿MA20或持股满5天 + """) + + st.markdown("---") + + # 参数设置 + col1, col2 = st.columns([2, 1]) + + with col1: + top_n = st.slider( + "筛选数量", + min_value=3, + max_value=10, + value=5, + step=1, + help="选择展示的股票数量" + ) + + with col2: + st.info(f"💡 将筛选成交额最小的前{top_n}只股票") + + st.markdown("---") + + # 开始选股按钮 + if st.button("🚀 开始低价擒牛选股", type="primary", width='content'): + + with st.spinner("正在获取数据,请稍候..."): + # 创建选股器 + selector = LowPriceBullSelector() + + # 获取股票 + success, stocks_df, message = selector.get_low_price_stocks(top_n=top_n) + + if success and stocks_df is not None: + # 保存结果 + st.session_state.low_price_bull_stocks = stocks_df + st.session_state.low_price_bull_selector = selector + + st.success(f"✅ {message}") + + # 发送钉钉通知 + send_dingtalk_notification(stocks_df, top_n) + + st.rerun() + else: + st.error(f"❌ {message}") + + # 显示选股结果 + if 'low_price_bull_stocks' in st.session_state: + display_stock_results( + st.session_state.low_price_bull_stocks, + st.session_state.get('low_price_bull_selector') + ) + + +def display_stock_results(stocks_df: pd.DataFrame, selector): + """显示选股结果""" + + st.markdown("---") + st.markdown("## 📊 选股结果") + + # 统计信息 + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("筛选数量", f"{len(stocks_df)} 只") + + with col2: + # 智能计算平均净利增长率(过滤无效值) + growth_col = stocks_df.get('净利润增长率', stocks_df.get('净利润同比增长率', pd.Series([]))) + valid_growth = growth_col[growth_col.notna() & (growth_col != '') & (growth_col != 'N/A')] + if len(valid_growth) > 0: + avg_growth = pd.to_numeric(valid_growth, errors='coerce').mean() + if not pd.isna(avg_growth): + st.metric("平均净利增长率", f"{avg_growth:.1f}%") + else: + st.metric("平均净利增长率", "-") + else: + st.metric("平均净利增长率", "-") + + with col3: + # 智能计算平均股价(过滤无效值) + price_col = stocks_df.get('股价', stocks_df.get('最新价', pd.Series([]))) + valid_price = price_col[price_col.notna() & (price_col != '') & (price_col != 'N/A')] + if len(valid_price) > 0: + avg_price = pd.to_numeric(valid_price, errors='coerce').mean() + if not pd.isna(avg_price): + st.metric("平均股价", f"{avg_price:.2f} 元") + else: + st.metric("平均股价", "-") + else: + st.metric("平均股价", "-") + + st.markdown("---") + + # 显示股票列表 + st.markdown("### 📋 精选股票列表") + + for idx, row in stocks_df.iterrows(): + # 获取股票代码和简称 + code = row.get('股票代码', 'N/A') + name = row.get('股票简称', 'N/A') + + # 获取价格信息作为标题补充 + price = row.get('股价', row.get('最新价', None)) + price_str = '' + if price is not None and not pd.isna(price): + try: + price_float = float(price) + price_str = f" | 价格: {price_float:.2f}元" + except: + pass + + with st.expander( + f"【第{idx+1}名】{code} - {name}{price_str}", + expanded=(idx < 3) + ): + display_stock_detail(row) + + # 完整数据表格 + st.markdown("---") + st.markdown("### 📊 完整数据表格") + + # 选择关键列显示 + display_cols = ['股票代码', '股票简称'] + + # 智能匹配列名 + for pattern in ['股价', '最新价']: + matching = [col for col in stocks_df.columns if pattern in col] + if matching: + display_cols.append(matching[0]) + break + + for pattern in ['净利润增长率', '净利润同比增长率']: + matching = [col for col in stocks_df.columns if pattern in col] + if matching: + display_cols.append(matching[0]) + break + + for pattern in ['成交额']: + matching = [col for col in stocks_df.columns if pattern in col] + if matching: + display_cols.append(matching[0]) + break + + for col_name in ['总市值', '市盈率', '市净率', '所属行业']: + matching = [col for col in stocks_df.columns if col_name in col] + if matching: + display_cols.append(matching[0]) + + # 选择存在的列 + final_cols = [col for col in display_cols if col in stocks_df.columns] + + if final_cols: + st.dataframe(stocks_df[final_cols], width='content', height=400) + + # 下载按钮 + csv = stocks_df[final_cols].to_csv(index=False, encoding='utf-8-sig') + st.download_button( + label="📥 下载股票列表CSV", + data=csv, + file_name=f"low_price_bull_{datetime.now().strftime('%Y%m%d')}.csv", + mime="text/csv" + ) + + # 量化交易模拟 + st.markdown("---") + display_strategy_simulation(stocks_df, selector) + + +def display_stock_detail(row: pd.Series): + """显示单个股票详情""" + + def is_valid_value(value): + """判断值是否有效(非None、非NaN、非空字符串、非'N/A')""" + if value is None: + return False + if pd.isna(value): + return False + if str(value).strip() in ['', 'N/A', 'nan', 'None']: + return False + return True + + def format_value(value, suffix=''): + """格式化显示值""" + if isinstance(value, float): + if abs(value) >= 100000000: # 亿 + return f"{value/100000000:.2f}亿{suffix}" + elif abs(value) >= 10000: # 万 + return f"{value/10000:.2f}万{suffix}" + else: + return f"{value:.2f}{suffix}" + return f"{value}{suffix}" + + # 先检查是否有任何财务数据 + has_any_data = False + financial_fields = [ + ('所属行业', row.get('所属行业', row.get('所属同花顺行业', None))), + ('总市值', row.get('总市值', row.get('总市值[20241211]', None))), + ('市盈率', row.get('市盈率', row.get('市盈率pe', None))), + ('市净率', row.get('市净率', row.get('市净率pb', None))), + ('流通市值', row.get('流通市值', row.get('流通市值[20241211]', None))), + ('换手率', row.get('换手率', row.get('换手率[%]', None))) + ] + + for _, value in financial_fields: + if is_valid_value(value): + has_any_data = True + break + + # 只有当存在有效数据时才显示两列布局 + if has_any_data: + col1, col2 = st.columns(2) + else: + col1 = st.container() + col2 = None + + with col1: + st.markdown("#### 📊 基本信息") + + # 股票代码(必显示) + code = row.get('股票代码', '') + if is_valid_value(code): + st.markdown(f"**股票代码**: {code}") + + # 股票简称(必显示) + name = row.get('股票简称', '') + if is_valid_value(name): + st.markdown(f"**股票简称**: {name}") + + # 当前价格 + price = row.get('股价', row.get('最新价', None)) + if is_valid_value(price): + st.markdown(f"**当前价格**: {format_value(price, '元')}") + + # 净利润增长率 + growth = row.get('净利润增长率', row.get('净利润同比增长率', None)) + if is_valid_value(growth): + st.markdown(f"**净利润增长率**: {format_value(growth, '%')}") + + # 成交额 + turnover = row.get('成交额', None) + if is_valid_value(turnover): + st.markdown(f"**成交额**: {format_value(turnover, '元')}") + + # 涨跌幅 + change_pct = row.get('涨跌幅', row.get('涨跌幅:前复权[%]', None)) + if is_valid_value(change_pct): + st.markdown(f"**涨跌幅**: {format_value(change_pct, '%')}") + + # 只有当有财务数据时才显示财务指标栏目 + if col2 is not None: + with col2: + st.markdown("#### 💼 财务指标") + + # 所属行业 + industry = row.get('所属行业', row.get('所属同花顺行业', None)) + if is_valid_value(industry): + st.markdown(f"**所属行业**: {industry}") + + # 总市值 + market_cap = row.get('总市值', row.get('总市值[20241211]', None)) + if is_valid_value(market_cap): + st.markdown(f"**总市值**: {format_value(market_cap, '元')}") + + # 市盈率 + pe = row.get('市盈率', row.get('市盈率pe', None)) + if is_valid_value(pe): + st.markdown(f"**市盈率**: {format_value(pe, '')}") + + # 市净率 + pb = row.get('市净率', row.get('市净率pb', None)) + if is_valid_value(pb): + st.markdown(f"**市净率**: {format_value(pb, '')}") + + # 流通市值 + float_cap = row.get('流通市值', row.get('流通市值[20241211]', None)) + if is_valid_value(float_cap): + st.markdown(f"**流通市值**: {format_value(float_cap, '元')}") + + # 换手率 + turnover_rate = row.get('换手率', row.get('换手率[%]', None)) + if is_valid_value(turnover_rate): + st.markdown(f"**换手率**: {format_value(turnover_rate, '%')}") + + # 添加监控按钮 + st.markdown("---") + st.markdown("#### 📊 策略监控") + + from low_price_bull_monitor_ui import add_stock_to_monitor_button + + stock_code = row.get('股票代码', '') + stock_name = row.get('股票简称', '') + price = row.get('股价', row.get('最新价', None)) + + # 去掉代码后缀 + if isinstance(stock_code, str) and '.' in stock_code: + stock_code = stock_code.split('.')[0] + + # 转换价格 + try: + price_float = float(price) if price and not pd.isna(price) else None + except: + price_float = None + + if stock_code and stock_name: + add_stock_to_monitor_button(stock_code, stock_name, price_float) + + +def display_strategy_simulation(stocks_df: pd.DataFrame, selector): + """显示量化交易策略模拟""" + + st.markdown("## 🎯 策略监控与模拟") + + st.info(""" + **监控说明**: + - 在上方股票列表中点击"➕ 加入策略监控"按钮即可加入 + - 监控条件:① 持股满5天第6天开盘提醒卖出 ② MA5下穿MA20提醒卖出 + - 扫描频率:每分钟扫描1次(可在监控面板配置) + - 提醒卖出后自动移出监控列表 + - 点击右上角"📊 策略监控"按钮查看监控面板 + """) + + col1, col2 = st.columns(2) + + with col1: + if st.button("🎮 开始策略模拟", type="primary", width='content'): + st.session_state.show_strategy_simulation = True + + with col2: + if st.button("🔗 连接MiniQMT实盘", type="secondary", width='content'): + st.warning("⚠️ MiniQMT实盘交易功能需要先配置环境变量,详见系统配置") + + # 显示模拟结果 + if st.session_state.get('show_strategy_simulation'): + run_strategy_simulation(stocks_df) + + +def run_strategy_simulation(stocks_df: pd.DataFrame): + """运行策略模拟""" + + st.markdown("---") + st.markdown("### 📈 策略模拟执行") + + # 创建策略实例 + strategy = LowPriceBullStrategy(initial_capital=1000000.0) + + # 模拟买入(按成交额排序,优先买入成交额小的) + st.markdown("#### 1️⃣ 模拟买入信号") + + buy_results = [] + current_date = datetime.now().strftime("%Y-%m-%d") + + for idx, row in stocks_df.head(strategy.max_daily_buy).iterrows(): + code = str(row.get('股票代码', '')).split('.')[0] + name = row.get('股票简称', 'N/A') + price = float(row.get('股价', row.get('最新价', 0))) + + if price > 0: + success, message, trade = strategy.buy(code, name, price, current_date) + buy_results.append({ + 'success': success, + 'message': message, + 'trade': trade + }) + + # 显示买入结果 + for result in buy_results: + if result['success']: + st.success(result['message']) + else: + st.warning(f"⚠️ {result['message']}") + + # 显示持仓 + st.markdown("---") + st.markdown("#### 2️⃣ 当前持仓") + + positions = strategy.get_positions() + if positions: + positions_df = pd.DataFrame(positions) + st.dataframe(positions_df, width='content') + else: + st.info("暂无持仓") + + # 显示账户摘要 + st.markdown("---") + st.markdown("#### 3️⃣ 账户摘要") + + summary = strategy.get_portfolio_summary() + + col1, col2, col3, col4 = st.columns(4) + + with col1: + st.metric("初始资金", f"{summary['initial_capital']:,.0f} 元") + + with col2: + st.metric("可用资金", f"{summary['available_cash']:,.0f} 元") + + with col3: + st.metric("持仓市值", f"{summary['position_value']:,.0f} 元") + + with col4: + st.metric("总资产", f"{summary['total_value']:,.0f} 元") + + st.markdown("---") + + # 策略说明 + st.markdown("#### 📝 策略执行说明") + st.markdown(""" + **后续操作**: + 1. **持有期管理**:系统会自动跟踪每只股票的持有天数 + 2. **卖出信号监测**: + - 每日收盘后计算MA5和MA20 + - 如果MA5下穿MA20,触发卖出信号 + - 如果持股满5天,强制卖出 + 3. **轮动买入**:卖出后释放资金,继续买入新的符合条件的股票 + + **风险提示**: + - ⚠️ 本策略为模拟演示,实际交易存在滑点、手续费等成本 + - ⚠️ 历史业绩不代表未来收益 + - ⚠️ 请谨慎评估风险,理性投资 + """) + + +def send_dingtalk_notification(stocks_df: pd.DataFrame, top_n: int): + """发送钉钉通知""" + + try: + # 检查webhook配置 + webhook_config = notification_service.get_webhook_config_status() + + if not webhook_config['enabled'] or not webhook_config['configured']: + st.info("💡 未配置Webhook通知,如需接收钉钉消息请在环境配置中设置") + return + + # 构建消息内容 + keyword = notification_service.config.get('webhook_keyword', 'aiagents通知') + + message_text = f"### {keyword} - 低价擒牛选股完成\n\n" + message_text += f"**筛选策略**: 股价<10元 + 净利润增长率≥100% + 深圳A股\n\n" + message_text += f"**筛选数量**: {len(stocks_df)} 只\n\n" + message_text += f"**精选股票**:\n\n" + + for idx, row in stocks_df.head(top_n).iterrows(): + code = row.get('股票代码', '') + name = row.get('股票简称', '') + + # 只显示有效的信息 + message_text += f"{idx+1}. **{code} {name}**\n" + + # 股价 + price = row.get('股价', row.get('最新价', None)) + if price is not None and not pd.isna(price) and str(price).strip() not in ['', 'N/A']: + try: + price_float = float(price) + message_text += f" - 股价: {price_float:.2f}元\n" + except: + pass + + # 净利润增长率 + growth = row.get('净利润增长率', row.get('净利润同比增长率', None)) + if growth is not None and not pd.isna(growth) and str(growth).strip() not in ['', 'N/A']: + try: + growth_float = float(growth) + message_text += f" - 净利增长: {growth_float:.2f}%\n" + except: + pass + + # 成交额 + turnover = row.get('成交额', None) + if turnover is not None and not pd.isna(turnover) and str(turnover).strip() not in ['', 'N/A']: + try: + turnover_float = float(turnover) + if turnover_float >= 100000000: # 亿 + message_text += f" - 成交额: {turnover_float/100000000:.2f}亿元\n" + elif turnover_float >= 10000: # 万 + message_text += f" - 成交额: {turnover_float/10000:.2f}万元\n" + else: + message_text += f" - 成交额: {turnover_float:.2f}元\n" + except: + pass + + # 所属行业 + industry = row.get('所属行业', row.get('所属同花顺行业', None)) + if industry is not None and not pd.isna(industry) and str(industry).strip() not in ['', 'N/A']: + message_text += f" - 所属行业: {industry}\n" + + message_text += "\n" + + message_text += f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + message_text += "_此消息由AI股票分析系统自动发送_" + + # 直接发送钉钉Webhook(不使用notification_service的默认格式) + if notification_service.config['webhook_type'] == 'dingtalk': + import requests + + data = { + "msgtype": "markdown", + "markdown": { + "title": f"{keyword}", + "text": message_text + } + } + + try: + response = requests.post( + notification_service.config['webhook_url'], + json=data, + headers={'Content-Type': 'application/json'}, + timeout=10 + ) + + if response.status_code == 200: + result = response.json() + if result.get('errcode') == 0: + st.success("✅ 已发送钉钉通知") + else: + st.warning(f"⚠️ 钉钉通知发送失败: {result.get('errmsg')}") + else: + st.warning(f"⚠️ 钉钉通知请求失败: HTTP {response.status_code}") + except Exception as e: + st.warning(f"⚠️ 发送钉钉通知失败: {str(e)}") + + except Exception as e: + st.warning(f"⚠️ 发送通知时出错: {str(e)}") diff --git a/notification_service.py b/notification_service.py index bdc06cc..dbc62d2 100644 --- a/notification_service.py +++ b/notification_service.py @@ -318,25 +318,42 @@ class NotificationService: title_prefix = f"{keyword} - " if keyword else "" content_prefix = f"### {keyword} - " if keyword else "### " - data = { - "msgtype": "markdown", - "markdown": { - "title": f"{title_prefix}{notification['symbol']} {notification['name']}", - "text": f"""{content_prefix}股票监测提醒 + # 构建增强的消息内容 + message_text = f"""{content_prefix}股票监测提醒 **股票代码**: {notification['symbol']} **股票名称**: {notification['name']} -**提醒类型**: {notification['type']} +**📊 实时行情**: +- 当前价格: {notification.get('current_price', 'N/A')}元 +- 涨跌幅: {notification.get('change_pct', 'N/A')}% +- 涨跌额: {notification.get('change_amount', 'N/A')}元 +- 成交量: {notification.get('volume', 'N/A')}手 +- 换手率: {notification.get('turnover_rate', 'N/A')}% -**提醒内容**: {notification['message']} +**🎯 AI决策**: {notification['type']} -**触发时间**: {notification['triggered_at']} +**📝 分析内容**: {notification['message']} + +**💰 持仓信息**: +- 持仓状态: {notification.get('position_status', '未知')} +- 持仓成本: {notification.get('position_cost', 'N/A')}元 +- 浮动盈亏: {notification.get('profit_loss_pct', 'N/A')}% + +**⏰ 触发时间**: {notification['triggered_at']} + +**🕐 交易时段**: {notification.get('trading_session', '未知')} --- _此消息由AI股票分析系统自动发送_""" + + data = { + "msgtype": "markdown", + "markdown": { + "title": f"{title_prefix}{notification['symbol']} {notification['name']}", + "text": message_text } } @@ -756,4 +773,11 @@ _此消息由AI股票分析系统自动发送_""" return False # 全局通知服务实例 -notification_service = NotificationService() \ No newline at end of file +notification_service = NotificationService() + + + + + + + diff --git a/smart_monitor.db b/smart_monitor.db index 2b952869ff688750ed05a91b13257117cbf00c19..81035a36f6f24602e84b1ebd7c1343d29fff3a46 100644 GIT binary patch literal 315392 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 D>)~)= delta 284 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

U{?0>BJF1|vwZ4^&QL)X>wp__g zht(zsY{6n@zlAgk78(T)p-|EWnTF<-{tTs?d$J=Vz^*ch*BdsJPAm;ijbvIW|vWXWtCMtPd77c9u?ej_qFus zi}&=P=KTD~=wYRUahTQ7@s`yYX~Rm(Vcqd@?WSP@M>J$GRrkN7n4TW^KoXVB+Yv?I znO2zR8Nwq@Y2IH7bE*Exe<`FTudd86&wSx&sB)Qzso_@PcSZo_Uy32SgZY1IiKzt1r$gnx3@>!UIN@1n&ko@t zn1exO4z~k6*WsoHMs%`n)&`%505xsDJ;A>ogD6w{qlXtFOZda9x?*W#=ij1|_2AQ$ zsgXnRN8ZWziq-3g1@l3pCA9IXu4&!Q^qCw@61q~a&N~QfPwj_JXg*R#*Sy(WWOGw07L#8q~4pWUyc?tPBl=#&c zKnuBIi1lt%w=e40*{a{d@KXq}PWk0%28O5{&n(V}tl!jr(0^iWzp%^6e>Jw@i-aSo zo}rjTI+zj?j}(omz$`o9lgz_k9&jqsLKP zEdGw!&%)&ZWumm^hb=WxlsXuAhSu;GqELVCpY`2|VYwrubyw(_1lDqpN#=ZzO)+E4 zi|jgZO*q><>-}{1@Ff`yQ?t@2;jX)T(aCYtGuo}!|3;iQYz8;Qqq#4i_xbBG`vP|J z_Lcuipd0@E*EssuZ=kyj=RWCS1hT*Xrb^|H%@ zQq?=# z75POp=(hTF|Bd6(9<)%@2!8FBEpaI}yg)73S$t1g{-@uiA%$=ziYDKAM-t_3JDlir z%(&d^kp8b29v6-8GBZ$q;Y=SzJ>HQ|&D#zqWjf+I-cw>Y1{*i^W^YWbAB9s2yusw+${(C`I1a3+jubz}fe|c$YM&=%_bI~(C7}g1d z7hay4MgRDFfse-C_E98lJO-xPH1@Wr(Wh7281PBRLMSNyk$(G7W3MLG&|?!Uu}y#HmU z^sq{rJ|f(UK@gqC_H*;WT6FH(gm{a>#yGjwWH^YurMYX}0c1MR8Wx(C+ZEQc)i38L zNahaI5D;VAV#fIe@4KDa*4Kht^^W>5{Ih-UF?f1i@geu#T}KyxJUNR_J{LeiHkUgNLn7uyIR;C_W{z647_$L*-8FoXIjv}Ylw=#T<53G!(QL9n?wC~NZ^oDmv zq|%g4;c0R08*31i!J}{kDxLMNZzNs~pG?1k6&v1Fq8LF=#mpq`LAZcBxj}w85FMy? zgsVzT&!gD(hPT2pBYEOEPTCxFk+{VZ!|2K=-{2CsSS^|;RV~ulv>zc5I(G^aV(M@4 zcxN(XEYOY*UOyhB`-#&;_d2s$QtO~49_90+UNF^;Q;W1fVE0^VDpv}8_ruGe~ zW?hD|gm273WzI*G1ij3T?xp8%jp#FL`W5YKiP7~M(bW!K0o0?GuJhNMd9IzKlhnv+ z(25Cz0ETm-xdcoNW~MB#(ARGXFG++xslD4 zuC>sCph!Ch|KQ{|F!~fcBk}`Q0YXT(ijMK!*OX-JI|POK;p$tCnKJ}0H?(PNA{g9aDnwu3uXB0R4M9K>EqVOdG2Ku}DKYsE1iCrEvV@M7QLe0T>)hxA;YEEzfS*v^COj(25Jz8p>zjko85NP26SSDGjry#Lkzx2w9?97Y>XmU`w4xsk)et5NXTFTdP1( zIRteV9BoVTxnDBUU&o(DT@_jZE9ts0hOB-sS92qni479k>TL%AF3*7_X(cOn;WySK zmM*+|Z8&|`V){s1�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 Jc3Rk`{|hus2IK$$ diff --git a/stock_monitor.db b/stock_monitor.db index 27bc553f036015639625fd0126d8ba3745dece78..7d84a01f99148e2ea17f6af7c776ffcdfc35d3f9 100644 GIT binary patch delta 312 zcmZozz}T>Wae_4Cl!-FVj8iryEaVqu00IF92A)~SaI>I7I?v=|%(7pA z3?|OWzhyr#F^X?~D<>kz4%Il>!N6g1fPn!Jb8K!fSjxu1z`${vk=>YevZ1{6WDj#? zpjM8llk?4;Kn(xMTOkb2$&MD%lf^95fuih(fgF%Dd+p?Q2*Z5xSulg`Bak6IS$U-JjKeHIgueBs99q2Co65n z#L05j&On_k^CzcTe_-Kxz>qOH(x!uXBg4AQPi!I>S@?Mw)&Y%>-W+Y0#K`D2d7r%- NGcaH_vpf9c2LOSxQltO? delta 356 zcmZozz}T>Wae_2s-$WT_#=eaS3;9L)UNW%p%wpg#<}2X+&Rfj$pJx`&tj&T7={%Eb zWtv!uxC9s`Gs}KqDdzw(e#?GfV(8lZR!&6Fnq8Ganw^P(JCP@p$B37iXD@FbUp${A z-z)yR{L{I9a&6|y zvKzBbHk6m1>|w6V!oHC~d2+tF6ASxyAY&_#!C?zzI9fhy`|RDA8Tn^yW_S3>4*(R2V0Hii diff --git a/test_tdx_api.py b/test_tdx_api.py new file mode 100644 index 0000000..c777723 --- /dev/null +++ b/test_tdx_api.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +TDX API配置测试脚本 +用于测试TDX API连接和数据获取是否正常 +""" + +import os +import sys +import requests +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 获取TDX API URL +TDX_API_URL = os.getenv('TDX_BASE_URL', 'http://127.0.0.1:5000') + +print("=" * 60) +print("TDX API配置测试") +print("=" * 60) +print(f"\n1. TDX API地址: {TDX_API_URL}") + +# 测试1: 健康检查 +print("\n2. 测试健康检查接口...") +try: + response = requests.get(f"{TDX_API_URL}/api/health", timeout=5) + if response.status_code == 200: + print(" ✅ 健康检查成功") + print(f" 响应: {response.text}") + else: + print(f" ❌ 健康检查失败: HTTP {response.status_code}") + sys.exit(1) +except Exception as e: + print(f" ❌ 连接失败: {e}") + print("\n提示:") + print(" - 请检查TDX API服务是否已启动") + print(" - 请检查.env中的TDX_API_URL配置是否正确") + print(" - 默认地址: http://192.168.1.222:8181") + sys.exit(1) + +# 测试2: 获取K线数据 +print("\n3. 测试K线数据接口...") + +# 尝试不同的代码格式 +test_codes = [ + ("SZ000001", "平安银行"), + ("000001", "平安银行(纯数字)"), + ("SH600000", "浦发银行"), + ("600000", "浦发银行(纯数字)"), +] + +data = None +test_code = None + +for code, name in test_codes: + print(f"\n 尝试股票: {code} ({name})") + + try: + url = f"{TDX_API_URL}/api/kline" + params = { + 'code': code, + 'type': 'day' + } + + response = requests.get(url, params=params, timeout=10) + + if response.status_code == 200: + data = response.json() + + # 支持两种数据格式 + kline_list = None + if isinstance(data, dict) and 'data' in data: + # 嵌套格式: {code: 0, message: "success", data: {List: [...]}} + if data.get('code') == 0: + data_obj = data.get('data', {}) + kline_list = data_obj.get('List', []) + elif isinstance(data, list): + # 直接数组格式 + kline_list = data + + if kline_list and len(kline_list) > 0: + test_code = code + data = kline_list # 保存为全局变量 + print(f" ✅ K线数据获取成功!") + print(f" 数据条数: {len(kline_list)}") + break + else: + print(f" ⚠️ 数据为空") + else: + print(f" ❌ HTTP {response.status_code}") + except Exception as e: + print(f" ❌ 错误: {e}") + +if data is None or test_code is None: + print(f"\n ❌ 所有代码格式都失败,无法继续测试") + print("\n提示:") + print(" - 请检查TDX API服务是否正确启动") + print(" - 请确认API支持的股票代码格式") + print(" - 可能的格式:SZ000001, 000001, SH600000, 600000") + sys.exit(1) + +print(f"\n 成功的代码格式: {test_code}") + +# 显示最新一条数据 +if len(data) > 0: + latest = data[-1] + print(f"\n 最新K线数据:") + # 支持两种字段名格式:小写和大写 + print(f" - 日期: {latest.get('date') or latest.get('Time', 'N/A')}") + print(f" - 开盘: {latest.get('open') or latest.get('Open', 'N/A')}") + print(f" - 收盘: {latest.get('close') or latest.get('Close', 'N/A')}") + print(f" - 最高: {latest.get('high') or latest.get('High', 'N/A')}") + print(f" - 最低: {latest.get('low') or latest.get('Low', 'N/A')}") + print(f" - 成交量: {latest.get('volume') or latest.get('Volume', 'N/A')}") + +# 检查数据量是否足够计算MA20 +if len(data) >= 20: + print(f" ✅ 数据量充足,可以计算MA20(需要至少20条)") +else: + print(f" ⚠️ 数据量不足,仅{len(data)}条,需要至少20条才能计算MA20") + print(f" 请尝试其他股票或等待数据积累") + +# 测试3: 计算均线 +print("\n4. 测试均线计算...") +try: + import pandas as pd + + df = pd.DataFrame(data) + + # 支持两种字段名:小写close和大写Close + if 'Close' in df.columns and 'close' not in df.columns: + df['close'] = df['Close'] + + df['close'] = pd.to_numeric(df['close'], errors='coerce') + + # 计算MA5和MA20 + df['MA5'] = df['close'].rolling(window=5).mean() + df['MA20'] = df['close'].rolling(window=20).mean() + + latest = df.iloc[-1] + + if pd.notna(latest['MA5']) and pd.notna(latest['MA20']): + print(f" ✅ 均线计算成功") + print(f" - 收盘价: {latest['close']:.2f}") + print(f" - MA5: {latest['MA5']:.2f}") + print(f" - MA20: {latest['MA20']:.2f}") + + # 判断MA5和MA20的关系 + if latest['MA5'] > latest['MA20']: + print(f" - 趋势: 🟢 MA5 > MA20 (多头)") + elif latest['MA5'] < latest['MA20']: + print(f" - 趋势: 🔴 MA5 < MA20 (空头)") + else: + print(f" - 趋势: 🟡 MA5 = MA20 (震荡)") + else: + print(f" ❌ 均线计算失败,数据包含NaN") + sys.exit(1) + +except Exception as e: + print(f" ❌ 均线计算失败: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +# 所有测试通过 +print("\n" + "=" * 60) +print("✅ 所有测试通过!TDX API配置正常") +print("=" * 60) +print("\n提示:") +print(" - 现在可以启动低价擒牛策略监控服务") +print(" - 在监控面板中点击'▶️ 启动监控服务'") +print(" - 服务将每60秒扫描一次监控列表中的股票") +print("")