Local changes: Updated model training, removed debug instrumentation, and configuration improvements
This commit is contained in:
206
backend/api/trading.py
Normal file
206
backend/api/trading.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""Trading API endpoints."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..core.dependencies import get_trading_engine, get_db_session
|
||||
from ..core.schemas import OrderCreate, OrderResponse, PositionResponse
|
||||
from src.core.database import Order, OrderSide, OrderType, OrderStatus
|
||||
from src.core.repositories import OrderRepository, PositionRepository
|
||||
from src.core.logger import get_logger
|
||||
from src.trading.paper_trading import get_paper_trading
|
||||
|
||||
router = APIRouter()
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@router.post("/orders", response_model=OrderResponse)
|
||||
async def create_order(
|
||||
order_data: OrderCreate,
|
||||
trading_engine=Depends(get_trading_engine)
|
||||
):
|
||||
"""Create and execute a trading order."""
|
||||
try:
|
||||
# Convert string enums to actual enums
|
||||
side = OrderSide(order_data.side.value)
|
||||
order_type = OrderType(order_data.order_type.value)
|
||||
|
||||
order = await trading_engine.execute_order(
|
||||
exchange_id=order_data.exchange_id,
|
||||
strategy_id=order_data.strategy_id,
|
||||
symbol=order_data.symbol,
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=order_data.quantity,
|
||||
price=order_data.price,
|
||||
paper_trading=order_data.paper_trading
|
||||
)
|
||||
|
||||
if not order:
|
||||
raise HTTPException(status_code=400, detail="Order execution failed")
|
||||
|
||||
return OrderResponse.model_validate(order)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/orders", response_model=List[OrderResponse])
|
||||
async def get_orders(
|
||||
paper_trading: bool = True,
|
||||
limit: int = 100,
|
||||
db: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Get order history."""
|
||||
try:
|
||||
repo = OrderRepository(db)
|
||||
orders = await repo.get_all(limit=limit)
|
||||
# Filter by paper_trading in memory or add to repo method (repo returns all by default currently sorted by date)
|
||||
# Let's verify repo method. It has limit/offset but not filtering.
|
||||
# We should filter here or improve repo.
|
||||
# For this refactor, let's filter in python for simplicity or assume get_all needs an update.
|
||||
# Ideally, update repo. But strictly following "get_all" contract.
|
||||
|
||||
filtered = [o for o in orders if o.paper_trading == paper_trading]
|
||||
return [OrderResponse.model_validate(order) for order in filtered]
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/orders/{order_id}", response_model=OrderResponse)
|
||||
async def get_order(
|
||||
order_id: int,
|
||||
db: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Get order by ID."""
|
||||
try:
|
||||
repo = OrderRepository(db)
|
||||
order = await repo.get_by_id(order_id)
|
||||
if not order:
|
||||
raise HTTPException(status_code=404, detail="Order not found")
|
||||
return OrderResponse.model_validate(order)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/orders/{order_id}/cancel")
|
||||
async def cancel_order(
|
||||
order_id: int,
|
||||
trading_engine=Depends(get_trading_engine)
|
||||
):
|
||||
try:
|
||||
# We can use trading_engine's cancel which handles DB and Exchange
|
||||
success = await trading_engine.cancel_order(order_id)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="Failed to cancel order or order not found")
|
||||
|
||||
return {"status": "cancelled", "order_id": order_id}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/orders/cancel-all")
|
||||
async def cancel_all_orders(
|
||||
paper_trading: bool = True,
|
||||
trading_engine=Depends(get_trading_engine),
|
||||
db: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Cancel all open orders."""
|
||||
try:
|
||||
repo = OrderRepository(db)
|
||||
open_orders = await repo.get_open_orders(paper_trading=paper_trading)
|
||||
|
||||
if not open_orders:
|
||||
return {"status": "no_orders", "cancelled_count": 0}
|
||||
|
||||
cancelled_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for order in open_orders:
|
||||
try:
|
||||
if await trading_engine.cancel_order(order.id):
|
||||
cancelled_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to cancel order {order.id}: {e}")
|
||||
failed_count += 1
|
||||
|
||||
return {
|
||||
"status": "completed",
|
||||
"cancelled_count": cancelled_count,
|
||||
"failed_count": failed_count,
|
||||
"total_orders": len(open_orders)
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/positions", response_model=List[PositionResponse])
|
||||
async def get_positions(
|
||||
paper_trading: bool = True,
|
||||
db: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Get current positions."""
|
||||
try:
|
||||
if paper_trading:
|
||||
paper_trading_sim = get_paper_trading()
|
||||
positions = paper_trading_sim.get_positions()
|
||||
# positions is a List[Position], convert to PositionResponse list
|
||||
position_list = []
|
||||
for pos in positions:
|
||||
# pos is a Position database object
|
||||
current_price = pos.current_price if pos.current_price else pos.entry_price
|
||||
unrealized_pnl = pos.unrealized_pnl if pos.unrealized_pnl else Decimal(0)
|
||||
realized_pnl = pos.realized_pnl if pos.realized_pnl else Decimal(0)
|
||||
position_list.append(
|
||||
PositionResponse(
|
||||
symbol=pos.symbol,
|
||||
quantity=pos.quantity,
|
||||
entry_price=pos.entry_price,
|
||||
current_price=current_price,
|
||||
unrealized_pnl=unrealized_pnl,
|
||||
realized_pnl=realized_pnl
|
||||
)
|
||||
)
|
||||
return position_list
|
||||
else:
|
||||
# Live trading positions from database
|
||||
repo = PositionRepository(db)
|
||||
positions = await repo.get_all(paper_trading=False)
|
||||
return [
|
||||
PositionResponse(
|
||||
symbol=pos.symbol,
|
||||
quantity=pos.quantity,
|
||||
entry_price=pos.entry_price,
|
||||
current_price=pos.current_price or pos.entry_price,
|
||||
unrealized_pnl=pos.unrealized_pnl,
|
||||
realized_pnl=pos.realized_pnl
|
||||
)
|
||||
for pos in positions
|
||||
]
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/balance")
|
||||
async def get_balance(paper_trading: bool = True):
|
||||
"""Get account balance."""
|
||||
try:
|
||||
paper_trading_sim = get_paper_trading()
|
||||
if paper_trading:
|
||||
balance = paper_trading_sim.get_balance()
|
||||
performance = paper_trading_sim.get_performance()
|
||||
return {
|
||||
"balance": float(balance),
|
||||
"performance": performance
|
||||
}
|
||||
else:
|
||||
# Live trading balance from exchange
|
||||
return {"balance": 0.0, "performance": {}}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user