#!/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))