merge: pipeline fixes + false-alarm cleanup
This commit is contained in:
@@ -139,8 +139,8 @@ def generate():
|
|||||||
insights.append("风险板块: " + " | ".join(loser_insights[:3]))
|
insights.append("风险板块: " + " | ".join(loser_insights[:3]))
|
||||||
|
|
||||||
# ── 洞察4:资金流向异动 ──
|
# ── 洞察4:资金流向异动 ──
|
||||||
big_inflow = [s for s in sectors if s.get("net_inflow", 0) > 50]
|
big_inflow = [s for s in sectors if (s.get("net_inflow") or 0) > 50]
|
||||||
big_outflow = [s for s in sectors if s.get("net_inflow", 0) < -50]
|
big_outflow = [s for s in sectors if (s.get("net_inflow") or 0) < -50]
|
||||||
if big_inflow:
|
if big_inflow:
|
||||||
top = max(big_inflow, key=lambda s: s["net_inflow"])
|
top = max(big_inflow, key=lambda s: s["net_inflow"])
|
||||||
insights.append(
|
insights.append(
|
||||||
|
|||||||
@@ -868,22 +868,95 @@ def build_report():
|
|||||||
})
|
})
|
||||||
|
|
||||||
# JSON文件
|
# JSON文件
|
||||||
|
# 已迁移到DB的旧JSON文件:不再报"无读取方"假警报,真实健康信号看DB表新鲜度
|
||||||
|
MIGRATED_TO_DB = {
|
||||||
|
"multi_tf_cache.json": "mtf_cache",
|
||||||
|
"macro_context.json": "macro_context_log",
|
||||||
|
"market.json": "market_snapshots",
|
||||||
|
"live_prices.json": "live_prices",
|
||||||
|
"price_history.json": "price_events",
|
||||||
|
"macro_risk_state.json": "macro_context_log",
|
||||||
|
}
|
||||||
json_entities = []
|
json_entities = []
|
||||||
for jf in sorted(WEB_DATA.glob("*.json")):
|
for jf in sorted(WEB_DATA.glob("*.json")):
|
||||||
if jf.name == "stocks": continue
|
if jf.name == "stocks": continue
|
||||||
if jf.stem.startswith("temp_"): continue
|
if jf.stem.startswith("temp_"): continue
|
||||||
readers = flows["json_read"].get(jf.name, [])
|
readers = flows["json_read"].get(jf.name, [])
|
||||||
size = jf.stat().st_size / 1024
|
size = jf.stat().st_size / 1024
|
||||||
|
migrated = MIGRATED_TO_DB.get(jf.name)
|
||||||
|
desc = JSON_DESC.get(jf.name, "")
|
||||||
|
if migrated:
|
||||||
|
desc = (desc + " " if desc else "") + f"(已迁移到DB表 {migrated},此为遗留文件)"
|
||||||
json_entities.append({
|
json_entities.append({
|
||||||
"name": jf.name,
|
"name": jf.name,
|
||||||
"desc": JSON_DESC.get(jf.name, ""),
|
"desc": desc,
|
||||||
"size_kb": round(size, 1),
|
"size_kb": round(size, 1),
|
||||||
"readers": readers[:10],
|
"readers": readers[:10],
|
||||||
"writers": [], # 难以精确追踪
|
"writers": [], # 难以精确追踪
|
||||||
"last_modified": datetime.fromtimestamp(jf.stat().st_mtime).strftime("%m-%d %H:%M"),
|
"last_modified": datetime.fromtimestamp(jf.stat().st_mtime).strftime("%m-%d %H:%M"),
|
||||||
"warn": len(readers) == 0 and jf.name not in ("portfolio.json", "market.json"),
|
"warn": (len(readers) == 0 and jf.name not in ("portfolio.json", "market.json")
|
||||||
|
and not migrated),
|
||||||
|
"migrated_to_db": migrated or None,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ── DB表新鲜度:真实数据管道健康信号(替代对遗留JSON文件的mtime检查)──
|
||||||
|
# 注意:活跃数据在 /home/hmo/MoFin/data/mofin.db(live_prices/mtf_cache 今日有写入),
|
||||||
|
# 不用 get_conn()(它指向 web-dashboard 的库,那边部分表是旧的)
|
||||||
|
db_freshness = []
|
||||||
|
FRESHNESS_TABLES = [
|
||||||
|
("mtf_cache", "updated_at", "多周期均线缓存"),
|
||||||
|
("macro_context_log", "created_at", "宏观上下文"),
|
||||||
|
("market_snapshots", "created_at", "市场快照"),
|
||||||
|
("live_prices", "updated_at", "实时价格"),
|
||||||
|
("price_events", "created_at", "价格事件"),
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
_fc = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=10)
|
||||||
|
for tname, tcol, label in FRESHNESS_TABLES:
|
||||||
|
try:
|
||||||
|
row = _fc.execute(
|
||||||
|
f"SELECT MAX({tcol}) FROM {tname}").fetchone()
|
||||||
|
if row and row[0]:
|
||||||
|
last_dt = datetime.fromisoformat(str(row[0]).replace("Z", ""))
|
||||||
|
age_h = (now - last_dt).total_seconds() / 3600
|
||||||
|
db_freshness.append({
|
||||||
|
"table": tname, "label": label,
|
||||||
|
"last_record": last_dt.strftime("%m-%d %H:%M"),
|
||||||
|
"age_hours": round(age_h, 1),
|
||||||
|
"warn": age_h > 24,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
db_freshness.append({"table": tname, "label": label,
|
||||||
|
"last_record": None, "age_hours": -1, "warn": True})
|
||||||
|
except Exception:
|
||||||
|
pass # 表不存在或列名不同,跳过
|
||||||
|
_fc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# price_events 特殊处理:活跃存储是 price_events.json(price_monitor 实时写入),
|
||||||
|
# DB 表是旧遗留。读 JSON 最后一条事件的时间。
|
||||||
|
try:
|
||||||
|
_pe_path = Path("/home/hmo/web-dashboard/data/price_events.json")
|
||||||
|
if _pe_path.exists():
|
||||||
|
_pe = json.loads(_pe_path.read_text(encoding="utf-8"))
|
||||||
|
_items = _pe if isinstance(_pe, list) else _pe.get("events", [])
|
||||||
|
if _items:
|
||||||
|
_last = _items[-1]
|
||||||
|
_ts = _last.get("timestamp") or _last.get("created_at") or ""
|
||||||
|
_dt = datetime.fromisoformat(str(_ts).replace("Z", ""))
|
||||||
|
_age = (now - _dt).total_seconds() / 3600
|
||||||
|
# 替换 db_freshness 里 price_events 那条(DB 旧数据)
|
||||||
|
db_freshness = [f for f in db_freshness if f["table"] != "price_events"]
|
||||||
|
db_freshness.append({
|
||||||
|
"table": "price_events.json", "label": "价格事件",
|
||||||
|
"last_record": _dt.strftime("%m-%d %H:%M"),
|
||||||
|
"age_hours": round(_age, 1),
|
||||||
|
"warn": _age > 24,
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# ── Tab 3: 流程/cron映射 ──
|
# ── Tab 3: 流程/cron映射 ──
|
||||||
pipelines = []
|
pipelines = []
|
||||||
for j in sorted(cron_jobs, key=lambda x: x.get("name","")):
|
for j in sorted(cron_jobs, key=lambda x: x.get("name","")):
|
||||||
@@ -912,6 +985,7 @@ def build_report():
|
|||||||
"entities": entities,
|
"entities": entities,
|
||||||
"json_files": json_entities,
|
"json_files": json_entities,
|
||||||
"pipelines": pipelines,
|
"pipelines": pipelines,
|
||||||
|
"db_freshness": db_freshness,
|
||||||
}
|
}
|
||||||
out_path = WEB_DATA / "mofin_health.json"
|
out_path = WEB_DATA / "mofin_health.json"
|
||||||
with open(out_path, "w") as f:
|
with open(out_path, "w") as f:
|
||||||
|
|||||||
@@ -19,6 +19,23 @@ from strategy_lifecycle import regenerate_all
|
|||||||
result = regenerate_all(stdout=True)
|
result = regenerate_all(stdout=True)
|
||||||
print(f"\n重评完成: {result.get('ok',0)}/{result.get('total',0)}成功")
|
print(f"\n重评完成: {result.get('ok',0)}/{result.get('total',0)}成功")
|
||||||
|
|
||||||
|
# Step 1.5: 持仓 12 维 LLM 深度分析——后台分离执行(12-40分钟,不能阻塞 cron 的 120s 超时)
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("🧠 持仓12维LLM分析(后台分离启动)")
|
||||||
|
print("=" * 50)
|
||||||
|
import subprocess as _sp
|
||||||
|
analysis_result = {"mode": "detached"}
|
||||||
|
try:
|
||||||
|
_log = open("/tmp/holdings_12d_daily.log", "a")
|
||||||
|
_sp.Popen(
|
||||||
|
["python3", "/home/hmo/.hermes/profiles/position-analyst/scripts/batch_reassess.py",
|
||||||
|
"--type", "holding", "--today"],
|
||||||
|
stdout=_log, stderr=_log, start_new_session=True)
|
||||||
|
print(" ✅ 12维分析已后台启动,日志: /tmp/holdings_12d_daily.log(结果落DB,不阻塞盘前流程)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ 12维分析启动失败: {e}")
|
||||||
|
analysis_result = {"mode": "detached", "error": str(e)[:100]}
|
||||||
|
|
||||||
# Step 2: 自选退出
|
# Step 2: 自选退出
|
||||||
print("\n" + "=" * 50)
|
print("\n" + "=" * 50)
|
||||||
print("🔍 自选退出检查")
|
print("🔍 自选退出检查")
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ def run_once(round_label=""):
|
|||||||
# === 第三步:买入区偏离检测 + 自动重评 ===
|
# === 第三步:买入区偏离检测 + 自动重评 ===
|
||||||
reassesed_codes = []
|
reassesed_codes = []
|
||||||
# 先做急跌检测(仅持仓,自选股不推送暴跌告警)
|
# 先做急跌检测(仅持仓,自选股不推送暴跌告警)
|
||||||
holdings_codes = {d["code"] for d in active if d.get("shares", 0) > 0}
|
holdings_codes = {d["code"] for d in active if (d.get("shares") or 0) > 0}
|
||||||
for d in active:
|
for d in active:
|
||||||
code = d["code"]
|
code = d["code"]
|
||||||
# 非持仓跳过
|
# 非持仓跳过
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from datetime import datetime
|
|||||||
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
|
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conn = sqlite3.connect(str(DB_PATH))
|
conn = sqlite3.connect(str(DB_PATH), timeout=30)
|
||||||
|
conn.execute("PRAGMA busy_timeout=30000")
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
|
||||||
# 读未提拔候选(按评分降序)
|
# 读未提拔候选(按评分降序)
|
||||||
|
|||||||
Executable → Regular
@@ -0,0 +1,9 @@
|
|||||||
|
import json
|
||||||
|
d = json.load(open('/home/hmo/.hermes/auth.json'))
|
||||||
|
pool = d.get('credential_pool', {})
|
||||||
|
for name, entries in pool.items():
|
||||||
|
if 'custom' in name or 'ocg' in name.lower():
|
||||||
|
for e in entries:
|
||||||
|
print(f"{name}: label={e.get('label')} priority={e.get('priority')} "
|
||||||
|
f"last_status={e.get('last_status')} err_code={e.get('last_error_code')} "
|
||||||
|
f"req_count={e.get('request_count')}")
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import json
|
||||||
|
d = json.load(open('/home/hmo/.hermes/cron/jobs.json'))
|
||||||
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
||||||
|
for j in jobs:
|
||||||
|
if j.get('name') in ('wiki-self-growth', 'evolution-pulse', '大脑任务执行', '知识研究-日常', '梦境循环-知识库归并'):
|
||||||
|
print(j['name'])
|
||||||
|
print(' provider:', j.get('provider'), '| model:', j.get('model'), '| base_url:', j.get('base_url'))
|
||||||
|
print(' no_agent:', j.get('no_agent'), '| script:', j.get('script'))
|
||||||
|
print(' timeout:', j.get('timeout'), '| max_turns:', j.get('max_turns'))
|
||||||
|
print(' prompt head:', str(j.get('prompt'))[:120])
|
||||||
|
print()
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import sqlite3, json
|
||||||
|
# price_events in web-dashboard db
|
||||||
|
c = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||||
|
try:
|
||||||
|
r = c.execute("SELECT MAX(created_at), COUNT(*) FROM price_events").fetchone()
|
||||||
|
print('web-dashboard mofin.db price_events:', r)
|
||||||
|
except Exception as e:
|
||||||
|
print('web-dashboard db err:', e)
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
# price_events.json tail
|
||||||
|
try:
|
||||||
|
d = json.load(open('/home/hmo/web-dashboard/data/price_events.json'))
|
||||||
|
print('price_events.json type:', type(d).__name__, 'len:', len(d) if hasattr(d, '__len__') else '?')
|
||||||
|
items = d if isinstance(d, list) else d.get('events', [])
|
||||||
|
if items:
|
||||||
|
print('last event:', json.dumps(items[-1], ensure_ascii=False)[:250])
|
||||||
|
except Exception as e:
|
||||||
|
print('json err:', e)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import sqlite3
|
||||||
|
conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||||
|
for t in ('mtf_cache', 'macro_context_log', 'live_prices', 'price_events', 'market_snapshots'):
|
||||||
|
try:
|
||||||
|
cols = [c[1] for c in conn.execute(f"PRAGMA table_info({t})")]
|
||||||
|
cnt = conn.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
|
||||||
|
print(f"{t}: rows={cnt} cols={cols}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{t}: ERROR {e}")
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import json, os
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
out = []
|
||||||
|
for jf, prof in [('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json', 'pa'),
|
||||||
|
('/home/hmo/.hermes/cron/jobs.json', 'default')]:
|
||||||
|
d = json.load(open(jf))
|
||||||
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
||||||
|
for j in jobs:
|
||||||
|
st = j.get('last_status')
|
||||||
|
en = j.get('enabled', True)
|
||||||
|
if st == 'error' or not en:
|
||||||
|
lr = str(j.get('last_run_at') or '?')[:19]
|
||||||
|
err = str(j.get('last_error') or '')[:300].replace('\n', ' | ')
|
||||||
|
out.append({
|
||||||
|
'profile': prof, 'name': j.get('name'), 'script': j.get('script'),
|
||||||
|
'no_agent': j.get('no_agent'), 'status': st, 'enabled': en,
|
||||||
|
'last_run': lr, 'schedule': j.get('schedule_display') or str(j.get('schedule')),
|
||||||
|
'error': err,
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"TOTAL problem jobs: {len(out)}\n")
|
||||||
|
for o in out:
|
||||||
|
flag = 'DISABLED' if not o['enabled'] else 'ERROR'
|
||||||
|
print(f"[{flag}] ({o['profile']}) {o['name']}")
|
||||||
|
print(f" script={o['script']} no_agent={o['no_agent']} sched={o['schedule']} last={o['last_run']}")
|
||||||
|
print(f" err: {o['error'][:250]}")
|
||||||
|
print()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json'))
|
||||||
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
||||||
|
targets = ['价格监控-高频', '知微洞察生成', '候选股自动提拔-每30分', '健康监控数据采集-每15分', '盘前全量重评-自选退出']
|
||||||
|
for j in jobs:
|
||||||
|
if j.get('name') in targets:
|
||||||
|
print('='*70)
|
||||||
|
print(j['name'], '| last:', str(j.get('last_run_at'))[:19])
|
||||||
|
print(str(j.get('last_error'))[:1200])
|
||||||
|
print()
|
||||||
|
|
||||||
|
d2 = json.load(open('/home/hmo/.hermes/cron/jobs.json'))
|
||||||
|
jobs2 = d2 if isinstance(d2, list) else d2.get('jobs', [])
|
||||||
|
for j in jobs2:
|
||||||
|
if j.get('name') in ('市场数据采集', '记忆守卫-每日', 'wiki-self-growth', 'evolution-pulse', '大脑任务执行'):
|
||||||
|
print('='*70)
|
||||||
|
print('[default]', j['name'], '| last:', str(j.get('last_run_at'))[:19])
|
||||||
|
print(str(j.get('last_error'))[:800])
|
||||||
|
print()
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import json, os, subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
print("=== 1. DATA FILES ===")
|
||||||
|
for f in ['multi_tf_cache.json', 'macro_context.json']:
|
||||||
|
p = f'/home/hmo/MoFin/data/{f}'
|
||||||
|
if os.path.exists(p):
|
||||||
|
mtime = datetime.fromtimestamp(os.path.getmtime(p))
|
||||||
|
h = (now - mtime).total_seconds() / 3600
|
||||||
|
print(f'{f}: {mtime} ({h:.0f}h ago)')
|
||||||
|
else:
|
||||||
|
print(f'{f}: MISSING')
|
||||||
|
|
||||||
|
pm = '/home/hmo/web-dashboard/data/market.json'
|
||||||
|
print(f'market.json: {"EXISTS" if os.path.exists(pm) else "MISSING"}')
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=== 2. CRON MISSING SCRIPTS in profile scripts dir ===")
|
||||||
|
d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json'))
|
||||||
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
||||||
|
sdir = '/home/hmo/.hermes/profiles/position-analyst/scripts'
|
||||||
|
exist = set(os.listdir(sdir))
|
||||||
|
for j in jobs:
|
||||||
|
s = j.get('script', '')
|
||||||
|
if s and s not in exist and j.get('no_agent'):
|
||||||
|
f = subprocess.run(['find', '/home/hmo/MoFin', '-name', s, '-not', '-path', '*/venv/*'],
|
||||||
|
capture_output=True, text=True, timeout=15)
|
||||||
|
locs = [l for l in f.stdout.splitlines() if l.strip()]
|
||||||
|
print(f"MISSING: {s} (job: {j.get('name')}) -> {locs[:2] if locs else 'NOT FOUND'}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=== 3. suggestions table ===")
|
||||||
|
r = subprocess.run(['grep', '-rn', 'suggestions', '/home/hmo/MoFin/deploy/profile-scripts/', '--include=*.py'],
|
||||||
|
capture_output=True, text=True, timeout=10)
|
||||||
|
for l in r.stdout.splitlines():
|
||||||
|
if 'suggestions' in l.lower():
|
||||||
|
print(l.strip())
|
||||||
|
# Check if any cron references it
|
||||||
|
r2 = subprocess.run(['python3', '-c', '''
|
||||||
|
import json
|
||||||
|
d = json.load(open("/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json"))
|
||||||
|
jobs = d if isinstance(d, list) else d.get("jobs", [])
|
||||||
|
for j in jobs:
|
||||||
|
if "suggest" in str(j):
|
||||||
|
print(j.get("name",""), j.get("script",""), j.get("prompt","")[:100])
|
||||||
|
'''], capture_output=True, text=True, timeout=5)
|
||||||
|
print(r2.stdout)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=== 4. price_monitor newspaper ===")
|
||||||
|
r = subprocess.run(['grep', '-n', 'newspaper', '/home/hmo/MoFin/deploy/profile-scripts/price_monitor.py'], capture_output=True, text=True, timeout=5)
|
||||||
|
print(r.stdout[-500:] if len(r.stdout) > 500 else r.stdout)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=== 5. Which crons generate multi_tf_cache / macro_context ===")
|
||||||
|
for j in jobs:
|
||||||
|
if j.get('name') and ('多周期' in j.get('name','') or '宏观' in j.get('name','') or 'multi' in j.get('name','').lower() or 'market' in j.get('name','').lower()):
|
||||||
|
print(f"{j.get('name')}: script={j.get('script','?')} status={j.get('last_status','?')} last_run={j.get('last_run_at','?')} err={str(j.get('last_error',''))[:80]}")
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import json, os
|
||||||
|
|
||||||
|
# 1. Check data pipeline stagnation
|
||||||
|
print("=== DATA FILE AGES ===")
|
||||||
|
for f in ['multi_tf_cache.json', 'macro_context.json', 'market.json']:
|
||||||
|
p = f'/home/hmo/MoFin/data/{f}'
|
||||||
|
if os.path.exists(p):
|
||||||
|
sec = (os.path.getmtime(p))
|
||||||
|
from datetime import datetime
|
||||||
|
print(f' {f}: {datetime.fromtimestamp(sec)} ({int((os.path.getmtime(p))/3600)}h ago)')
|
||||||
|
else:
|
||||||
|
print(f' {f}: MISSING')
|
||||||
|
|
||||||
|
# 2. Cron scripts missing from profile scripts dir
|
||||||
|
print()
|
||||||
|
print("=== MISSING CRON SCRIPTS ===")
|
||||||
|
d = json.load(open('/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json'))
|
||||||
|
jobs = d if isinstance(d, list) else d.get('jobs', [])
|
||||||
|
scripts_dir = '/home/hmo/.hermes/profiles/position-analyst/scripts'
|
||||||
|
existing = set(os.listdir(scripts_dir))
|
||||||
|
missing = []
|
||||||
|
for j in jobs:
|
||||||
|
s = j.get('script', '')
|
||||||
|
if s and s not in existing and j.get('no_agent'):
|
||||||
|
missing.append((j.get('name'), s))
|
||||||
|
print(f' MISSING: {s} (job: {j.get(\"name\",\"?\")})')
|
||||||
|
if not missing:
|
||||||
|
print(' (none missing)')
|
||||||
|
|
||||||
|
# 3. Find source locations for missing scripts
|
||||||
|
print()
|
||||||
|
print("=== FIND MISSING SCRIPTS IN MoFin ===")
|
||||||
|
for name, script in missing:
|
||||||
|
import subprocess
|
||||||
|
r = subprocess.run(['find', '/home/hmo/MoFin', '-name', script, '-not', '-path', '*/venv/*'],
|
||||||
|
capture_output=True, text=True, timeout=15)
|
||||||
|
found = [l for l in r.stdout.splitlines() if l.strip()]
|
||||||
|
print(f' {script}: {found if found else "NOT FOUND"}')
|
||||||
|
|
||||||
|
# 4. market.json generator
|
||||||
|
print()
|
||||||
|
print("=== market.json GENERATOR ===")
|
||||||
|
r = subprocess.run(['grep', '-rn', "'market.json'", '/home/hmo/MoFin/deploy/profile-scripts/', '/home/hmo/MoFin/scripts/'],
|
||||||
|
capture_output=True, text=True, timeout=10)
|
||||||
|
for l in r.stdout.splitlines():
|
||||||
|
if 'market.json' in l:
|
||||||
|
print(f' {l}')
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import sqlite3, uuid, time
|
||||||
|
|
||||||
|
db = sqlite3.connect('/home/hmo/.hermes/kanban.db')
|
||||||
|
print("=== statuses ===")
|
||||||
|
for r in db.execute("SELECT status, COUNT(*) FROM tasks GROUP BY status"):
|
||||||
|
print(r)
|
||||||
|
print()
|
||||||
|
# a pending example if any
|
||||||
|
r = db.execute("SELECT id, title, status, assignee FROM tasks WHERE status NOT IN ('done','cancelled') LIMIT 5").fetchall()
|
||||||
|
for x in r:
|
||||||
|
print(x)
|
||||||
|
|
||||||
|
# create the card for zhiwei
|
||||||
|
tid = 't_' + uuid.uuid4().hex[:8]
|
||||||
|
now = int(time.time())
|
||||||
|
title = "12维分析管道已系统化 — 每日自动刷新,请知悉并验证"
|
||||||
|
body = """笑笑(Sisyphus)完成系统级修复,知微不用再做任何手动补评。
|
||||||
|
|
||||||
|
背景:老爸发现盘前全量重评只更新了技术参数,12维LLM深度分析(full_analysis)为空/陈旧。
|
||||||
|
|
||||||
|
已落地的系统改动(已全部部署到246并提交git):
|
||||||
|
1. premarket_full_review.py 新增 Step 1.5:每交易日08:10自动跑 batch_reassess.py --type holding --today,14只持仓12维分析每日强制刷新
|
||||||
|
2. batch_reassess.py 升级:--type holding|watchlist|all 全覆盖;分析超20h视为过期自动重评;现金/总资产改为从 portfolio_summary 实时读取(不再硬编码);港股前缀修复(00700等5位代码)
|
||||||
|
3. 新增每日12:30 cron「自选12维分析补全-每日午间」(watchlist_12d_backfill.py),109只缺分析的自选股每日补全
|
||||||
|
4. 验证:300308 已生成1920字12维分析(信号=观望)并写入DB;当前正在后台跑14只持仓的全量补评(/tmp/holdings_12d_backfill.log)
|
||||||
|
|
||||||
|
需要知微做的:
|
||||||
|
- 验证今天开盘简报/盯盘里能正常引用最新12维分析
|
||||||
|
- 观察今日12:30自选补全任务是否正常触发
|
||||||
|
- 有问题在kanban回复或XMPP找笑笑"""
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO tasks (id, title, body, assignee, status, priority, created_by, created_at) VALUES (?,?,?,?,?,?,?,?)",
|
||||||
|
(tid, title, body, 'zhiwei', 'pending', 1, 'xxm', now))
|
||||||
|
db.commit()
|
||||||
|
print()
|
||||||
|
print('created:', tid)
|
||||||
|
for r in db.execute("SELECT id, title, status, assignee, created_by FROM tasks WHERE id=?", (tid,)):
|
||||||
|
print(r)
|
||||||
|
db.close()
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import sqlite3
|
||||||
|
db = sqlite3.connect('/home/hmo/.hermes/kanban.db')
|
||||||
|
db.execute("UPDATE tasks SET status='ready' WHERE id='t_2ff55641'")
|
||||||
|
db.commit()
|
||||||
|
for r in db.execute("SELECT id, status, assignee FROM tasks WHERE id='t_2ff55641'"):
|
||||||
|
print(r)
|
||||||
|
db.close()
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import sqlite3, uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
db_path = '/home/hmo/.hermes/kanban.db'
|
||||||
|
db = sqlite3.connect(db_path)
|
||||||
|
# inspect schema
|
||||||
|
schema = db.execute("SELECT sql FROM sqlite_master WHERE name='tasks'").fetchone()
|
||||||
|
print(schema[0] if schema else 'no tasks table')
|
||||||
|
print()
|
||||||
|
# recent rows to see id format and fields
|
||||||
|
for r in db.execute("SELECT id, title, status, assignee, created_by, created_at FROM tasks ORDER BY created_at DESC LIMIT 5"):
|
||||||
|
print(r)
|
||||||
|
db.close()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import json, urllib.request
|
||||||
|
|
||||||
|
msg = """[笑笑] 12维分析管道已系统化完成 ✅
|
||||||
|
|
||||||
|
你早上指出的缺口(盘前重评只有技术参数、12维LLM分析为空/陈旧)已从系统层面修复,不是手动缝补:
|
||||||
|
|
||||||
|
1️⃣ 盘前管道改造:每交易日 08:10 自动跑持仓12维分析(batch_reassess --type holding --today,每日强制刷新14只)
|
||||||
|
2️⃣ batch_reassess 升级:持仓/自选全覆盖、分析超20h自动重评、现金/总资产改从 portfolio_summary 实时读(之前硬编码的是几周前的旧值)、港股前缀修复
|
||||||
|
3️⃣ 自选补全:新增每日12:30 cron「自选12维分析补全」,109只缺分析的自选股每日自动补
|
||||||
|
4️⃣ 验证:300308 已生成1920字12维分析(观望)写入DB;此刻后台正在跑14只持仓全量补评
|
||||||
|
|
||||||
|
代码已提交并同步到246 repo(merge 4dcfee81)。
|
||||||
|
已通过 kanban 通知知微(t_2ff55641),让她验证今日开盘简报和12:30自选任务。
|
||||||
|
|
||||||
|
另外凌晨的自愈巡检还修了:promote候选崩溃、DB锁、符号链接阻断、candidate_filter等问题,今天开盘应该全部正常。"""
|
||||||
|
|
||||||
|
payload = json.dumps({"to": "hmo@yoin.fun", "body": msg, "type": "chat"}).encode()
|
||||||
|
req = urllib.request.Request("http://127.0.0.1:5805/", data=payload,
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
try:
|
||||||
|
resp = urllib.request.urlopen(req, timeout=10)
|
||||||
|
print("XMPP sent:", resp.read().decode()[:100])
|
||||||
|
except Exception as e:
|
||||||
|
print("XMPP fail:", e)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||||
|
for t, c in [('mtf_cache','updated_at'),('macro_context_log','created_at'),
|
||||||
|
('live_prices','updated_at'),('price_events','created_at'),('market_snapshots','created_at')]:
|
||||||
|
try:
|
||||||
|
row = conn.execute(f"SELECT MAX({c}) FROM {t}").fetchone()
|
||||||
|
print(f"{t}.{c} MAX = {row[0]!r}")
|
||||||
|
if row and row[0]:
|
||||||
|
try:
|
||||||
|
dt = datetime.fromisoformat(str(row[0]).replace('Z',''))
|
||||||
|
print(f" parsed OK: {dt}")
|
||||||
|
except Exception as pe:
|
||||||
|
print(f" PARSE FAIL: {pe}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{t}: QUERY ERROR {e}")
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import json
|
||||||
|
d = json.load(open('/home/hmo/web-dashboard/static/mofin_health.json'))
|
||||||
|
print('generated:', d['generated_at'])
|
||||||
|
print('db_freshness:')
|
||||||
|
for f in d.get('db_freshness', []):
|
||||||
|
print(f" {f['label']}({f['table']}): last={f['last_record']} age={f['age_hours']}h warn={f['warn']}")
|
||||||
|
print()
|
||||||
|
warned = [j['name'] for j in d['json_files'] if j.get('warn')]
|
||||||
|
print(f'json_files warned: {len(warned)}')
|
||||||
|
migrated = [j['name'] for j in d['json_files'] if j.get('migrated_to_db')]
|
||||||
|
print(f'migrated (no longer warned): {migrated}')
|
||||||
Reference in New Issue
Block a user