自动刷新改用 tail update 与 scrollToPosition 恢复视窗,避免 setData 后跳到最右;拆分 chart_tv 模块并扩展 analyze/recent API。同步威科夫分析、pipeline 增量构建及相关策略与配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
7.3 KiB
Python
196 lines
7.3 KiB
Python
"""Decision Engine — multi-timeframe fusion and tradability (Architecture v1.0)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from crypto_wyckoff.domain_models import (
|
||
DecisionSignal,
|
||
EngineResult,
|
||
RiskLevel,
|
||
WyckoffCycle,
|
||
WyckoffEvent,
|
||
WyckoffPhase,
|
||
)
|
||
|
||
BULL_CYCLES = {
|
||
WyckoffCycle.ACCUMULATION.value,
|
||
WyckoffCycle.RE_ACCUMULATION.value,
|
||
WyckoffCycle.MARKUP.value,
|
||
}
|
||
BEAR_CYCLES = {
|
||
WyckoffCycle.DISTRIBUTION.value,
|
||
WyckoffCycle.RE_DISTRIBUTION.value,
|
||
WyckoffCycle.MARKDOWN.value,
|
||
}
|
||
|
||
|
||
class DecisionEngine:
|
||
name = "Decision"
|
||
version = "1.0.0"
|
||
|
||
def run(
|
||
self,
|
||
monthly_cycle: EngineResult,
|
||
weekly_cycle: EngineResult,
|
||
weekly_phase: EngineResult,
|
||
weekly_event: EngineResult,
|
||
daily_event: EngineResult,
|
||
daily_signal: EngineResult,
|
||
) -> EngineResult:
|
||
m_cycle = monthly_cycle.payload.get("cycle", WyckoffCycle.UNKNOWN.value)
|
||
w_cycle = weekly_cycle.payload.get("cycle", WyckoffCycle.UNKNOWN.value)
|
||
w_phase = weekly_phase.payload.get("phase", WyckoffPhase.NONE.value)
|
||
w_event = weekly_event.payload.get("current_event", WyckoffEvent.NONE.value)
|
||
d_event = daily_event.payload.get("current_event", WyckoffEvent.NONE.value)
|
||
|
||
trend_score = float(monthly_cycle.payload.get("trend_score", monthly_cycle.score))
|
||
structure_score = float(weekly_phase.payload.get("structure_score", weekly_phase.score))
|
||
entry_score = float(daily_event.payload.get("entry_score", daily_event.score))
|
||
|
||
overall_score = 0.30 * trend_score + 0.30 * structure_score + 0.40 * entry_score
|
||
|
||
reasons: list[str] = []
|
||
warnings: list[str] = []
|
||
alignment = 50.0
|
||
|
||
m_bull = m_cycle in BULL_CYCLES
|
||
m_bear = m_cycle in BEAR_CYCLES
|
||
w_bull = w_cycle in BULL_CYCLES
|
||
d_bullish_event = d_event in {
|
||
WyckoffEvent.SPRING.value,
|
||
WyckoffEvent.TEST.value,
|
||
WyckoffEvent.SOS.value,
|
||
WyckoffEvent.LPS.value,
|
||
WyckoffEvent.JUMP.value,
|
||
WyckoffEvent.BACKUP.value,
|
||
}
|
||
d_bearish_event = d_event in {
|
||
WyckoffEvent.UTAD.value,
|
||
WyckoffEvent.SOW.value,
|
||
WyckoffEvent.LPSY.value,
|
||
}
|
||
|
||
# Alignment scoring
|
||
if m_bull and w_bull and d_bullish_event:
|
||
alignment = 92.0
|
||
reasons.append("✓ 月/周多头结构与日线多头事件一致")
|
||
elif m_bull and d_bullish_event:
|
||
alignment = 78.0
|
||
reasons.append("✓ 月线支持,日线有入场事件")
|
||
if not w_bull:
|
||
warnings.append("周线结构未完全确认")
|
||
alignment -= 8
|
||
elif m_bear and d_bullish_event:
|
||
alignment = 35.0
|
||
reasons.append("✗ 月线派发/下跌,日线弹簧可能只是反弹")
|
||
elif m_bear and d_bearish_event:
|
||
alignment = 85.0
|
||
reasons.append("✓ 空头多周期一致")
|
||
else:
|
||
alignment = 55.0
|
||
reasons.append("○ 多周期部分一致,需观察")
|
||
|
||
if w_phase in (WyckoffPhase.D.value, WyckoffPhase.E.value) and m_bull:
|
||
alignment = min(98.0, alignment + 6)
|
||
reasons.append(f"✓ 周线阶段 {w_phase} 结构成熟({w_event})")
|
||
active = daily_event.payload.get("active_events") or daily_event.payload.get("recent_events") or []
|
||
if d_event == WyckoffEvent.SPRING.value and len(active) >= 3:
|
||
alignment = min(98.0, alignment + 4)
|
||
reasons.append("✓ 日线多重事件同时确认")
|
||
|
||
# Decision signal — hard gate on monthly bear + daily spring
|
||
decision = DecisionSignal.WATCH.value
|
||
risk = RiskLevel.MEDIUM.value
|
||
|
||
if m_bear and d_event == WyckoffEvent.SPRING.value:
|
||
decision = DecisionSignal.WATCH.value
|
||
risk = RiskLevel.HIGH.value
|
||
overall_score = min(overall_score, 55.0)
|
||
reasons.append("→ 决策:观察(月线不支持,禁止追日线弹簧)")
|
||
elif m_bear and d_bullish_event:
|
||
decision = DecisionSignal.AVOID.value
|
||
risk = RiskLevel.HIGH.value
|
||
overall_score = min(overall_score, 48.0)
|
||
reasons.append("→ 决策:回避(逆大周期多头事件)")
|
||
elif (
|
||
m_bull
|
||
and w_phase in (WyckoffPhase.D.value, WyckoffPhase.E.value, WyckoffPhase.C.value)
|
||
and d_event in (WyckoffEvent.SPRING.value, WyckoffEvent.LPS.value, WyckoffEvent.SOS.value)
|
||
and alignment >= 85
|
||
and overall_score >= 80
|
||
):
|
||
decision = DecisionSignal.STRONG_BUY.value
|
||
risk = RiskLevel.LOW.value
|
||
reasons.append("→ 决策:强烈买入(三级共振)")
|
||
elif m_bull and d_bullish_event and overall_score >= 68 and alignment >= 70:
|
||
decision = DecisionSignal.BUY.value
|
||
risk = RiskLevel.LOW.value if alignment >= 80 else RiskLevel.MEDIUM.value
|
||
reasons.append("→ 决策:买入")
|
||
elif m_bear and d_bearish_event and overall_score >= 65:
|
||
decision = DecisionSignal.SELL.value
|
||
risk = RiskLevel.MEDIUM.value
|
||
reasons.append("→ 决策:卖出")
|
||
else:
|
||
decision = DecisionSignal.WATCH.value
|
||
reasons.append("→ 决策:观察")
|
||
|
||
# Stars from score + alignment
|
||
combo = 0.6 * overall_score + 0.4 * alignment
|
||
if combo >= 90:
|
||
stars = 5
|
||
elif combo >= 80:
|
||
stars = 4
|
||
elif combo >= 65:
|
||
stars = 3
|
||
elif combo >= 50:
|
||
stars = 2
|
||
else:
|
||
stars = 1
|
||
|
||
overall_confidence = (
|
||
0.25 * monthly_cycle.confidence
|
||
+ 0.25 * weekly_phase.confidence
|
||
+ 0.25 * daily_event.confidence
|
||
+ 0.25 * daily_signal.confidence
|
||
)
|
||
# Weak event pulls overall down
|
||
if daily_event.confidence < 60:
|
||
overall_confidence = min(overall_confidence, daily_event.confidence + 15)
|
||
|
||
return EngineResult(
|
||
name=self.name,
|
||
version=self.version,
|
||
confidence=overall_confidence,
|
||
score=overall_score,
|
||
reasons=reasons,
|
||
warnings=warnings,
|
||
metrics={
|
||
"trend_score": trend_score,
|
||
"structure_score": structure_score,
|
||
"entry_score": entry_score,
|
||
"alignment": alignment,
|
||
"stars": stars,
|
||
},
|
||
payload={
|
||
"decision_signal": decision,
|
||
"alignment": alignment,
|
||
"stars": stars,
|
||
"risk": risk,
|
||
"overall_score": overall_score,
|
||
"overall_confidence": overall_confidence,
|
||
"trend_score": trend_score,
|
||
"structure_score": structure_score,
|
||
"entry_score": entry_score,
|
||
"m_cycle": m_cycle,
|
||
"w_cycle": w_cycle,
|
||
"w_phase": w_phase,
|
||
"w_event": w_event,
|
||
"d_event": d_event,
|
||
# Facts preserved — never overwritten
|
||
"facts": {
|
||
"monthly": {"cycle": m_cycle},
|
||
"weekly": {"cycle": w_cycle, "phase": w_phase, "event": w_event},
|
||
"daily": {"event": d_event},
|
||
},
|
||
},
|
||
)
|