Add GTL price-traffic chart diagnostic
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
@@ -29,10 +28,12 @@ class _FakeLevel:
|
||||
|
||||
|
||||
class _FakeOHLCV:
|
||||
"""Mimics an OHLCVRecord with a close attribute."""
|
||||
"""Mimics an OHLCVRecord with price attributes."""
|
||||
|
||||
def __init__(self, close: float):
|
||||
def __init__(self, close: float, high: float | None = None, low: float | None = None):
|
||||
self.close = close
|
||||
self.high = high if high is not None else close + 1.0
|
||||
self.low = low if low is not None else close - 1.0
|
||||
|
||||
|
||||
def _make_app() -> FastAPI:
|
||||
@@ -62,6 +63,71 @@ SAMPLE_LEVELS = [
|
||||
SAMPLE_OHLCV = [_FakeOHLCV(100.0)]
|
||||
|
||||
|
||||
class TestGateTargetLadderRouter:
|
||||
@patch("app.routers.sr_levels.detect_gate_target_ladder")
|
||||
@patch("app.routers.sr_levels.query_ohlcv", new_callable=AsyncMock)
|
||||
def test_returns_transient_price_traffic_proposals(self, mock_ohlcv, mock_detect):
|
||||
mock_ohlcv.return_value = [
|
||||
_FakeOHLCV(100.0, high=101.0, low=99.0),
|
||||
_FakeOHLCV(102.0, high=103.0, low=100.0),
|
||||
]
|
||||
mock_detect.return_value = [
|
||||
{
|
||||
"price_level": 105.0,
|
||||
"type": "resistance",
|
||||
"strength": 80,
|
||||
"detection_method": "merged",
|
||||
"sources": ["pivot_point", "range_grid"],
|
||||
"rejection_count": 7,
|
||||
},
|
||||
{
|
||||
"price_level": 95.0,
|
||||
"type": "support",
|
||||
"strength": 60,
|
||||
"detection_method": "range_grid",
|
||||
"sources": ["range_grid"],
|
||||
"rejection_count": 4,
|
||||
},
|
||||
]
|
||||
|
||||
response = TestClient(_make_app()).get("/api/v1/gate-target-ladder/aapl")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()["data"]
|
||||
assert data["symbol"] == "AAPL"
|
||||
assert data["lookback_bars"] == 2
|
||||
assert [level["price_level"] for level in data["levels"]] == [95.0, 105.0]
|
||||
assert data["levels"][1] == {
|
||||
"price_level": 105.0,
|
||||
"type": "resistance",
|
||||
"strength": 80,
|
||||
"detection_method": "merged",
|
||||
"sources": ["pivot_point", "range_grid"],
|
||||
"traffic_count": 7,
|
||||
}
|
||||
mock_detect.assert_called_once_with(
|
||||
[101.0, 103.0],
|
||||
[99.0, 100.0],
|
||||
[100.0, 102.0],
|
||||
)
|
||||
|
||||
@patch("app.routers.sr_levels.detect_gate_target_ladder")
|
||||
@patch("app.routers.sr_levels.query_ohlcv", new_callable=AsyncMock)
|
||||
def test_empty_history_returns_empty_ladder(self, mock_ohlcv, mock_detect):
|
||||
mock_ohlcv.return_value = []
|
||||
|
||||
response = TestClient(_make_app()).get("/api/v1/gate-target-ladder/AAPL")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["data"] == {
|
||||
"symbol": "AAPL",
|
||||
"levels": [],
|
||||
"count": 0,
|
||||
"lookback_bars": 0,
|
||||
}
|
||||
mock_detect.assert_not_called()
|
||||
|
||||
|
||||
class TestSRLevelsRouterZones:
|
||||
"""Tests for max_zones parameter and zone inclusion in response."""
|
||||
|
||||
@@ -207,7 +273,7 @@ class TestSRLevelsRouterVisibleLevels:
|
||||
), f"visible level price {price} not within any zone bounds"
|
||||
|
||||
# visible_levels must be a subset of levels (by id)
|
||||
level_ids = {l["id"] for l in data["levels"]}
|
||||
level_ids = {level["id"] for level in data["levels"]}
|
||||
for lvl in visible:
|
||||
assert lvl["id"] in level_ids
|
||||
|
||||
|
||||
Reference in New Issue
Block a user