Integrate unused indicators into technical scoring; fix indicator dropdown
- Technical dimension now uses all directional indicators: 0.30*ADX + 0.20*EMA + 0.20*RSI + 0.15*EMA_Cross (bullish=80 / neutral=50 / bearish=20) + 0.10*Volume_Profile (POC proximity) + 0.05*Pivot_Points (structure confluence); weights re-normalize when data is insufficient, as before - ATR stays out of scoring (volatility input for scanner stops, not a directional signal) - IndicatorSelector uses the shared Select so the option list is dark instead of the native white popup - Update technical scoring tests for the six-component breakdown Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,8 +44,9 @@ async def test_returns_none_tuple_when_no_records(db_session):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_breakdown_with_all_sub_scores(db_session):
|
||||
"""With enough data, returns score and breakdown with ADX, EMA, RSI sub-scores."""
|
||||
records = _make_ohlcv_records(50)
|
||||
"""With enough data, returns score and breakdown with all six sub-scores."""
|
||||
# 60 bars: enough for every indicator incl. EMA Cross (needs 51)
|
||||
records = _make_ohlcv_records(60)
|
||||
|
||||
with patch(
|
||||
"app.services.price_service.query_ohlcv",
|
||||
@@ -66,17 +67,27 @@ async def test_returns_breakdown_with_all_sub_scores(db_session):
|
||||
assert "ADX" in names
|
||||
assert "EMA" in names
|
||||
assert "RSI" in names
|
||||
assert "EMA_Cross" in names
|
||||
assert "Volume_Profile" in names
|
||||
assert "Pivot_Points" in names
|
||||
|
||||
# Verify weights
|
||||
# Verify weights sum to 1 and match the formula
|
||||
weight_map = {s["name"]: s["weight"] for s in breakdown["sub_scores"]}
|
||||
assert weight_map["ADX"] == 0.4
|
||||
assert weight_map["EMA"] == 0.3
|
||||
assert weight_map["RSI"] == 0.3
|
||||
assert weight_map["ADX"] == 0.30
|
||||
assert weight_map["EMA"] == 0.20
|
||||
assert weight_map["RSI"] == 0.20
|
||||
assert weight_map["EMA_Cross"] == 0.15
|
||||
assert weight_map["Volume_Profile"] == 0.10
|
||||
assert weight_map["Pivot_Points"] == 0.05
|
||||
assert sum(weight_map.values()) == pytest.approx(1.0)
|
||||
|
||||
# Verify raw_value is present and numeric
|
||||
# Verify raw_value is present (numeric except EMA_Cross's signal string)
|
||||
for sub in breakdown["sub_scores"]:
|
||||
assert sub["raw_value"] is not None
|
||||
assert isinstance(sub["raw_value"], (int, float))
|
||||
if sub["name"] == "EMA_Cross":
|
||||
assert sub["raw_value"] in ("bullish", "neutral", "bearish")
|
||||
else:
|
||||
assert isinstance(sub["raw_value"], (int, float))
|
||||
assert sub["description"]
|
||||
|
||||
assert breakdown["unavailable"] == []
|
||||
@@ -112,8 +123,8 @@ async def test_partial_sub_scores_with_insufficient_data(db_session):
|
||||
@pytest.mark.asyncio
|
||||
async def test_all_sub_scores_unavailable(db_session):
|
||||
"""With very few bars (not enough for any indicator), returns None score with breakdown."""
|
||||
# 5 bars: not enough for any indicator
|
||||
records = _make_ohlcv_records(5)
|
||||
# 4 bars: not enough for any indicator (Pivot Points needs 5+)
|
||||
records = _make_ohlcv_records(4)
|
||||
|
||||
with patch(
|
||||
"app.services.price_service.query_ohlcv",
|
||||
@@ -125,12 +136,15 @@ async def test_all_sub_scores_unavailable(db_session):
|
||||
assert score is None
|
||||
assert breakdown is not None
|
||||
assert breakdown["sub_scores"] == []
|
||||
assert len(breakdown["unavailable"]) == 3
|
||||
assert len(breakdown["unavailable"]) == 6
|
||||
|
||||
unavail_names = [u["name"] for u in breakdown["unavailable"]]
|
||||
assert "ADX" in unavail_names
|
||||
assert "EMA" in unavail_names
|
||||
assert "RSI" in unavail_names
|
||||
assert "EMA_Cross" in unavail_names
|
||||
assert "Volume_Profile" in unavail_names
|
||||
assert "Pivot_Points" in unavail_names
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user