feat: 迁移 trading scripts 第3批 (scan_*/dca_scanner + 清理)

【迁移】
- dividend-scanner/scripts/scan_cn.sh 改 cd 路径 (原来硬编码 /home/openclaw/scripts)
- dividend-scanner/scripts/scan_hk.sh / scan_us.sh 同改
- dividend-scanner/scripts/dca_scanner.py (scan_hk/us 调它)
- 删除本地 ~/.hermes/scripts/{scan_cn,scan_hk,scan_us,scan_cn.py,dca_scanner}.*

【cron 路径】
- 84c46e13 (港股高息扫描) → dividend-scanner/scripts/scan_hk.sh
- 89a3e7a6 (美股高息扫描) → dividend-scanner/scripts/scan_us.sh
- 5d62894c (A股高息扫描) → dividend-scanner/scripts/scan_cn.sh

【清理】git rm --cached lottery-hk/scripts/__pycache__/lottery.cpython-311.pyc (不该 commit 编译产物)
+ .gitignore 加 __pycache__/

【保留本地 ~/.hermes/scripts/】(非 trading)
- analyze_cmb.sh / memory_check.sh / sync_qdrant_memory.sh / cleanup_disk.sh (Hermes 平台工具)
- hk_set_sl_now.sh / okx_trade.sh / merge_hype_shorts.sh / rgti_*.py (一次性工具)
- longbridge_hosts_*.sh / update_longbridge_token.sh (长桥 host 维护)
- ths_query.py / intraday_cost_check.py / lottery_analyze.sh / lb_test.py
- check-gateway-health.sh / clear-telegram-session.sh / mcp_auth_helper.py / mihomo_watchdog.sh / push_to_qq.sh / verify_lb_token.py
- stocks/, crypto/ (子目录, 工具集合)
- dca_positions.json / rgti_alert_state.json / rgti_t_state.json (数据文件)

【未迁移】crypto/ stocks/ 子目录 (待清理, 不紧急)
【测试】scan_cn.sh 从新位置跑通
This commit is contained in:
2026-07-24 11:44:22 +08:00
parent 913d1945d4
commit 80063a1cac
6 changed files with 247 additions and 3 deletions
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""
高股息股票扫描器
- 港股/美股/ A股:全部走LongPort API
- A股股息率:使用预设数据(定期更新)
"""
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
from longport.openapi import CalcIndex
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": "白酒"},
}
# === 港股/美股候选池 ===
STOCK_POOLS = {
"hk": {
"flag": "🇭🇰", "label": "港股", "min_yield": 7.0,
"tickers": [
"00939.HK", "01398.HK", "00857.HK", "00883.HK", "01088.HK",
"00941.HK", "06823.HK", "00823.HK", "02191.HK", "00778.HK",
"00405.HK", "02778.HK", "01898.HK", "03988.HK", "01288.HK",
"00006.HK", "00002.HK", "00003.HK", "00012.HK", "00016.HK",
"00388.HK", "02318.HK", "00027.HK", "01299.HK",
],
"freq": {
"00939.HK": "半年", "01398.HK": "半年", "00857.HK": "半年",
"00883.HK": "半年", "01088.HK": "半年", "00941.HK": "半年",
"06823.HK": "半年", "00823.HK": "半年", "02191.HK": "季度",
"00778.HK": "半年", "00405.HK": "半年", "02778.HK": "半年",
"01898.HK": "半年", "03988.HK": "半年", "01288.HK": "半年",
"00006.HK": "半年", "00002.HK": "半年", "00003.HK": "半年",
"00012.HK": "半年", "00016.HK": "半年", "00388.HK": "半年",
"02318.HK": "半年", "00027.HK": "半年", "01299.HK": "半年",
},
},
"us": {
"flag": "🇺🇸", "label": "美股", "min_yield": 7.0,
"tickers": [
"NLY.US", "HTGC.US", "ARCC.US", "AGNC.US", "PSEC.US",
"TWO.US", "STWD.US", "OFS.US", "MAIN.US", "ABR.US",
"CIM.US", "NYMT.US", "SBR.US", "PDI.US", "PTY.US",
],
"freq": {
"NLY.US": "季度", "HTGC.US": "季度", "ARCC.US": "季度",
"AGNC.US": "月度", "PSEC.US": "月度", "TWO.US": "季度",
"STWD.US": "季度", "OFS.US": "季度", "MAIN.US": "月度",
"ABR.US": "季度", "CIM.US": "季度", "NYMT.US": "季度",
"SBR.US": "月度", "PDI.US": "月度", "PTY.US": "季度",
},
},
}
def scan_with_longport(market_key):
"""用LongPort扫描港股/美股,获取实时价格+股息率"""
pool = STOCK_POOLS[market_key]
tickers = pool["tickers"]
min_yield = pool["min_yield"]
flag = pool["flag"]
freq_map = pool["freq"]
results = []
all_quotes = {}
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),
"name": q.name if hasattr(q, 'name') else q.symbol,
}
except:
pass
try:
indexes = ctx.calc_indexes(batch, [CalcIndex.DividendYield])
for idx in indexes:
if idx.symbol in all_quotes:
dy = getattr(idx, 'dividend_yield', None)
if dy is not None:
all_quotes[idx.symbol]["yield"] = float(dy) * 100
except:
pass
for sym, info in all_quotes.items():
dy = info.get("yield", 0)
if dy >= min_yield:
change_pct = (info["price"] - info["prev_close"]) / info["prev_close"] * 100
results.append({
"symbol": sym, "name": info.get("name", sym),
"price": info["price"], "change_pct": change_pct,
"yield": dy, "freq": freq_map.get(sym, "未知"),
"flag": flag,
})
results.sort(key=lambda x: x["yield"], reverse=True)
return results[:5]
def scan_a_share():
"""用LongPort获取A股实时价格,配合预设股息率数据"""
flag = "🇨🇳"
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),
"name": q.name if hasattr(q, 'name') else q.symbol,
}
except:
pass
results = []
for sym, info in A_SHARE_POOL.items():
if info["yield"] < min_yield:
continue
q = all_quotes.get(sym)
if not q or q["price"] <= 0:
continue
change_pct = (q["price"] - q["prev_close"]) / q["prev_close"] * 100
results.append({
"symbol": sym, "name": q.get("name", info["name"]),
"price": q["price"], "change_pct": change_pct,
"yield": info["yield"], "freq": info["freq"],
"flag": flag, "sector": info.get("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(label, flag, results, total_scanned):
now = datetime.now().strftime("%Y-%m-%d %H:%M")
if not results:
return "" # 无候选时不输出,避免空推送
if "error" in results[0]:
return f"{flag} {label}高息扫描 | {now}\n\n{results[0]['error']}"
lines = [f"{flag} {label}高息TOP | {now}", ""]
for i, r in enumerate(results, 1):
medal = ["🥇", "🥈", "🥉", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"][min(i-1, 9)]
chg = "📈" if r["change_pct"] >= 0 else "📉"
sector = f" [{r['sector']}]" if r.get("sector") else ""
lines.append(f"{medal} {r['symbol']}{sector}")
lines.append(f" {r['name']}")
lines.append(f" 💰 现价: {r['price']:.2f} {chg} {r['change_pct']:+.1f}%")
lines.append(f" 📊 股息率: {r['yield']:.1f}% 派息: {r['freq']}")
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"📋 共扫描 {total_scanned} 只,筛出 {len(results)}")
return "\n".join(lines)
if __name__ == "__main__":
market = sys.argv[1] if len(sys.argv) > 1 else "all"
if market == "cn":
results = scan_a_share()
output = format_result("A股", "🇨🇳", results, len(A_SHARE_POOL))
if output.strip():
print(output)
elif market == "hk":
results = scan_with_longport("hk")
output = format_result("港股", "🇭🇰", results, len(STOCK_POOLS["hk"]["tickers"]))
if output.strip():
print(output)
elif market == "us":
results = scan_with_longport("us")
output = format_result("美股", "🇺🇸", results, len(STOCK_POOLS["us"]["tickers"]))
if output.strip():
print(output)
elif market == "all":
outputs = []
outputs.append(format_result("港股", "🇭🇰", scan_with_longport("hk"), len(STOCK_POOLS["hk"]["tickers"])))
outputs.append(format_result("美股", "🇺🇸", scan_with_longport("us"), len(STOCK_POOLS["us"]["tickers"])))
outputs.append(format_result("A股", "🇨🇳", scan_a_share(), len(A_SHARE_POOL)))
# 过滤空结果
outputs = [o for o in outputs if o.strip()]
if outputs:
print("\n\n".join(outputs))
# 无候选时不输出任何内容
else:
print(f"用法: python3 {sys.argv[0]} [hk|us|cn|all]")
+2 -1
View File
@@ -1,3 +1,4 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
# 改成从当前目录跑 (skill 仓库下, 用相对路径)
cd "$(dirname "$0")"
python3 scan_cn.py
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
cd "$(dirname "$0")"
python3 dca_scanner.py hk
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
cd /home/openclaw/.hermes/scripts
cd "$(dirname "$0")"
python3 dca_scanner.py us