feat: 迁移 trading 相关脚本到 skill 仓库 (第2批: 🟡 中优先级)

【迁移内容】
- dividend-investing/scripts/dca_monitor.py + dca_monitor_us.sh
- dividend-scanner/scripts/scan_cn.{py,sh} + scan_hk.sh + scan_us.sh
- okx-auto-position/scripts/signal_queue_retry.sh
- lottery-hk/scripts/verify_lottery.sh
- longbridge-cli/scripts/longport_http.py (公共模块)

【配套修改】
- 5 个 cron 任务 script 路径更新 (jobs.json):
  - 8929ca09 (DCA港股上午) → dividend-investing/scripts/dca_monitor.py
  - 9469c128 (DCA港股下午) → dividend-investing/scripts/dca_monitor.py
  - 3a4bef2c (DCA美股凌晨) → dividend-investing/scripts/dca_monitor_us.sh
  - a82a3ab0 (signal-queue-retry) → okx-auto-position/scripts/
  - b4868849 (lottery-verify-result) → lottery-hk/scripts/

【删除】本地 ~/.hermes/scripts/{dca_monitor,scan_*,signal_queue_retry,verify_lottery,longport_http}
【未迁移】scan_cn/hk/us 仍需挂 cron, 暂不删 (走 scanner skill)
This commit is contained in:
2026-07-24 11:42:13 +08:00
parent 1877a85cc5
commit 913d1945d4
9 changed files with 640 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""
DCA阶梯买入监控脚本
检查当前价格 vs 阶梯价位,触发时输出买入信号
"""
import os, sys, re, json
from datetime import datetime
# === 市场过滤参数 ===
market_filter = None
for arg in sys.argv[1:]:
if arg.startswith("--market="):
market_filter = arg.split("=")[1].upper() # HK / US / CN
# === Load env ===
env_vars = {}
with open(os.path.expanduser('~/.bashrc')) as f:
for line in f:
line = line.strip()
if line.startswith('export LONGBRIDGE_') or line.startswith('export LONGPORT_'):
parts = line.replace('export ', '').split('=', 1)
if len(parts) == 2:
env_vars[parts[0]] = parts[1]
# 2026-07-21 修复: 强制走海外域, 避免国内 socket 连不上
env_vars.setdefault('LONGPORT_HTTP_URL', 'https://openapi.longbridge.com')
env_vars.setdefault('LONGBRIDGE_HTTP_URL', 'https://openapi.longbridge.com')
env_vars.setdefault('LONGBRIDGE_REGION', 'ap')
env_vars.setdefault('LONGBRIDGE_TRADE_ENABLED', 'true')
for key, val in env_vars.items():
if '${' not in val:
os.environ[key] = val
for key, val in env_vars.items():
if '${' in val:
os.environ[key] = re.sub(r'\$\{(\w+)\}', lambda m: os.environ.get(m.group(1), ''), val)
from longport import openapi
import subprocess
cfg = openapi.Config.from_env()
# 不再使用 ctx.quote() (WSS 不稳定, 2026-07-21 改用 longport CLI HTTP 端点)
# ctx = openapi.QuoteContext(config=cfg)
# === Load positions ===
config_path = os.path.expanduser('~/.hermes/scripts/dca_positions.json')
with open(config_path) as f:
config = json.load(f)
positions = config['positions']
trigger_pct = config['alert_settings']['trigger_pct'] # 2% within ladder price
# === 股息率过滤:低于7%的标的跳过 ===
MIN_YIELD = 7.0
filtered_out = []
for sym in list(positions.keys()):
if positions[sym].get('yield', 0) < MIN_YIELD:
filtered_out.append(f"{sym}({positions[sym]['name']} {positions[sym]['yield']}%)")
del positions[sym]
# === 市场过滤 ===
if market_filter:
before = set(positions.keys())
positions = {k: v for k, v in positions.items() if v.get('market', '').upper() == market_filter}
skipped = before - set(positions.keys())
# if skipped:
# print(f"⏭️ 跳过非{market_filter}标的: {', '.join(skipped)}")
# === Get current prices (2026-07-21 改用 longport CLI 走 HTTP, 避免 WSS 不稳定) ===
all_symbols = list(positions.keys())
quotes = {}
import re
for sym in all_symbols:
try:
result = subprocess.run(
['proxychains4', '-f', '/home/openclaw/.proxychains/proxychains.conf',
'/home/openclaw/.local/bin/longbridge', '--profile', 'lb_real',
'quote', sym],
capture_output=True, text=True, timeout=10
)
if result.returncode == 0 and result.stdout.strip():
m = re.search(r'\s*' + re.escape(sym) + r'\s*│\s*([\d.]+)\s*│', result.stdout)
if m:
quotes[sym] = float(m.group(1))
except Exception:
pass
# === Check ladder triggers ===
alerts = []
summary_lines = []
for sym, pos in positions.items():
current_price = quotes.get(sym)
if current_price is None:
continue
name = pos['name']
market = pos['market']
flag = "🇭🇰" if market == "HK" else "🇺🇸"
for ladder in pos['ladder']:
tier = ladder['tier']
target = ladder['price']
alloc = ladder['alloc_pct']
status = ladder['status']
if status == 'done':
continue
# Check if price is within trigger range (at or below target)
if target > 0 and current_price <= target * (1 + trigger_pct / 100):
pct_diff = (current_price - target) / target * 100
action = "🟢 到价可买" if current_price <= target else "🟡 接近目标"
alerts.append({
'symbol': sym,
'name': name,
'flag': flag,
'tier': tier,
'target': target,
'current': current_price,
'pct_diff': pct_diff,
'alloc': alloc,
'yield': pos['yield'],
'action': action,
})
# Always add to summary
nearest = min(pos['ladder'], key=lambda l: abs(l['price'] - current_price) if l['status'] != 'done' and l['price'] > 0 else 9999)
gap_pct = (current_price - nearest['price']) / nearest['price'] * 100 if nearest['price'] > 0 else 0
summary_lines.append(f"{flag} {sym} {name}: 现价{current_price} → 最近档{nearest['price']}(T{nearest['tier']}) 差{gap_pct:+.1f}% 股息{pos['yield']}% [{pos.get('div_freq', '未知')}]")
# === Output ===
now = datetime.now().strftime('%Y-%m-%d %H:%M')
if alerts:
# Sort by urgency (closest to target first)
alerts.sort(key=lambda a: a['pct_diff'])
lines = [f"🔔 DCA买入信号 [{now}]", ""]
for a in alerts:
if a['pct_diff'] <= 0:
emoji = "🚨"
tag = "已触达"
else:
emoji = "🟡"
tag = f"{a['pct_diff']:.1f}%"
lines.append(f"{emoji} {a['flag']} {a['symbol']} {a['name']}")
lines.append(f"{a['tier']}档目标: {a['target']} 现价: {a['current']} {tag}")
lines.append(f" 建议仓位: {a['alloc']}% 股息率: {a['yield']}%")
lines.append("")
lines.append("━━━━━━━━━━━━━")
lines.append("📋 全部监控标的:")
for s in summary_lines:
lines.append(f" {s}")
print("\n".join(lines))
else:
# No alerts - silent (empty output = no notification sent)
# 2026-07-21: 让 cron always 输出 summary (即使没 alert, 让你看到 status)
lines = [f"📊 DCA {market_filter} 监控 [{now}] (无买入信号)", ""]
lines.append("━━━━━━━━━━━━━")
lines.append("📋 全部监控标的:")
for s in summary_lines:
lines.append(f" {s}")
print("\n".join(lines))
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
python3 dca_monitor.py --market=us
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""
A股高股息扫描器(LongPort取价 + 预设股息率)
"""
import os, sys, json
from datetime import datetime
# === 读取LongPort环境变量 ===
bashrc = open(os.path.expanduser("~/.bashrc")).read()
for line in bashrc.splitlines():
if line.startswith("export LONGPORT_") or line.startswith("export LONGBRIDGE_"):
parts = line.replace("export ", "").split("=", 1)
if len(parts) == 2:
os.environ[parts[0]] = parts[1].strip('"').strip("'")
from longport import openapi
cfg = openapi.Config.from_env()
ctx = openapi.QuoteContext(config=cfg)
# === A股候选池 ===
A_SHARE_POOL = {
"601088.SH": {"name": "中国神华", "yield": 6.7, "freq": "年+中期", "sector": "煤炭"},
"601328.SH": {"name": "交通银行", "yield": 6.2, "freq": "年度", "sector": "银行"},
"601398.SH": {"name": "工商银行", "yield": 5.9, "freq": "年度", "sector": "银行"},
"601288.SH": {"name": "农业银行", "yield": 5.8, "freq": "年度", "sector": "银行"},
"601939.SH": {"name": "建设银行", "yield": 6.0, "freq": "年度", "sector": "银行"},
"601988.SH": {"name": "中国银行", "yield": 5.7, "freq": "年度", "sector": "银行"},
"600900.SH": {"name": "长江电力", "yield": 3.8, "freq": "年度", "sector": "电力"},
"601857.SH": {"name": "中国石油", "yield": 5.5, "freq": "年度", "sector": "能源"},
"600028.SH": {"name": "中国石化", "yield": 5.2, "freq": "年度", "sector": "能源"},
"601728.SH": {"name": "中国电信", "yield": 4.8, "freq": "年度", "sector": "电信"},
"600036.SH": {"name": "招商银行", "yield": 4.5, "freq": "年度", "sector": "银行"},
"601166.SH": {"name": "兴业银行", "yield": 5.8, "freq": "年度", "sector": "银行"},
"601818.SH": {"name": "光大银行", "yield": 5.9, "freq": "年度", "sector": "银行"},
"600377.SH": {"name": "宁沪高速", "yield": 6.2, "freq": "年度", "sector": "高速"},
"601666.SH": {"name": "平煤股份", "yield": 6.2, "freq": "年度", "sector": "煤炭"},
"600023.SH": {"name": "浙能电力", "yield": 5.5, "freq": "年度", "sector": "电力"},
"000858.SZ": {"name": "五粮液", "yield": 10.5, "freq": "年度", "sector": "白酒"},
"000568.SZ": {"name": "泸州老窖", "yield": 7.0, "freq": "年度", "sector": "白酒"},
"000937.SZ": {"name": "冀中能源", "yield": 11.0, "freq": "年度", "sector": "煤炭"},
"002304.SZ": {"name": "洋河股份", "yield": 10.8, "freq": "年度", "sector": "白酒"},
"000596.SZ": {"name": "古井贡酒", "yield": 6.9, "freq": "年度", "sector": "白酒"},
"000001.SZ": {"name": "平安银行", "yield": 5.4, "freq": "年度", "sector": "银行"},
"600519.SH": {"name": "贵州茅台", "yield": 5.0, "freq": "年+中期", "sector": "白酒"},
}
def scan_a_share():
min_yield = 5.0
tickers = list(A_SHARE_POOL.keys())
all_quotes = {}
# LongPort批量获取A股价格
for i in range(0, len(tickers), 15):
batch = tickers[i:i+15]
try:
quotes = ctx.quote(batch)
for q in quotes:
all_quotes[q.symbol] = {
"price": float(q.last_done),
"prev_close": float(q.prev_close),
}
except Exception as e:
pass
results = []
for sym, info in A_SHARE_POOL.items():
if info["yield"] < min_yield:
continue
q = all_quotes.get(sym)
if q and q["price"] > 0:
change_pct = (q["price"] - q["prev_close"]) / q["prev_close"] * 100 if q["prev_close"] else 0
price = q["price"]
else:
# LongPort没拿到价格,用预设
price = 0
change_pct = 0
results.append({
"symbol": sym, "name": info["name"],
"price": price, "change_pct": change_pct,
"yield": info["yield"], "freq": info["freq"],
"flag": "🇨🇳", "sector": info["sector"],
})
results.sort(key=lambda x: x["yield"], reverse=True)
return results[:10]
def calc_ladder(price):
return [
{"tier": 1, "pct": -3, "price": round(price * 0.97, 2)},
{"tier": 2, "pct": -6, "price": round(price * 0.94, 2)},
{"tier": 3, "pct": -10, "price": round(price * 0.90, 2)},
]
def format_result(results):
now = datetime.now().strftime("%Y-%m-%d %H:%M")
if not results:
return f"🇨🇳 A股高息扫描 | {now}\n\n暂无符合条件的标的(≥5%"
lines = [f"🇨🇳 A股高息TOP | {now}", ""]
for i, r in enumerate(results, 1):
medal = ["🥇", "🥈", "🥉", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"][min(i-1, 9)]
lines.append(f"{medal} {r['name']} [{r['sector']}]")
lines.append(f" {r['symbol']}")
if r["price"] > 0:
chg = "📈" if r["change_pct"] >= 0 else "📉"
lines.append(f" 💰 现价: {r['price']:.2f} {chg} {r['change_pct']:+.1f}%")
else:
lines.append(f" 💰 价格: 盘后/未获取")
lines.append(f" 📊 股息率: {r['yield']:.1f}% 派息: {r['freq']}")
if r["price"] > 0:
ladder = calc_ladder(r["price"])
lstr = "".join([f"T{l['tier']}:{l['price']:.2f}({l['pct']}%)" for l in ladder])
lines.append(f" 🪜 阶梯: {lstr}")
lines.append("")
lines.append("━━━━━━━━━━━━━")
lines.append(f"📋 共扫描 {len(A_SHARE_POOL)} 只,筛出 {len(results)}")
return "\n".join(lines)
if __name__ == "__main__":
results = scan_a_share()
print(format_result(results))
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
python3 scan_cn.py
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
python3 dca_scanner.py hk
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
python3 dca_scanner.py us
+219
View File
@@ -0,0 +1,219 @@
"""
longport_http.py - 长桥 HTTP 公共模块 (替代 longport SDK WSS)
用户原话 2026-07-21: WSS 不稳定, 改用 HTTP 走 longport CLI 走 mihomo.
所有长桥脚本都应统一改用这个 module (避免每个脚本自己写 subprocess + 正则).
用法:
from longport_http import get_quote, get_quotes, submit_order, get_positions
设计:
- 所有函数返回 None / [] / {} 表示失败(不抛异常, 调用方自己检查)
- subprocess 走 proxychains4 走 mihomo (国内 VPS 走海外 WSS 必须)
- 单次调用超时 10 秒 (防止 cron 卡住)
"""
import subprocess
import re
import json
from typing import List, Dict, Optional, Union
# 路径
LONGBRIDGE_BIN = "/home/openclaw/.local/bin/longbridge"
PROXYCHAINS = "proxychains4"
PROXYCHAINS_CONF = "/home/openclaw/.proxychains/proxychains.conf"
PROFILE = "lb_real"
TIMEOUT = 10
def _run(*args) -> str:
"""底层调用: proxychains4 + longbridge CLI. 返回 stdout (失败返回空)."""
cmd = [PROXYCHAINS, "-f", PROXYCHAINS_CONF, LONGBRIDGE_BIN, "--profile", PROFILE, *args]
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=TIMEOUT)
# proxychains 诊断行混在 stdout (或 stderr) 里,统一过滤
combined = r.stdout + r.stderr
lines = [l for l in combined.splitlines() if not l.startswith("[proxychains]")]
if r.returncode != 0 and not lines:
return ""
return "\n".join(lines)
except subprocess.TimeoutExpired:
return ""
except Exception:
return ""
def get_quote(symbol: str) -> Optional[Dict]:
"""
拿 1 只票的实时报价 (替代 openapi.QuoteContext().quote([symbol]))
策略:
1. 先试 quote (实时, 但表格可能截断 A 股)
2. 失败则用 candlesticks day --count 1 (取收盘价)
返回: {"symbol": "NVDA.US", "price": 123.45, "change_pct": 1.2} 或 None
"""
out = _run("quote", symbol)
if out:
# 表格: │ NVDA.US │ 856.61 │ +1.20% │ ...
# 表格列宽限制会截断长 symbol: │ 600519… │ 1308.0… │
symbol_trunc = symbol[:7] + ""
for cand in [symbol, symbol_trunc]:
m = re.search(
r"\s*" + re.escape(cand) + r"\s*│\s*([\d.]+)\s*│\s*([+\-\d.%]+)\s*│",
out
)
if m:
price_str = m.group(1)
# 如果是 1308.0… 这种截断, candlesticks 取完整价
if "" in price_str or len(price_str) < 4:
break
price = float(price_str)
change_pct = float(m.group(2).rstrip("%"))
return {"symbol": symbol, "price": price, "change_pct": change_pct}
# fallback: candlesticks 拿日线收盘价
cs_out = _run("candlesticks", symbol, "day", "--count", "1")
if cs_out:
# 表格: │ 2026-07-21 00:00 │ 1338.980 │ 1344.700 │ 1296.870 │ 1308.000 │ 77,148 │
m = re.search(r"\s*[\d\-]+\s*[\d:\s]*│\s*([\d.]+)\s*│\s*([\d.]+)\s*│\s*([\d.]+)\s*│\s*([\d.]+)\s*│", cs_out)
if m:
close = float(m.group(4))
return {"symbol": symbol, "price": close, "change_pct": 0, "source": "candlestick_close"}
return None
def get_quotes(symbols: List[str]) -> Dict[str, Dict]:
"""
批量拿报价 (替代 openapi.QuoteContext().quote(batch))
返回: {"NVDA.US": {"price": 123, "change_pct": 1.2}, ...}
失败的 symbol 不会出现在结果里
"""
result = {}
for sym in symbols:
q = get_quote(sym)
if q:
result[sym] = q
return result
def get_positions() -> List[Dict]:
"""
查持仓 (替代 openapi.TradeContext().position_list)
返回: [{"symbol": "NVDA.US", "quantity": 10, "cost_price": 100, ...}, ...]
"""
out = _run("positions")
if not out:
return []
# 解析长桥表格 (只解析包含股票代码的行)
results = []
# 表格行格式: │ NVDA.US │ 10 │ 100.00 │ 856.00 │ ... │
pattern = re.compile(
r"\s*([A-Z\d]{1,6}\.(US|HK|SH|SZ))\s*│\s*(\d+)\s*│\s*([\d.]+)\s*│"
)
for m in pattern.finditer(out):
results.append({
"symbol": m.group(1),
"market": m.group(2),
"quantity": int(m.group(3)),
"cost_price": float(m.group(4))
})
return results
def submit_order(
symbol: str,
side: str, # "buy" / "sell"
quantity: float,
order_type: str = "MO", # "MO" = 市价, "LO" = 限价
price: Optional[float] = None, # LO 必填
time_in_force: str = "Day"
) -> Optional[Dict]:
"""
下单 (替代 openapi.TradeContext().submit_order)
返回: {"order_id": "1234567890", "side": "buy", "quantity": 0.31, "price": 862.35}
或 None (失败)
"""
args = ["submit", symbol, side, "--qty", str(quantity),
"--order-type", order_type,
"--tif", time_in_force, "-y"]
if order_type == "LO" and price is not None:
args.extend(["--price", str(price)])
out = _run(*args)
if not out:
return None
# 长桥返回: 订单号 1234567890
m = re.search(r"订单号[:\s]*(\d+)", out)
if not m:
return None
return {
"order_id": m.group(1),
"symbol": symbol,
"side": side,
"quantity": quantity,
"price": price
}
def get_candlesticks(symbol: str, period: str = "day", count: int = 30) -> Optional[List[Dict]]:
"""
拿 K 线数据 (替代 openapi.QuoteContext().candlesticks)
period: 'day' | '5m' | '15m' | '1h' | '1m'
返回: [{"timestamp": "2026-07-21", "open": 100, "high": 105, "low": 99, "close": 103, "volume": 12345}, ...]
或 None (失败)
"""
out = _run("candlesticks", symbol, period, "--count", str(count))
if not out:
return None
# 表格格式: │ 时间 │ 开盘 │ 最高 │ 最低 │ 收盘 │ 成交量 │
# A 股时间: '2026-07-21 09:30' / '2026-07-21 00:00' (日线)
# 数字带千分位: '1,234,567'
results = []
pattern = re.compile(
r"\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\s*│"
r"\s*([\d,.]+)\s*│\s*([\d,.]+)\s*│\s*([\d,.]+)\s*│\s*([\d,.]+)\s*│\s*([\d,.]+)\s*│"
)
def parse_num(s):
return float(s.replace(",", ""))
for m in pattern.finditer(out):
ts_str = m.group(1).replace(" ", "T")
# 日线时间格式: '2026-07-21T00:00'
if ts_str.endswith("T00:00") and "T" + m.group(1).split()[1] == ts_str:
ts_str = m.group(1).replace(" ", "T")
results.append({
"timestamp": ts_str,
"open": parse_num(m.group(2)),
"high": parse_num(m.group(3)),
"low": parse_num(m.group(4)),
"close": parse_num(m.group(5)),
"volume": parse_num(m.group(6)),
})
return results if results else None
# 测试
if __name__ == "__main__":
print("=== 测试 longport_http 模块 ===\n")
# 1. 单只报价
print("1. get_quote('NVDA.US'):")
q = get_quote("NVDA.US")
print(f" {q}\n")
# 2. 批量报价
print("2. get_quotes(['NLY.US', 'HTGC.US', 'ARCC.US']):")
qs = get_quotes(["NLY.US", "HTGC.US", "ARCC.US"])
for sym, data in qs.items():
print(f" {sym}: ${data['price']} ({data['change_pct']:+.2f}%)\n")
# 3. 持仓
print("3. get_positions():")
pos = get_positions()
for p in pos:
print(f" {p['symbol']}: {p['quantity']}股 @ ${p['cost_price']}\n")
print(f" (共 {len(pos)} 个持仓)\n")
+96
View File
@@ -0,0 +1,96 @@
#!/bin/bash
# 核验当前期预测 vs 实际开奖,记录命中率
# 22:30 北京时间(开奖21:30后1小时)跑
set -e
DB="$HOME/.hermes/trading/lottery.db"
OUT_DIR="$HOME/.hermes/cron/output/lottery-verify"
mkdir -p "$OUT_DIR"
# 当前期号 - 优先代理,失败直连(VPS 出口有时被墙)
QI=$(curl -s --proxy http://127.0.0.1:7890 --max-time 8 "https://btc.tktk.app/data/v_xg.json" 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('Qi',''))" 2>/dev/null)
if [ -z "$QI" ]; then
QI=$(curl -s --noproxy '*' --max-time 8 "https://btc.tktk.app/data/v_xg.json" 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('Qi',''))" 2>/dev/null)
fi
if [ -z "$QI" ]; then
echo "❌ 无法获取当前期号(代理+直连都失败)"
exit 1
fi
# 实际开奖结果
ACTUAL=$(sqlite3 "$DB" "SELECT n1||','||n2||','||n3||','||n4||','||n5||','||n6||','||special FROM draws WHERE period='$QI';")
if [ -z "$ACTUAL" ]; then
echo "❌ 期号 $QI 还没入库开奖结果"
exit 1
fi
IFS=',' read -r A1 A2 A3 A4 A5 A6 AS <<< "$ACTUAL"
ACTUAL_FLAT="$A1 $A2 $A3 $A4 $A5 $A6"
ACTUAL_SPECIAL="$AS"
# 找对应的分析文件 - 看 Qi 是几(7-19/21的cron跑的就是 Qi-1 期预测)
QI_NUM=$((10#$QI))
PREV_PERIOD=$((QI_NUM - 1))
# 标题格式有几种:'077期热数据采集' / '热数据采集 · 077期' / '077期数据汇总'
# 简单找包含期号+期 的最近文件
ANALYSIS_FILE=$(grep -lE "${PREV_PERIOD}期数据汇总|${PREV_PERIOD}期热数据采集|热数据采集.*${PREV_PERIOD}" "$HOME/.hermes/cron/output/5bec1f60f77f/"*.md 2>/dev/null | tail -1)
[ -z "$ANALYSIS_FILE" ] && ANALYSIS_FILE=$(grep -l "${PREV_PERIOD}" "$HOME/.hermes/cron/output/5bec1f60f77f/"*.md 2>/dev/null | tail -1)
OUT_FILE="$OUT_DIR/$(date +%Y-%m-%d_%H-%M-%S)_${QI}.md"
{
echo "## 🎯 核验报告 | $QI期"
echo ""
echo "**开奖时间**: $(date +%Y-%m-%d) 21:30 北京"
echo "**核验时间**: $(date +%Y-%m-%d) 22:30 北京"
echo ""
echo "### 实际开奖"
echo "| 位置 | 号码 |"
echo "|------|------|"
echo "| 平码 | $A1 · $A2 · $A3 · $A4 · $A5 · $A6 |"
echo "| 特码 | **$AS** |"
echo ""
if [ -n "$ANALYSIS_FILE" ]; then
echo "### 分析文件"
echo "📄 $ANALYSIS_FILE"
echo ""
echo "### 预测 vs 实际"
PREDICTED_SPECIAL=$(grep -o "特码重点.*\*\*[0-9]\+\*\*\|特码.*\*\*[0-9]\+\*\*" "$ANALYSIS_FILE" | head -1)
PREDICTED_FLAT=$(grep "平码优先" "$ANALYSIS_FILE" | head -1)
echo "**预测**: $PREDICTED_SPECIAL · $PREDICTED_FLAT"
echo ""
echo "### 命中分析"
HIT_SPECIAL="❌ 未中"
if echo "$PREDICTED_SPECIAL" | grep -q "\*\*$AS\*\*"; then
HIT_SPECIAL="✅ **特码命中**"
fi
echo "- 特码 $AS: $HIT_SPECIAL"
HIT_FLAT=$(echo "$ACTUAL_FLAT" | tr ' ' '\n' | while read n; do
if echo "$PREDICTED_FLAT" | grep -q "\*\*$n\*\*\|\b$n\b"; then
echo "$n"
fi
done | tr '\n' ' ')
echo "- 平码命中: ${HIT_FLAT:-}"
TOTAL_HIT=$(echo "$ACTUAL_FLAT" | tr ' ' '\n' | while read n; do
if grep -qE "\*\*$n\*\*| $n[、,]|\b$n( |$)" "$ANALYSIS_FILE"; then
echo "1"
fi
done | wc -l)
echo ""
echo "**总命中**: 特码 + 平码 共 $((TOTAL_HIT+0)) / 7 球"
else
echo "### ⚠️ 未找到分析文件"
echo " 查找路径: $HOME/.hermes/cron/output/5bec1f60f77f/"
echo " 期号: $QI"
fi
echo ""
echo "---"
echo "_生成时间: $(date '+%Y-%m-%d %H:%M:%S')_"
} > "$OUT_FILE"
cat "$OUT_FILE"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# 信号队列重试脚本 - 每5分钟检查一次待处理信号
# 由 signal-queue-retry cron job 调用
QUEUE_SCRIPT="$HOME/.hermes/skills/trading/okx-auto-position/scripts/signal_queue.py"
PROCESS_SCRIPT="$HOME/.hermes/skills/trading/okx-auto-position/scripts/process_signal.py"
DB="$HOME/.hermes/trading/signal_queue.db"
# 检查是否有待处理信号
if [ ! -f "$DB" ]; then
exit 0
fi
PENDING=$(sqlite3 "$DB" "SELECT COUNT(*) FROM queue WHERE status IN ('pending','failed') AND retries < 3")
if [ "$PENDING" -eq 0 ]; then
exit 0
fi
# 有待处理信号,运行重试
python3 "$QUEUE_SCRIPT" retry