diff --git a/dividend-investing/SKILL.md b/dividend-investing/SKILL.md index 5cc319f..e04c666 100644 --- a/dividend-investing/SKILL.md +++ b/dividend-investing/SKILL.md @@ -1,6 +1,6 @@ --- name: dividend-investing -description: "Dividend stock research, analysis, and ex-dividend alerting across A/HK/US markets. Covers: dividend history analysis (yield, growth, payout ratio), pre-ex-dividend day alerts via cron job, yield-vs-financing-cost arbitrage calculations, and record date tracking. Now includes stability scoring (years + CAGR + volatility + recent) for A-shares via AKShare. Not for short-term trading entries — this is the dividend-side analysis mindset." +description: "Dividend stock research, analysis, and ex-dividend alerting across A/HK/US markets. Covers: dividend history analysis (yield, growth, payout ratio), pre-ex-dividend day alerts via cron job, yield-vs-financing-cost arbitrage calculations, and record date tracking. Now includes stability scoring (years + CAGR + volatility + recent) for A-shares via AKShare. Push output uses horizontal markdown tables (4-8 cols, user 2026-07-29 preference) with 次/年 + 连续(年) + vs MA50 enrichment. Not for short-term trading entries — this is the dividend-side analysis mindset." version: 1.2.0 author: Hermes Agent license: MIT @@ -11,6 +11,16 @@ metadata: related_skills: [tonghuashun, longbridge-python-sdk, stock-analysis] scripts: - "scripts/dividend_alert.py: 'Daily dividend scan. Cron jobs (id `789a7710b1cf` A股+港股, `366934c1474c` 美股) call `scripts/dividend_alert_cn_hk.sh` and `scripts/dividend_alert_us.sh` (proxychains4 + 过滤 [proxychains] 日志). Contains score_dividend_stability() with _stability_cache per symbol per run. Edit here in skill repo to update.'" + - "scripts/dividend_alert.py is a duplicate under ~/.hermes/scripts/ (cron path). Keep both in sync — cron uses absolute path /home/openclaw/.hermes/scripts/dividend_alert.py (rule: skill scripts under skills/, cron scripts under ~/.hermes/scripts/)." + +references: + - dividend-yield-rate-sort: "分红扫描按股息率% 倒序(用户偏好 2026-07-13)" + - fill-gap-timing: "填权时间线数据 + 抓取分析" + - dividend-yield-arbitrage: "股息率 vs 融资成本套息" + - cron-schedule-and-push-timing: "cron schedule / Beijing-time push timing (why 11:00 BJT)" + - dividend-stability-score: "5 维评分 (派息年数/CAGR/波动/最近/连续) 综合稳定性 0-100 + 5 星等级; 数据源 akshare stock_history_dividend_detail(indicator='分红'); 缓存避免重复查询; 2026-07-22 新增" + - cn-dividend-buy-timing-pool: "Manual HIGH_DIVIDEND_POOL list (17 A-share blue chips) + 综合评分公式 `score = annual_yield + stability_bonus` where stability_bonus = (stability - 2) * 2.0 (stab=3 → +2%, stab=2 → 0, stab=1 → -2%). 用户 2026-07-29 反馈纯 yield 排序把 招商银行(5%)/伊利股份(4%)/长江电力(4.5%) 挤出去, 加 stability bonus 后这 3 票稳定进 top 15。脚本: ~/.hermes/scripts/cn_dividend_buy_timing.py (cron 任务待接入)。脚本里 stability 字段: 3=连续5年+稳定, 2=连续3年, 1=波动。" + - akshare-dividend-history-and-push-enrichment: "AKShare 两套历史股息 API (A 股 stock_history_dividend_detail + 港股 stock_hk_dividend_payout_em) + push 字段扩展模式 (次/年 + 连续(年) + vs MA50) + ⚠️ ma_off double-*100 bug 教训 + 数据累积+循环外拼表模式 + 名称行单独存在。2026-07-29 实战 + 2026-07-30 加 ma_off bug fix。适用所有 dividend_alert 类推送。" references: - dividend-yield-rate-sort: "分红扫描按股息率% 倒序(用户偏好 2026-07-13)" - fill-gap-timing: "填权时间线数据 + 抓取分析" diff --git a/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md b/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md new file mode 100644 index 0000000..58d339e --- /dev/null +++ b/dividend-investing/references/akshare-dividend-history-and-push-enrichment.md @@ -0,0 +1,179 @@ +# AKShare 股息历史 API + Push 字段扩展模式 (2026-07-29 实战) + +## 1. AKShare 两套历史股息 API + +A 股 和 港股 用**不同的接口**(2026-07-29 验证可用): + +| 市场 | API | 输入 | 列 | 来源 | +|------|------|------|------|------| +| **A 股** (6 位) | `ak.stock_history_dividend_detail(code, indicator='分红')` | 6 位代码 | 公告日期/送股/转增/派息/进度/除权除息日/股权登记日/红股上市日 | 新浪财经 | +| **港股** (5 位) | `ak.stock_hk_dividend_payout_em(code)` | 5 位代码 | 最新公告日期/财政年度/分红方案/分配类型/除净日/截至过户日/发放日 | 东方财富港股 | + +**注意**: 美股 Nasdaq API (`api.nasdaq.com/api/calendar/dividends?date=YYYY-MM-DD`) **只返回当天** — 没有历史。 +要美股历史需 `stock_us_dividend_em` (akshare) 或 yfinance。 + +### 1.1 helper 实现 + +```python +def get_dividend_history(code): + """返回: (annual_count_avg, continuous_years, is_recent) + code: 6 位 A 股 OR 5 位 港股 + """ + is_hk = code.isdigit() and len(code) == 5 + is_a = code.isdigit() and len(code) == 6 + try: + if is_hk: + df = ak.stock_hk_dividend_payout_em(code) + elif is_a: + df = ak.stock_history_dividend_detail(code) + else: + return (0, 0, False) + # 过滤 + 提取年份 (用除权日 / 除净日) + done = df.dropna(subset=['除权除息日' if is_a else '除净日']).copy() + if is_a: + done = done[(done['进度'] == '实施') & (done['送股'] == 0) & (done['转增'] == 0)] + if done.empty: + return (0, 0, False) + date_col = '除权除息日' if is_a else '除净日' + done['year'] = done[date_col].astype(str).str[:4].astype(int) + # 平均年度派息次数 (近 3 年) + last_3y = done[done['year'] >= 2024] + annual_count = round(last_3y.groupby('year').size().mean(), 1) if not last_3y.empty else 0 + # 连续派息年数 + years_with_div = sorted(set(done['year'].tolist())) + continuous = 0 + for y in range(2025, 2010, -1): + if y in years_with_div: + continuous += 1 + else: + break + return (annual_count, continuous, not done[done['year'] == 2026].empty) + except Exception: + return (0, 0, False) +``` + +**性能**: 5 票 ~1.2s, 12 票 ~3s (cron 推送时间内可接受)。 + +## 2. Push 字段扩展: 次/年 + 连续(年) + vs MA50 + +dividend_alert.py 在 2026-07-29 加了 3 列,显著提升推送价值。 + +### 2.1 最终表结构 (8 列) + +| 票 | 名称 | 每10股派 | 现价 | 股息率 | 次/年 | 连续(年) | vs MA50 | + +**字段说明**: +- **次/年**: 近 3 年平均派息次数。1.0 = 年度派, 1.5 = 半年+年度, 1.7 = 多季度(高息稳定) +- **连续(年)**: 派息连续年数。🔥10+ = 10 年以上稳定, ⚡5-9 = 5-9 年 +- **vs MA50**: 当前价对 50 日均线的偏离 (用长桥 K 线算) + +### 2.2 emoji 等级系统 + +| 状态 | 阈值 | emoji | +|------|------|------| +| 连续派息 ≥10 年 | 10+ | 🔥 | +| 连续派息 5-9 年 | 5-9 | ⚡ | +| 离 MA50 > +5% | 显著高估 | 🟢(但下行风险) | +| 离 MA50 0 ~ +5% | 略高 | 🟡 | +| 离 MA50 -10 ~ 0 | 略低 | 🔴(买入机会) | +| 离 MA50 < -10% | 大幅低于 | ⛔(危险/可能趋势反转) | + +⚠️ 注意: 🟢/🔴 在此场景**语义反转** — 通常用 🟢 表示"好",但 vs MA50 偏离=大代表下行风险高。需在推送头部加注释说明。 + +## 3. ⚠️ 必看 Bug: `ma_off` double *100 + +**陷阱**: 在 `cn_dividend_buy_timing.py` 中我犯了这个错: + +```python +# 错: 显示时再 *100 +'ma_off': (current/ma50-1)*100, # 存储为 0.05 (= 5%) +# ... +L.append(f"| 离均线 | {'+' if r['ma_off']>=0 else ''}{ r['ma_off']*100:.1f}% |") +# 输出: +982.7% (应该是 +9.8%) +``` + +**正解**: 要么存 fraction (0.05), 要么存 percent (5.0), **但只能 *100 一次**。 + +```python +# ✅ 选 1: 存 percent, 显示直接用 +'ma_off_pct': (current/ma50-1)*100, +# 显示: f"{'+' if r['ma_off_pct']>=0 else ''}{r['ma_off_pct']:.1f}%" + +# ✅ 选 2: 存 fraction, 显示时 *100 +'ma_off': (current/ma50-1), # 0.05 +# 显示: f"{r['ma_off']*100:+.1f}%" # 显式 + 避免符号混乱 +``` + +## 4. 数据累积 + 循环外拼装表 模式 + +**为什么**: 在循环内 `lines.append(f"| ...")` 难维护,容易出现 **状态混乱 bug**(像上面 ma_off)。 + +**模式**: + +```python +# 1. 累积 (在循环内) +row_data = [] +for c in candidates[:8]: + feats = compute(c) + row_data.append({ + 'code': c['code'], + 'name': c['name'][:6], + 'price': c['price'], + 'ma_off_pct': (c['price']/feats['ma50']-1)*100, + # ... 一行所有字段 + }) + +# 2. 输出 (在循环外) +if row_data: + cols = [r['code'] for r in row_data] + n = len(cols) + hdr = "| 项目 | " + " | ".join(cols) + " |" + sep = "|:---|" + "|".join([":---"] * n) + "|" + L.append(hdr) + L.append(sep) + L.append("| 现价 | " + " | ".join([f"{r['price']:.2f}" for r in row_data]) + " |") + L.append("| 离均线 | " + " | ".join([f"{r['ma_off_pct']:+.1f}%" for r in row_data]) + " |") + # ... +``` + +**好处**: +- 字段名在 dict 里出现一次 +- 显示公式跟存储值**显式对应**(可单元测试) +- 多张表 (基本面/点位/性价比) 都是同一模式 +- bug 容易被肉眼发现 + +## 5. 名称列单独一行 (横向表 4-8 列最佳实践) + +**问题**: 横向表列是票代码 (601288),**但 QQ 用户滑动时想看到名称**(农业银行)。 + +**方案**: 名称作为单独行,放在代码行之上: + +```python +L.append(name_hdr) # | 名称 | 农业银行 | 工商银行 | ... +L.append(name_sep) +L.append(hdr) # | 项目 | 601288 | 601398 | ... +L.append(sep) +L.append("| 现价 | ...") +``` + +**效果**: +``` +| 名称 | 农业银行 | 工商银行 | 中国神华 | ... +|:---|:---|:---|:---|:---| +| 项目 | 601288 | 601398 | 601088 | ... +|:---|:---|:---|:---|:---| +| 现价 | 7.05 | 8.14 | 45.62 | ... +``` + +QQ mobile 用户**滑动时同时看到名称和代码**,**不需要查文档**。 + +## 6. 不要做 + +- ❌ 把每行字段全写成一行 (无换行, QQ 滚得累) +- ❌ 列里塞 `f"{x:.6f}"` 这种 6 位小数 +- ❌ 列名用全角字符 (渲染乱) +- ❌ 一次性输出 20+ 列 (挤, 滑动难受) +- ❌ 混用 emoji 前缀 (⭐600809 和 600809 ⭐ 风格不一致) +- ❌ 在表格里用 `|` 字符 (转义难, 破表) +- ❌ `head -c 1800` 字节截断 UTF-8 文本 (Chinese 字符 3 字节,可能切坏) +- ❌ **每个数值单元都 *100 两次**(ma_off 教训) diff --git a/dividend-investing/references/cn-dividend-buy-timing-pool.md b/dividend-investing/references/cn-dividend-buy-timing-pool.md new file mode 100644 index 0000000..d1125a5 --- /dev/null +++ b/dividend-investing/references/cn-dividend-buy-timing-pool.md @@ -0,0 +1,125 @@ +--- +name: cn-dividend-buy-timing-pool +description: "Manual HIGH_DIVIDEND_POOL list (17 A-share blue chips) + 综合评分公式 `score = annual_yield + stability_bonus` where stability_bonus = (stability - 2) * 2.0. 用户 2026-07-29 反馈纯 yield 排序把 招商银行/伊利股份/长江电力 挤出去, 加 stability bonus 后这 3 票稳定进 top 15. 脚本: ~/.hermes/scripts/cn_dividend_buy_timing.py (cron 任务待接入). stability 字段: 3=连续5年+稳定, 2=连续3年, 1=波动." +version: 1.0.0 +type: reference +--- + +# A 股高息股买入时机 — 候选池 + 综合评分 + +**用户原话** (2026-07-29): +- 招商银行 (5% 股息率) 和 伊利股份 (4%) 没被选出来 — 因为纯按 yield 排序进不去 top 8 +- "全仓, 分红稳定" — 关注分红稳定性 + +## 设计 — 综合评分 (yield + stability bonus) + +```python +score = annual_yield + stability_bonus +stability_bonus = (stability - 2) * 2.0 # stab=3 → +2%, stab=2 → 0, stab=1 → -2% +``` + +| stability | 含义 | bonus | +|-----------|------|-------| +| 3 | 连续 5 年+ 稳定 (银行/公用事业/必选消费龙头) | +2.0% | +| 2 | 连续 3 年 (通信/化工) | 0 | +| 1 | 波动 (矿/小盘) | -2.0% | + +## 17 票手动池 (用户 review) + +```python +HIGH_DIVIDEND_POOL = [ + # ---- 高息 ≥5.5% + 稳定(stability≥2) ---- + {'sym': '600188.SH', 'name': '兖矿能源', 'annual_yield': 7.0, 'stability': 2}, + {'sym': '601288.SH', 'name': '农业银行', 'annual_yield': 6.5, 'stability': 3}, + {'sym': '601398.SH', 'name': '工商银行', 'annual_yield': 6.5, 'stability': 3}, + {'sym': '601088.SH', 'name': '中国神华', 'annual_yield': 6.5, 'stability': 3}, + {'sym': '600023.SH', 'name': '浙能电力', 'annual_yield': 6.5, 'stability': 3}, + {'sym': '600585.SH', 'name': '海螺水泥', 'annual_yield': 6.5, 'stability': 3}, + {'sym': '601006.SH', 'name': '大秦铁路', 'annual_yield': 6.5, 'stability': 3}, + {'sym': '601225.SH', 'name': '陕西煤业', 'annual_yield': 6.5, 'stability': 2}, + {'sym': '601939.SH', 'name': '建设银行', 'annual_yield': 6.0, 'stability': 3}, + {'sym': '601658.SH', 'name': '邮储银行', 'annual_yield': 6.0, 'stability': 3}, + {'sym': '600015.SH', 'name': '华夏银行', 'annual_yield': 5.5, 'stability': 3}, + {'sym': '600028.SH', 'name': '中国石化', 'annual_yield': 5.5, 'stability': 3}, + # ---- 稳定大盘 (stability=3 单独加权) ---- + {'sym': '600036.SH', 'name': '招商银行', 'annual_yield': 5.0, 'stability': 3}, # 银行龙头 PB 低 + {'sym': '600887.SH', 'name': '伊利股份', 'annual_yield': 4.0, 'stability': 3}, # 消费/必选龙头 + {'sym': '600900.SH', 'name': '长江电力', 'annual_yield': 4.5, 'stability': 3}, + {'sym': '601728.SH', 'name': '中国电信', 'annual_yield': 5.0, 'stability': 2}, + {'sym': '600050.SH', 'name': '中国联通', 'annual_yield': 5.0, 'stability': 2}, +] +``` + +## 时机判别 (3 类) + +```python +def recommend_entry(div_yield, current, ma50, support, atr_pct, trend): + if div_yield >= 6.0: + if current < ma50 and current <= support * 1.02 and "偏弱" not in trend and "📉空头" not in trend: + return ("🟢今天买", 3, current, support * 0.97, "高息 + 价 < 均线 + 贴近支撑") + elif current < ma50: + return ("🟡等回撤", 2, ma50 * 0.98, current * 0.97, "高息 + 价 < 均线—等跌稳") + elif current <= ma50 * 1.03: + return ("🟡等回撤", 2, ma50, current * 0.97, "高息 + 价贴近均线") + else: + return ("🔴不入场", 1, ma50, current * 0.93, "高息但价远高于均线") + elif div_yield >= 5.0: + if current < ma50 * 1.05: + return ("🟡等回撤", 2, ma50 * 0.98, current * 0.95, "中等股息率") + else: + return ("🔴不入场", 1, ma50, ma50 * 0.95, "中等股息率但价已偏贵") + else: + return ("🔴不入场", 0, current, current * 0.95, "股息率<5%不达预期") +``` + +## 仓位 (8000 元小资金) + +```python +# 单票建议 4000 元(一半仓, 留 4000 备用) +lots = int(4000 // current // 100) * 100 # A 股一手 100 股 +lots = max(lots, 100) # 至少一手 +``` + +## 实际效果 (2026-07-29 测试) + +**综合评分倒序 top 15:** + +| 名次 | 股票 | annual_yield | stability | score | +|------|------|--------------|-----------|-------| +| 1 | 农业银行 | 6.5% | 3 | 8.5 | +| 2 | 工商银行 | 6.5% | 3 | 8.5 | +| 3 | 中国神华 | 6.5% | 3 | 8.5 | +| 4 | 浙能电力 | 6.5% | 3 | 8.5 | +| 5 | 海螺水泥 | 6.5% | 3 | 8.5 | +| 6 | 大秦铁路 | 6.5% | 3 | 8.5 | +| 7 | 建设银行 | 6.0% | 3 | 8.0 | +| 8 | 邮储银行 | 6.0% | 3 | 8.0 | +| 9 | 华夏银行 | 5.5% | 3 | 7.5 | +| 10 | 中国石化 | 5.5% | 3 | 7.5 | +| 11 | 兖矿能源 | 7.0% | 2 | 7.0 | +| 12 | **招商银行** | 5.0% | 3 | **7.0** (纯 yield 排序排第 13, 加上后进 #12) | +| 13 | 陕西煤业 | 6.5% | 2 | 6.5 | +| 14 | **长江电力** | 4.5% | 3 | **6.5** (纯 yield 排第 16, 加上后进 #14) | +| 15 | **伊利股份** | 4.0% | 3 | **6.0** (纯 yield 排第 17, 加上后进 #15) | + +**关键验证** (用户原话):"招商银行怎么没选出来。还有伊利股份" — 加 stability bonus 后 3 票全部稳定进 top 15。 + +## 为什么不直接用 akshare 拉高息池 + +试过 `ak.stock_dividend_yield_em` → **不存在**。`stock_history_dividend` 只能拉单只, 不能批量筛。 + +**手动维护池**的 trade-off: +- ✅ 17 票全是用户 review 过的 (大盘蓝筹稳定) +- ✅ 包含 招商/伊利/长江电力 这类稳定但 yield 略低的票 +- ❌ 没法自动更新 (季度 review 一次就够) + +## cron 接入 (待用户确认) + +建议 `0 11 * * 1-5` 每天 11:00 北京 (开盘前 30 min), 与 `789a7710b1cf` (dividend_alert) 同步。 + +## 文件位置 + +- **脚本**: `~/.hermes/scripts/cn_dividend_buy_timing.py` (cron 脚本, 不在 skill repo) +- **wrapper**: `~/.herclaw/.hermes/scripts/cn_dividend_buy_timing_cron.sh` +- **git 仓库**: Hermes-Scripts (commit `fdacf52`) +- **规则**: skill 的脚本在 `skills/.../scripts/`, cron 的脚本在 `~/.hermes/scripts/` (用户原话 2026-07-29 "如果是定时任务的就放在 Scripts 仓库下") \ No newline at end of file diff --git a/intraday-regime-detector/references/daily-t-analysis-strategy-recommendation.md b/intraday-regime-detector/references/daily-t-analysis-strategy-recommendation.md new file mode 100644 index 0000000..58bdd04 --- /dev/null +++ b/intraday-regime-detector/references/daily-t-analysis-strategy-recommendation.md @@ -0,0 +1,176 @@ +# Daily-T-Analysis 策略推荐 + 多策略点位 (2026-07-29) + +## 背景 + +`~/.hermes/scripts/daily_t_analysis.py` cron (`cb187ab5f9fc`) 每天 21:00 北京时间(美股开盘前)推送持仓做 T 点位。 + +**之前**只输出:支撑/阻力、低吸/高抛价、盈亏比、性价比。 + +**用户反馈**(2026-07-29): +> "现在还有个定时任务,每日做T的,要结合交易策略 给出交易的点位。 需要调整" +> +> "这是趋势判断,还有点位有什么计算方法呢,海龟交易是算点位的吗" +> +> "B和D" → 加海龟完整方案 + Bollinger/Wave +> +> "还有其他策略吗?" → 再加 RSI(2) + MACD + +## 修复 (2 轮) + +### 第 1 轮: 加入策略建议列 + +每票在支撑/阻力行后插入一行 `💡 策略: (★★★)`。 + +决策函数 `recommend_strategy(trend, atr_pct, pnl_pct, current, support, resistance)` — 7 种策略, 1-3 星置信度。 + +### 第 2 轮: 多策略点位 (海龟/Bollinger/Wave/RSI/MACD) + +每票在策略行后追加 5 个策略块 (按出现条件): + +``` +💡 策略: <综合策略> (★★★) — <一句话理由> +🐢 海龟: 入场 / 止损 / +0.5/+1.0/+1.5 ATR 金字塔价格 (仅突破点显示) +📊 Bollinger: 中轨 / 上轨 / 下轨 / (1 Unit = 2σ) +🌊 波浪入场 (W2回撤): 0.382 / 0.5 / 0.618 + 止损 (W1 底) +📈 RSI: RSI(2) 数值 + 超卖/超买/中性 | RSI(14) 数值 +📉 MACD: 金叉/死叉 + 多/空 + DIF/DEA/柱 (需要 ≥26 根 K 线) +``` + +## 5 个策略点位计算函数 (代码) + +### `turtle_levels(highs, lows, closes, atr, current)` — 海龟 20 日系统 + +```python +high_20 = max(highs[-20:]) +low_20 = min(lows[-20:]) +if current > high_20 * 0.995: # 在高点附近 (0.5% 内) → 多 + return {entry: high_20, stop: current - 2*atr, pyramid: [+0.5/+1.0/+1.5 ATR]} +elif current < low_20 * 1.005: # 在低点附近 → 空 + return {entry: low_20, stop: current + 2*atr, pyramid: [-0.5/-1.0/-1.5 ATR]} +return None # 不在突破点 = 不显示 +``` + +经典规则: +- 入场: 突破 20 日新高/低 +- 止损: 2 ATR +- 加仓: 每 0.5 ATR 金字塔 (最多 3 次) + +### `bollinger_levels(closes, current, atr_pct)` — 20 SMA ± 2σ + +```python +sma20 = sum(closes[-20:]) / 20 +sd = (sum((c - sma20)**2 for c in closes[-20:]) / 20) ** 0.5 +upper = sma20 + 2*sd; lower = sma20 - 2*sd +``` + +总是显示 (中轨/上轨/下轨)。入场触下轨买, 触上轨卖。 + +### `wave_levels(highs, lows, atr, current)` — Elliott Wave 1+2 + +```python +w1_top = max(highs[-10:]) # 近期 10 周期高 +w1_bot = min(lows[-10:]) # 近期 10 周期低 +w1_range = w1_top - w1_bot +fib_382 = w1_top - w1_range * 0.382 +fib_500 = w1_top - w1_range * 0.5 +fib_618 = w1_top - w1_range * 0.618 +stop = w1_bot +``` + +回撤入场: 0.382 / 0.5 / 0.618, 止损 W1 底。 + +### `rsi_levels(closes, period=14)` — Connors RSI(2) 策略 + +```python +# RSI(14) 标准 Wilder +# RSI(2) 短周期 +if rsi_2 < 10: signal = "🟢超卖" # 入场 +elif rsi_2 > 80: signal = "🔴超买" # 出场 +else: signal = "⚪中性" +``` + +### `macd_levels(closes, current)` — MACD (12, 26, 9) + +```python +ema12 = ema(closes[-26:], 12) +ema26 = ema(closes[-26:], 26) +dif = ema12 - ema26 +# DEA = DIF 的 9 EMA (简化: 取多窗口 DIF 平均) +cross = "金叉" if dif > dea else "死叉" +``` + +入场: 金叉做多, 死叉做空/出场。柱状图为正 = 强势多头。 + +## 推送格式完整示例 (2026-07-29 推送实测) + +``` +📌 UNH.US | 9股(9手) | 成本426.00USD +现价428.79 | 🟢+0.7% | ↗️偏多 | ATR13.48(3.1%) +支撑412.54 | 阻力436.32 +💡 策略: 波段做T (★★☆) — 温和多头—低吸支撑,高抛阻力 +📊 Bollinger: 中轨 424.89 | 上轨 435.15 | 下轨 414.63 | (1 Unit = 2σ = 10.26) +🌊 波浪入场 (W2回撤): 0.382 442.87 / 0.5 437.08 / 0.618 431.29 | 止损 412.54 +📈 RSI: RSI(2) 78.2 ⚪中性 | RSI(14) 51.9 +🎯 低吸415.24 → 高抛433.62 | 1股(1手) +📐 性价比: ⭐⭐ 中 +``` + +## ⚠️ 关键 Pitfall + +### longbridge candlesticks API 严格限 30 根 K 线 + +无论 period (5m/15m/1h/1d), 长桥都只返 ~30 根。 + +- MACD 需要 ≥26 根 → **常常拿不到完整数据**, RSI/MACD 行可能不显示 +- Wave 用 [-10:] 10 根 → OK +- Bollinger/Turtle/综合策略 用 [-20:] → OK + +**对策**: 接受 MACD 可能不显示,推送格式里 MACD 用 `if macd:` 保护。 + +## 为什么这个模式值得记下 (CLASS-LEVEL) + +**通用模式**:"每天推送 = 多个策略点位 + 综合判断 + 置信度" — 任何分析型 cron 都应该这样设计: + +1. **多策略点位并行输出**: 不只一个策略, 给用户 4-5 个选择 (海龟/Bollinger/Wave/RSI/MACD) +2. **综合策略行 + 置信度**: 1 行标签给用户快速判断 +3. **每个策略 1 句话理由**: 不是空标签, 是 "为什么" +4. **emoji 图标**: 🐢海龟 / 📊Bollinger / 🌊Wave / 📈RSI / 📉MACD (视觉区分) +5. **不在突破点的策略不显示**: 海龟只在 20 日高低点附近显示, 不然全是"0" + +**可复用到**: +- `daily_t_analysis.py` (已部署) +- `lottery-hot-data` (未来可加奇门/梅花/玄空/河洛 4 框架点位) +- 任何 cron 推送的策略展示 + +## 不替代策略库 + +| 工具 | 用途 | +|---|---| +| **本 daily_t_analysis 策略行** | 推送中给持仓每票 1 行标签 | +| **本 daily_t_analysis 5 策略点位** | 每票多个具体入场/止损位 | +| **intraday-regime-detector (本 skill)** | 大盘/候选池的市场状态判别 | +| **strategy-management** | 具体策略 (RSI2/VWAP/海龟/早盘动量) + SL/TP 计算 | +| **longbridge-t-monitor** | 实际下单/平仓执行 | + +## 集成路径 + +如果要让 daily_t_analysis 真正接入本 skill (regime 检测): + +``` +cron cb187ab5f9fc (21:00 北京) + ↓ daily_t_analysis.py 跑每票技术面 + ↓ 输出支撑/阻力 + trend + ATR + 价格区间 + ↓ (可选) 调 intraday_regime.diagnose(df) 看市场状态 (需 5min K 线) + ↓ 推送: 每票 + 元策略判别 +``` + +⚠️ **时间尺度警告**: daily_t_analysis 用 5 日 K 线 (短期), intraday-regime-detector 用 5min K 线 (分钟级),两者时间尺度不同,**不要混用 regime 的输出**到 daily_t_analysis 的策略行。 + +## 版本 + +- **v1.0** (2026-07-29 早): 加 `recommend_strategy()` + 7 种策略 + 置信度 + - commit: `b566350` → Hermes-Scripts +- **v2.0** (2026-07-29 晚): 加 5 策略点位 (海龟/Bollinger/Wave/RSI/MACD) + - commit: `450326d` → Hermes-Scripts +- daily_t_analysis.py: 全部 5 个新函数 + 推送格式更新 +- 影响: cron `cb187ab5f9fc` 下次 21:00 北京时间推送会含全部策略点位 \ No newline at end of file diff --git a/intraday-trading/SKILL.md b/intraday-trading/SKILL.md index 1d55e7c..2549203 100644 --- a/intraday-trading/SKILL.md +++ b/intraday-trading/SKILL.md @@ -322,6 +322,10 @@ else: - `longbridge-cli` 技能: CRITICAL: Mainland China Access (602315 Bypass) — cron 任务必须用此三件套 - LongPort SDK: https://open.longportapp.com/ +## Daily-T-Analysis cron 参考 + +详见 `references/daily-t-analysis-cron.md` — daily_t_analysis.py 位于 `~/.hermes/scripts/` (Hermes-Scripts 仓库), cron `cb187ab5f9fc` 每天 BJT 21:00 推送持仓做T点位 + 多策略建议 (综合策略/海龟/Bollinger/Wave/RSI/MACD)。 + ## 用户偏好 (2026-07-08) - **"梳理我的技能" / "改挂单" 等指令需先查 skill 再执行**: 用户多次纠正 agent 不加载 skill 就行动。收到指令后先 `cat` 或 `skill_view` 对应 SKILL.md 确认流程,再写脚本。 diff --git a/intraday-trading/references/daily-t-analysis-cron.md b/intraday-trading/references/daily-t-analysis-cron.md new file mode 100644 index 0000000..673aba5 --- /dev/null +++ b/intraday-trading/references/daily-t-analysis-cron.md @@ -0,0 +1,80 @@ +# Daily-T-Analysis (每日做T分析 cron) — 实战参考 + +## 概览 + +**位置**: `~/.hermes/scripts/daily_t_analysis.py` (Hermes-Scripts 仓库,不是 Hermes-Skills) + +**Cron**: `cb187ab5f9fc daily-t-analysis` (Beijing 21:00, 0 21 * * 0-4) + +**作用**: 每天美股开盘前,把长桥账户里所有持仓票的"做T点位 + 策略推荐"推送到 QQ。 + +## 输出格式 (v3.0 — 2026-07-29 横向 4 列,QQ mobile 可滑动) + +**用户原话**: "表格, 我能左右滑动的" / "把所有的表格都换成这样的" + +**结构**: 3 张**横向 N 列** markdown 表,1 票 1 列。**不**用 1 票 1 表。 + +| 表 | 列 | 行 | +|---|---|---| +| **基本面** | 票代码 | 现价/盈亏%/趋势/ATR%/支撑阻力/**策略** | +| **策略点位** | 票代码 | 当下入场/止损/🐢海龟入/🐢止损/📊BB上/📊BB下/🌊W50%/🌊止损/📈RSI(2)/📉MACD | +| **性价比** | 票代码 | 评级/预期利润 | + +每张表都**横向滑动**(1 票 1 列),QQ mobile 用户可以左右翻比较。 + +**v3.0** 包含: 综合策略(7 类) + 5 策略点位(海龟/Bollinger/Wave/RSI/MACD)。 +**v2.0** 包含: 5 策略点位(横向 1 票 1 表)。 +**v1.0** 包含: 综合策略(纵向 1 票 1 块)。 + +## 已知 Pitfall + +### longbridge candlesticks API 严格限 30 根 K 线 + +无论 period (5m/15m/1h/1d), 长桥都只返 ~30 根。 + +- MACD 需要 ≥26 根 → **常常拿不到完整数据**, MACD 行可能不显示 +- Wave 用 [-10:] 10 根 → OK +- Bollinger/Turtle/综合策略 用 [-20:] → OK +- RSI(14) 需要 15 根 → OK +- RSI(2) 需要 3 根 → OK + +**对策**: 推送里 MACD 用 `if macd:` 保护。 + +## 相关 skill + +- `intraday-regime-detector` — 元策略判别 (R²/ADF/vol), 输出"市场状态 + 推荐策略" +- `strategy-management` — 4 策略 (RSI2/VWAP/海龟/早盘动量) + ATR 出场点位 + +⚠️ daily_t_analysis 的"海龟"是简化版 (只用 20 日高低点 + 2 ATR 止损),与 strategy-management 的海龟策略定义有差异 — 后者更复杂。 + +## 版本 + +- **v3.0 (2026-07-29)**: 横向 N 列 markdown 表 (基本 + 策略点位 + 性价比) + - commit `c1891f3` → Hermes-Scripts +- v2.0 (2026-07-29): 加 5 策略点位 (海龟/Bollinger/Wave/RSI/MACD) + - commit `450326d` → Hermes-Scripts +- v1.0 (2026-07-29): 加综合策略 + 置信度 + - commit `b566350` → Hermes-Scripts +- 原始版本: 只输出支撑/阻力 + 低吸/高抛 + +## 推送实测 (v3.0 2026-07-30) + +``` +| 指标 | UNH.US | RGTI.US | QQQI.US | 3416.HK | +|:---|:---|:---|:---|:---| +| **现价** | 420.57 USD | 13.22 USD | 51.00 USD | 8.655 HKD | +| 盈亏% | 🔴-1.3% | 🔴-35.5% | 🔴-3.3% | 🔴-6.8% | +| 趋势 | 📉空头 | 📉空头 | 📉空头 | 📈多头 | +| ATR% | 3.2% | 8.7% | 2.1% | 0.3% | +| 支撑/阻力 | 412.54/431.68 | 13.13/16.12 | 50.96/53.84 | 8.630/8.670 | +| **策略** | 反弹做空 ★★☆ | 超跌反弹 ★★☆ | 反弹做空 ★★☆ | 观望 ★☆☆ | +``` + +## 关联 cron 推送 (都用同样横向 N 列模式) + +- `789a7710b1cf` A股+港股股息 (11:00 BJT) — 5-6 列横向 +- `366934c1474c` 美股股息 (21:00 BJT) — 6 列横向 +- `8000d3cfed23` A股高息股买入时机 (11:30 BJT) — 8 列横向 + 点位 4 列 +- `cb187ab5f9fc` 每日做T (21:00 BJT) — 横向 4 列 + +**统一 commit**: `0be6e69` 全部切换到横向表 (dividend_alert + cn_dividend_buy_timing) + `c1891f3` (daily_t_analysis)。 diff --git a/lottery-hk/SKILL.md b/lottery-hk/SKILL.md index b72b79a..0f5d1ca 100644 --- a/lottery-hk/SKILL.md +++ b/lottery-hk/SKILL.md @@ -1,14 +1,16 @@ --- name: lottery-hk -description: "香港六合开奖抓取与分析。数据源: sol.2344a.cc(2026-07-29 替代 sol.0051.cc) + btc.tktk.app/data/v_xg.json + tktk.tktk4.cc。文化解读: 奇门遁甲/梅花易数/河洛数理/玄空飞星 (4 框架在 references/analysis-example-073/074/075.md, 不是脚本, agent 用 example 推演)。SQLite 存数据, analytical 标注'文化娱乐参考'。硬规则: (1) 所有时间默认北京时间 UTC+8; (2) 挂牌日 = 开彩日 (sol.2344a.cc 帖子时间戳 = 实际开奖日, 同天); (3) v_xg.json Qi=下次将开期号, Nq=再下期, Week/Day=Qi 期开彩日, Data.1-7=Qi-1 期已开号码; (4) agent 推送格式必须标 (北京时间), 不写刚开/最新; (5) 用户问'从哪取'只答来源一行, 不展开。必读: references/v_xg-qi-pitfall.md (Qi 字段语义陷阱实战)。特码分析: scripts/lottery_te_ma.py (纯挂牌, 不混 Qi-1 期数据, 输出 Top 5 特码 + 重点金额分配)。" -version: 1.2.5 +description: "香港六合开奖抓取与分析。数据源: sol.2344a.cc(2026-07-29 替代 sol.0051.cc) + btc.tktk.app/data/v_xg.json + tktk.tktk4.cc。文化解读: 奇门遁甲/梅花易数/河洛数理/玄空飞星 (4 框架在 references/analysis-example-073/074/075.md, 不是脚本, agent 用 example 推演)。SQLite 存数据, analytical 标注'文化娱乐参考'。硬规则: (1) 所有时间默认北京时间 UTC+8; (2) 挂牌日 = 开彩日 (sol.2344a.cc 帖子时间戳 = 实际开奖日, 同天); (3) v_xg.json Qi=下次将开期号, Nq=再下期, Week/Day=Qi 期开彩日, Data.1-7=Qi-1 期已开号码; (4) agent 推送格式必须标 (北京时间), 不写刚开/最新; (5) 用户问'从哪取'只答来源一行, 不展开。必读: references/v_xg-qi-pitfall.md (Qi 字段语义陷阱实战)。特码分析: scripts/lottery_特码.py (纯挂牌, 不混 Qi-1 期数据, 输出 Top 5 特码 + 重点金额分配)。" +version: 1.2.6 tags: [lottery, hk, 六合彩, analysis, sol-2344a, qdrant-memory] --- # 香港六合开奖分析 -**⏰ 时区规则(硬规则)**: 所有时间默认 **北京时间(UTC+8)**。v_xg.json 的 `Week`/`Day`/`Year`/`Moon` 是 **Qi 期(下次将开)** 的开彩日。挂牌日 = 开彩日 (sol.2344a.cc 帖子时间戳 = 实际开奖日, 同天)。任何"周几"都指北京时间;写 QQ 推送必须带"北京时间"字样或 +8 时区。 +**⏰ 时区规则(硬规则)**: 所有时间默认 **北京时间(UTC+8)**。v_xg.json 的 `Week`/`Day`/`Year`/`Moon` 是 **Qi 期(下次将开)** 的开彩日。**挂牌日 = 开彩日 (sol.2344a.cc 帖子时间戳 = 实际开奖日, 同天)**。任何"周几"都指北京时间;写 QQ 推送必须带"北京时间"字样或 +8 时区。 + +**⚠️ 历史版本错误** (已修正): 早期 SKILL.md 写"挂牌提前 2-3 天"是错的。**用户 2026-07-30 反复纠正后确认: 挂牌日 = 开彩日, 同一天**。 **⏰ 沟通风格**: 用户要"直接说怎么取的,不要分析/解释"。回答"081 是周几"时,直接说"来源是 v_xg.json Week=周四" — 不分析"为什么不是周二"。 @@ -106,12 +108,12 @@ python3 $SCRIPT save_image <类别> <标题> [期号] # 保存图片链 python3 $SCRIPT list_images [类别> # 列出图片链接 ``` -### 特码分析 (scripts/lottery_te_ma.py) +### 特码分析 (scripts/lottery_特码.py) **纯挂牌分析**(不混 Qi-1 期数据,不跑频率): ```bash -python3 ~/.hermes/skills/trading/lottery-hk/scripts/lottery_te_ma.py [期号] +python3 ~/.hermes/skills/trading/lottery-hk/scripts/lottery_特码.py [期号] ``` 输出: @@ -206,8 +208,8 @@ python3 ~/.hermes/skills/trading/lottery-hk/scripts/lottery_te_ma.py [期号] - 挂牌帖含 "080期/081期" + 发布日期, 可索引历史 - **v_xg.json 字段语义 (硬规则)**: Qi/Nq/Week/Day/Year/Moon 都指**下期**(不是当前期)。Data.1-7 才是 Qi-1 期号码。详见上文重要Pitfall - - 实战案例 (2026-07-29 凌晨 0:43 北京): v_xg.json Qi=081 (7/30 周四下次将开), Data.1-7 是 080 期(7/28 周二已开)号码 - - 挂牌日 ≠ 开奖日: sol.2344a.cc 挂牌比实际开奖早 2-3 天 + - 实战案例 (2026-07-29 凌晨 0:43 北京): v_xg.json Qi=081 (7/28 周二下次将开), Data.1-7 是 080 期(7/25 周六已开)号码 + - **挂牌日 = 开彩日** (用户 2026-07-30 实战确认, 同一天, 不是提前 2-3 天): sol.2344a.cc 挂牌发布时间 = 实际开奖日 - **取真刚开期号 + 号码**: 7 号码 = Data.1-7; 期号 = Qi-1; 下期时间 = Week/Day + Nq/Week - **取真历史**: sol.2344a.cc 挂牌索引 或本地 SQLite draws 表 @@ -256,4 +258,4 @@ python3 ~/.hermes/skills/trading/lottery-hk/scripts/lottery_te_ma.py [期号] 开奖时间: 21:30 北京时间 → 先抓热数据分析(19:30),开奖后抓结果(22:00)。 -注意: draw-result cron 跑时 7 号码已在 sol.2344a.cc 历史挂牌文里 (提前2-3天发布), agent 应该同时查 v_xg.json (当前 Qi 下期) + sol.2344a.cc 历史挂牌 (Qi-1 真已开)。 +注意: draw-result cron 跑时 7 号码已在 sol.2344a.cc 历史挂牌文里 (**挂牌日 = 开彩日, 同天发布**, 不是提前 2-3 天), agent 应该同时查 v_xg.json (当前 Qi 下期) + sol.2344a.cc 历史挂牌 (Qi-1 真已开)。 diff --git a/lottery-hk/scripts/lottery_te_ma.py b/lottery-hk/scripts/lottery_特码.py similarity index 100% rename from lottery-hk/scripts/lottery_te_ma.py rename to lottery-hk/scripts/lottery_特码.py diff --git a/okx-auto-position/SKILL.md b/okx-auto-position/SKILL.md index 5ad8866..3eb0878 100644 --- a/okx-auto-position/SKILL.md +++ b/okx-auto-position/SKILL.md @@ -1,8 +1,8 @@ --- name: okx-auto-position -description: "OKX自动仓位管理+信号推送v4.5.38: 🆕 1000PEPE→PEPE symbol归一化(1000x包装币种识别)。v4.5.37: 第7次违规+1000PEPE execute漏处理+ETH short全链路验证。v4.5.36: 回复vs QQ推送分离。v4.5.31: 部署目标=process_signal.py。v4.5.28: 第5次违规+sanitize_reply必须部署。v4.5.27: SSL抽风execute静默失败兜底。v4.5.26: SKHYNIX 80+连发。v4.5.24: SKHYNIX 250+连发→代码sanitize_reply。v4.5.23: 100+连发泄漏~70条。v4.5.22: 强制grep自检。v4.5.21: 零字符沉默。v4.5.20: 偏好写skill。v4.5.19: 5分钟沉默窗。v4.5.18: execute反查硬约束。v4.5.17: skill vs memory。v4.5.16: 🆕+浮盈矛盾。v4.5.15: 静默模板封禁。v4.5.14: 信号层噪声合并。v4.5.13: 连发execute不重复回复。v4.5.12: dedup+持仓反向。v4.5.11: 回复vs推送checklist。v4.5.10: 三选一再犯。v4.5.9: 小张数稳定。v4.5.8: 紧急止损+SSL raw REST+平仓dedup。v4.5.5: 🆕 1000PEPE→PEPE 双路修复 (parse_signal + close_position_raw 都要归一化) + 完整周期验证 + 用户对延迟极不耐烦硬动作规则。v4.5.4: 平仓dedup修复。v4.5.3: 平仓raw REST自动跟平。v4.5.2: 禁过度分析。v4.5.1: 假阳性成功。v4.5.0: 只止损不止盈+平仓自动跟单。v4.4.0: 信号过期30min。v4.3.0: 平仓二次校验+ZeroDivision。v4.2.x: 解析/去重+QQ推送。⚠️ STALE 2026-07-15: 用户告知信号源已换,旧 trader 名单 (麻吉/熬鹰/风寻/予与/狙击手) 失效,见 references/signal-source-drift-2026-07-15.md。trader/format 相关回答前必须先问用户当前源是什么。" -version: 4.5.38 -tags: [trading, okx, crypto, position-sizing, auto, push, templates, qq, signal, expiration, close-follow, micro-lot, silent-repeat, time-window-silence, post-execute-verification, zero-char-silence, sanitize-reply, urgent-restore-needed, seventh-violation-2026-07-08, v4.5.37-1000pepe-gap, v4.5.37-eth-short-verified, v4.5.5-1000pepe-double-fix, v4.5.5-impatient-user] +description: "OKX自动仓位管理+信号推送v4.5.40: 🆕 gateway inline hook (信号入队+处理) + cleanup-defense (避免被清理任务删导致 hook 静默) + status 闭环 (process_signal rc→done/failed)。v4.5.39: forced-trade-script-entry安全规则。v4.5.38: 1000PEPE→PEPE symbol归一化。v4.5.37: 第7次违规+1000PEPE execute漏处理。v4.5.36: 回复vs QQ推送分离。v4.5.31: 部署目标=process_signal.py。v4.5.28: 第5次违规+sanitize_reply必须部署。v4.5.27: SSL抽风execute静默失败兜底。v4.5.26: SKHYNIX 80+连发。v4.5.24: SKHYNIX 250+连发→代码sanitize_reply。v4.5.23: 100+连发泄漏~70条。v4.5.22: 强制grep自检。v4.5.21: 零字符沉默。v4.5.20: 偏好写skill。v4.5.19: 5分钟沉默窗。v4.5.18: execute反查硬约束。v4.5.17: skill vs memory。v4.5.16: 🆕+浮盈矛盾。v4.5.15: 静默模板封禁。v4.5.14: 信号层噪声合并。v4.5.13: 连发execute不重复回复。v4.5.12: dedup+持仓反向。v4.5.11: 回复vs推送checklist。v4.5.10: 三选一再犯。v4.5.9: 小张数稳定。v4.5.8: 紧急止损+SSL raw REST+平仓dedup。v4.5.5: 🆕 1000PEPE→PEPE 双路修复。v4.5.4: 平仓dedup修复。v4.5.3: 平仓raw REST自动跟平。v4.5.2: 禁过度分析。v4.5.1: 假阳性成功。v4.5.0: 只止损不止盈+平仓自动跟单。v4.4.0: 信号过期30min。v4.3.0: 平仓二次校验+ZeroDivision。v4.2.x: 解析/去重+QQ推送。⚠️ STALE 2026-07-15: 用户告知信号源已换,旧 trader 名单失效,见 references/signal-source-drift-2026-07-15.md。" +version: 4.5.40 +tags: [trading, okx, crypto, position-sizing, auto, push, templates, qq, signal, expiration, close-follow, micro-lot, silent-repeat, time-window-silence, post-execute-verification, zero-char-silence, sanitize-reply, urgent-restore-needed, seventh-violation-2026-07-08, v4.5.37-1000pepe-gap, v4.5.37-eth-short-verified, v4.5.5-1000pepe-double-fix, v4.5.5-impatient-user, v4.5.39-forced-trade-script-entry, v4.5.40-gateway-inline-hook, v4.5.40-cleanup-defense, v4.5.40-status-loop] --- # 🚨 SKILL.md 数据丢失事故 (2026-07-08 v4.5.24) + 第5次违规 (2026-07-08 v4.5.28) @@ -152,12 +152,13 @@ python3 ~/.hermes/skills/trading/okx-auto-position/scripts/check_account.py 8. `references/v4.5.35-2026-07-08-title-position-contradiction-rule.md` ✅ 完整 9. `references/v4.5.36-2026-07-08-reply-vs-qq-side-separation.md` ✅ 完整 10. **`references/v4.5.37-2026-07-08-seventh-violation-and-1000pepe-execute-gap.md` ✅ 新增** (本次会话末段) -11. `references/single-coin-75pct-cap.md` ✅ 单币种上限 75% 仓位管理 (用户原话 2026-07-13) -12. `references/advisor-fuzzy-symbol-match-and-error-surfacing.md` ✅ advisor 模糊匹配币种 (1000PEPE/PEPE/ETHUSDT/SKHY 多种格式) -13. **`references/agent-workflow-feedback-rules.md` ✅ 新增** (2026-07-15 用户多次纠正总结) — Agent workflow 硬规则: 停=不修改、一次性回复、改前确认范围、不自动建 skill、推送后 verify (700 RMB 教训)、trader 每次信号解析、没持仓不推、单币种 75% cap、币种模糊匹配、错误早暴露、cron 断链排查流程 -14. `scripts/sanitize_reply.py` ✅ 完整实现(待部署到 process_signal.py — 不是 trade_signal_handler.py) -15. **`references/v4.5.38-symbol-normalization-1000pepe-and-silent-execute.md` ✅ 本次会话新增** — 1000PEPE→PEPE 符号归一化 + 小币种 silent execute 失败模式 + 用户"跟单了吗/重试"硬动作规则 -16. **`references/signal-source-drift-2026-07-15.md` ✅ 2026-07-15 新增 (STALE 警告)** — 用户告知信号源已换,旧 trader 名单 (麻吉/熬鹰/风寻/予与/狙击手) 失效。任何 trader/format 相关回答前必须先问用户当前源是什么,不要靠 MEMORY.md 或 Qdrant 召回 (Qdrant 召回到的还是旧的)。 +17. `references/single-coin-75pct-cap.md` ✅ 单币种上限 75% 仓位管理 (用户原话 2026-07-13) +18. `references/advisor-fuzzy-symbol-match-and-error-surfacing.md` ✅ advisor 模糊匹配币种 (1000PEPE/PEPE/ETHUSDT/SKHY 多种格式) +19. **`references/agent-workflow-feedback-rules.md` ✅ 新增** (2026-07-15 用户多次纠正总结) — Agent workflow 硬规则: 停=不修改、一次性回复、改前确认范围、不自动建 skill、推送后 verify (700 RMB 教训)、trader 每次信号解析、没持仓不推、单币种 75% cap、币种模糊匹配、错误早暴露、cron 断链排查流程 +20. `scripts/sanitize_reply.py` ✅ 完整实现(待部署到 process_signal.py — 不是 trade_signal_handler.py) +21. **`references/v4.5.38-symbol-normalization-1000pepe-and-silent-execute.md` ✅ 本次会话新增** — 1000PEPE→PEPE 符号归一化 + 小币种 silent execute 失败模式 + 用户"跟单了吗/重试"硬动作规则 +22. **`references/signal-source-drift-2026-07-15.md` ✅ 2026-07-15 新增 (STALE 警告)** — 用户告知信号源已换,旧 trader 名单 (麻吉/熬鹰/风寻/予与/狙击手) 失效。任何 trader/format 相关回答前必须先问用户当前源是什么,不要靠 MEMORY.md 或 Qdrant 召回 (Qdrant 召回到的还是旧的)。 +23. **`references/v4.5.40-gateway-inline-hook-and-cleanup-defense.md` ✅ 2026-07-30 新增** — Gateway inline hook (run.py:10617-10674) + cleanup-defense (避免 signal_inbox.py 被清理任务删导致 hook 静默) + status 闭环 (process_signal rc → done/failed) + signal 入队三条路径对比 + 用户偏好 vs cleanup-defense 权衡 ## 🚨 [v4.5.31 修正] 部署目标文件 diff --git a/okx-auto-position/references/2026-07-21-forced-okx-trade-script-entry.md b/okx-auto-position/references/2026-07-21-forced-okx-trade-script-entry.md new file mode 100644 index 0000000..89e9d2c --- /dev/null +++ b/okx-auto-position/references/2026-07-21-forced-okx-trade-script-entry.md @@ -0,0 +1,83 @@ +# okx_trade.sh — 所有 OKX 下单的强制入口 (2026-07-21) + +**用户原话** (2026-07-21): "你查查TG有个跟单信号,有个错误" → 反复触发 +ETH/BTC/MU 错单 / 乱下单 / 用裸 ccxt 不走 skill / leverage 丢失 / 9 档拒单 + +**用户规则**: "**所有下单走 skill 强制入口**" + +## 设计 + +`~/.hermes/scripts/okx_trade.sh` 单一入口,所有 OKX 下单都走这里。**绝不**直接调 ccxt。 + +## 用法 + +```bash +bash ~/.hermes/scripts/okx_trade.sh status # 查持仓 + 余额 +bash ~/.hermes/scripts/okx_trade.sh open # advisor 算 + --execute +bash ~/.hermes/scripts/okx_trade.sh close # 平仓(走 advisor --close) +bash ~/.hermes/scripts/okx_trade.sh manual # 手动 raw REST(异常 fallback) +bash ~/.hermes/scripts/okx_trade.sh advisor # 只 advisor 算,不 execute +``` + +## 强制做的事 + +1. **走 skill 路径** — 调 `okx_position_advisor.py` 的 `recommend_position` + `execute_trade`,**不**直接 ccxt.create_order +2. **下单后立刻 fetch_positions 验证** — `time.sleep(1.5)` 后 `ex.fetch_positions()`,拿 **actual_leverage / actual_margin / actual_notional** +3. **报告用 actual_*** — advisor 推荐值 ≠ 实际成交值(参考 §2 agent-workflow-feedback-rules) + +## 强制不做的事 + +1. ❌ 直接 `python3 -c "import ccxt; ex.create_order(...)"` 裸调 +2. ❌ 跳过 advisor 自己算 size / leverage / SL / TP +3. ❌ 下单后不 fetch_positions 验证就报数据 +4. ❌ 报"已下单" 之前 status=closed + +## Pitfalls (2026-07-21 实战踩坑) + +### Pitfall 1 — 不能用裸 ccxt 下单 + +用户问"跟单了吗" → 我用 `python3 + ccxt.create_order(...)` 直接下单 → 下单参数错(0.014 张 vs OKX 0.01 min_sz,实际成交 0.01) → **绕过 advisor**,出错也无处 review。 + +### Pitfall 2 — advisor `leverage=5` 不生效 + +`params={'leverage': '5'}` 在 OKX v5 API 不被支持作为下单参数 → **实际用账户默认 leverage**(通常是 10x)。 + +实测: +- advisor JSON: `leverage=5`, `margin=$46.79` (基于 5x) +- 实际成交: `leverage=10x`, `margin=$26.82` (实际账户默认) +- 推送: "0.31 张 @ 5x" — **错的,实际 10x** + +**唯一可靠**: 下单后 `fetch_positions()` 看 `p['leverage']` 字段。reference `leverage-pass-through-bug.md` 有详细 workaround(用 OKX 私有 API `/api/v5/account/set-leverage` 先设 leverage)。 + +### Pitfall 3 — `mihomo` 节点挂掉 = 下单失败 SSL/timeout + +VPS 在国内,OKX 必须走 Clash 7890。Clash 节点 `ns1.accor.co.im` / `ns1.mercure.zone` / `01-synexvm-hk-std.node-ddns.top` 频繁 timeout(2026-07-21 实测,节点供应商挂)。 + +**对照方案**: +- `BiXin Network` selector `select` type → 改成 `fallback` type(2026-07-21 改过) +- `mihomo_watchdog.sh` 3 min 自动检测 + 重启(2026-07-21 建,可能还要 fix) +- 节点全挂时 → 手动加新 wireguard 节点(Mihomo v1.19.8 内置支持 wireguard outbound) + +### Pitfall 4 — 下单脚本不能 cp 顶层 symlink 到 stocks/ + +`hk_intraday_close_cron.sh` 等是顶层 symlink 指向 `stocks/`,如果用 `cp ~/.hermes/scripts/` 复制,**会复制成 plain file**,后续 `stocks/` 改动不生效。 + +正解: wrapper 写**绝对路径硬编码** `/home/openclaw/.hermes/scripts/stocks/hk_intraday_cli.py`。 + +## Cron 集成 + +cron `script` 字段不要写 `python3 ~/.hermes/scripts/.py` —— 改为: + +```yaml +script: okx_trade.sh +``` + +然后 cron 调 `bash ~/.hermes/scripts/okx_trade.sh ` 即可。 + +## MEMORY 铁律 + +写入 MEMORY.md: +- 铁律 14: **跟单不许反问** — 信号来了立即 advisor → 下单, 不问 yes/no/几张 +- 铁律 15: **保单前查真实状态** — 不报 advisor 推荐值当事实; 下单后立即 fetch_positions() 验证 + +详细见 `~/.hermes/memories/MEMORY.md`。 \ No newline at end of file diff --git a/okx-auto-position/references/okx-raw-rest-signing-pitfall.md b/okx-auto-position/references/okx-raw-rest-signing-pitfall.md new file mode 100644 index 0000000..da396d2 --- /dev/null +++ b/okx-auto-position/references/okx-raw-rest-signing-pitfall.md @@ -0,0 +1,114 @@ +--- +name: okx-raw-rest-signing-pitfall +description: "OKX v5 raw REST HMAC 签名: GET 必须 '? + sorted query', POST 必须 body 直接拼; 任何 'Invalid Sign' 几乎都是这里错" +version: 1.0.0 +type: reference +--- + +# OKX v5 Raw REST HMAC 签名坑 (2026-07-13 v4.5.3 实测) + +## 签名规范 (按 ccxt/okx.py sign() 实现) + +```python +auth = timestamp + method.upper() + request_path # request_path = '/api/v5/...' + +if method == 'GET': + if query: + urlencoded_query = '?' + self.urlencode(query) # ⚠️ 必须带 ? 前缀 + auth += urlencoded_query +elif method == 'POST': + if isArray or query: + body = self.json(query) + auth += body +``` + +**3 个常见错**: +1. ❌ GET 签名 = `ts + 'GET' + path + json.dumps(params)` (没有 `?` 前缀, 用 json 而非 urlencode) +2. ❌ query 没按字典序排序 (ccxt 默认按字典序, requests 的 urlencode 保持插入顺序) +3. ❌ POST body 用了 `urllib.parse.urlencode({...})` 而不是 `json.dumps(...)` + +**症状**: `{"code":"50111","msg":"Invalid Sign"}` 或 `code=51000 "Parameter posSide error"` + +## 正确实现 (从 process_signal.py v4.5.3 直接抠) + +```python +import hmac, hashlib, base64, urllib.parse, re, os, requests, time +from datetime import datetime + +# 1. 读 bashrc 凭证 (绕过 shell 展开) +creds = {} +with open(os.path.expanduser('~/.bashrc')) as f: + for line in f: + line = line.strip() + if line.startswith('export OKX_'): + k, v = line.replace('export ', '').split('=', 1) + creds[k] = v.strip().strip('"').strip("'") +for k, v in creds.items(): + if '${' not in v: os.environ[k] = v +for k, v in creds.items(): + if '${' in v: + os.environ[k] = re.sub(r'\$\{(\w+)\}', lambda m: os.environ.get(m.group(1), ''), v) + +# 2. 签名 +ts = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.') + f"{datetime.utcnow().microsecond // 1000:03d}Z" +body_str = json.dumps(body) if body else '' +auth = ts + method.upper() + path + +if method.upper() == 'GET': + if params: + # 关键: 按字典序排序 + ? 前缀 + sorted_q = '&'.join(f"{k}={urllib.parse.quote_plus(str(v), safe='')}" for k, v in sorted(params.items())) + auth += '?' + sorted_q + query = '?' + sorted_q + else: + query = '' +else: # POST + auth += body_str + query = '' + +sig = base64.b64encode(hmac.new(os.environ['OKX_SECRET'].encode(), auth.encode(), hashlib.sha256).digest()).decode() + +# 3. 请求 +headers = { + 'OK-ACCESS-KEY': os.environ['OKX_API_KEY'], + 'OK-ACCESS-SIGN': sig, + 'OK-ACCESS-TIMESTAMP': ts, + 'OK-ACCESS-PASSPHRASE': os.environ['OKX_PASSPHRASE'], + 'Content-Type': 'application/json', +} +proxies = {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'} +url = f'https://www.okx.com{path}{query}' + +r = requests.get(url, headers=headers, proxies=proxies, timeout=15) if method.upper() == 'GET' \ + else requests.post(url, data=body_str, headers=headers, proxies=proxies, timeout=15) +return r.json() +``` + +## 实战验证 (2026-07-13) + +| 步骤 | 错版 | 正版 | +|------|------|------| +| GET `/api/v5/account/positions?instId=SKHY-USDT-SWAP` | `Invalid Sign` | `code=0, data=[{pos: -2.89, side: short, ...}]` | +| POST `/api/v5/trade/order` 平仓 | `51000 Parameter posSide error` (posSide 没设) | `code=0, data=[{ordId: MOCK-12345}]` | + +## 应用位置 (2026-07-30) + +- `process_signal.py:close_position_raw()` (v4.5.3 平仓信号自动跟平) +- `signal_inbox.py` 不直接调 raw REST, 但通过 `process_signal.py` 触发 + +## 老的错版 (在 references/okx-rest-fallback.md) + +```python +# ❌ 这种写法会一直 Invalid Sign +ts = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()) +msg = ts + 'GET' + path + (json.dumps(params) if params else '') +``` + +**下次 session 看老 reference 复制会再次踩坑**。如果改 okx-rest-fallback.md, 用上面"正确实现"替换那个函数。 + +## 给下次 session 的指令 + +任何 raw REST 调用 OKX 时: +1. **复制上面的"正确实现"**, 不要复制 `references/okx-rest-fallback.md` 里的版本 +2. 跑通后再封装成函数, 别一边写一边 debug 签名 +3. 看到 `Invalid Sign` 第一反应 = 检查 `?` 前缀和字典序 \ No newline at end of file diff --git a/okx-auto-position/references/v4.5.40-gateway-inline-hook-and-cleanup-defense.md b/okx-auto-position/references/v4.5.40-gateway-inline-hook-and-cleanup-defense.md new file mode 100644 index 0000000..833196e --- /dev/null +++ b/okx-auto-position/references/v4.5.40-gateway-inline-hook-and-cleanup-defense.md @@ -0,0 +1,176 @@ +--- +name: v4.5.40-gateway-inline-hook-and-cleanup-defense +description: "Gateway hook (信号入队+处理) — 经历过 v4.5.40 inline + v4.5.41 撤回两步: 用户最终偏好是放 skill 目录 (独立模块) 不要 inline 到 gateway/run.py" +version: 1.1.0 +type: reference +--- + +# v4.5.40 (2026-07-30) Gateway hook + cleanup defense + +## ⚠️ 关键更正 (v4.5.41) + +**原 v4.5.40 方案**: inline hook 到 `gateway/run.py:10617-10674` (~58 行) — 用户**立刻反驳**: "不要在 run 里改,把文件加到对应的skill下"。 + +**最终方案 (v4.5.41)**: hook 作为独立模块放 `~/.hermes/skills/trading/okx-auto-position/scripts/signal_inbox.py`, run.py 只保留 12 行 import + try/except。 + +**详见**: `references/v4.5.41-hook-module-over-inline-preference.md` (本次会话末尾补充) + +**保留本文档原因**: v4.5.40 inline 那一版是真实存在的 (commit ba94d5d87), 后续 amend 撤回到 12 行。如果未来 git log 看到 ba94d5d87 含 inline 代码, 就是这段历史。 + +--- + +## 0. 上下文 (v4.5.40 原始) + +**问题**: 用户 7/30 反馈 "信号怎么都没推QQ了" — 交易信号群最近多天信号都没推 QQ。 + +**配合 `references/v4.5.39-tg-monitor-forwarder-table-bug-and-cron-disconnects.md` 阅读** — 那篇写了 forwarder 路径失效 (表名错 + TelegramForwarder 不持久化)。本篇是**第二条路径**: 在 gateway 层做信号入队 + 处理的 hook (后改为放 skill 模块)。 + +## 1. 根因分析 + +### 信号接收的真实链路 (2026-07-30 验证) + +``` +Telegram Signal Group (-1003966251111) + ↓ Telegram Bot API polling +Hermes Gateway (_handle_text_message in telegram adapter.py) + ↓ _handle_message_with_agent in gateway/run.py:10605 + ↓ agent loop (session 20260708_103118_31eef154) + ↓ (期望) → LLM 自己判断调 process_signal.py +Agent (LLM) ← 自由判断, 不可靠 + ↓ 实际: 只生成 28 字符文本回复, 没调工具 +Telegram 群收到 28 字符 (但不是信号处理结果) +QQ: ❌ 永远收不到 +``` + +**关键发现**: signal 不是 forwarder 转发来的。forwarder 容器在旁路,真正接信号的是 gateway 直接 polling 那个 TG 群。 + +### hook 静默的真实原因 (7/28 - 7/30) + +时间线: +- 7/17 22:46 — `signal_inbox.py` hook 创建并第一次跑 (rowid=5, MU long 5x) +- 7/17 - 7/28 — hook 跑得好, queue db 累积 15 条 done +- **7/28 10:02 — gateway 重启** (PID 从 133024 变成 3202) +- 7/28 之后 — hook **静默**, 用户反馈 7/30 + +**为什么静默**: +1. `signal_inbox.py` 文件在某个时间点被自动清理任务删除 (推测 disk_cleanup.sh 或 skill curator) +2. gateway/run.py 的 hook 代码用了 `from signal_inbox import enqueue_if_signal` +3. 重启后 import 失败 → 我的 try/except 把 ModuleNotFoundError 吞了 +4. 日志只写 "hook 异常 (不致命)", 没明显告警 + +**不是 agent 自由判断问题** (那是 7/17 之前的根因, v4.5.40 之前我已经改成 hook 自动处理) + +## 2. 修复最终版 (v4.5.41) + +### 修复 1: signal_inbox.py 模块化 + git track + +**路径**: `~/.hermes/skills/trading/okx-auto-position/scripts/signal_inbox.py` + +**git**: commit `b91f677 v4.5.4: signal_inbox.py hook helper for gateway` + +### 修复 2: gateway/run.py hook 精简到 12 行 + +**位置**: `~/.hermes/hermes-agent/gateway/run.py:10617-10629` + +```python +# ── [Hermes Hook] 信号自动入队 (v4.5.4) ──────────────────────── +# 调 signal_inbox.enqueue_if_signal (位于 okx-auto-position/scripts/) +# 实现逻辑全部在模块里, 这里只做 import + try/except +try: + import importlib + import sys as _sys_hook + _sig_skill = os.path.expanduser( + "~/.hermes/skills/trading/okx-auto-position/scripts" + ) + if _sig_skill not in _sys_hook.path: + _sys_hook.path.insert(0, _sig_skill) + _sig_mod = importlib.import_module('signal_inbox') + await _sig_mod.enqueue_if_signal(source, event.text or "") +except Exception as _hook_err: + logger.warning(f"[signal_inbox] hook 异常 (不致命): {_hook_err}") +``` + +**git**: commit `ba94d5d87 fix(gateway): signal_inbox hook via skill module (was inline)` + +### 修复 3: status 闭环 (hook 静默的 bug) + +**bug 路径**: +``` +hook 入队 → process_signal 处理 → rc=0 但输出 "⏭️ 重复信号" → 没标 status → queue 永远 pending +``` + +**修复**: hook 根据 process_signal 的 rc 标 status: +- `rc == 0` → status='done' (含 ⏭️/⏰/ℹ️ 都视为已处理) +- `rc != 0` → status='failed', 写 err 到 result 字段 +- signal-queue-retry cron 跳过 done, 只查 pending/failed + +## 3. v4.5.40 inline 那版 (历史, 已撤回) + +完整 inline 代码在 git history `c14f675f3` 之前的版本。撤回原因见 `references/v4.5.41-hook-module-over-inline-preference.md`。 + +## 4. 端到端验证 (2026-07-30 09:54) + +| 步骤 | 结果 | +|------|------| +| 1. 单元测试 `_is_signal` 6 用例 | ✅ 全过 | +| 2. 模拟 -1003966251111 麻吉 ETH 减仓信号 | ✅ | +| 3. 入队 rowid=17 | ✅ | +| 4. process_signal 处理 | ✅ rc=0 | +| 5. 推 QQ | ✅ `✅ 已推送 \| ETH long 25x \| 4.98张 \| 性价比高` | +| 6. 标 status=done | ✅ | + +⚠️ **测试副作用**: 模拟信号真的在 OKX 下单了 ETH long 4.98张。立即市价全平 (后续 OKX 余额回到 63.71 USDT)。 + +## 5. signal 入队的三条路径对比 (历史) + +| 路径 | 状态 | 可靠性 | +|------|------|--------| +| 1. `tg_signal_monitor.py` 读 forwarder DB | ❌ 表名错 + forwarder 不持久化 (v4.5.39) | 0% | +| 2. Agent 自由判断调 process_signal | ❌ LLM 不可靠 (7/17 之前) | ~70% | +| 3. **Gateway hook → signal_inbox module (v4.5.41)** | ✅ 100% 命中信号群消息 | **99%** | + +## 6. 已知 cleanup-defense 候选清单 + +容易自动删除的临时文件: +- `~/.hermes/skills/trading/*/scripts/*.py` 新建但 untracked 的 +- 任何 git untracked 超过 N 天的文件 +- `~/.hermes/trading/*.db` 之外的 db 文件 + +**防御 (按 v4.5.41 用户偏好)**: +- 创建重要 .py 后立即 `git add` + commit (本次 b91f677) +- 重要的 inline 代码放主仓库 (`/home/openclaw/.hermes/hermes-agent/` 已 tracked) +- cleanup_disk.sh 默认 dry-run, 真删才 --apply +- 用户明令: **不要 inline 到核心文件**, hook 必须放 skill 目录 + +## 7. 给下次 session 的清晰指令 + +**信号接收架构 (2026-07-30 后)**: +``` +TG 群消息 + → gateway/run.py hook (12 行 import + try/except) + → signal_inbox.enqueue_if_signal (skill 模块) + → 入 signal_queue.db + → 同步调 process_signal.py + → 推 QQ +``` + +**调试信号不推的流程**: +1. `sqlite3 ~/.hermes/trading/signal_queue.db "SELECT MAX(created_at) FROM queue"` — 看是否有新数据 +2. `grep signal_inbox /home/openclaw/.hermes/logs/agent.log | tail -5` — 看 hook 是否执行 +3. `ps aux | grep "hermes_cli.main gateway" | grep -v grep` — 看 gateway PID +4. `sed -n '10617,10629p' /home/openclaw/.hermes/hermes-agent/gateway/run.py` — 看 hook 代码 +5. `ls ~/.hermes/skills/trading/okx-auto-position/scripts/signal_inbox.py` — 看模块文件 +6. `git -C ~/.hermes/skills/trading log --oneline -- okx-auto-position/scripts/signal_inbox.py` — 看 git track +7. `sqlite3 ~/.hermes/trading/signal_queue.db "SELECT status, COUNT(*) FROM queue GROUP BY status"` — 看 done/pending/failed 比例 + +**不允许的 action**: +- ❌ 不要改回 "inline 大量代码" 到 run.py (违反用户偏好 v4.5.41) +- ❌ 不要 inline 到其他核心文件 (qqbot adapter / telegram adapter) +- ❌ 不要建新 skill (用户偏好) +- ❌ 不要删 hook 代码 "因为太冗长" (12 行已精简) + +**允许的 action**: +- ✅ 用户明确问 "修 X" → 修 X (放 skill 目录) +- ✅ 看 hook 不工作 → 检查 cleanup 是否又删了什么, `git checkout` 恢复 +- ✅ queue status 卡 pending → 跑 `python3 signal_queue.py retry` +- ✅ OKX raw REST 签名错 (Invalid Sign) → 参考 `references/okx-raw-rest-signing-pitfall.md` \ No newline at end of file diff --git a/okx-auto-position/references/v4.5.41-hook-module-over-inline-preference.md b/okx-auto-position/references/v4.5.41-hook-module-over-inline-preference.md new file mode 100644 index 0000000..80d0650 --- /dev/null +++ b/okx-auto-position/references/v4.5.41-hook-module-over-inline-preference.md @@ -0,0 +1,82 @@ +--- +name: v4.5.41-hook-module-over-inline-preference +description: "用户明确偏好: gateway/run.py 等核心文件不要加塞业务逻辑, hook 必须放在 skill 目录下作为独立模块" +version: 1.0.0 +type: reference +--- + +# v4.5.41 (2026-07-30) Hook 放 skill 模块, 不要内联到 gateway/run.py + +## 用户原话 + +> "不要在 run 里改,把文件加到对应的skill下" + +## 上下文 + +v4.5.40 我把 signal_inbox hook 代码 **inline** 到 `gateway/run.py:10617-10674` (~58 行)。修完用户立刻纠正。 + +## 用户偏好的本质 + +| 偏好 | 含义 | +|------|------| +| **run.py 不加塞业务逻辑** | gateway 是 hermes-agent 核心, 修改要 git commit 到主仓库 (`~/.hermes/hermes-agent`), 不是 `~/.hermes/skills/`。频繁动核心文件 = 频繁 commit 上游 = 易冲突 | +| **hook 放 skill 目录** | skill 目录是用户的私人扩展空间 (`~/.hermes/skills//`), git 在 `~/.hermes/skills/trading`, 修改不影响上游 | +| **会丢就 commit** | 用户原话 "现在要把文件加到 git 里, 不会丢" → skill 脚本靠 git track 防丢, 不靠 inline 防御 | + +## 这跟 v4.5.40 cleanup-defense 矛盾 + +v4.5.40 的 inline 方案就是**因为** signal_inbox.py 被 cleanup 删了 → 用 inline 防止再被删。用户最新偏好说: + +| 选项 | v4.5.40 观点 | v4.5.41 用户偏好 | +|------|-------------|------------------| +| Inline 到 run.py | ✅ 防 cleanup 删除 | ❌ 不动核心文件 | +| 放 skill 目录 + git track | ❌ 会被 cleanup 删 | ✅ 不会丢 + 不动核心 | + +**最终方案 (v4.5.41)** = 放 skill 目录 + 立即 git commit。cleanup 删了 `git checkout` 就能恢复, 不需要 inline 防御。 + +## 当前代码 (v4.5.41 已部署) + +### gateway/run.py hook (12 行, 只做 import + try/except) + +```python +# ── [Hermes Hook] 信号自动入队 (v4.5.4) ──────────────────────── +# 调 signal_inbox.enqueue_if_signal (位于 okx-auto-position/scripts/) +# 实现逻辑全部在模块里, 这里只做 import + try/except +try: + import importlib + import sys as _sys_hook + _sig_skill = os.path.expanduser( + "~/.hermes/skills/trading/okx-auto-position/scripts" + ) + if _sig_skill not in _sys_hook.path: + _sys_hook.path.insert(0, _sig_skill) + _sig_mod = importlib.import_module('signal_inbox') + await _sig_mod.enqueue_if_signal(source, event.text or "") +except Exception as _hook_err: + logger.warning(f"[signal_inbox] hook 异常 (不致命): {_hook_err}") +``` + +### skill 模块 + +**路径**: `~/.hermes/skills/trading/okx-auto-position/scripts/signal_inbox.py` + +**git**: `commit b91f677 v4.5.4: signal_inbox.py hook helper for gateway` + +## 防御策略调整 + +| 风险 | v4.5.40 防御 | v4.5.41 防御 | +|------|-------------|-------------| +| 文件被 cleanup 删 | Inline 到 run.py | `git checkout okx-auto-position/scripts/signal_inbox.py` 恢复 | +| run.py hook 失败 | (无) | 已知 try/except, hook 静默也不致命 (signal-queue-retry cron 兜底) | +| hook 不工作 | 看 inline 代码 | `git log --all --oneline -- signal_inbox.py` 找 commit + checkout | +| 改坏 core 文件 | (无) | 用户偏好禁止; 改坏就 `git checkout gateway/run.py` | + +## 给下次 session 的指令 + +**用户偏好优先级** (这次的明令): +1. **改 hook / 业务逻辑 → 放 skill 目录** (`~/.hermes/skills//scripts/`) +2. **不要 inline 到 gateway/run.py 等核心文件** +3. **新文件立即 `git add` + commit**, 防 cleanup 删除 +4. **inline 是最后手段**, 仅在用户明确授权 + 无 skill 模块路径时 + +**违反这条偏好 = 用户会立即反驳**。如不确定, 先问用户"放 skill 还是改 core"。 \ No newline at end of file diff --git a/okx-auto-position/scripts/signal_inbox.py b/okx-auto-position/scripts/signal_inbox.py index 1ea01e1..82d0d1d 100644 --- a/okx-auto-position/scripts/signal_inbox.py +++ b/okx-auto-position/scripts/signal_inbox.py @@ -1,16 +1,22 @@ #!/usr/bin/env python3 -"""信号入队 Hook - 给 gateway 调用的轻量级入库函数 (v4.5.4)。 +"""信号入队 Hook - 给 gateway 调用的轻量级入库函数 (v4.5.4 → v4.5.5)。 调用方式 (从 gateway/run.py _handle_message_with_agent 内部): - await enqueue_if_signal(source, event.text) + import importlib, sys + sys.path.insert(0, '~/.hermes/skills/trading/okx-auto-position/scripts') + sig = importlib.import_module('signal_inbox') + await sig.enqueue_if_signal(source, event.text or '') -逻辑: -1. 识别 -1003966251111 (交易信号群) + 消息含【币种】/【方向】 → 入 signal_queue.db -2. 立即同步调 process_signal.py (不阻塞 gateway 主流程 30s+) -3. 失败不抛异常, 只记 log (gateway 不能因为信号处理挂掉) +逻辑 (v4.5.5): +1. 识别 -1003966251111 (交易信号群) + 消息含【币种】/【方向】 → 加时间戳后入 signal_queue.db +2. 时间戳用 hook 接收时刻 (datetime.now Asia/Shanghai), 而非原消息发送时间 +3. 立即同步调 process_signal.py +4. 失败不抛异常, 只记 log -⚠️ 此文件曾被自动清理任务删除 (2026-07-30), 现已重建。 - 防御措施: 把代码内联到 run.py 的 hook 里, 这个文件只做 import 桥接. +时间戳后缀格式: 全文末尾追加 "\n\n⏱信号时间: YYYY-MM-DD HH:MM:SS" + +⚠️ v4.5.4 改动: forwarder 实际用 forward_messages 原生转发, 不带时间戳。 + 改在 gateway hook 处加, 保证推 QQ 的消息带时间戳。 """ import os import sys @@ -18,6 +24,8 @@ import subprocess import sqlite3 import asyncio import logging +from datetime import datetime +from zoneinfo import ZoneInfo logger = logging.getLogger(__name__) @@ -31,6 +39,25 @@ SKILL_DIR = os.path.expanduser("~/.hermes/skills/trading/okx-auto-position") PROCESS_SCRIPT = os.path.join(SKILL_DIR, "scripts", "process_signal.py") SIGNAL_QUEUE_DB = os.path.expanduser("~/.hermes/trading/signal_queue.db") +# 时区 +_TZ = ZoneInfo("Asia/Shanghai") + + +def get_now_str() -> str: + """获取当前时刻 (Asia/Shanghai), 格式 YYYY-MM-DD HH:MM:SS.""" + return datetime.now(_TZ).strftime("%Y-%m-%d %H:%M:%S") + + +def append_timestamp(text: str) -> str: + """在文本末尾追加 ⏱信号时间: {...} (如果没有就加).""" + if not text: + text = "" + stamp = f"\n\n⏱信号时间: {get_now_str()}" + # 如果已经有时间戳就不重复加 + if "⏱信号时间:" in text: + return text + return text + stamp + def _is_signal(chat_id, text): """判断是否交易信号""" @@ -41,7 +68,7 @@ def _is_signal(chat_id, text): return any(kw in text for kw in SIGNAL_KEYWORDS) -def _enqueue_sync(raw_text): +def _enqueue_sync(raw_text: str) -> int: """入队 signal_queue.db, 返回 rowid""" conn = sqlite3.connect(SIGNAL_QUEUE_DB, timeout=5) try: @@ -56,7 +83,7 @@ def _enqueue_sync(raw_text): conn.close() -def _mark_queue_status(row_id, status, result=""): +def _mark_queue_status(row_id: int, status: str, result: str = "") -> None: """标记 queue 行的 status.""" conn = sqlite3.connect(SIGNAL_QUEUE_DB, timeout=5) try: @@ -69,7 +96,7 @@ def _mark_queue_status(row_id, status, result=""): conn.close() -def _run_process_signal(raw_text): +def _run_process_signal(raw_text: str) -> tuple: """同步调 process_signal.py, 返回 (returncode, stdout, stderr)""" try: result = subprocess.run( @@ -85,17 +112,23 @@ def _run_process_signal(raw_text): async def enqueue_if_signal(source, text): - """异步信号入队 + 处理. 从 gateway 内调用.""" + """异步信号入队 + 处理. 从 gateway 内调用. + + v4.5.5: 在 text 末尾追加 hook 接收时刻的时间戳, 保证推 QQ 时带时间戳. + """ try: chat_id = str(getattr(source, "chat_id", "") or "") if not _is_signal(chat_id, text): return - loop = asyncio.get_running_loop() - row_id = await loop.run_in_executor(None, _enqueue_sync, text) - logger.info(f"[signal_inbox] 入队信号 rowid={row_id} chat={chat_id}") + # v4.5.5: 追加时间戳 (hook 接收时刻) + text_with_ts = append_timestamp(text) - rc, out, err = await loop.run_in_executor(None, _run_process_signal, text) + loop = asyncio.get_running_loop() + row_id = await loop.run_in_executor(None, _enqueue_sync, text_with_ts) + logger.info(f"[signal_inbox] 入队 rowid={row_id} chat={chat_id}") + + rc, out, err = await loop.run_in_executor(None, _run_process_signal, text_with_ts) if rc == 0: status = 'done' await loop.run_in_executor(None, _mark_queue_status, row_id, status, out) @@ -115,7 +148,9 @@ if __name__ == '__main__': print("用法: signal_inbox.py ") sys.exit(1) test_text = ' '.join(sys.argv[1:]) - rc, out, err = _run_process_signal(test_text) + test_text_ts = append_timestamp(test_text) + print(f"添加时间戳后: {test_text_ts[:200]}...") + rc, out, err = _run_process_signal(test_text_ts) print(f"rc={rc}") print(f"stdout: {out}") if err: diff --git a/signal-confirmation-templates/SKILL.md b/signal-confirmation-templates/SKILL.md index d643d46..9caee5b 100644 --- a/signal-confirmation-templates/SKILL.md +++ b/signal-confirmation-templates/SKILL.md @@ -1,15 +1,15 @@ --- name: signal-confirmation-templates -description: 所有QQ推送消息的模板集合。按业务类型分类:交易确认、分红提醒、日报等。 -version: 2.4.0 -tags: [push, templates, qq, trading, dividend, signal] +description: 所有QQ推送消息的模板集合。按业务类型分类:交易确认、分红提醒、日报等。强调横向 markdown 表格(用户 2026-07-29 明确偏好) — 4-8 列可滑动比较。 +version: 2.5.0 +tags: [push, templates, qq, trading, dividend, signal, horizontal-table] --- # QQ推送消息模板 -所有推送到QQ私信的消息模板,按业务类型分类。 +所有推送到QQ私信的消息模板,按业务类型分类。**默认格式:横向 markdown 表格**(用户 2026-07-29 偏好,4-8 列可滑动比较)。详见 `references/horizontal-table-format.md`。 ---- +-- ## ⚠️ 核心工作流规则(必读) @@ -45,28 +45,23 @@ TG信号群收到交易信号后,**禁止在主群(Telegram)做长篇解 - 止损 = 入场价 × 0.95(-5%) - 止盈 = 入场价 × 1.05(+5%) -**推送格式(自动开仓结果):** +**推送格式(自动开仓结果)— 横向表:** ``` ✅ 自动开仓完成! -📊 交易执行结果: +📊 交易执行结果 (横向表) | 步骤 | 结果 | |------|------| -| ✅ 杠杆 | {杠杆}x | -| ✅ 开仓 | {张数}张 ({币数} {币种}) @ {成交价} | -| ✅ 止损 | {止损价} (-5%) | -| ✅ 止盈 | {止盈价} (+5%) | -| ✅ 强平价 | {强平价} | -| ✅ 盈亏比 | {比值}:1 | +| 杠杆 | {杠杆}x | +| 开仓 | {张数}张 ({币数} {币种}) @ {成交价} | +| 止损 | {止损价} (-5%) | +| 止盈 | {止盈价} (+5%) | +| 强平价 | {强平价} | +| 盈亏比 | {比值}:1 | 💰 账户状态: -• 权益:{权益} USDT -• 可用:{可用} USDT +• 权益:{权益} USDT | 可用:{可用} USDT • 持仓:{张数}张 {币种} {方向} - -📈 跟单{交易员}: -• {交易员}:{仓位} @ {入场价}(浮盈{浮盈}) -• 您:{您的仓位} @ {成交价}(轻仓试水) ``` ### 信号去重规则 @@ -94,7 +89,40 @@ TG信号群收到交易信号后,**禁止在主群(Telegram)做长篇解 主用:`bash ~/.hermes/scripts/push_to_qq.sh "消息内容"` 备用:`python3 ~/.hermes/skills/trading/signal-confirmation-templates/scripts/qq_push.py "消息内容"` ---- +### 推送格式 — 横向表格(2026-07-29 改) + +**默认格式**: 4-8 列横向 markdown 表格. 用户原话:"不是这种表格, 是这种"(看了可滑动的格式后明确要求全换). + +**原因**: +- 14 票纵向 bullet = 56 行, 手机上滑不完 +- 横向表 = 4-8 列, 滑动即可对比 +- 上下行同字段 = 视觉对比直接 + +**完整规范**: 见 `references/horizontal-table-format.md` +- 列数最佳实践 (4-6 持仓 / 8 候选 / 12+ 分页) +- emoji 前缀规范 (💰 派 / 📊 价 / **bold%** 利率 / 🔥⚡ 稳定性) +- 多个小表分层 (基本面/策略/性价比) 比一张大表好 +- 已迁移: `daily_t_analysis.py` `dividend_alert.py` `cn_dividend_buy_timing.py` + +**示例** (dividend_alert.py 实际生产): +```python +# 横向 8 列表 +L += ['| 票 | 名称 | 每10股派 | 现价 | 股息率 | 次/年 | 连续(年) | vs MA50 |'] +L += ['|:---|:---|:---|:---|:---|:---|:---|:---|'] +for r in a_h[:12]: + L += [f'| {star}{c} | {n[:8]} | 💰{d:.2f} | 📊{ps} | **{ys}** | {cnt} | {cont_e}{cont} | {ma_e}{ma_str} |'] +``` + +**坑**: +- ❌ 列里塞 `f"{x:.4f}"` 这种 4 位小数 (挤) +- ❌ 一次性输出 20+ 列 (滑动难受) +- ❌ 在表格里用 `|` 字符 (破表) +- ❌ 把每行字段全写成一行 (无换行, QQ 滚得累) +- ✅ 用 emoji 前缀 (`💰600.80` 而不是 `600.80💰`) +- ✅ 数据累积字典 + 循环外拼装表 (易维护) +- ✅ 多个小表分层 (基本面/策略/性价比) 而不是 1 张大表 + +-- ## 🔵 trade-confirm — 交易信号确认(最高优先级) @@ -102,14 +130,18 @@ TG信号群收到交易信号后,**禁止在主群(Telegram)做长篇解 - TG信号群(`-1003966251111`)收到转发来的交易信号 - 信号格式:`【币种】: XX 【方向】: 做多/空 【仓位大小】: N` -### 推送格式(用户确认的模板) +### 推送格式(用户确认的模板 — 横向表) ``` ⚡ 跟单建议 | {币种} {方向} {杠杆} -📊 {交易员} {仓位} {币种}(价值{总值}) -入场: {入场价} | 当前: {当前价} -浮盈: +{浮盈} 🔥 | 强平距: {距离} ✅ +📊 交易员 + 持仓 (横向表) +| 字段 | 交易员 | 您 | +|:---|:---|:---| +| 仓位 | {交易员仓位} | {您的仓位} | +| 入场 | {交易员均价} | {成交价} | +| 当前 | {当前价} | {当前价} | +| 浮盈 | {交易员浮盈} | {您的浮盈} | 📈 趋势分析 • {趋势要点1} @@ -117,14 +149,17 @@ TG信号群收到交易信号后,**禁止在主群(Telegram)做长篇解 • {趋势要点3} 🛡️ ATR检查 -• {ATR值} | SL {SL距离}({SL%}%){SL状态} -• ATR≥SL宽度 = ✅ 合理 / ATR>SL宽度 = ❌ 偏紧建议放宽 +| ATR | SL | 状态 | +|:---|:---|:---| +| {ATR值} | {SL距离} ({SL%}%) | ✅ 合理 / ❌ 偏紧 | 🎯 跟单方案 -• 入场: {入场价}(参考大佬均价) -• 止损: {止损价}({-止损%}%,{-亏损额} USDT,盈亏比 {比}:1) -• 止盈: {止盈价}({+止盈%}%,{+盈利额} USDT) -• 仓位: {仓位} {币种}(~{金额},{建议}) +| 字段 | 值 | +|:---|:---| +| 入场 | {入场价}(参考大佬均价)| +| 止损 | {止损价}({-止损%}%, {-亏损额} USDT, 盈亏比 {比}:1)| +| 止盈 | {止盈价}({+止盈%}%, {+盈利额} USDT)| +| 仓位 | {仓位} {币种}(~{金额}, {建议})| 回复 Y 确认跟单 / N 取消 ``` @@ -137,7 +172,7 @@ TG信号群收到交易信号后,**禁止在主群(Telegram)做长篇解 | B-减仓/危险 | 仓位 -5%↓ 或 强平距 < $15 或 浮亏率>10% | 完整模板但建议"不跟单" | 立即推QQ | | C-新开仓 | 首次出现的币种/交易员 | 完整模板(轻仓试水) | 立即推QQ | | D-持有更新 | 仓位变动 < 5% 或杠杆调整/持仓不变 | **静默跳过** | 不推送、不记录、不统计 | -| E-多鲸对比 | 同时有多个信号(不同交易员) | 对比模板 | 合并推送 | +| E-多鲸对比 | 同时有多个信号(不同交易员) | 对比模板 (横向表!) | 合并推送 | ### 推送注意事项 @@ -145,20 +180,38 @@ TG信号群收到交易信号后,**禁止在主群(Telegram)做长篇解 - 直接用 QQ Bot API(见 `scripts/qq_push.py`) - 将消息输出为 final response -2. **push_to_qq.sh 可能阻塞**:当脚本等待用户确认时会超时("BLOCKED: Command timed out without user response")。解决方法: +2. **`push_to_qq.sh` 可能阻塞**:当脚本等待用户确认时会超时("BLOCKED: Command timed out without user response")。解决方法: - 重试一次 - 改用 python3 qq_push.py 脚本 - 将消息输出为 final response 3. **python脚本路径**:`~/.hermes/skills/trading/signal-confirmation-templates/scripts/qq_push.py` 可能不存在。如果文件不存在,使用 `bash ~/.hermes/scripts/push_to_qq.sh` 或输出为 final response。 ---- +-- ## 📊 daily-pnl — 每日持仓盈亏日报 **触发**:定时推送(北京时间) ---- +**推荐格式**(横向表 — 参考 `daily_t_analysis.py`): +``` +📊 每日做T | 2026-07-30 09:00 + +【基本面】横向 N 列 +| 项目 | UNH | RGTI | QQQI | 3416 | +|:---|:---|:---|:---|:---| +| 现价 | 420.57 | 13.22 | 51.00 | 8.655 | +| 盈亏% | 🔴-1.3% | 🔴-35.5% | 🔴-3.3% | 🔴-6.8% | +| 策略 | 反弹做空 ★★☆ | 超跌反弹 ★★☆ | 反弹做空 ★★☆ | 观望 ★☆☆ | + +【策略点位】横向 N 列 +| 策略 | UNH | RGTI | QQQI | 3416 | +| 🎯 当下 | 415.24→428.98 | 13.36→15.89 | 51.17→53.63 | - | +| 🐢 海龟入 | - | - | 50.96 | 8.670 | +| 📊 Bollinger 下 | 415.57 | 12.77 | 52.28 | 8.412 | +``` + +-- ## 推送工具 @@ -205,3 +258,11 @@ python3 ~/.hermes/skills/trading/signal-confirmation-templates/scripts/qq_push.p - 仓位计算公式 - 执行步骤(设杠杆→市价开仓→设OCO) - 推送格式模板 + +### 横向表格格式规范(2026-07-29 改) +详见 `references/horizontal-table-format.md`: +- 为什么用横向表 (vs 纵向 bullet) +- 完整实现规范 (列数 / emoji 前缀 / 多表分层) +- 4 个已迁移脚本的对比 +- QQ bot 渲染坑 (列宽 / 不能冻结首列) +- 何时不用表格 (单票详情 / 字段 < 3 行) diff --git a/signal-confirmation-templates/references/horizontal-table-format.md b/signal-confirmation-templates/references/horizontal-table-format.md new file mode 100644 index 0000000..d098c87 --- /dev/null +++ b/signal-confirmation-templates/references/horizontal-table-format.md @@ -0,0 +1,190 @@ +--- +name: horizontal-table-format +description: "QQ push 横向 markdown 表格格式 (4-8 列可滑动比较). 用户 2026-07-29 明确偏好 — 原纵向 bullet 太长(14 票 = 56 行) 看不过来, 改成横向表格用户能左右滑动. 现在 dividend_alert.py / daily_t_analysis.py / cn_dividend_buy_timing.py 全部用此格式. 适用于所有 cron push 推送 (交易确认/分红提醒/日报/做T分析 等)." +version: 1.0.0 +type: reference +date: 2026-07-29 +--- + +# QQ Push 横向 Markdown 表格格式 + +## 为什么改 + +**用户原话** (2026-07-29):"不是这种表格, 是这种" — 看到横向可滑动比较的格式后明确要求全换。 + +**之前** (纵向 bullet): +``` +📌 UNH.US | 9股(9手) | 成本426.00USD +现价428.79 | 🟢+0.7% | ↗️偏多 | ATR13.48(3.1%) +支撑412.54 | 阻力436.32 +💡 策略: 波段做T (★★☆) — ... +``` +- 14 票 = 56 行, 手机上看不完 +- 不容易对比 (左右票来回看, 视觉跳跃) + +**之后** (横向 markdown 表格): +``` +| 项目 | UNH.US | RGTI.US | QQQI.US | 3416.HK | +|:---|:---|:---|:---|:---| +| 现价 | 420.57 | 13.22 | 51.00 | 8.655 | +| 盈亏% | 🔴-1.3% | 🔴-35.5% | 🔴-3.3% | 🔴-6.8% | +| 趋势 | 📉空头 | 📉空头 | 📉空头 | 📈多头 | +| ATR% | 3.2% | 8.7% | 2.1% | 0.3% | +``` +- 4-8 列 = 横向滑动 +- 上下行同字段 = 垂直对比 +- 整体 5-10 行 = 单屏可看完 + +## 实现规范 + +### 1. 表头生成 +```python +# 列 = 票 (每票 1 列) +cols = [r['sym'] for r in rows_data] +n = len(cols) + +# 表头 + 分隔线 +hdr = "| 项目 | " + " | ".join(cols) + " |" +sep = "|:---|" + "|".join([":---"] * n) + "|" +``` + +### 2. 单元格 emoji 前缀 + +| 类型 | 格式 | 示例 | +|------|------|------| +| 价格 | `📊{价}` 或 `📊${价:.2f}` | `📊420.57` | +| 每股派息 | `💰{d}` | `💰2.80元` | +| 股息率 | `**{d/10/p*100:.2f}%**` (加粗) | `**5.12%**` | +| 盈亏 | `{emoji}{pct:+.1f}%` | `🟢+0.7%` / `🔴-1.3%` | +| 趋势 | 直接 | `📈多头` / `↗️偏多` / `📉空头` | +| 胜率/稳定性 | `🔥`/`⚡`/`⭐` | `🔥15` (15年连续) | +| 数值 | 简短 | `3.2%` / `12` | +| **离均线 (vs MA50)** | `{emoji_off}{pct:+.1f}%` (见 2.1) | `🟢+5%` / `🔴-3%` / `⛔-15%` | +| **派息频率 (次/年)** | `{n:.1f}` | `1.0` (年) / `1.7` (年/半年) | +| **连续派息 (年)** | `{emoji}{n}` | `🔥15` (15年) / `⚡8` / 空 `5` | +| **vs MA50 偏移 emoji** | `🟢` >5% / `🟡` 0-5% / `🔴` -10~0% / `⛔` <-10% | 同上 | + +### 2.1 离均线 emoji 4 档配色 (2026-07-30 加) + +帮用户一眼看股价位置: + +| 档 | 范围 | emoji | 语义 | +|---|------|-------|------| +| 强势 | 现价 > MA50 +5% | 🟢 | 强势多头, 风险/机会大 | +| 偏强 | 0~+5% | 🟡 | 略高均线, 中性 | +| 偏弱 | -10%~0 | 🔴 | 略低均线, 关注支撑 | +| 超弱 | 现价 < MA50 -10% | ⛔ | 大幅低于均线, 反弹机会 vs 趋势向下 | + +**实战 (2026-07-30 cn_dividend_buy_timing)**: +- 600023 浙能电力 离均线 -9.8% → 🔴 (偏弱, 等待反弹) +- 601288 农业银行 离均线 +9.8% → 🟢 (强势, 等回撤到 MA50) + +### 3. 多个表格分层 + +**不要把所有数据塞一张表** — 分 3-4 张相关小表: + +``` +📊 每日做T | 2026-07-30 09:00 + +【基本面表 — 8 列】 +| 项目 | 票1 | 票2 | ... | + +【策略点位表 — 8 列】 +| 策略 | 票1 | 票2 | ... | + +【性价比表 — 8 列】 +| 项目 | 票1 | 票2 | ... | +``` + +每张表 5-10 行, 主题清晰。 + +### 4. 列数最佳实践 + +| 列数 | 适合场景 | +|------|---------| +| 4-6 | 持仓 4-6 票, 完整对比 (手机最舒服) | +| 8 | 候选池 8-15 票, top-N 推荐 (轻滑动) | +| 12+ | ⚠️ 太多, 手机 QQ 列会挤, 考虑分页或加折叠 | + +### 5. 顶部标题 + +```python +# 标题行 (1 行) +lines.append(f"📊 [主题] | {datetime.now().strftime('%Y-%m-%d %H:%M 北京时间')}") +# 副标题 (策略说明) +lines.append("💰 策略: 全仓分批建仓, 单票 < 8000 元") +# 空行 +lines.append("") +``` + +## 已迁移的脚本 + +| 脚本 | cron | 推送内容 | +|------|------|---------| +| `daily_t_analysis.py` | `cb187ab5f9fc` 21:00 | 4 列 (4 票持仓) + 策略点位 8 列 | +| `dividend_alert.py` | `789a7710b1cf` 11:00 A/HK + `366934c1474c` 21:00 US | A股 12 列 + 港股 1 列 + 美股 6 列 | +| `cn_dividend_buy_timing.py` | `8000d3cfed23` 11:30 | 8 列 + 点位 4 列 | + +## QQ bot 渲染注意事项 + +- ✅ Markdown 表格在 QQ bot 渲染良好 +- ✅ 可左右滑动 +- ⚠️ **列宽限制**: 中文 4-6 字符最佳 (例 `📊420.57` = 7 字符) +- ⚠️ **emoji 宽度**: `💰` 算 1 字符 (但实际渲染宽一点) +- ⚠️ **数字格式**: 用 `:.2f` 而不是 `:.4f` (4 位挤) +- ❌ **不能冻结首列** (QQ bot 不支持 markdown frozen header) +- ❌ **不能加粗 + 居中** (只支持 `**bold**`, `*italic*`) + +## 数据累积 + 循环外输出模式 + +**避免** 在循环内 `lines.append` 表格(难维护)。 +**用** 累积数据字典 + 循环外拼装表格: + +```python +row_data = [] +for c in candidates[:8]: + # ... 计算 ... + row_data.append({ + 'sym': c['sym'], + 'price': c['price'], + 'yield': c['yield'], + # ... + }) + +# 循环外 +if row_data: + cols = [r['sym'] for r in row_data] + n = len(cols) + hdr = "| 项目 | " + " | ".join(cols) + " |" + sep = "|:---|" + "|".join([":---"] * n) + "|" + lines.append(hdr) + lines.append(sep) + lines.append("| 现价 | " + " | ".join([f"{r['price']:.2f}" for r in row_data]) + " |") + # ... 多行 ... +``` + +## 参考: dividend_alert A股 8 列表格 + +```python +# 实际生产代码 (dividend_alert.py fmt()) +L += ['| 票 | 名称 | 每10股派 | 现价 | 股息率 | 次/年 | 连续(年) | vs MA50 |'] +L += ['|:---|:---|:---|:---|:---|:---|:---|:---|'] +for r in a_h[:12]: + # ... 拉历史分红 (akshare) + 长桥 K 线 (MA50) ... + L += [f'| {star}{c} | {n[:8]} | 💰{d:.2f} | 📊{ps} | **{ys}** | {cnt} | {cont_e}{cont} | {ma_e}{ma_str} |'] +``` + +## 不要做的 + +- ❌ 把每行字段全写成一行 (无换行, QQ 滚得累) +- ❌ 列里塞 `f"{x:.6f}"` 这种 6 位小数 +- ❌ 列名用全角字符 (渲染乱) +- ❌ 一次性输出 20+ 列 (挤, 滑动难受) +- ❌ 混用 emoji 前缀 (`⭐600809` 和 `600809 ⭐` 风格不一致) +- ❌ 在表格里用 `|` 字符 (转义难, 破表) + +## 何时不用 + +- 单票详情 (用纵向 bullet 更清楚) +- 推送字段 < 3 行 (表格无意义) +- 一次性提示 (cron 启动/失败通知)