feat: add liquidity_sweep and price_action_reversal algorithms (#15, #16)

- Add detect_liquidity_levels() and detect_price_action_signal() to indicator_service.py
- Wire both algorithms into candle_service.py get_indicators()
- Add scoring logic and correlation groups in signal_scoring.py
- Add liquidity_sweep and price_action_reversal to STRATEGY_NAMES/DISPLAY in strategy.py
- Add funding_service.py for funding_oi algorithm (algorithm #14)
- Add rate_limiter.py for auth endpoints
- Fix: add slowapi==0.1.9 to Dockerfile
- Fix: get_db -> get_db_session in analytics.py
- Fix: remove from __future__ import annotations in auth.py

System now runs 16 algorithms: 13 original + funding_oi + liquidity_sweep + price_action_reversal
This commit is contained in:
2026-07-12 10:38:44 +00:00
parent be28fdb983
commit e258d51f37
9 changed files with 732 additions and 17 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ async def analytics_root():
@router.get("/performance")
async def get_performance(db: AsyncSession = Depends(get_db)):
async def get_performance(db: AsyncSession = Depends(get_db_session)):
"""Performance summary: win rate, PnL, profit factor."""
try:
# Try to use materialized view first (faster)
+34 -9
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, Depends, Request, status
@@ -7,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_user, get_db_session
from app.core.exceptions import AppException
from app.core.rate_limiter import limiter, is_login_locked, record_failed_login, clear_failed_attempts
from app.models import User
from app.schemas import (
ChangePasswordRequest,
@@ -25,8 +24,10 @@ router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/register", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
@limiter.limit("3/hour")
async def register(
req: RegisterRequest,
request: Request,
db: AsyncSession = Depends(get_db_session),
) -> UserResponse:
"""Register a new user account."""
@@ -34,18 +35,42 @@ async def register(
@router.post("/login", response_model=TokenResponse)
@limiter.limit("5/15 minutes")
async def login(
req: LoginRequest,
request: Request,
db: AsyncSession = Depends(get_db_session),
) -> TokenResponse:
"""Authenticate with username/password and receive a token pair."""
return await auth_service.login(
db=db,
req=req,
user_agent=request.headers.get("User-Agent", ""),
ip_address=request.client.host if request.client else "",
)
"""Authenticate with username/password and receive a token pair.
Rate limited: 5 per 15 min. After 5 failed attempts from the same IP
within 15 minutes, further login attempts are blocked for 15 minutes.
"""
ip_address = request.client.host if request.client else "unknown"
# Check if this IP is locked due to too many failed attempts
is_locked, seconds_until_unlock = is_login_locked(ip_address)
if is_locked:
raise AppException(
status_code=429,
detail=f"Too many failed login attempts. Try again in {seconds_until_unlock} seconds.",
error_code="LOGIN_LOCKED"
)
try:
result = await auth_service.login(
db=db,
req=req,
user_agent=request.headers.get("User-Agent", ""),
ip_address=ip_address,
)
# Clear failed attempts on successful login
clear_failed_attempts(ip_address)
return result
except AppException:
# Record failed login attempt
record_failed_login(ip_address, reason="invalid_credentials")
raise
@router.post("/refresh", response_model=TokenResponse)