refactor: retire price_events.json completely — DB is the only store
User directive: no JSON, retire it fully, fix all related code. Changes: - price_monitor.py: record_event writes DB only; removed EVENTS_PATH/ load_events/save_events entirely - strategy_feedback.py: price events read from DB only (removed JSON fallback) - system_health_check.py: removed price_events.json from file-check list, DB-only event stats (was showing 0/0 due to wrong-DB resolution) - mo_config.py: removed dead price_events_path property (no callers) - mofin_health.py: price_events freshness reads DB table (authoritative now) - mofin_db.py: DATA_DIR/DB_PATH now ABSOLUTE (/home/hmo/MoFin/data) — was relative __file__.parent, so each hardlinked copy of mofin_db.py resolved to a DIFFERENT database (canonical vs web-dashboard vs profile-local third DB with 0 rows of everything except market_snapshots). This fragmentation was the real cause of health checks reading empty tables. - Unified all 4 mofin_db copies (root/scripts/deploy/profile) via hardlink - price_events.json archived to trashbox (fully backfilled: 6353 rows in DB) Verified: - record_event lands in DB only, JSON not recreated - system_health_check: 历史事件 6353 / 今日事件 2965 (was 0/0) - strategy_feedback + price_monitor full runs clean
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import sqlite3
|
||||
|
||||
for label, path in [('canonical', '/home/hmo/MoFin/data/mofin.db'),
|
||||
('profile-local', '/home/hmo/.hermes/profiles/position-analyst/scripts/data/mofin.db'),
|
||||
('web-dashboard', '/home/hmo/web-dashboard/data/mofin.db')]:
|
||||
try:
|
||||
c = sqlite3.connect(path, timeout=5)
|
||||
tables = [r[0] for r in c.execute("SELECT name FROM sqlite_master WHERE type='table'")]
|
||||
print(f'=== {label}: {path}')
|
||||
for t in ['price_events', 'holdings', 'live_prices', 'holding_strategies', 'market_snapshots']:
|
||||
if t in tables:
|
||||
cnt = c.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
|
||||
try:
|
||||
col = 'created_at' if t in ('price_events', 'market_snapshots') else 'updated_at'
|
||||
last = c.execute(f"SELECT MAX({col}) FROM {t}").fetchone()[0]
|
||||
except Exception:
|
||||
last = '?'
|
||||
print(f' {t}: {cnt} rows, last={last}')
|
||||
else:
|
||||
print(f' {t}: MISSING')
|
||||
c.close()
|
||||
except Exception as e:
|
||||
print(f'{label}: ERROR {e}')
|
||||
print()
|
||||
@@ -65,11 +65,6 @@ class MoConfig:
|
||||
warnings.warn("watchlist_path is deprecated — use mo_data.read_watchlist() for DB data", DeprecationWarning, stacklevel=2)
|
||||
return Path()
|
||||
|
||||
@property
|
||||
def price_events_path(self) -> Path:
|
||||
"""⚠️ 已废弃!数据在 mofin.db price_events 表。"""
|
||||
return self.data_dir / "price_events.json"
|
||||
|
||||
@property
|
||||
def live_prices_path(self) -> Path:
|
||||
"""⚠️ DEPRECATED: 实时价格已迁移到 mofin_db.live_prices 表。"""
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, Callable
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
DATA_DIR = Path("/home/hmo/MoFin/data") # 绝对路径:全系统唯一权威数据目录
|
||||
DB_PATH = DATA_DIR / "mofin.db"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import json, urllib.request
|
||||
|
||||
msg = """[笑笑] 补充:price_events 已统一到 DB
|
||||
|
||||
你问得对——"不是都全 DB 了吗",之前确实不一致:
|
||||
|
||||
【查出的问题】
|
||||
- price_monitor 的 record_event() 只写 price_events.json,从没调过 mofin_db 的写入函数
|
||||
- 更隐蔽的是:price_events.code 有外键 → stocks(code),未注册的股票(新候选、港股)写 DB 时外键失败被静默吞掉,这就是 7/6 之后 DB 表再也没新事件的真正原因
|
||||
|
||||
【已修复】
|
||||
1. record_event 改为双写:DB(权威)+ JSON(兼容 mo_config/strategy_feedback 等遗留读取方)
|
||||
2. 写事件前自动把未注册股票登记进 stocks 表,外键不再失败
|
||||
3. 一次性回填:JSON 里 4064 条历史事件全部灌入 DB(现共 6353 行,最新到今天 17:06)
|
||||
4. 实测:新事件 DB/JSON 双落盘 ✅
|
||||
|
||||
以后价格事件的权威存储就是 DB 表,健康监控的 db_freshness 也是读它了。"""
|
||||
|
||||
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:
|
||||
print("XMPP:", urllib.request.urlopen(req, timeout=10).read().decode()[:80])
|
||||
except Exception as e:
|
||||
print("XMPP fail:", e)
|
||||
+21
-31
@@ -12,7 +12,6 @@ from mo_data import read_decisions
|
||||
|
||||
BREACH_PATH = "/home/hmo/.hermes/zone_breach.json"
|
||||
STATE_PATH = "/home/hmo/.hermes/price_trigger_state.json"
|
||||
EVENTS_PATH = "/home/hmo/web-dashboard/data/price_events.json"
|
||||
|
||||
# DB 模块(同步实时价到 mofin.db)
|
||||
sys.path.insert(0, "/home/hmo/MoFin")
|
||||
@@ -319,37 +318,28 @@ def save_breaches(data):
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def load_events():
|
||||
try:
|
||||
with open(EVENTS_PATH) as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return {"events": []}
|
||||
|
||||
|
||||
def save_events(events):
|
||||
os.makedirs(os.path.dirname(EVENTS_PATH), exist_ok=True)
|
||||
with open(EVENTS_PATH, 'w') as f:
|
||||
json.dump(events, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def record_event(code, name, event_type, price, trigger_value, event_label=""):
|
||||
"""记录一次价格触发事件到 price_events.json"""
|
||||
events = load_events()
|
||||
"""记录一次价格触发事件到 DB price_events 表(唯一权威存储,JSON 已退役)。
|
||||
|
||||
price_events.code 有 FK -> stocks(code),未注册的股票(新候选/港股)
|
||||
先注册再写事件,否则 FK 失败事件丢失。
|
||||
"""
|
||||
now = datetime.now().isoformat()
|
||||
events["events"].append({
|
||||
"code": code,
|
||||
"name": name,
|
||||
"event_type": event_type, # entry_zone, stop_loss, take_profit, exit_zone
|
||||
"price": round(price, 2),
|
||||
"trigger_value": trigger_value,
|
||||
"event_label": event_label,
|
||||
"timestamp": now,
|
||||
"date": datetime.now().strftime("%Y-%m-%d"),
|
||||
})
|
||||
# 保留最近10000条
|
||||
events["events"] = events["events"][-10000:]
|
||||
save_events(events)
|
||||
|
||||
if HAS_DB:
|
||||
try:
|
||||
from mofin_db import get_conn, write_price_event
|
||||
_c = get_conn()
|
||||
_exch, _typ = ("HK", "H") if len(str(code)) == 5 else (("SH", "A") if str(code).startswith(("6", "9")) else ("SZ", "A"))
|
||||
_c.execute("INSERT OR IGNORE INTO stocks (code, name, exchange, type, updated_at) VALUES (?,?,?,?,?)",
|
||||
(str(code), name or str(code), _exch, _typ, now))
|
||||
_c.commit()
|
||||
write_price_event(_c, code=code, name=name, event_type=event_type,
|
||||
price=round(price, 2), trigger_value=str(trigger_value),
|
||||
event_label=event_label)
|
||||
_c.close()
|
||||
except Exception as e:
|
||||
print(f"[price_events DB写入失败] {e}", file=sys.stderr)
|
||||
|
||||
|
||||
def get_trigger_zones(trigger):
|
||||
@@ -581,7 +571,7 @@ def run_once(round_label=""):
|
||||
# === 第三步:买入区偏离检测 + 自动重评 ===
|
||||
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:
|
||||
code = d["code"]
|
||||
# 非持仓跳过
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import ast, sys
|
||||
files = ['price_monitor', 'strategy_feedback', 'system_health_check', 'mo_config', 'mofin_health']
|
||||
ok = True
|
||||
for f in files:
|
||||
p = f'/home/hmo/MoFin/deploy/profile-scripts/{f}.py'
|
||||
try:
|
||||
ast.parse(open(p).read())
|
||||
print('OK', f)
|
||||
except SyntaxError as e:
|
||||
print('FAIL', f, e)
|
||||
ok = False
|
||||
sys.exit(0 if ok else 1)
|
||||
@@ -0,0 +1,16 @@
|
||||
import sys, os
|
||||
sys.path.insert(0, '/home/hmo/.hermes/profiles/position-analyst/scripts')
|
||||
import price_monitor as pm
|
||||
pm.record_event('000850', '华茂股份', 'entry_zone', 3.98, '3.91~4.04', '加仓区间')
|
||||
print('record_event done')
|
||||
|
||||
import sqlite3
|
||||
c = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||
r = c.execute("SELECT code,name,price,created_at FROM price_events WHERE code='000850' ORDER BY id DESC LIMIT 1").fetchone()
|
||||
print('DB:', r)
|
||||
c.execute("DELETE FROM price_events WHERE code='000850' AND created_at > '2026-07-20 17:50'")
|
||||
c.commit()
|
||||
print('cleaned test row')
|
||||
|
||||
# JSON must NOT be recreated
|
||||
print('JSON exists?', os.path.exists('/home/hmo/web-dashboard/data/price_events.json'))
|
||||
Reference in New Issue
Block a user