114 lines
4.3 KiB
Python
114 lines
4.3 KiB
Python
"""Independent short strategy builders.
|
|
|
|
Short setups are not inverted long setups. They need their own trigger,
|
|
confirmation, invalidation and review lineage.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from app.core.factor_roles import CONFIRMATION, ENTRY, RISK, TRIGGER
|
|
from app.core.strategy_contract import StrategySignal, current_strategy_version
|
|
from app.core.strategy_registry import BREAKDOWN_RETEST_SHORT_1H_STRATEGY
|
|
|
|
|
|
def _safe_float(value, default=0.0) -> float:
|
|
try:
|
|
if value is None or value == "":
|
|
return default
|
|
return float(value)
|
|
except Exception:
|
|
return default
|
|
|
|
|
|
def build_breakdown_retest_short_1h_signal(
|
|
*,
|
|
symbol: str,
|
|
current_price: float,
|
|
detection: dict,
|
|
entry_plan: dict | None = None,
|
|
market_regime: dict | None = None,
|
|
decision_log: dict | None = None,
|
|
) -> StrategySignal | None:
|
|
"""Build a short signal for breakdown -> failed retest setups.
|
|
|
|
Expected detection fields are intentionally simple so scanner/confirm
|
|
implementations can evolve without changing the strategy contract:
|
|
detected, breakdown_level, retest_zone, stop_level, target_1, quality,
|
|
retest_rejected, score.
|
|
"""
|
|
if not (detection or {}).get("detected"):
|
|
return None
|
|
entry_plan = dict(entry_plan or {})
|
|
market_regime = market_regime or {}
|
|
quality = str(detection.get("quality") or "")
|
|
retest_rejected = bool(detection.get("retest_rejected"))
|
|
current_price = _safe_float(current_price)
|
|
retest_zone = _safe_float(detection.get("retest_zone") or detection.get("breakdown_level"))
|
|
distance_pct = abs(current_price / retest_zone - 1) * 100 if current_price > 0 and retest_zone > 0 else 0.0
|
|
risk_level = str(market_regime.get("risk_level") or "medium").lower()
|
|
reasons = []
|
|
status = "candidate"
|
|
if quality not in {"良好", "优质"}:
|
|
status = "observe"
|
|
reasons.append(f"反抽质量 {quality or '未知'},不直接做空")
|
|
if not retest_rejected:
|
|
status = "observe"
|
|
reasons.append("尚未确认反抽失败")
|
|
if risk_level in {"low", "medium"} and not bool(detection.get("relative_weakness")):
|
|
status = "observe"
|
|
reasons.append("市场并非明显弱势,且缺少相对弱势确认")
|
|
if distance_pct > 8:
|
|
status = "observe"
|
|
reasons.append(f"当前价离反抽区 {distance_pct:.1f}%,不追空")
|
|
|
|
entry_plan.setdefault("side", "short")
|
|
entry_plan.setdefault("entry_action", "可即刻买入" if status == "candidate" else "观察")
|
|
entry_plan.setdefault("entry_price", current_price)
|
|
entry_plan.setdefault("stop_loss", detection.get("stop_level"))
|
|
entry_plan.setdefault("tp1", detection.get("target_1"))
|
|
entry_plan.setdefault("risk_reward_ok", True)
|
|
|
|
score = _safe_float(detection.get("score"))
|
|
confidence = min(100.0, max(0.0, score * 8 + (12 if retest_rejected else 0)))
|
|
return StrategySignal(
|
|
strategy_code=BREAKDOWN_RETEST_SHORT_1H_STRATEGY,
|
|
strategy_version=current_strategy_version(),
|
|
symbol=symbol,
|
|
direction="short",
|
|
status=status,
|
|
confidence=confidence,
|
|
score=score,
|
|
trigger={
|
|
"factor_code": "breakdown_retest_1h_short",
|
|
"factor_label": "1H破位反抽做空",
|
|
"breakdown_level": detection.get("breakdown_level"),
|
|
"retest_zone": retest_zone,
|
|
"stop_level": detection.get("stop_level"),
|
|
"target_1": detection.get("target_1"),
|
|
"quality": quality,
|
|
"retest_rejected": retest_rejected,
|
|
"distance_to_retest_zone_pct": round(distance_pct, 4),
|
|
"risk_level": risk_level,
|
|
},
|
|
factor_roles={
|
|
"breakdown_retest_1h_short": TRIGGER,
|
|
"retest_reject_15m_short": ENTRY,
|
|
"market_risk_off_short": CONFIRMATION,
|
|
"false_breakout": RISK,
|
|
"funding_extreme": RISK,
|
|
},
|
|
entry_plan=entry_plan,
|
|
risk_plan={
|
|
"invalid_if": ["重新站回破位区", "反抽放量突破", "BTC/ETH快速转强", "RR不足"],
|
|
"risk_reasons": reasons,
|
|
},
|
|
decision_log=decision_log or {
|
|
"module": BREAKDOWN_RETEST_SHORT_1H_STRATEGY,
|
|
"decision": status,
|
|
"reasons": reasons,
|
|
},
|
|
)
|
|
|
|
|
|
__all__ = ["build_breakdown_retest_short_1h_signal"]
|