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

内容:
- 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
+226 -5
View File
@@ -541,10 +541,29 @@ def reassess_strategy(code, name, price, cost, shares, current_action,
new_stop = trailing_stop
print(f" 已启用移动止损: {new_stop} (保护+{profit_pct:.1f}%利润)", file=sys.stderr)
# 最小止损距离:止损不能高于现价的97%(即至少3%下行空间
min_stop = round(price * 0.97, 2)
# 最小止损距离 —— 随趋势强度调整(2026-06-23 震度保护规则
# 强趋势(多周期看多 + MA多头排列):最小1.5%下行空间
# 普通/弱势:最小3%下行空间
is_strong_trend = False
trend_align = mtf_adj.get("trend_alignment", "")
strong_trend_indicators = ["多周期看多", "多周期多头", "上升"]
try:
if any(ind in trend_align for ind in strong_trend_indicators) and ma20 > ma60 and cur >= ma20:
is_strong_trend = True
except (NameError, TypeError):
pass # ma20/ma60/cur may be unbound if MTF data insufficient
if is_strong_trend:
min_stop_gap = 0.015 # 1.5%
else:
min_stop_gap = 0.03 # 3%
min_stop = round(price * (1 - min_stop_gap), 2)
if new_stop > min_stop and not is_deep_loss:
old_stop = new_stop
new_stop = min_stop
if old_stop != new_stop:
print(f" 最小止损 {round(min_stop_gap*100)}%间距约束: {old_stop}{new_stop} (趋势{'' if is_strong_trend else '普通'})")
# ----- 止盈设置 -----
if is_short_term_strong_trend and not is_new_entry:
@@ -866,11 +885,155 @@ def load_fundamentals(code):
return {}
def _get_portfolio_risk_state():
"""读取 portfolio 组合风险状态(2026-06-23 引擎协调)"""
try:
# 数据一致性检查:警告多副本(2026-06-23 bugfix
_check_portfolio_consistency()
p = json.load(open('/home/hmo/web-dashboard/data/portfolio.json'))
pos_pct = p.get('position_pct', 0)
cash = p.get('cash', 0)
holdings = p.get('holdings', [])
weak_cnt = sum(1 for h in holdings if h.get('change_pct', 0) < -15)
total = len(holdings) or 1
weak_ratio = weak_cnt / total
return {
'position_pct': pos_pct,
'cash': cash,
'is_high_position': pos_pct > 80,
'is_very_high_position': pos_pct > 90,
'is_high_weak': weak_ratio > 0.35,
'weak_ratio': round(weak_ratio * 100),
'total_holdings': total,
}
except:
return {}
def _is_buy_signal(signal):
"""判断信号是否为买入/持有类(用于防洗盘)"""
if not signal:
return False
buy_keywords = ['买入', '持有', '加仓', '阳线企稳', '温和放量', '量价齐升',
'缩量回踩', '接近支撑', '关注', '回调机会', '价稳']
for kw in buy_keywords:
if kw in signal:
return True
return False
def _check_portfolio_consistency():
"""数据一致性检查:如果存在多份 portfolio.json 则报警(2026-06-23 bugfix"""
main = '/home/hmo/web-dashboard/data/portfolio.json'
main_cash = None
try:
import json
main_cash = json.load(open(main)).get('cash')
except Exception:
return
for path in [
'/home/hmo/data/portfolio.json',
'/home/hmo/projects/MoFin/data/portfolio.json',
'/home/hmo/web-dashboard.bak/data/portfolio.json',
]:
if os.path.exists(path):
try:
other = json.load(open(path))
if other.get('cash') != main_cash:
print(f"⚠️ 数据一致性: {os.path.realpath(path)} cash={other.get('cash')} ≠ 主文件 cash={main_cash} (需清理)", file=sys.stderr)
except Exception:
pass
def _check_contradiction(code, today_only=True):
"""反馈循环核——检查本股是否有刚卖出的记录
返回 dict or None:
- sold_reason: 'portfolio_trim'|'stop_loss'
- sold_at: 卖出日期
- days_ago: 卖出距今交易日数
- is_today: 是否今日卖出
- tag: 追加到信号的标注
"""
try:
from datetime import datetime, date
dec = json.load(open('/home/hmo/web-dashboard/data/decisions.json'))
for e in dec.get('decisions', []):
if e.get('code') != code:
continue
sold_at = e.get('sold_at', '')
if not sold_at:
return None
try:
sd = datetime.strptime(sold_at, '%Y-%m-%d').date()
td = date.today()
days = (td - sd).days
except:
return None
reason = e.get('sold_reason', 'portfolio_trim')
if reason == 'stop_loss':
tag = '止损离场(逻辑破坏,短期不关注)'
else:
tag = '组合减仓后关注(已清仓,等回踩确认)'
return {
'sold_reason': reason,
'sold_at': sold_at,
'days_ago': days,
'is_today': days == 0,
'tag': tag,
}
except:
return None
return None
def _get_sell_priority_list():
"""减仓优先级排序:深套>亏损>微盈>盈利(2026-06-23 反馈循环)
返回 [(code, name, change_pct, position_pct, priority_label), ...]
按卖出的优先顺序排列(最先应该卖的在最前)
"""
try:
p = json.load(open('/home/hmo/web-dashboard/data/portfolio.json'))
holdings = p.get('holdings', [])
ranked = []
for h in holdings:
chg = h.get('change_pct', 0)
pos = h.get('position_pct', 0)
if chg < -30:
label = '深套(>30%),优先减'
rank = 0
elif chg < -20:
label = '深套(>20%),优先减'
rank = 1
elif chg < -10:
label = '亏损,建议减'
rank = 2
elif chg < 0:
label = '微亏,可减'
rank = 3
elif chg < 10:
label = '微盈,持有'
rank = 4
else:
label = '盈利,最后减'
rank = 5
ranked.append((rank, h['code'], h.get('name',''), chg, pos, label))
ranked.sort(key=lambda x: (x[0], -x[4])) # 优先 rank, 其次仓位大优先
return [{'code':c,'name':n,'change_pct':chg,'position_pct':pos,'label':l}
for r,c,n,chg,pos,l in ranked]
except:
return []
def enrich_timing_signal(base_signal, macro_desc="", sector_note="",
profit_pct=0, stock_category="", is_new_entry=False,
fundamentals=None, news_sentiment=None,
timing_signal_override=None):
"""多因子合成timing_signal——大盘+行业+基本面+技术
timing_signal_override=None,
portfolio_context=None): # 新增参数
"""多因子合成timing_signal——大盘+行业+基本面+技术+组合风险
返回 (enriched_signal, factors_list)
- enriched_signal: 可读的多因子信号描述
@@ -960,6 +1123,21 @@ def enrich_timing_signal(base_signal, macro_desc="", sector_note="",
if base_signal and base_signal != "neutral":
factors.append(base_signal)
# 5.5 组合风险因子(2026-06-23 双引擎协调)
if portfolio_context and not is_new_entry:
if portfolio_context.get('is_very_high_position'):
factors.append("组合仓位极重(>90%)")
elif portfolio_context.get('is_high_position'):
factors.append("组合仓位偏重(>80%)")
if portfolio_context.get('is_high_weak'):
factors.append(f"弱势占{portfolio_context.get('weak_ratio')}%")
elif portfolio_context and is_new_entry:
# 新买入推荐:注明组合上下文
if portfolio_context.get('is_high_position'):
factors.append(f"{portfolio_context.get('position_pct')}%现金有限")
elif portfolio_context.get('is_high_weak'):
factors.append("组合风险信号")
# 如果没有足够因素,返回信号不充分
if not factors:
return "信号不充分", []
@@ -995,7 +1173,7 @@ def reassess_with_context(code, name, price, cost, shares, current_action,
news_sentiment = {}
fund = {}
enriched, _ = enrich_timing_signal(
enriched, factors = enrich_timing_signal(
base_signal=result.get("timing_signal", ""),
macro_desc=macro_desc,
sector_note=sector_note,
@@ -1004,9 +1182,52 @@ def reassess_with_context(code, name, price, cost, shares, current_action,
is_new_entry=is_watchlist,
fundamentals=fund,
news_sentiment=news_sentiment,
portfolio_context=_get_portfolio_risk_state(),
)
result["timing_signal"] = enriched
# 6. 防洗盘:信号不要一天一翻(2026-06-23)
# 如果旧信号是买入/持有类,新信号是谨慎/等待类,但中期趋势未破→维持旧信号
try:
dec = json.load(open('/home/hmo/web-dashboard/data/decisions.json'))
for e in dec.get('decisions', []):
if e.get('code') == code:
old_signal = e.get('timing_signal', '')
if old_signal and _is_buy_signal(old_signal) and not _is_buy_signal(enriched):
# 中等趋势检查:MA5 > MA20 + 多周期看多
mtf = result.get('multi_tf_context', '')
if '看多' in mtf or '多头' in mtf:
try:
closes = [float(k.split()[2]) for k in mtf.split('|') if 'MA5' in k]
except:
closes = []
has_uptrend = 'MA5' in mtf and 'MA20' in mtf
if has_uptrend:
print(f" 防洗盘: {old_signal}→保持旧信号(中期趋势完整)")
result["timing_signal"] = f"{old_signal}(正常回调价稳)"
break
except Exception as e:
print(f" 防洗盘跳过: {e}")
# 7. 反馈循环核:检查本股是否有刚卖出的记录(2026-06-23)
contradiction = _check_contradiction(code)
if contradiction and contradiction.get('is_today'):
# 今日刚卖出 → 不屏蔽信号,但必须自标注矛盾
print(f" 反馈循环: {contradiction.get('tag')} (sold_at={contradiction.get('sold_at')})")
if _is_buy_signal(result.get('timing_signal', '')):
result['action_note'] = contradiction['tag']
# 在 timing_signal 中追加反馈标注,供报告层可见
curr_signal = result.get('timing_signal', '')
if '⚠️' not in curr_signal:
result['timing_signal'] = f"⚠️{contradiction['tag']}|{curr_signal}"
elif contradiction:
# 非今日卖出但近期卖出 → 标注已清仓
print(f" 近期清仓: sold_at={contradiction.get('sold_at')} ({contradiction.get('days_ago')}日前)")
if _is_buy_signal(result.get('timing_signal', '')):
curr_signal = result.get('timing_signal', '')
if '已清仓' not in curr_signal:
result['timing_signal'] = f"已清仓,{curr_signal}"
# 重建 action 文本(同步多因子信号)
try:
if new_action_needs_refresh(result, {"source": "auto"}, price):