【迁移内容】
- 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)
128 lines
5.7 KiB
Python
Executable File
128 lines
5.7 KiB
Python
Executable File
#!/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))
|