from datetime import datetime from typing import Any, Optional from fastapi import APIRouter, Depends, Query, HTTPException, status, Response from sqlalchemy.orm import Session from sqlalchemy import desc from app.db.session import get_db from app.models.arbitrage import ArbitrageOpportunity from app.schemas.arbitrage import ( OpportunitiesList, ArbitrageOpportunityCreate, ArbitrageOpportunityUpdate, ArbitrageOpportunity as ArbitrageOpportunitySchema ) router = APIRouter() @router.get("", response_model=OpportunitiesList) async def get_arbitrage_opportunities( viable_only: bool = Query(True, description="Show only viable opportunities that meet profit threshold"), token_address: Optional[str] = Query(None, description="Filter by specific token address"), min_profit_percent: Optional[float] = Query(None, description="Filter by minimum profit percentage"), limit: int = Query(20, ge=1, le=100, description="Number of opportunities to return"), offset: int = Query(0, ge=0, description="Pagination offset"), db: Session = Depends(get_db) ) -> Any: """ Retrieve arbitrage opportunities with optional filtering. """ query = db.query(ArbitrageOpportunity) # Apply filters if viable_only: query = query.filter(ArbitrageOpportunity.is_viable.is_(True)) if token_address: query = query.filter(ArbitrageOpportunity.token_address == token_address) if min_profit_percent is not None: query = query.filter(ArbitrageOpportunity.price_difference_percent >= min_profit_percent) # Get total count total_count = query.count() # Get paginated results opportunities = query.order_by(desc(ArbitrageOpportunity.created_at)).offset(offset).limit(limit).all() return { "opportunities": opportunities, "count": total_count, "timestamp": datetime.utcnow() } @router.get("/{opportunity_id}", response_model=ArbitrageOpportunitySchema) async def get_arbitrage_opportunity( opportunity_id: int, db: Session = Depends(get_db) ) -> Any: """ Get a specific arbitrage opportunity by ID. """ opportunity = db.query(ArbitrageOpportunity).filter(ArbitrageOpportunity.id == opportunity_id).first() if not opportunity: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Arbitrage opportunity with ID {opportunity_id} not found" ) return opportunity @router.post("", response_model=ArbitrageOpportunitySchema, status_code=status.HTTP_201_CREATED) async def create_arbitrage_opportunity( opportunity_in: ArbitrageOpportunityCreate, db: Session = Depends(get_db) ) -> Any: """ Create a new arbitrage opportunity. """ db_opportunity = ArbitrageOpportunity( token_address=opportunity_in.token_address, token_symbol=opportunity_in.token_symbol, source_dex=opportunity_in.source_dex, target_dex=opportunity_in.target_dex, source_price=opportunity_in.source_price, target_price=opportunity_in.target_price, price_difference=opportunity_in.price_difference, price_difference_percent=opportunity_in.price_difference_percent, estimated_profit_usd=opportunity_in.estimated_profit_usd, estimated_profit_token=opportunity_in.estimated_profit_token, max_trade_amount_usd=opportunity_in.max_trade_amount_usd, max_trade_amount_token=opportunity_in.max_trade_amount_token, slippage_estimate=opportunity_in.slippage_estimate, fees_estimate=opportunity_in.fees_estimate, is_viable=opportunity_in.is_viable, was_executed=False ) db.add(db_opportunity) db.commit() db.refresh(db_opportunity) return db_opportunity @router.put("/{opportunity_id}", response_model=ArbitrageOpportunitySchema) async def update_arbitrage_opportunity( opportunity_id: int, opportunity_update: ArbitrageOpportunityUpdate, db: Session = Depends(get_db) ) -> Any: """ Update an existing arbitrage opportunity. """ db_opportunity = db.query(ArbitrageOpportunity).filter(ArbitrageOpportunity.id == opportunity_id).first() if not db_opportunity: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Arbitrage opportunity with ID {opportunity_id} not found" ) # Update fields if provided in the request update_data = opportunity_update.dict(exclude_unset=True) for field, value in update_data.items(): setattr(db_opportunity, field, value) db.commit() db.refresh(db_opportunity) return db_opportunity @router.delete("/{opportunity_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) async def delete_arbitrage_opportunity( opportunity_id: int, db: Session = Depends(get_db) ) -> None: """ Delete an arbitrage opportunity. """ db_opportunity = db.query(ArbitrageOpportunity).filter(ArbitrageOpportunity.id == opportunity_id).first() if not db_opportunity: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Arbitrage opportunity with ID {opportunity_id} not found" ) db.delete(db_opportunity) db.commit() return None