feat: add algorithm settings backend + integration guide

- Create /api/v1/settings/algorithms endpoint for algorithm management
- Enable/disable Algorithm #15 (liquidity_sweep) and #16 (price_action_reversal)
- Settings persist in User.preferences JSON column
- Settings wired to signal_scoring filter (disabled algos vote 0.0)
- Add comprehensive ALGORITHM_INTEGRATION_GUIDE.md documentation
- Add unit tests for both algorithms in isolation and together
- Vote weights: liquidity_sweep ±2.0, price_action_reversal ±2.5
- Correlation dampening: 0.45 when both vote same direction (pattern group)
- Tested: algorithms called in get_indicators(), passed through signal pipeline
This commit is contained in:
2026-07-10 11:21:41 +00:00
parent 79b3d21ec2
commit 81907cf3aa
5 changed files with 900 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
"""Test for Algorithm #15 (liquidity_sweep) and Algorithm #16 (price_action_reversal) integration."""
from app.services.signal_scoring import _compute_adjusted_score
def test_liquidity_sweep_algorithm_voting():
"""Test Algorithm #15 — Liquidity Sweep votes correctly."""
# Mock candle data near a liquidity level
close_price = 100.0
# Liquidity data: nearest_high at 101.0 (within 1% proximity)
liquidity_data = {
"liquidity_highs": [101.0, 98.0],
"liquidity_lows": [97.0],
"nearest_high": 101.0,
"nearest_low": 97.0,
}
# Minimal required data for scoring
bb = {
"upper": [105.0],
"middle": [100.0],
"lower": [95.0],
"upper_1": [102.0],
"lower_1": [98.0],
}
rsi = [50.0]
sma = [100.0]
# Call the scoring function with liquidity data
override_sig, override_str, adjusted_score, confidence, raw_scores = _compute_adjusted_score(
close_price=close_price,
bb=bb,
rsi=rsi,
sma=sma,
macd_data=None,
st_data=None,
vol_data=None,
ichi_data=None,
rsi_div=(None, None),
macd_div=(None, None),
smc_data=None,
mtf_votes=[],
obv_data=None,
stoch_rsi_data=None,
mfi_data=None,
fvg_data=None,
candlestick_score=None,
rates=None,
enabled_strategies=None,
funding_data=None,
liquidity_data=liquidity_data,
pa_signal=None,
)
# Algorithm #15 should vote +1.0 (price near nearest_high)
assert "liquidity_sweep" in raw_scores
assert raw_scores["liquidity_sweep"] != 0.0, "Liquidity sweep should vote when price is near level"
print(f"✓ Liquidity Sweep vote: {raw_scores['liquidity_sweep']}")
def test_price_action_reversal_algorithm_voting():
"""Test Algorithm #16 — Price Action Reversal votes correctly."""
close_price = 100.0
# Price action signal: bullish pin bar at liquidity level
pa_signal = {
"pattern_type": "PIN_BAR",
"direction": "BULLISH",
"strength": 1.5,
"proximity_to_level": "AT_LEVEL",
"price_level": 99.5,
}
# Minimal required data
bb = {
"upper": [105.0],
"middle": [100.0],
"lower": [95.0],
"upper_1": [102.0],
"lower_1": [98.0],
}
rsi = [50.0]
sma = [100.0]
# Call the scoring function with price action signal
override_sig, override_str, adjusted_score, confidence, raw_scores = _compute_adjusted_score(
close_price=close_price,
bb=bb,
rsi=rsi,
sma=sma,
macd_data=None,
st_data=None,
vol_data=None,
ichi_data=None,
rsi_div=(None, None),
macd_div=(None, None),
smc_data=None,
mtf_votes=[],
obv_data=None,
stoch_rsi_data=None,
mfi_data=None,
fvg_data=None,
candlestick_score=None,
rates=None,
enabled_strategies=None,
funding_data=None,
liquidity_data=None,
pa_signal=pa_signal,
)
# Algorithm #16 should vote bullish (positive)
assert "price_action_reversal" in raw_scores
assert raw_scores["price_action_reversal"] > 0.0, "Price action reversal should vote bullish"
print(f"✓ Price Action Reversal vote: {raw_scores['price_action_reversal']}")
def test_both_algorithms_together():
"""Test both Algorithm #15 and #16 voting together for confluent signals."""
close_price = 100.5
# Both signals present: liquidity level + price action pattern
liquidity_data = {
"liquidity_highs": [101.0],
"liquidity_lows": [99.0],
"nearest_high": 101.0,
"nearest_low": 99.0,
}
pa_signal = {
"pattern_type": "PIN_BAR",
"direction": "BULLISH",
"strength": 1.5,
"proximity_to_level": "AT_LEVEL",
"price_level": 101.0,
}
bb = {
"upper": [105.0],
"middle": [100.0],
"lower": [95.0],
"upper_1": [102.0],
"lower_1": [98.0],
}
rsi = [45.0] # Neutral RSI
sma = [100.0]
override_sig, override_str, adjusted_score, confidence, raw_scores = _compute_adjusted_score(
close_price=close_price,
bb=bb,
rsi=rsi,
sma=sma,
macd_data=None,
st_data=None,
vol_data=None,
ichi_data=None,
rsi_div=(None, None),
macd_div=(None, None),
smc_data=None,
mtf_votes=[],
obv_data=None,
stoch_rsi_data=None,
mfi_data=None,
fvg_data=None,
candlestick_score=None,
rates=None,
enabled_strategies=None,
funding_data=None,
liquidity_data=liquidity_data,
pa_signal=pa_signal,
)
# Both algorithms should vote
liquidity_vote = raw_scores.get("liquidity_sweep", 0.0)
pa_vote = raw_scores.get("price_action_reversal", 0.0)
print(f"✓ Combined signals:")
print(f" - Liquidity Sweep: {liquidity_vote}")
print(f" - Price Action Reversal: {pa_vote}")
print(f" - Adjusted Score: {adjusted_score}")
print(f" - Confidence: {confidence}")
assert liquidity_vote != 0.0, "Liquidity sweep should vote"
assert pa_vote != 0.0, "Price action should vote"
def test_algorithm_correlation_dampening():
"""Test that Algorithm #15 and #16 correlation dampening works.
Both algorithms are in the 'pattern' group, so when both vote in the
same direction, their combined impact should be dampened by correlation
weighting to avoid overconfidence.
"""
close_price = 100.0
liquidity_data = {
"nearest_high": 101.0,
"nearest_low": 99.0,
}
pa_signal = {
"pattern_type": "PIN_BAR",
"direction": "BULLISH",
"strength": 2.0,
"proximity_to_level": "AT_LEVEL",
}
bb = {
"upper": [105.0],
"middle": [100.0],
"lower": [95.0],
"upper_1": [102.0],
"lower_1": [98.0],
}
rsi = [50.0]
sma = [100.0]
# Compute with both algorithms
override_sig, override_str, adjusted_score, confidence, raw_scores = _compute_adjusted_score(
close_price=close_price,
bb=bb,
rsi=rsi,
sma=sma,
macd_data=None,
st_data=None,
vol_data=None,
ichi_data=None,
rsi_div=(None, None),
macd_div=(None, None),
smc_data=None,
mtf_votes=[],
obv_data=None,
stoch_rsi_data=None,
mfi_data=None,
fvg_data=None,
candlestick_score=None,
rates=None,
enabled_strategies=None,
funding_data=None,
liquidity_data=liquidity_data,
pa_signal=pa_signal,
)
# Sum should be dampened due to correlation
combined_raw = (raw_scores.get("liquidity_sweep", 0.0) +
raw_scores.get("price_action_reversal", 0.0))
print(f"✓ Correlation dampening test:")
print(f" - Raw scores sum: {combined_raw}")
print(f" - Adjusted score: {adjusted_score}")
print(f" - Dampening applied: {adjusted_score < combined_raw}")
if __name__ == "__main__":
test_liquidity_sweep_algorithm_voting()
test_price_action_reversal_algorithm_voting()
test_both_algorithms_together()
test_algorithm_correlation_dampening()
print("\n✅ All algorithm tests passed!")