Files
A_Share_DP/tests/test_wyckoff_plan_and_fallback.py
T
jackyu66gitandCursor 03851d2247 feat: 威科夫多周期选股引擎与中文图表界面
新增规则驱动的月/周/日结构识别、决策融合与交易计划,提供扫描 API、本地 K 线(成交量/MACD/吸筹区间标注)及回填调度;K 线无起始日时默认取最近 N 根。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 14:26:46 +08:00

95 lines
3.1 KiB
Python

"""Plan gate + insufficient TF fallback tests."""
from datetime import date, timedelta
from ashare_dp.domain.wyckoff import DecisionSignal, EngineResult, OHLCVFrame, WyckoffCycle
from ashare_dp.wyckoff.cycle import CycleEngine
from ashare_dp.wyckoff.decision import DecisionEngine
from ashare_dp.wyckoff.features import FeatureEngine
from ashare_dp.wyckoff.plan import PlanEngine
from ashare_dp.wyckoff.pipeline import analyze_symbol
from ashare_dp.wyckoff.phase import PhaseEngine
from ashare_dp.wyckoff.event import EventEngine
from ashare_dp.wyckoff.signal import SignalEngine
def _er(name, payload, confidence=80.0, score=80.0):
return EngineResult(name=name, confidence=confidence, score=score, payload=payload)
def test_plan_no_entry_on_watch_even_if_spring_event():
plan = PlanEngine()
feat = _er("Feature", {"close": 10.0, "atr": 0.3, "swing_low": 9.0, "swing_high": 11.0, "range_high": 11.0})
decision = _er(
"Decision",
{
"decision_signal": DecisionSignal.WATCH.value,
"d_event": "Spring",
},
confidence=90,
score=50,
)
out = plan.run(feat, decision)
assert out.payload["entry"] is None
assert out.payload["stop"] is None
def test_plan_entry_on_buy():
plan = PlanEngine()
feat = _er("Feature", {"close": 10.0, "atr": 0.3, "swing_low": 9.0, "swing_high": 11.0, "range_high": 11.0})
decision = _er("Decision", {"decision_signal": DecisionSignal.BUY.value, "d_event": "Spring"})
out = plan.run(feat, decision)
assert out.payload["entry"] == 10.0
assert out.payload["stop"] is not None
def test_feature_insufficient_for_short_monthly():
fe = FeatureEngine()
base = date(2024, 1, 1)
n = 10
frame = OHLCVFrame(
ts_code="000001.SZ",
timeframe="1M",
trade_dates=[base + timedelta(days=30 * i) for i in range(n)],
open=[10.0] * n,
high=[11.0] * n,
low=[9.0] * n,
close=[10.0] * n,
volume=[1e6] * n,
)
out = fe.run(frame, "1M")
assert out.payload["insufficient"] is True
cyc = CycleEngine().run(out, "1M")
assert cyc.payload["cycle"] == WyckoffCycle.UNKNOWN.value
def test_pipeline_does_not_borrow_daily_as_monthly():
"""Daily-only data → monthly cycle Unknown, not inferred from daily."""
base = date(2024, 1, 1)
n = 120
closes = [100 + i * 0.4 for i in range(n)]
daily = OHLCVFrame(
ts_code="000001.SZ",
timeframe="1d",
trade_dates=[base + timedelta(days=i) for i in range(n)],
open=closes,
high=[c * 1.01 for c in closes],
low=[c * 0.99 for c in closes],
close=closes,
volume=[1e6] * n,
)
result = analyze_symbol(
daily,
None,
None,
feature_eng=FeatureEngine(),
cycle_eng=CycleEngine(),
phase_eng=PhaseEngine(),
event_eng=EventEngine(),
signal_eng=SignalEngine(),
decision_eng=DecisionEngine(),
plan_eng=PlanEngine(),
)
assert result["f_m"].payload.get("insufficient") is True
assert result["c_m"].payload["cycle"] == WyckoffCycle.UNKNOWN.value