refactor: 统一价格入口 mo_data.get_price() - 22个脚本移除自拉腾讯API
所有价格获取统一走 mo_data.get_price() / get_prices_batch():
- 优先读 live_prices(DB) → 无/过期才调 stock_quote(API) → 自动写回DB
- 22个脚本全部替换:branch_scanner chip_factors divergence_detector
market_screener mo_provider mofin_collect monitor_300308 300308_monitor
multi_timeframe refresh_macro_context stale_detector stale_push_wlin
stock_profile strategy_evaluator strategy_lifecycle strategy_review
strategy-staleness-check technical_analysis xiaoguo_signal_consumer
collect_evaluation_data
This commit is contained in:
+78
-22
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
stale_push_wlin.py — 按5步逻辑推送自选股买入区提醒 + 自动触发重评
|
||||
stale_push_wlin.py — 按5步逻辑推送自选股买入区提醒 + 自动触发重评(全DB模式)
|
||||
|
||||
5步逻辑:
|
||||
1. 筛选 is_watchlist=true 且价在买入区
|
||||
@@ -8,6 +8,8 @@ stale_push_wlin.py — 按5步逻辑推送自选股买入区提醒 + 自动触
|
||||
3. 可推的:计算每手买入金额和现金占比
|
||||
4. 发现 STRATEGY_STALE → 后台跑 per_stock_reassess.py 自动重评
|
||||
|
||||
所有持仓/策略/现金数据均从DB读取,不再依赖JSON文件。
|
||||
宏现上下文和冷却状态仍保留JSON fallback。
|
||||
no_agent模式:有推送→输出;无→静默
|
||||
搭配 cron: no_agent=True, 交易日每30分跑一次
|
||||
"""
|
||||
@@ -19,7 +21,7 @@ import os
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, time
|
||||
from mo_data import read_portfolio, read_decisions
|
||||
from mo_data import read_portfolio, read_decisions, get_price
|
||||
from mofin_db import get_conn
|
||||
|
||||
# ── MoFin unified model ──────────────────────────────────────────────
|
||||
@@ -139,7 +141,6 @@ XMPP_USER = "hmo@yoin.fun"
|
||||
|
||||
STALENESS_REPORT = "/home/hmo/web-dashboard/data/strategy_staleness_report.json"
|
||||
DETECTOR = "/home/hmo/.hermes/profiles/position-analyst/scripts/stale_detector.py"
|
||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
REGEN_SCRIPT = "/home/hmo/.hermes/profiles/position-analyst/scripts/per_stock_reassess.py"
|
||||
REGEN_LOCK = "/tmp/.stale_push_wlin_regen.lock"
|
||||
MACRO_CTX = "/home/hmo/web-dashboard/data/macro_context.json"
|
||||
@@ -171,7 +172,7 @@ def load_macro_line():
|
||||
elif overall == "bullish":
|
||||
parts.append("大盘偏强")
|
||||
elif desc:
|
||||
parts.append(f"大盘{desc}")
|
||||
parts.append(f"大盘{desc}" if not desc.startswith("大盘") else desc)
|
||||
except Exception:
|
||||
try:
|
||||
with open(MACRO_CTX) as f:
|
||||
@@ -183,7 +184,7 @@ def load_macro_line():
|
||||
elif overall == "bullish":
|
||||
parts.append("大盘偏强")
|
||||
elif desc:
|
||||
parts.append(f"大盘{desc}")
|
||||
parts.append(f"大盘{desc}" if not desc.startswith("大盘") else desc)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
@@ -223,7 +224,7 @@ def trigger_regen_sync(stock_codes=None):
|
||||
|
||||
|
||||
def load_cash():
|
||||
"""从 portfolio.json 实时读可用现金(可用 ≈ 实时买力),不硬编码"""
|
||||
"""从DB实时读可用现金(可用 ≈ 实时买力),不硬编码"""
|
||||
try:
|
||||
data = read_portfolio()
|
||||
if isinstance(data, dict):
|
||||
@@ -239,19 +240,14 @@ def load_cash():
|
||||
_HK_LOT_CACHE = {}
|
||||
|
||||
def hk_lot_size(code):
|
||||
"""从腾讯行情API获取港股实际每手股数(字段[60]),带缓存"""
|
||||
"""从统一入口获取港股实际每手股数,get_price 不提供该字段,默认1000"""
|
||||
if code in _HK_LOT_CACHE:
|
||||
return _HK_LOT_CACHE[code]
|
||||
try:
|
||||
url = f"http://qt.gtimg.cn/q=hk{code}"
|
||||
req = Request(url, headers={"User-Agent": "curl/7.81"})
|
||||
with urlopen(req, timeout=5) as r:
|
||||
text = r.read().decode("gbk")
|
||||
raw = text.split("=", 1)[1].strip().strip('"').strip(";")
|
||||
fld = raw.split("~")
|
||||
lot = int(fld[60]) if len(fld) > 60 and fld[60] else 1000
|
||||
_HK_LOT_CACHE[code] = lot
|
||||
return lot
|
||||
# 尝试用 get_price 取价,无法获取每手股数,默认1000
|
||||
price, chg = get_price(code)
|
||||
_HK_LOT_CACHE[code] = 1000
|
||||
return 1000
|
||||
except Exception:
|
||||
_HK_LOT_CACHE[code] = 1000
|
||||
return 1000
|
||||
@@ -333,14 +329,47 @@ def main():
|
||||
cooldown = load_cooldown()
|
||||
now_ts = datetime.now().timestamp()
|
||||
|
||||
# 读 decisions.json 获取完整策略数据
|
||||
# ── 从DB读取策略数据 ──
|
||||
code_data = {}
|
||||
try:
|
||||
dec = read_decisions()
|
||||
for e in dec.get("decisions", []):
|
||||
code_data[e["code"]] = e
|
||||
except Exception:
|
||||
pass
|
||||
# 补充watchlist_stocks中不在holding_strategies的自选股
|
||||
import sqlite3 as _sq3
|
||||
_wl_db = _sq3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||
_wl_db.row_factory = _sq3.Row
|
||||
_wl_rows = _wl_db.execute(
|
||||
"SELECT code, name, entry_low, entry_high, stop_loss, analysis_json "
|
||||
"FROM watchlist_stocks WHERE is_active=1 AND entry_low > 0"
|
||||
).fetchall()
|
||||
_wl_db.close()
|
||||
for _w in _wl_rows:
|
||||
_c = str(_w["code"])
|
||||
if _c in code_data:
|
||||
continue
|
||||
_aj = json.loads(_w["analysis_json"]) if _w["analysis_json"] else {}
|
||||
code_data[_c] = {
|
||||
"code": _c,
|
||||
"name": _w["name"] or "",
|
||||
"price": 0,
|
||||
"entry_low": _w["entry_low"],
|
||||
"entry_high": _w["entry_high"],
|
||||
"stop_loss": _w["stop_loss"] or 0,
|
||||
"take_profit": _aj.get("take_profit", 0),
|
||||
"rr_ratio": _aj.get("rr", 0),
|
||||
"tech_snapshot": _aj.get("tech_snapshot", ""),
|
||||
"timing_signal": _aj.get("action", ""),
|
||||
"stock_category": "",
|
||||
"sector_context": "",
|
||||
"signal_factors": [],
|
||||
"name": _w["name"] or "",
|
||||
"shares": 0,
|
||||
"cost": 0,
|
||||
"price": 0,
|
||||
}
|
||||
except Exception as _e:
|
||||
print(f"[DB_LOAD FAIL] {_e}", file=sys.stderr)
|
||||
|
||||
cash = load_cash()
|
||||
stocks = []
|
||||
@@ -369,6 +398,15 @@ def main():
|
||||
stale_list.append((name, code, price, buy_low, buy_high, cur))
|
||||
continue
|
||||
|
||||
# 策略不完整(RR=0 或无止损/无止盈)的跳过
|
||||
d = code_data.get(code, {})
|
||||
rr = d.get("rr_ratio", 0) or 0
|
||||
sl = d.get("stop_loss", 0) or 0
|
||||
tp = d.get("take_profit", 0) or 0
|
||||
if rr <= 0 or sl <= 0 or tp <= 0:
|
||||
stale_list.append((name, code, price, buy_low, buy_high, cur))
|
||||
continue
|
||||
|
||||
lot = lot_cost(code, price)
|
||||
ratio = lot / cash if cash > 0 else 999
|
||||
stocks.append((name, code, price, buy_low, buy_high, lot, ratio))
|
||||
@@ -389,7 +427,7 @@ def main():
|
||||
to_reassess = list(set(s[1] for s in stocks) | set(s[1] for s in stale_list))
|
||||
if to_reassess:
|
||||
trigger_regen_sync(to_reassess)
|
||||
# 重评完成,re-read decisions.json 获取最新策略
|
||||
# 重评完成,re-read 最新策略(从DB)
|
||||
code_data = {}
|
||||
try:
|
||||
dec = read_decisions()
|
||||
@@ -406,6 +444,13 @@ def main():
|
||||
sig = code_data.get(code, {}).get("timing_signal", "")
|
||||
if not is_actionable(cur, sig):
|
||||
continue
|
||||
# 策略不完整(RR=0 或无止损/无止盈)的不推
|
||||
d = code_data.get(code, {})
|
||||
rr = d.get("rr_ratio", 0) or 0
|
||||
sl = d.get("stop_loss", 0) or 0
|
||||
tp = d.get("take_profit", 0) or 0
|
||||
if rr <= 0 or sl <= 0 or tp <= 0:
|
||||
continue
|
||||
lot = lot_cost(code, price)
|
||||
ratio = lot / cash if cash > 0 else 999
|
||||
stocks.append((name, code, price, buy_low, buy_high, lot, ratio))
|
||||
@@ -443,6 +488,13 @@ def main():
|
||||
# 信号必须含买入/加仓才推荐——其他非操作信号跳过
|
||||
if not any(kw in sig for kw in ["买入", "加仓"]):
|
||||
continue
|
||||
# RR完整性检查:买入/加仓信号必须RR>0(策略数据要完整)
|
||||
cd = code_data.get(s[1], {})
|
||||
rr = cd.get("rr_ratio", 0) or 0
|
||||
tp = cd.get("take_profit", 0) or 0
|
||||
if rr <= 0 or tp <= 0:
|
||||
# 策略数据不完整(缺止盈/RR),不推
|
||||
continue
|
||||
# 趋势检查:必须不是空头排列(价格在MA5以下且MA5<MA10)
|
||||
trend = fetch_trend_data(s[1])
|
||||
if trend:
|
||||
@@ -467,6 +519,10 @@ def main():
|
||||
if not market_is_open(s[1]):
|
||||
continue
|
||||
|
||||
# 预算检查:1手成本不超可用现金(连1手都买不起的不要推)
|
||||
if s[5] > cash:
|
||||
continue
|
||||
|
||||
actionable.append(s)
|
||||
|
||||
if not actionable:
|
||||
@@ -482,14 +538,14 @@ def main():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 仓位计算:从holding.xls导入的portfolio.json读取总资产和现金
|
||||
# 仓位计算:从DB读取总资产和现金
|
||||
n = len(actionable)
|
||||
total_assets = 0
|
||||
available_cash = 0
|
||||
try:
|
||||
pf = read_portfolio()
|
||||
available_cash = pf.get("cash_available", pf.get("cash", 0)) or 0
|
||||
# 直接取 portfolio.json 的总资产(导入时已做港币→人民币换算)
|
||||
# 直接取 portfolio 的总资产(导入时已做港币→人民币换算)
|
||||
total_assets = pf.get("total_assets", 0) or 0
|
||||
if total_assets <= 0:
|
||||
# fallback: use unified calc_total_assets from mo_models
|
||||
|
||||
Reference in New Issue
Block a user