Add CRUD endpoints for opportunities, trades, and system events

- Add GET endpoint for individual arbitrage opportunities
- Add POST endpoint for creating arbitrage opportunities
- Add GET endpoint for individual trades
- Add POST endpoint for creating trades
- Add endpoints for system events (GET list, GET by ID, POST)
- Update API router to include the new events endpoints
- Fix linting issues
This commit is contained in:
Automated Action 2025-06-05 19:43:15 +00:00
parent 572a7c0dd0
commit b7f18dcaec
5 changed files with 210 additions and 11 deletions

View File

@ -1,8 +1,9 @@
from fastapi import APIRouter
from app.api.api_v1.endpoints import status, opportunities, trades
from app.api.api_v1.endpoints import status, opportunities, trades, events
api_router = APIRouter(prefix="/api/v1")
api_router.include_router(status.router, prefix="/status", tags=["status"])
api_router.include_router(opportunities.router, prefix="/opportunities", tags=["opportunities"])
api_router.include_router(trades.router, prefix="/trades", tags=["trades"])
api_router.include_router(trades.router, prefix="/trades", tags=["trades"])
api_router.include_router(events.router, prefix="/events", tags=["events"])

View File

@ -0,0 +1,84 @@
from typing import Any, Optional, List
from fastapi import APIRouter, Depends, Query, HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import desc
from app.db.session import get_db
from app.models.arbitrage import SystemEvent
from app.schemas.arbitrage import SystemEventCreate, SystemEvent as SystemEventSchema
router = APIRouter()
@router.get("", response_model=List[SystemEventSchema])
async def get_system_events(
event_type: Optional[str] = Query(None, description="Filter by event type (startup, shutdown, error, warning, info)"),
component: Optional[str] = Query(None, description="Filter by component"),
limit: int = Query(50, ge=1, le=200, description="Number of events to return"),
offset: int = Query(0, ge=0, description="Pagination offset"),
db: Session = Depends(get_db)
) -> Any:
"""
Retrieve system events with optional filtering.
"""
query = db.query(SystemEvent)
# Apply filters
if event_type:
query = query.filter(SystemEvent.event_type == event_type)
if component:
query = query.filter(SystemEvent.component == component)
# Get paginated results
events = query.order_by(desc(SystemEvent.timestamp)).offset(offset).limit(limit).all()
return events
@router.get("/{event_id}", response_model=SystemEventSchema)
async def get_system_event(
event_id: int,
db: Session = Depends(get_db)
) -> Any:
"""
Get a specific system event by ID.
"""
event = db.query(SystemEvent).filter(SystemEvent.id == event_id).first()
if not event:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"System event with ID {event_id} not found"
)
return event
@router.post("", response_model=SystemEventSchema, status_code=status.HTTP_201_CREATED)
async def create_system_event(
event_in: SystemEventCreate,
db: Session = Depends(get_db)
) -> Any:
"""
Create a new system event.
"""
# Validate event type
valid_event_types = ["startup", "shutdown", "error", "warning", "info"]
if event_in.event_type not in valid_event_types:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid event type. Must be one of: {', '.join(valid_event_types)}"
)
# Create the system event
db_event = SystemEvent(
event_type=event_in.event_type,
component=event_in.component,
message=event_in.message,
details=event_in.details
)
db.add(db_event)
db.commit()
db.refresh(db_event)
return db_event

View File

@ -1,12 +1,12 @@
from datetime import datetime
from typing import Any, Optional
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, HTTPException, status
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
from app.schemas.arbitrage import OpportunitiesList, ArbitrageOpportunityCreate, ArbitrageOpportunity as ArbitrageOpportunitySchema
router = APIRouter()
@ -45,4 +45,53 @@ async def get_arbitrage_opportunities(
"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

View File

@ -1,12 +1,12 @@
from datetime import datetime, timedelta
from typing import Any, Optional
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import desc, func
from app.db.session import get_db
from app.models.arbitrage import Trade
from app.schemas.arbitrage import TradesList
from app.models.arbitrage import Trade, ArbitrageOpportunity
from app.schemas.arbitrage import TradesList, TradeCreate, Trade as TradeSchema
router = APIRouter()
@ -53,4 +53,70 @@ async def get_trades(
"count": total_count,
"timestamp": datetime.utcnow(),
"total_profit_usd": total_profit
}
}
@router.get("/{trade_id}", response_model=TradeSchema)
async def get_trade(
trade_id: int,
db: Session = Depends(get_db)
) -> Any:
"""
Get a specific trade by ID.
"""
trade = db.query(Trade).filter(Trade.id == trade_id).first()
if not trade:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Trade with ID {trade_id} not found"
)
return trade
@router.post("", response_model=TradeSchema, status_code=status.HTTP_201_CREATED)
async def create_trade(
trade_in: TradeCreate,
db: Session = Depends(get_db)
) -> Any:
"""
Create a new trade record.
"""
# Check if the opportunity exists
opportunity = db.query(ArbitrageOpportunity).filter(
ArbitrageOpportunity.id == trade_in.opportunity_id
).first()
if not opportunity:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Arbitrage opportunity with ID {trade_in.opportunity_id} not found"
)
# Create the trade
db_trade = Trade(
opportunity_id=trade_in.opportunity_id,
token_address=trade_in.token_address,
token_symbol=trade_in.token_symbol,
source_dex=trade_in.source_dex,
target_dex=trade_in.target_dex,
input_amount=trade_in.input_amount,
input_amount_usd=trade_in.input_amount_usd,
output_amount=trade_in.output_amount,
output_amount_usd=trade_in.output_amount_usd,
profit_amount=trade_in.profit_amount,
profit_amount_usd=trade_in.profit_amount_usd,
profit_percent=trade_in.profit_percent,
tx_signature=trade_in.tx_signature,
tx_status=trade_in.tx_status,
tx_error=trade_in.tx_error
)
db.add(db_trade)
# Mark the opportunity as executed
opportunity.was_executed = True
db.commit()
db.refresh(db_trade)
return db_trade

View File

@ -3,6 +3,7 @@ import logging
from typing import Dict, List, Optional, Any, Tuple
import base64
import base58
from app.core.config import settings
logger = logging.getLogger(__name__)
@ -54,8 +55,6 @@ except ImportError:
class TxOpts:
pass
from app.core.config import settings
# Initialize Solana client
solana_client = Client(settings.SOLANA_RPC_URL)