Files
MoFin/mo_data.py
T
知微 04b8a6d4bc mo_data: +write_cash_log 函数
cash_log表已存在,新增写入函数便于截图/交易变更时记录现金流水。
用于process_trade.py、手动改cash时调用,保证现金变更可追溯。

知识萃取-盘后 cron 已暂停(莫荷接手知识库报告)
2026-07-02 01:43:33 +08:00

231 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
mo_data.py — MoFin 统一数据读取层
替代 json.load(open(portfolio.json)) 等直接读 JSON 文件的方式。
所有数据从 DB 读取,币种字段强制存在。
用法:
from mo_data import read_portfolio, read_decisions, read_watchlist
pf = read_portfolio() # 返回和 portfolio.json 一样的 dict 结构
dec = read_decisions() # 返回和 decisions.json 一样的 dict 结构
wl = read_watchlist() # 返回和 watchlist.json 一样的 dict 结构
"""
import sqlite3, json, os
from datetime import datetime
DB_PATH = '/home/hmo/web-dashboard/data/mofin.db'
# JSON 文件路径(冷备,仅当 DB 不可用时 fallback
PORTFOLIO_JSON = '/home/hmo/web-dashboard/data/portfolio.json'
DECISIONS_JSON = '/home/hmo/web-dashboard/data/decisions.json'
WATCHLIST_JSON = '/home/hmo/web-dashboard/data/watchlist.json'
def _get_db():
db = sqlite3.connect(DB_PATH)
db.row_factory = sqlite3.Row
return db
# ── portfolio.json → holdings + portfolio_summary ─────────────────
def read_portfolio():
"""返回 portfolio.json 等价 dict。DB 优先,JSON 冷备。"""
try:
db = _get_db()
# holdings
rows = db.execute(
"SELECT code, name, shares, cost, price, market_value, "
"change_pct, currency, position_pct "
"FROM holdings WHERE is_active=1"
).fetchall()
holdings = []
for r in rows:
h = dict(r)
h['_currency'] = h.get('currency', 'CNY') # 兼容旧代码的 _currency 字段名
holdings.append(h)
# summary
sum_row = db.execute("SELECT * FROM portfolio_summary WHERE id=1").fetchone()
summary = dict(sum_row) if sum_row else {}
db.close()
return {
"holdings": holdings,
"total_assets": summary.get("total_assets", 0),
"total_mv": summary.get("total_mv", 0),
"stock_value": summary.get("stock_value", summary.get("total_mv", 0)),
"cash": summary.get("cash", 0),
"frozen_cash": summary.get("frozen_cash", 0),
"position_pct": summary.get("position_pct", 0),
"currency": summary.get("currency", "CNY"),
"updated_at": summary.get("updated_at", ""),
}
except Exception:
pass
# JSON 冷备
try:
return json.load(open(PORTFOLIO_JSON, encoding='utf-8'))
except:
return {"holdings": [], "total_assets": 0, "cash": 0, "frozen_cash": 0}
# ── decisions.json → holding_strategies ────────────────────────────
def read_decisions():
"""返回 decisions.json 等价 dict。DB 优先,JSON 冷备。"""
try:
db = _get_db()
rows = db.execute(
"SELECT code, name, version, price, cost, shares, "
"stop_loss, take_profit, entry_low, entry_high, "
"currency, strategy_type, action, timing_signal, "
"rr_ratio, tech_snapshot, stock_category, sector_context, "
"status, trigger_json, changelog_json, source, reason, "
"created_at, updated_at "
"FROM holding_strategies WHERE status IN ('active','updated') "
"ORDER BY code"
).fetchall()
decisions = []
for r in rows:
d = dict(r)
# 还原 trigger 字段
if d.get('trigger_json'):
try:
d['trigger'] = json.loads(d['trigger_json'])
except:
d['trigger'] = {}
else:
d['trigger'] = {}
# 还原 changelog 字段
if d.get('changelog_json'):
try:
d['changelog'] = json.loads(d['changelog_json'])
except:
d['changelog'] = []
else:
d['changelog'] = []
decisions.append(d)
db.close()
return {
"decisions": decisions,
"total": len(decisions),
"regenerated_at": datetime.now().strftime('%Y-%m-%d %H:%M'),
}
except Exception:
pass
try:
return json.load(open(DECISIONS_JSON, encoding='utf-8'))
except:
return {"decisions": [], "total": 0}
# ── watchlist.json → watchlist_stocks ──────────────────────────────
def read_watchlist():
"""返回 watchlist.json 等价 dict。DB 优先,JSON 冷备。"""
try:
db = _get_db()
rows = db.execute(
"SELECT code, name, price, entry_low, entry_high, "
"stop_loss, currency, source, source_detail, notes, "
"added_by, added_at, analysis_json "
"FROM watchlist_stocks WHERE is_active=1"
).fetchall()
stocks = []
for r in rows:
s = dict(r)
if s.get('source_detail'):
try: s['source_detail'] = json.loads(s['source_detail'])
except: pass
if s.get('analysis_json'):
try: s['analysis'] = json.loads(s['analysis_json'])
except: s['analysis'] = {}
else:
s['analysis'] = {}
stocks.append(s)
db.close()
return {
"stocks": stocks,
"updated_at": datetime.now().strftime('%Y-%m-%d %H:%M'),
}
except Exception:
pass
try:
return json.load(open(WATCHLIST_JSON, encoding='utf-8'))
except:
return {"stocks": []}
# ── 便捷函数 ────────────────────────────────────────────────────────
def read_portfolio_json():
"""别名(兼容旧代码直接 import 后调用)"""
return read_portfolio()
def read_decisions_json():
"""别名"""
return read_decisions()
def read_watchlist_json():
"""别名"""
return read_watchlist()
# ── cash_log 写入 ─────────────────────────────────────────────────────
def write_cash_log(cash_before, cash_after, frozen_before, frozen_after,
source, note, verified=0):
"""记录现金变更到 cash_log 表。
参数:
cash_before/after — 变更前后可用现金
frozen_before/after — 变更前后冻结资金
source — 'screenshot'/'manual'/'trade'/'import_xls'
note — 备注(如"卖出法拉电子200股"
verified — 0=未验证 1=Dad已确认
返回:
log_id (int)
"""
change_amount = round(cash_after - cash_before, 2) if cash_after is not None and cash_before is not None else 0
db = sqlite3.connect(DB_PATH)
try:
cur = db.execute(
"""INSERT INTO cash_log
(timestamp, cash_before, cash_after, frozen_before, frozen_after,
change_amount, source, note, verified)
VALUES (datetime('now','localtime'), ?, ?, ?, ?, ?, ?, ?, ?)""",
(cash_before, cash_after, frozen_before, frozen_after,
change_amount, source, note, verified)
)
db.commit()
return cur.lastrowid
finally:
db.close()
# ── 自检 ────────────────────────────────────────────────────────────
if __name__ == "__main__":
pf = read_portfolio()
print(f"portfolio: {len(pf.get('holdings',[]))} holdings, total_assets={pf.get('total_assets',0)}")
dec = read_decisions()
print(f"decisions: {len(dec.get('decisions',[]))} entries")
wl = read_watchlist()
print(f"watchlist: {len(wl.get('stocks',[]))} stocks")