自成长系统:四层循环架构文档 + 三个代码改动 + 所有日间修复

内容:
- docs/SELF_GROWTH_SYSTEM.md (NEW) — 完整的 Sense→Respond→Adapt→Improve 架构文档
- docs/SYSTEM_ARCHITECTURE.md (UPDATED) — 总索引指向新文档,cron数从14更新为31
- hk_rate.py (NEW) — HKD汇率模块,缓存+上次有效汇率自动恢复
- price_monitor.py (MODIFIED) — 价格监控注入分支评估+情景切换检测
- strategy_lifecycle.py (MODIFIED) — 策略生命周期评估上下文
- strategy_tree.py (NEW) — 情景化多分支决策引擎

日间修复(2026-06-23):
- stale_push_wlin: cash硬编码146837→读portfolio.json
- stale_push_wlin: lot_cost汇率0.93→hkd_to_cny动态
- stale_push_wlin: HK每手默认500股→Tencent API实时f[60]
- stale_push_wlin: 重评异步→串行(先重评再出报告)
- hk_rate: FALLBACK=0.87硬编码→缓存上次有效汇率
- 新增 cron: 分支扫描每30分, 分支剪枝周六, 硬编码审计17:25
- hardcode_scanner.py 每日扫描所有.py中大额数字
This commit is contained in:
知微
2026-06-24 00:04:26 +08:00
parent 44eef95718
commit e33a236bc1
6 changed files with 1149 additions and 19 deletions
+110 -11
View File
@@ -25,6 +25,26 @@ try:
except ImportError:
HAS_REASSESS = False
try:
from hk_rate import hkd_to_cny
HK_RATE = hkd_to_cny()
except Exception:
HK_RATE = 0.8700 # fallback
# 分支系统与情景检测
try:
sys.path.insert(0, '/home/hmo/MoFin')
from strategy_tree import detect_scenario, evaluate_branches
HAS_TREE = True
except Exception:
HAS_TREE = False
def detect_scenario(): return {}
def evaluate_branches(*a, **kw): return []
# 情景缓存(每次run_once刷新)
_SCENARIO_CACHE = {}
_BRANCH_CACHE = {} # code -> branches list
UA = "Mozilla/5.0"
# ── 批量拉取价格 ──────────────────────────────────────────────────────────
@@ -121,6 +141,9 @@ def refresh_data_prices():
if s['code'] in prices:
price, _, change_pct = prices[s['code']]
if price > 0:
# 港股:API返回HKD,需转RMB2026-06-23 bugfix
if str(s['code']).startswith(('0','1')) and len(str(s['code']))==5:
price = round(price * HK_RATE, 2)
old = s.get('price', 0)
if abs(old - price) > 0.001:
s['price'] = round(price, 2)
@@ -136,6 +159,9 @@ def refresh_data_prices():
if s['code'] in prices:
price, _, change_pct = prices[s['code']]
if price > 0:
# 港股:API返回HKD,需转RMB2026-06-23 bugfix
if str(s['code']).startswith(('0','1')) and len(str(s['code']))==5:
price = round(price * HK_RATE, 2)
old = s.get('price', 0)
if abs(old - price) > 0.001:
s['price'] = round(price, 2)
@@ -149,6 +175,43 @@ def refresh_data_prices():
return updated
# ── 分支系统辅助函数 ──────────────────────────────────────────────────────
def _branch_alert_suffix(code, price, shares=0, cost=0):
"""返回分支信息后缀:「 | 情景→动作」"""
if not HAS_TREE or not _SCENARIO_CACHE.get('id'):
return ""
try:
sc_id = _SCENARIO_CACHE['id']
results = evaluate_branches(code, sc_id, price, shares, cost)
for r in results:
if r.get('applicable'):
_record_branch_trigger(code, r.get('branch_id',''), price)
branch_action = r.get('action_type', r.get('action', 'hold'))
return f" | {sc_id}{branch_action}"
except Exception:
pass
return ""
def _record_branch_trigger(code, branch_id, price):
"""记录分支触发事件(自成长:trigger_count+1"""
try:
raw = json.load(open(DECISIONS_PATH))
for d in raw.get('decisions', []):
if d.get('code') == code and d.get('strategy_tree',{}).get('branches'):
for b in d['strategy_tree']['branches']:
if b['id'] == branch_id:
b.setdefault('trigger_count', 0)
b['trigger_count'] += 1
b['last_trigger_price'] = round(price, 2)
b['last_triggered'] = datetime.now().isoformat()
break
json.dump(raw, open(DECISIONS_PATH, 'w'), ensure_ascii=False, indent=2)
except Exception:
pass
# ── 区间偏离检测 ──────────────────────────────────────────────────────────
def load_state():
@@ -254,9 +317,22 @@ def get_trigger_zones(trigger):
def run_once(round_label=""):
"""执行一轮完整的监控流程"""
global _SCENARIO_CACHE, _BRANCH_CACHE
label = f" [{round_label}]" if round_label else ""
start = time.time()
# 刷新情景与分支缓存(每轮更新)
_SCENARIO_CACHE = detect_scenario() if HAS_TREE else {}
_BRANCH_CACHE = {}
try:
raw = json.load(open(DECISIONS_PATH))
for d in raw.get('decisions', []):
tree = d.get('strategy_tree', {})
if tree and tree.get('branches'):
_BRANCH_CACHE[d['code']] = tree['branches']
except Exception:
pass
# === 第一步:一次性刷新所有价格 ===
refreshed = refresh_data_prices()
@@ -310,7 +386,8 @@ def run_once(round_label=""):
if in_zone and prev_in_zone != True:
if key == "stop_loss":
outputs.append(f"⚠️ {name}({code}) {price} → 跌破止损{hi}")
branch_sfx = _branch_alert_suffix(code, price, d.get('shares',0), d.get('cost',0))
outputs.append(f"⚠️ {name}({code}) {price} → 跌破止损{hi}{branch_sfx}")
record_event(code, name, "stop_loss", price, str(hi))
else:
extra = ""
@@ -323,7 +400,8 @@ def run_once(round_label=""):
act = trig.get("take_profit_action", "")
if act:
extra = f"{act}"
outputs.append(f"{name}({code}) {price} → 进入{label}{lo}~{hi}{extra}")
branch_sfx = _branch_alert_suffix(code, price, d.get('shares',0), d.get('cost',0))
outputs.append(f"{name}({code}) {price} → 进入{label}{lo}~{hi}{extra}{branch_sfx}")
record_event(code, name, "entry_zone", price, f"{lo}~{hi}", label)
state[code][key] = True
state_updated = True
@@ -410,25 +488,46 @@ def run_once(round_label=""):
except Exception as e:
outputs.append(f" ⚠️ 全量重评失败: {e}")
# === 第四步:输出 ===
# === 第四步:情景变化检测 + 输出 → 直接推XMPP ===
now_str = datetime.now().strftime("%H:%M:%S")
elapsed = time.time() - start
# 情景变化检测(跨轮对比)
if HAS_TREE and _SCENARIO_CACHE.get('id'):
prev_scenario = state.get('_system', {}).get('last_scenario', '')
curr_scenario = _SCENARIO_CACHE['id']
if prev_scenario and curr_scenario != prev_scenario:
combo = _SCENARIO_CACHE.get('combo_action', '')
outputs.insert(0, f"🌀 情景切换: {prev_scenario}{curr_scenario} | {combo}")
if outputs:
state.setdefault('_system', {})['last_scenario'] = curr_scenario
state_updated = True
elif not prev_scenario:
state.setdefault('_system', {})['last_scenario'] = curr_scenario
state_updated = True
if outputs:
print(f"\n🔔 {now_str}{label}")
# 简短一行一个触发
for o in outputs:
print(o)
print(f"\n<structured_data>{json.dumps({'type':'价格监控','time':now_str,'triggers':outputs}, ensure_ascii=False)}</structured_data>")
else:
# 无触发时 SILENT(中继不推送)
print(f"[SILENT]{label} 价格正常 | {refreshed}只已刷新 | {elapsed:.1f}s")
# 直接推到老爸私信(免等待 cron_to_xmpp
try:
body = "\n".join([f"{now_str}"] + outputs)
payload = json.dumps({
"to": "hmo@yoin.fun", "body": body, "type": "chat",
}).encode("utf-8")
req = urllib.request.Request(
"http://127.0.0.1:5805/", data=payload,
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req, timeout=5)
except Exception:
pass
# else: SILENT — 无触发,无输出,不推
if state_updated:
save_state(state)
# 输出耗时
print(f"{label} {elapsed:.1f}s", flush=True)
def main():
"""每cron触发跑一轮"""