mirror of
https://github.com/The-Swarm-Corporation/AutoHedge.git
synced 2026-10-13 05:20:10 +08:00
476 lines
15 KiB
Python
476 lines
15 KiB
Python
import time
|
|
import uuid
|
|
from contextlib import contextmanager
|
|
from datetime import datetime, timedelta, timezone
|
|
from enum import Enum
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
import requests
|
|
from fastapi import (
|
|
Depends,
|
|
FastAPI,
|
|
HTTPException,
|
|
Path,
|
|
Query,
|
|
Security,
|
|
)
|
|
from fastapi.security import APIKeyHeader
|
|
from loguru import logger
|
|
from pydantic import BaseModel, EmailStr, Field
|
|
|
|
from autohedge.main import AutoHedge
|
|
|
|
|
|
def log_agent_data(data_dict: dict) -> dict | None:
|
|
"""
|
|
Logs agent data to the Swarms database with retry logic.
|
|
|
|
Args:
|
|
data_dict (dict): The dictionary containing the agent data to be logged.
|
|
retry_attempts (int, optional): The number of retry attempts in case of failure. Defaults to 3.
|
|
|
|
Returns:
|
|
dict | None: The JSON response from the server if successful, otherwise None.
|
|
|
|
Raises:
|
|
ValueError: If data_dict is empty or invalid
|
|
requests.exceptions.RequestException: If API request fails after all retries
|
|
"""
|
|
if not data_dict:
|
|
logger.error("Empty data dictionary provided")
|
|
raise ValueError("data_dict cannot be empty")
|
|
|
|
url = "https://swarms.world/api/get-agents/log-agents"
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Bearer sk-f24a13ed139f757d99cdd9cdcae710fccead92681606a97086d9711f69d44869",
|
|
}
|
|
|
|
requests.post(url, json=data_dict, headers=headers, timeout=10)
|
|
# response.raise_for_status()
|
|
|
|
return None
|
|
|
|
|
|
# Enhanced Pydantic Models
|
|
class TradeStatus(str, Enum):
|
|
PENDING = "pending"
|
|
EXECUTING = "executing"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
username: str = Field(..., min_length=3, max_length=50)
|
|
email: EmailStr
|
|
fund_name: str = Field(..., min_length=3, max_length=100)
|
|
fund_description: Optional[str] = Field(None, max_length=500)
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
fund_name: Optional[str] = Field(
|
|
None, min_length=3, max_length=100
|
|
)
|
|
fund_description: Optional[str] = Field(None, max_length=500)
|
|
email: Optional[EmailStr] = None
|
|
|
|
|
|
class User(UserCreate):
|
|
id: str
|
|
api_key: str
|
|
created_at: datetime
|
|
last_login: Optional[datetime] = None
|
|
is_active: bool = True
|
|
|
|
|
|
class TradingTask(BaseModel):
|
|
stocks: List[str] = Field(..., min_items=1)
|
|
task: str = Field(..., min_length=10)
|
|
allocation: float = Field(..., gt=0)
|
|
strategy_type: Optional[str] = Field(
|
|
None, description="Trading strategy type"
|
|
)
|
|
risk_level: Optional[int] = Field(
|
|
None, ge=1, le=10, description="Risk level from 1-10"
|
|
)
|
|
|
|
|
|
class TradeResponse(BaseModel):
|
|
id: str
|
|
user_id: str
|
|
task: TradingTask
|
|
status: TradeStatus
|
|
created_at: datetime
|
|
executed_at: Optional[datetime]
|
|
result: Optional[Dict[str, Any]]
|
|
performance_metrics: Optional[Dict[str, float]]
|
|
|
|
|
|
class HistoricalAnalytics(BaseModel):
|
|
total_trades: int
|
|
success_rate: float
|
|
average_return: float
|
|
total_allocation: float
|
|
risk_adjusted_return: float
|
|
top_performing_stocks: List[Tuple[str, float]]
|
|
|
|
|
|
# Configure Loguru with more detailed formatting
|
|
logger.add(
|
|
"logs/autohedge_{time}.log",
|
|
rotation="500 MB",
|
|
retention="10 days",
|
|
level="INFO",
|
|
format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {module}:{function}:{line} | {message}",
|
|
backtrace=True,
|
|
diagnose=True,
|
|
)
|
|
|
|
|
|
class AutoHedgeAPI:
|
|
def __init__(self, *args, **kwargs):
|
|
self.app = FastAPI(
|
|
title="AutoHedge API",
|
|
version="1.0.0",
|
|
description="Production-grade API for automated hedge fund management",
|
|
*args,
|
|
**kwargs,
|
|
)
|
|
self.api_key_header = APIKeyHeader(name="X-API-Key")
|
|
self.users: Dict[str, User] = {}
|
|
self.api_keys: Dict[str, str] = {}
|
|
self.trades: Dict[str, TradeResponse] = {}
|
|
self.performance_cache: Dict[str, Dict] = {}
|
|
|
|
self._setup_routes()
|
|
logger.info(
|
|
"AutoHedge API initialized with enhanced features"
|
|
)
|
|
|
|
@contextmanager
|
|
def _log_execution_time(
|
|
self, operation: str, user_id: Optional[str] = None
|
|
):
|
|
start_time = time.time()
|
|
try:
|
|
yield
|
|
finally:
|
|
execution_time = time.time() - start_time
|
|
{
|
|
"operation": operation,
|
|
"execution_time": execution_time,
|
|
"user_id": user_id,
|
|
"timestamp": datetime.now(timezone.utc),
|
|
}
|
|
# logger.info(f"Operation '{operation}' metrics: {json_data}")
|
|
|
|
async def _get_current_user(
|
|
self, api_key: str = Security(APIKeyHeader(name="X-API-Key"))
|
|
) -> User:
|
|
user_id = self.api_keys.get(api_key)
|
|
if not user_id or user_id not in self.users:
|
|
logger.warning(
|
|
f"Invalid API key attempt: {api_key[:8]}..."
|
|
)
|
|
raise HTTPException(
|
|
status_code=401, detail="Invalid API key"
|
|
)
|
|
|
|
user = self.users[user_id]
|
|
user.last_login = datetime.now(timezone.utc)
|
|
return user
|
|
|
|
def _calculate_performance_metrics(
|
|
self, trade_result: Dict
|
|
) -> Dict[str, float]:
|
|
"""Calculate performance metrics for a trade"""
|
|
try:
|
|
# Implementation would depend on the structure of trade_result
|
|
return {
|
|
"return_percentage": 0.0, # Placeholder
|
|
"sharpe_ratio": 0.0,
|
|
"max_drawdown": 0.0,
|
|
"volatility": 0.0,
|
|
}
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Error calculating performance metrics: {str(e)}"
|
|
)
|
|
return {}
|
|
|
|
def _setup_routes(self):
|
|
# User Management Routes
|
|
@self.app.post("/users", response_model=User)
|
|
async def create_user(user: UserCreate):
|
|
with self._log_execution_time("create_user"):
|
|
user_id = str(uuid.uuid4())
|
|
api_key = str(uuid.uuid4())
|
|
|
|
user_data = User(
|
|
id=user_id,
|
|
api_key=api_key,
|
|
created_at=datetime.now(timezone.utc),
|
|
**user.dict(),
|
|
)
|
|
|
|
self.users[user_id] = user_data
|
|
self.api_keys[api_key] = user_id
|
|
|
|
logger.info(f"New user created: {user.username}")
|
|
return user_data
|
|
|
|
@self.app.get("/users/me", response_model=User)
|
|
async def get_current_user(
|
|
current_user: User = Depends(self._get_current_user),
|
|
):
|
|
return current_user
|
|
|
|
@self.app.put("/users/me", response_model=User)
|
|
async def update_user(
|
|
user_update: UserUpdate,
|
|
current_user: User = Depends(self._get_current_user),
|
|
):
|
|
with self._log_execution_time(
|
|
"update_user", current_user.id
|
|
):
|
|
updated_data = current_user.dict()
|
|
updated_data.update(
|
|
user_update.dict(exclude_unset=True)
|
|
)
|
|
|
|
updated_user = User(**updated_data)
|
|
self.users[current_user.id] = updated_user
|
|
|
|
logger.info(f"User updated: {current_user.id}")
|
|
return updated_user
|
|
|
|
# Trading Routes
|
|
@self.app.post("/trades", response_model=TradeResponse)
|
|
async def create_trade(
|
|
task: TradingTask,
|
|
current_user: User = Depends(self._get_current_user),
|
|
):
|
|
with self._log_execution_time(
|
|
"create_trade", current_user.id
|
|
):
|
|
trade_id = str(uuid.uuid4())
|
|
|
|
try:
|
|
trading_system = AutoHedge(
|
|
name=current_user.fund_name,
|
|
description=current_user.fund_description
|
|
or "",
|
|
stocks=task.stocks,
|
|
)
|
|
|
|
trade_response = TradeResponse(
|
|
id=trade_id,
|
|
user_id=current_user.id,
|
|
task=task,
|
|
status=TradeStatus.EXECUTING,
|
|
created_at=datetime.now(timezone.utc),
|
|
executed_at=None,
|
|
result=None,
|
|
performance_metrics=None,
|
|
)
|
|
|
|
# Execute trade
|
|
result = trading_system.run(task=task.task)
|
|
|
|
# Update trade response
|
|
trade_response.status = TradeStatus.COMPLETED
|
|
trade_response.executed_at = datetime.now(
|
|
timezone.utc
|
|
)
|
|
trade_response.result = result
|
|
trade_response.performance_metrics = (
|
|
self._calculate_performance_metrics(result)
|
|
)
|
|
|
|
# Store trade
|
|
self.trades[trade_id] = trade_response
|
|
|
|
# Log to Swarms
|
|
self._log_to_swarms(
|
|
{
|
|
"trade_id": trade_id,
|
|
"user_id": current_user.id,
|
|
"task": task.dict(),
|
|
"result": result,
|
|
"performance_metrics": trade_response.performance_metrics,
|
|
}
|
|
)
|
|
|
|
return trade_response
|
|
|
|
except Exception as e:
|
|
logger.error(f"Trade execution failed: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500, detail=str(e)
|
|
)
|
|
|
|
@self.app.get("/trades", response_model=List[TradeResponse])
|
|
async def list_trades(
|
|
current_user: User = Depends(self._get_current_user),
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(10, ge=1, le=100),
|
|
status: Optional[TradeStatus] = None,
|
|
):
|
|
with self._log_execution_time(
|
|
"list_trades", current_user.id
|
|
):
|
|
user_trades = [
|
|
trade
|
|
for trade in self.trades.values()
|
|
if trade.user_id == current_user.id
|
|
and (status is None or trade.status == status)
|
|
]
|
|
|
|
return sorted(
|
|
user_trades,
|
|
key=lambda x: x.created_at,
|
|
reverse=True,
|
|
)[skip : skip + limit]
|
|
|
|
@self.app.get(
|
|
"/trades/{trade_id}", response_model=TradeResponse
|
|
)
|
|
async def get_trade(
|
|
trade_id: str = Path(
|
|
..., title="The ID of the trade to get"
|
|
),
|
|
current_user: User = Depends(self._get_current_user),
|
|
):
|
|
if trade_id not in self.trades:
|
|
raise HTTPException(
|
|
status_code=404, detail="Trade not found"
|
|
)
|
|
|
|
trade = self.trades[trade_id]
|
|
if trade.user_id != current_user.id:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="Not authorized to access this trade",
|
|
)
|
|
|
|
return trade
|
|
|
|
@self.app.delete("/trades/{trade_id}")
|
|
async def delete_trade(
|
|
trade_id: str = Path(
|
|
..., title="The ID of the trade to delete"
|
|
),
|
|
current_user: User = Depends(self._get_current_user),
|
|
):
|
|
if trade_id not in self.trades:
|
|
raise HTTPException(
|
|
status_code=404, detail="Trade not found"
|
|
)
|
|
|
|
trade = self.trades[trade_id]
|
|
if trade.user_id != current_user.id:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="Not authorized to delete this trade",
|
|
)
|
|
|
|
del self.trades[trade_id]
|
|
logger.info(f"Trade deleted: {trade_id}")
|
|
return {"message": "Trade deleted successfully"}
|
|
|
|
# Analytics Routes
|
|
@self.app.get(
|
|
"/analytics/history", response_model=HistoricalAnalytics
|
|
)
|
|
async def get_historical_analytics(
|
|
current_user: User = Depends(self._get_current_user),
|
|
days: int = Query(30, ge=1, le=365),
|
|
):
|
|
with self._log_execution_time(
|
|
"get_historical_analytics", current_user.id
|
|
):
|
|
start_date = datetime.now(timezone.utc) - timedelta(
|
|
days=days
|
|
)
|
|
|
|
# Filter user's trades within the time period
|
|
user_trades = [
|
|
trade
|
|
for trade in self.trades.values()
|
|
if trade.user_id == current_user.id
|
|
and trade.created_at >= start_date
|
|
]
|
|
|
|
if not user_trades:
|
|
return HistoricalAnalytics(
|
|
total_trades=0,
|
|
success_rate=0.0,
|
|
average_return=0.0,
|
|
total_allocation=0.0,
|
|
risk_adjusted_return=0.0,
|
|
top_performing_stocks=[],
|
|
)
|
|
|
|
# Calculate analytics
|
|
successful_trades = len(
|
|
[
|
|
t
|
|
for t in user_trades
|
|
if t.status == TradeStatus.COMPLETED
|
|
]
|
|
)
|
|
total_trades = len(user_trades)
|
|
|
|
analytics = HistoricalAnalytics(
|
|
total_trades=total_trades,
|
|
success_rate=successful_trades
|
|
/ total_trades
|
|
* 100,
|
|
average_return=sum(
|
|
t.performance_metrics.get(
|
|
"return_percentage", 0
|
|
)
|
|
for t in user_trades
|
|
)
|
|
/ total_trades,
|
|
total_allocation=sum(
|
|
t.task.allocation for t in user_trades
|
|
),
|
|
risk_adjusted_return=0.0, # Would need more complex calculation
|
|
top_performing_stocks=[], # Would need aggregation of stock performance
|
|
)
|
|
|
|
return analytics
|
|
|
|
def _log_to_swarms(self, data_dict: dict) -> None:
|
|
"""Log data to Swarms with error handling and retry logic"""
|
|
try:
|
|
log_agent_data(data_dict)
|
|
logger.info("Successfully logged data to Swarms")
|
|
except Exception as e:
|
|
logger.error(f"Failed to log to Swarms: {str(e)}")
|
|
# Store failed logs for potential retry
|
|
self._store_failed_log(data_dict)
|
|
|
|
def _store_failed_log(self, data_dict: dict) -> None:
|
|
"""Store failed logs for retry"""
|
|
logger.info("Storing failed log for later retry")
|
|
# Implementation would depend on your storage solution
|
|
|
|
def run(
|
|
self, host: str = "0.0.0.0", port: int = 8000, *args, **kwargs
|
|
):
|
|
"""Run the FastAPI application"""
|
|
import uvicorn
|
|
|
|
logger.info(
|
|
f"Starting Enhanced AutoHedge API on {host}:{port}"
|
|
)
|
|
uvicorn.run(self.app, host=host, port=port, *args, **kwargs)
|
|
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
api = AutoHedgeAPI()
|
|
api.run()
|