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
+2
View File
@@ -18,6 +18,7 @@ from app.api.v1.strategies import router as strategies_router
from app.api.v1.analytics import router as analytics_router
from app.api.v1.audit import router as audit_router
from app.api.v1.alerts import router as alerts_router
from app.api.v1.settings import router as settings_router
api_router = APIRouter(prefix="/api/v1")
api_router.include_router(auth_router)
@@ -36,6 +37,7 @@ api_router.include_router(strategies_router)
api_router.include_router(analytics_router)
api_router.include_router(audit_router)
api_router.include_router(alerts_router)
api_router.include_router(settings_router)
# ── Users endpoint (no prefix, directly on api_router) ──
+221
View File
@@ -0,0 +1,221 @@
"""Settings API — algorithm toggles and configuration management."""
from __future__ import annotations
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_user, get_db_session
from app.models.user import User
from app.schemas.settings import (
AlgorithmSettingsResponse,
AlgorithmToggleRequest,
AlgorithmConfigResponse,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/settings", tags=["settings"])
# ---------------------------------------------------------------------------
# Algorithm Configuration
# ---------------------------------------------------------------------------
# Map algorithm ID to name and default enabled state
ALGORITHM_CONFIGS = {
"double_bb_rsi": {
"name": "Double Bollinger Bands + RSI",
"description": "Volatility + momentum oscillator",
"enabled": True,
"weight": 1.0,
},
"macd_crossover": {
"name": "MACD Crossover",
"description": "Trend-following momentum indicator",
"enabled": True,
"weight": 1.0,
},
"supertrend": {
"name": "SuperTrend",
"description": "Trend detection with ATR-based stops",
"enabled": True,
"weight": 1.0,
},
"volume_breakout": {
"name": "Volume Breakout",
"description": "Volume-driven support/resistance breaks",
"enabled": True,
"weight": 1.0,
},
"ichimoku": {
"name": "Ichimoku Cloud",
"description": "Multi-line equilibrium trend system",
"enabled": True,
"weight": 1.0,
},
"divergence": {
"name": "RSI/MACD Divergence",
"description": "Price-momentum divergence detection",
"enabled": True,
"weight": 1.0,
},
"smc": {
"name": "Smart Money Concepts (SMC)",
"description": "Market structure + order block detection",
"enabled": True,
"weight": 1.0,
},
"obv": {
"name": "On-Balance Volume (OBV)",
"description": "Volume flow and accumulation/distribution",
"enabled": True,
"weight": 1.0,
},
"stoch_rsi": {
"name": "Stochastic RSI",
"description": "RSI momentum in oversold/overbought zones",
"enabled": True,
"weight": 1.0,
},
"mfi": {
"name": "Money Flow Index (MFI)",
"description": "Volume-weighted momentum indicator",
"enabled": True,
"weight": 1.0,
},
"fvg": {
"name": "Fair Value Gap (FVG)",
"description": "Inefficiency zone detection for reversals",
"enabled": True,
"weight": 1.0,
},
"candlestick": {
"name": "Candlestick Patterns",
"description": "Pin bar, engulfing, and other reversal patterns",
"enabled": True,
"weight": 1.0,
},
"funding_oi": {
"name": "Funding Rate + Open Interest",
"description": "Perpetual futures positioning extremes (contrarian)",
"enabled": True,
"weight": 1.0,
},
"liquidity_sweep": {
"name": "Liquidity Sweep (Algorithm #15)",
"description": "Swing high/low liquidity level breaks",
"enabled": True,
"weight": 2.0,
},
"price_action_reversal": {
"name": "Price Action Reversal (Algorithm #16)",
"description": "Pin bar/engulfing at support/resistance zones",
"enabled": True,
"weight": 2.5,
},
}
@router.get("/algorithms", response_model=list[AlgorithmConfigResponse])
async def get_algorithm_settings(
current_user: User = Depends(get_current_user),
) -> list[AlgorithmConfigResponse]:
"""Get all available algorithms and their current enabled state.
Returns a list of all algorithms with their configuration, including
whether each is enabled for this user and its vote weight.
"""
prefs = current_user.preferences or {}
enabled_algos = prefs.get("enabled_algorithms", {})
result = []
for algo_id, config in ALGORITHM_CONFIGS.items():
is_enabled = enabled_algos.get(algo_id, config.get("enabled", True))
result.append(
AlgorithmConfigResponse(
id=algo_id,
name=config.get("name", algo_id),
description=config.get("description", ""),
enabled=is_enabled,
weight=config.get("weight", 1.0),
)
)
return result
@router.put("/algorithms/{algorithm_id}", response_model=AlgorithmSettingsResponse)
async def toggle_algorithm(
algorithm_id: str,
body: AlgorithmToggleRequest,
db: AsyncSession = Depends(get_db_session),
current_user: User = Depends(get_current_user),
) -> AlgorithmSettingsResponse:
"""Toggle an algorithm on/off for the current user.
Path Parameters:
- algorithm_id: Algorithm identifier (e.g., "liquidity_sweep", "price_action_reversal")
Request Body:
- enabled: boolean to enable/disable the algorithm
Returns the updated algorithm configuration and its new state.
"""
if algorithm_id not in ALGORITHM_CONFIGS:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Algorithm '{algorithm_id}' not found",
)
# Initialize preferences if needed
if current_user.preferences is None:
current_user.preferences = {}
if "enabled_algorithms" not in current_user.preferences:
current_user.preferences["enabled_algorithms"] = {}
# Update the algorithm's enabled state
current_user.preferences["enabled_algorithms"][algorithm_id] = body.enabled
# Persist to database
await db.flush()
await db.refresh(current_user)
config = ALGORITHM_CONFIGS.get(algorithm_id, {})
return AlgorithmSettingsResponse(
id=algorithm_id,
name=config.get("name", algorithm_id),
description=config.get("description", ""),
enabled=body.enabled,
weight=config.get("weight", 1.0),
message=f"Algorithm '{algorithm_id}' {'enabled' if body.enabled else 'disabled'}",
)
@router.post("/algorithms/reset", response_model=dict)
async def reset_algorithm_settings(
db: AsyncSession = Depends(get_db_session),
current_user: User = Depends(get_current_user),
) -> dict:
"""Reset all algorithm settings to defaults (all enabled).
This endpoint resets the user's algorithm preferences to the system defaults,
which enable all algorithms with their standard weights.
"""
if current_user.preferences is None:
current_user.preferences = {}
# Clear the enabled_algorithms override
current_user.preferences["enabled_algorithms"] = {}
await db.flush()
await db.refresh(current_user)
return {
"message": "Algorithm settings reset to defaults",
"status": "success",
}