feat(ECR-007): Wyckoff Live Structure with Confirmed/Live isolation
Add live.py lifecycle and event candidates; assemble confirmed vs live in engine; Summary partition; execution_signal source=confirmed only. Keep strategies untouched; do not lower Confirmed thresholds for Live. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -127,11 +127,13 @@ def test_analyze_http_contract_with_mocked_kl():
|
||||
assert payload is not None and "error" not in payload
|
||||
missing = [k for k in CONTRACT_KEYS if k not in payload]
|
||||
assert not missing, f"missing contract keys: {missing}"
|
||||
assert "wyckoff" not in payload
|
||||
assert "wyckoff" in payload
|
||||
for k in WYCKOFF_KEYS:
|
||||
assert k in payload["wyckoff"], f"missing wyckoff key: {k}"
|
||||
|
||||
|
||||
def test_analyze_http_wyckoff_opt_in():
|
||||
"""include_wyckoff=1 时响应含 wyckoff 约定键;默认不返回。"""
|
||||
def test_analyze_http_wyckoff_can_opt_out():
|
||||
"""include_wyckoff=0 时可显式跳过威科夫。"""
|
||||
from app import app
|
||||
from services.runtime import add_indicators
|
||||
|
||||
@@ -148,19 +150,49 @@ def test_analyze_http_wyckoff_opt_in():
|
||||
"symbol": "BTC/USDT:USDT",
|
||||
"timeframe": "5m",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"include_wyckoff": 1,
|
||||
"include_wyckoff": 0,
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.data[:500]
|
||||
payload = resp.get_json()
|
||||
assert payload is not None and "wyckoff" in payload
|
||||
w = payload["wyckoff"]
|
||||
for k in WYCKOFF_KEYS:
|
||||
assert k in w, f"missing wyckoff key: {k}"
|
||||
assert payload is not None and "wyckoff" not in payload
|
||||
|
||||
|
||||
def test_analyze_http_wyckoff_for_three_timeframes():
|
||||
"""主/次/次次均返回各自 wyckoff 载荷。"""
|
||||
from app import app
|
||||
from services.runtime import add_indicators
|
||||
|
||||
df = add_indicators(make_ohlcv(300))
|
||||
df = df.copy()
|
||||
if "timestamp" not in df.columns:
|
||||
df["timestamp"] = (pd.to_datetime(df["date"]).astype("int64") // 10**6).astype("int64")
|
||||
|
||||
with patch("api.analyze.get_kl_data", return_value=df):
|
||||
client = app.test_client()
|
||||
resp = client.get(
|
||||
"/api/analyze",
|
||||
query_string={
|
||||
"symbol": "BTC/USDT:USDT",
|
||||
"timeframe": "4h",
|
||||
"element_timeframe": "2h",
|
||||
"sub_sub_timeframe": "1h",
|
||||
"timezone": "Asia/Shanghai",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.data[:500]
|
||||
payload = resp.get_json()
|
||||
assert payload is not None and "error" not in payload
|
||||
assert "wyckoff" in payload
|
||||
assert "element_wyckoff" in payload
|
||||
assert "sub_sub_wyckoff" in payload
|
||||
for key in ("wyckoff", "element_wyckoff", "sub_sub_wyckoff"):
|
||||
for k in WYCKOFF_KEYS:
|
||||
assert k in payload[key], f"missing {k} in {key}"
|
||||
|
||||
|
||||
def test_analyze_http_wyckoff_skipped_when_elements_only():
|
||||
"""elements_only=true 时即使 include_wyckoff=1 也不返回 wyckoff。"""
|
||||
"""elements_only=true 时不返回 wyckoff。"""
|
||||
from app import app
|
||||
from services.runtime import add_indicators
|
||||
|
||||
@@ -179,7 +211,6 @@ def test_analyze_http_wyckoff_skipped_when_elements_only():
|
||||
"element_timeframe": "1m",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"elements_only": "true",
|
||||
"include_wyckoff": 1,
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.data[:500]
|
||||
|
||||
Reference in New Issue
Block a user