自动刷新改用 tail update 与 scrollToPosition 恢复视窗,避免 setData 后跳到最右;拆分 chart_tv 模块并扩展 analyze/recent API。同步威科夫分析、pipeline 增量构建及相关策略与配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
"""Plan Engine — Entry / Stop / Target / RR only when Decision is tradable."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from crypto_wyckoff.domain_models import DecisionSignal, EngineResult
|
||
|
||
|
||
_TRADABLE = {
|
||
DecisionSignal.STRONG_BUY.value,
|
||
DecisionSignal.BUY.value,
|
||
DecisionSignal.SELL.value,
|
||
}
|
||
|
||
|
||
class PlanEngine:
|
||
name = "Plan"
|
||
version = "1.0.0"
|
||
|
||
def run(self, daily_feature: EngineResult, decision: EngineResult) -> EngineResult:
|
||
f = daily_feature.payload
|
||
close = float(f.get("close") or 0)
|
||
atr = float(f.get("atr") or 0) or close * 0.02
|
||
swing_low = float(f.get("swing_low") or close - 2 * atr)
|
||
swing_high = float(f.get("swing_high") or close + 2 * atr)
|
||
range_high = float(f.get("range_high") or swing_high)
|
||
signal = decision.payload.get("decision_signal", DecisionSignal.WATCH.value)
|
||
|
||
entry = stop = t1 = t2 = rr = None
|
||
reasons: list[str] = []
|
||
|
||
if signal not in _TRADABLE or close <= 0:
|
||
reasons.append(f"无交易计划(信号={signal})")
|
||
return EngineResult(
|
||
name=self.name,
|
||
version=self.version,
|
||
confidence=decision.confidence,
|
||
score=decision.score,
|
||
reasons=reasons,
|
||
payload={
|
||
"entry": None,
|
||
"stop": None,
|
||
"target1": None,
|
||
"target2": None,
|
||
"rr": None,
|
||
},
|
||
)
|
||
|
||
if signal in (DecisionSignal.STRONG_BUY.value, DecisionSignal.BUY.value):
|
||
entry = round(close, 4)
|
||
stop = round(min(swing_low, close - 1.5 * atr), 4)
|
||
risk = max(entry - stop, 1e-6)
|
||
t1 = round(entry + 2.0 * risk, 4)
|
||
t2 = round(max(range_high, entry + 3.0 * risk), 4)
|
||
rr = round((t1 - entry) / risk, 2)
|
||
reasons.append(f"入场={entry} 止损={stop} 目标一={t1} 盈亏比={rr}")
|
||
else: # Sell
|
||
entry = round(close, 4)
|
||
stop = round(max(swing_high, close + 1.5 * atr), 4)
|
||
risk = max(stop - entry, 1e-6)
|
||
t1 = round(entry - 2.0 * risk, 4)
|
||
t2 = round(entry - 3.0 * risk, 4)
|
||
rr = round((entry - t1) / risk, 2)
|
||
reasons.append(f"做空计划 入场={entry} 止损={stop} 目标一={t1}")
|
||
|
||
return EngineResult(
|
||
name=self.name,
|
||
version=self.version,
|
||
confidence=decision.confidence,
|
||
score=decision.score,
|
||
reasons=reasons,
|
||
payload={
|
||
"entry": entry,
|
||
"stop": stop,
|
||
"target1": t1,
|
||
"target2": t2,
|
||
"rr": rr,
|
||
},
|
||
)
|