feat: 威科夫多周期选股引擎与中文图表界面
新增规则驱动的月/周/日结构识别、决策融合与交易计划,提供扫描 API、本地 K 线(成交量/MACD/吸筹区间标注)及回填调度;K 线无起始日时默认取最近 N 根。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user