自动刷新常态只拉 recent 尾部 K,每 1 分钟全量重算缠论;修复结构区缓存导入;默认指标/4h·1h·15m/近30天;同步 ECR-009 screener 相关改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
3.5 KiB
Python
124 lines
3.5 KiB
Python
"""ECR-009: page/API smoke without requiring live provider during assert."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
# Ensure repo root + web on path like app.py
|
|
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
_WEB = os.path.join(_ROOT, "web")
|
|
for p in (_ROOT, _WEB):
|
|
if p not in sys.path:
|
|
sys.path.insert(0, p)
|
|
|
|
os.environ.setdefault("CRYPTO_WYCKOFF_DISABLE", "1")
|
|
|
|
|
|
@pytest.fixture()
|
|
def client():
|
|
from app import create_app
|
|
|
|
app = create_app()
|
|
app.config["TESTING"] = True
|
|
with app.test_client() as c:
|
|
yield c
|
|
|
|
|
|
def test_wyckoff_crypto_page_ok(client):
|
|
resp = client.get("/wyckoff_crypto")
|
|
assert resp.status_code == 200
|
|
assert b"Crypto Wyckoff Screener" in resp.data
|
|
assert b"fCombo" in resp.data
|
|
assert b"chartCanvas" in resp.data
|
|
|
|
|
|
def test_wyckoff_crypto_meta_ok(client):
|
|
resp = client.get("/api/wyckoff_crypto/meta")
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert "engine_version" in data
|
|
assert data.get("combo", {}).get("id") == "h8_4_1"
|
|
assert data["combo"]["low"] == "1h"
|
|
ids = {c["id"] for c in data.get("combos") or []}
|
|
assert "h8_4_1" in ids and "d_w_m" in ids
|
|
|
|
|
|
def test_wyckoff_crypto_scan_ok(client):
|
|
resp = client.get("/api/wyckoff_crypto/scan?limit=5&combo_id=h8_4_1")
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert "rows" in data
|
|
assert data.get("combo", {}).get("id") == "h8_4_1"
|
|
|
|
|
|
def test_wyckoff_crypto_klines_bad_request(client):
|
|
resp = client.get("/api/wyckoff_crypto/klines")
|
|
assert resp.status_code == 400
|
|
|
|
|
|
def test_wyckoff_crypto_klines_ok(client):
|
|
resp = client.get(
|
|
"/api/wyckoff_crypto/klines?symbol=BTC/USDT:USDT&tf=1h&limit=10&combo_id=h8_4_1"
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert "items" in data
|
|
assert data.get("tf") == "1h"
|
|
assert data.get("intraday") is True
|
|
if data["items"]:
|
|
assert "datetime" in data["items"][0]
|
|
assert "ts" in data["items"][0]
|
|
assert "T" in data["items"][0]["datetime"]
|
|
assert "+08:00" in data["items"][0]["datetime"]
|
|
|
|
|
|
def test_wyckoff_crypto_klines_bad_limit_ok(client):
|
|
resp = client.get(
|
|
"/api/wyckoff_crypto/klines?symbol=BTC/USDT:USDT&tf=1h&limit=abc&combo_id=h8_4_1"
|
|
)
|
|
assert resp.status_code == 200
|
|
|
|
|
|
def test_wyckoff_crypto_overlay_ok(client):
|
|
resp = client.get(
|
|
"/api/wyckoff_crypto/overlay?symbol=BTC/USDT:USDT&tf=1h&bars=60&combo_id=h8_4_1"
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert "phases" in data
|
|
assert "events" in data
|
|
|
|
|
|
def test_combos_add_and_list(client, tmp_path, monkeypatch):
|
|
from crypto_wyckoff import combos as cm
|
|
|
|
monkeypatch.setattr(cm, "_COMBOS_FILE", tmp_path / "combos.json")
|
|
monkeypatch.setattr(cm, "_cache", None)
|
|
|
|
resp = client.get("/api/wyckoff_crypto/combos")
|
|
assert resp.status_code == 200
|
|
assert len(resp.get_json()["combos"]) >= 2
|
|
|
|
bad = client.post(
|
|
"/api/wyckoff_crypto/combos",
|
|
json={"high": "1h", "mid": "4h", "low": "8h"},
|
|
)
|
|
assert bad.status_code == 400
|
|
|
|
ok = client.post(
|
|
"/api/wyckoff_crypto/combos",
|
|
json={"high": "12h", "mid": "4h", "low": "1h", "label": "12h/4h/1h"},
|
|
)
|
|
assert ok.status_code == 200
|
|
cid = ok.get_json()["combo"]["id"]
|
|
assert cid == "12h_4h_1h"
|
|
|
|
deleted = client.delete(f"/api/wyckoff_crypto/combos/{cid}")
|
|
assert deleted.status_code == 200
|
|
|
|
builtin = client.delete("/api/wyckoff_crypto/combos/h8_4_1")
|
|
assert builtin.status_code == 400
|