refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块
删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""ECR-002:加深 /api/analyze 相关契约 —— mock 行情 + analyze_chan 关键字段快照。"""
|
||||
"""加深 /api/analyze 相关契约 —— mock 行情 + analyze_chan 关键字段快照。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -10,25 +10,21 @@ import pandas as pd
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WEB_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "web"))
|
||||
sys.path.insert(0, str(WEB_ROOT))
|
||||
|
||||
from tests.generate_golden import make_ohlcv # noqa: E402
|
||||
from tests.helpers import make_ohlcv # noqa: E402
|
||||
|
||||
|
||||
_CONTRACT_DOC = json.loads(
|
||||
(ROOT / "tests" / "fixtures" / "analyze_contract_keys.json").read_text(encoding="utf-8")
|
||||
(Path(__file__).resolve().parent / "fixtures" / "analyze_contract_keys.json").read_text(encoding="utf-8")
|
||||
)
|
||||
CONTRACT_KEYS = (
|
||||
_CONTRACT_DOC["required"]
|
||||
if isinstance(_CONTRACT_DOC, dict) and "required" in _CONTRACT_DOC
|
||||
else _CONTRACT_DOC
|
||||
)
|
||||
WYCKOFF_KEYS = (
|
||||
_CONTRACT_DOC.get("wyckoff_keys", [])
|
||||
if isinstance(_CONTRACT_DOC, dict)
|
||||
else []
|
||||
)
|
||||
|
||||
# analyze_chan 直接返回的对象字段(未序列化前)
|
||||
ANALYZE_CHAN_KEYS = {
|
||||
@@ -74,7 +70,6 @@ def test_klines_recent_returns_tail_only():
|
||||
from app import app
|
||||
|
||||
df = make_ohlcv(n=30)
|
||||
# analyze 蓝图 star-import 后绑定在 api.analyze 命名空间
|
||||
with patch("api.analyze.get_kl_data", return_value=df):
|
||||
client = app.test_client()
|
||||
resp = client.get(
|
||||
@@ -88,7 +83,6 @@ def test_klines_recent_returns_tail_only():
|
||||
assert isinstance(body.get("kline_data"), list)
|
||||
assert len(body["kline_data"]) == 2
|
||||
assert "bi_list" not in body
|
||||
assert "wyckoff" not in body
|
||||
|
||||
|
||||
def test_contract_keys_stable():
|
||||
@@ -119,7 +113,6 @@ def test_serialize_chan_macd_shape():
|
||||
result = analyze_chan(df)
|
||||
serialized = serialize_chan_macd_data(result["chan_macd"], timezone("Asia/Shanghai"))
|
||||
assert set(serialized.keys()) == CHAN_MACD_SERIALIZED_KEYS
|
||||
# JSON 可序列化
|
||||
json.dumps(serialized)
|
||||
|
||||
|
||||
@@ -133,7 +126,6 @@ def test_analyze_http_contract_with_mocked_kl():
|
||||
if "timestamp" not in df.columns:
|
||||
df["timestamp"] = (pd.to_datetime(df["date"]).astype("int64") // 10**6).astype("int64")
|
||||
|
||||
# analyze 路由使用 `from services.runtime import *`,须 patch 其模块命名空间
|
||||
with patch("api.analyze.get_kl_data", return_value=df):
|
||||
client = app.test_client()
|
||||
resp = client.get(
|
||||
@@ -149,93 +141,3 @@ 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" in payload
|
||||
for k in WYCKOFF_KEYS:
|
||||
assert k in payload["wyckoff"], f"missing wyckoff key: {k}"
|
||||
|
||||
|
||||
def test_analyze_http_wyckoff_can_opt_out():
|
||||
"""include_wyckoff=0 时可显式跳过威科夫。"""
|
||||
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": "5m",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"include_wyckoff": 0,
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.data[:500]
|
||||
payload = resp.get_json()
|
||||
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 时不返回 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": "5m",
|
||||
"element_timeframe": "1m",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"elements_only": "true",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.data[:500]
|
||||
payload = resp.get_json()
|
||||
assert payload is not None
|
||||
assert "wyckoff" not in payload
|
||||
|
||||
Reference in New Issue
Block a user