1. stale_detector新增自选股买入区偏离自动重评: - 每轮扫描watchlist_stocks, price偏离买入区中心>15%自动触发per_stock_reassess - 之前只标记[STRATEGY_STALE]不输出,改为标记+触发重评两步完成 - 策略完毕直接输出结果,不再等下次cron通知 2. 000850华茂股份全面重评: - 核心价值:纺织是壳,金融股权投资才是核心(国泰海通/广发/徽商银行) - PB=0.78破净, 7月3日分红3675万占年净利17.85% - 7/16临时股东会催化剂 - 结论:3.70~3.90区间可建仓1~2%,止损3.50,止盈4.30 - 修复:之前说'观望不建仓'是错的,低估了破净安全垫 3. watchlist_stocks DB加000850记录
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""将 decisions.json 全量同步到 SQLite holding_strategies 表"""
|
|
import json, sqlite3, sys
|
|
|
|
DECISIONS_PATH = '/home/hmo/web-dashboard/data/decisions.json'
|
|
DB_PATH = '/home/hmo/web-dashboard/data/mofin.db'
|
|
|
|
def main():
|
|
# 读 decisions.json
|
|
with open(DECISIONS_PATH) as f:
|
|
data = json.load(f)
|
|
entries = data.get('decisions', [])
|
|
print(f'Read {len(entries)} entries from decisions.json')
|
|
|
|
db = sqlite3.connect(DB_PATH)
|
|
|
|
# 先清空 holding_strategies(全量重建更干净)
|
|
db.execute('DELETE FROM holding_strategies')
|
|
|
|
inserted = 0
|
|
for d in entries:
|
|
code = d.get('code')
|
|
if not code:
|
|
continue
|
|
|
|
# 从 decisions.json 提取字段,映射到 DB schema
|
|
sql = '''INSERT INTO holding_strategies (
|
|
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,
|
|
avg_price, decision_timestamp, note, decision_type
|
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'''
|
|
|
|
# 确定 type/strategy_type
|
|
stype = d.get('strategy_type') or d.get('type') or '持仓策略'
|
|
# decision_type = d.get('decision_type') or stype
|
|
decision_type = stype
|
|
|
|
vals = (
|
|
code,
|
|
d.get('name', ''),
|
|
d.get('version', 1),
|
|
d.get('price'),
|
|
d.get('cost'),
|
|
d.get('shares', 0),
|
|
d.get('stop_loss'),
|
|
d.get('take_profit'),
|
|
d.get('entry_low'),
|
|
d.get('entry_high'),
|
|
d.get('currency', 'CNY' if code.startswith(('6','0','3','5')) else 'HKD'),
|
|
stype,
|
|
d.get('action', ''),
|
|
d.get('timing_signal', ''),
|
|
d.get('rr_ratio'),
|
|
d.get('tech_snapshot'),
|
|
d.get('stock_category'),
|
|
d.get('sector_context'),
|
|
d.get('status', 'active'),
|
|
json.dumps(d.get('trigger', {}), ensure_ascii=False) if d.get('trigger') else d.get('trigger_json'),
|
|
json.dumps(d.get('changelog', []), ensure_ascii=False) if d.get('changelog') else d.get('changelog_json'),
|
|
d.get('source', 'auto'),
|
|
d.get('reason'),
|
|
d.get('created_at'),
|
|
d.get('updated_at'),
|
|
d.get('avg_price'),
|
|
d.get('decision_timestamp') or d.get('timestamp'),
|
|
d.get('note'),
|
|
decision_type,
|
|
)
|
|
try:
|
|
db.execute(sql, vals)
|
|
inserted += 1
|
|
except Exception as e:
|
|
print(f'Error inserting {code} ({d.get("name")}): {e}')
|
|
|
|
db.commit()
|
|
|
|
# 验证
|
|
cnt = db.execute('SELECT COUNT(*) FROM holding_strategies').fetchone()[0]
|
|
active = db.execute('SELECT COUNT(*) FROM holding_strategies WHERE status IN ("active","updated")').fetchone()[0]
|
|
db.close()
|
|
|
|
print(f'Synced: {inserted} rows inserted')
|
|
print(f'holding_strategies: {cnt} total, {active} active/updated')
|
|
|
|
return 0 if inserted > 0 else 1
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|