Files

360 lines
13 KiB
Python
Raw Permalink Normal View History

from typing import Dict, Any, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from src.core.database import Exchange, get_database
from src.core.config import get_config
from src.security.key_manager import get_key_manager
from src.trading.paper_trading import get_paper_trading
router = APIRouter()
class RiskSettings(BaseModel):
max_drawdown_percent: float
daily_loss_limit_percent: float
position_size_percent: float
class PaperTradingSettings(BaseModel):
initial_capital: float
fee_exchange: str = "coinbase" # Which exchange's fee model to use
class LoggingSettings(BaseModel):
level: str
dir: str
retention_days: int
class GeneralSettings(BaseModel):
timezone: str = "UTC"
theme: str = "dark"
currency: str = "USD"
class ExchangeCreate(BaseModel):
name: str
api_key: Optional[str] = None
api_secret: Optional[str] = None
sandbox: bool = False
read_only: bool = True
enabled: bool = True
class ExchangeUpdate(BaseModel):
api_key: Optional[str] = None
api_secret: Optional[str] = None
sandbox: Optional[bool] = None
read_only: Optional[bool] = None
enabled: Optional[bool] = None
@router.get("/risk")
async def get_risk_settings():
"""Get risk management settings."""
try:
config = get_config()
return {
"max_drawdown_percent": config.get("risk.max_drawdown_percent", 20.0),
"daily_loss_limit_percent": config.get("risk.daily_loss_limit_percent", 5.0),
"position_size_percent": config.get("risk.position_size_percent", 2.0),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/risk")
async def update_risk_settings(settings: RiskSettings):
"""Update risk management settings."""
try:
config = get_config()
config.set("risk.max_drawdown_percent", settings.max_drawdown_percent)
config.set("risk.daily_loss_limit_percent", settings.daily_loss_limit_percent)
config.set("risk.position_size_percent", settings.position_size_percent)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/paper-trading")
async def get_paper_trading_settings():
"""Get paper trading settings."""
try:
config = get_config()
fee_exchange = config.get("paper_trading.fee_exchange", "coinbase")
# Get fee rates for current exchange
fee_rates = config.get(f"trading.exchanges.{fee_exchange}.fees",
config.get("trading.default_fees", {"maker": 0.001, "taker": 0.001}))
return {
"initial_capital": config.get("paper_trading.default_capital", 100.0),
"fee_exchange": fee_exchange,
"fee_rates": fee_rates,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/paper-trading")
async def update_paper_trading_settings(settings: PaperTradingSettings):
"""Update paper trading settings."""
try:
config = get_config()
config.set("paper_trading.default_capital", settings.initial_capital)
config.set("paper_trading.fee_exchange", settings.fee_exchange)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/paper-trading/fee-exchanges")
async def get_available_fee_exchanges():
"""Get available exchange fee models for paper trading."""
try:
config = get_config()
exchanges_config = config.get("trading.exchanges", {})
default_fees = config.get("trading.default_fees", {"maker": 0.001, "taker": 0.001})
current = config.get("paper_trading.fee_exchange", "coinbase")
exchanges = [{"name": "default", "fees": default_fees}]
for name, data in exchanges_config.items():
if "fees" in data:
exchanges.append({
"name": name,
"fees": data["fees"]
})
return {
"exchanges": exchanges,
"current": current,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/paper-trading/reset")
async def reset_paper_account():
"""Reset paper trading account."""
try:
paper_trading = get_paper_trading()
# Reset to capital from config
success = await paper_trading.reset()
if success:
return {"status": "success", "message": "Paper account reset successfully"}
else:
raise HTTPException(status_code=500, detail="Failed to reset paper account")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/logging")
async def get_logging_settings():
"""Get logging settings."""
try:
config = get_config()
return {
"level": config.get("logging.level", "INFO"),
"dir": config.get("logging.dir", ""),
"retention_days": config.get("logging.retention_days", 30),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/logging")
async def update_logging_settings(settings: LoggingSettings):
"""Update logging settings."""
try:
config = get_config()
config.set("logging.level", settings.level)
config.set("logging.dir", settings.dir)
config.set("logging.retention_days", settings.retention_days)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/general")
async def get_general_settings():
"""Get general settings."""
try:
config = get_config()
return {
"timezone": config.get("general.timezone", "UTC"),
"theme": config.get("general.theme", "dark"),
"currency": config.get("general.currency", "USD"),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/general")
async def update_general_settings(settings: GeneralSettings):
"""Update general settings."""
try:
config = get_config()
config.set("general.timezone", settings.timezone)
config.set("general.theme", settings.theme)
config.set("general.currency", settings.currency)
config.save()
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/exchanges")
async def create_exchange(exchange: ExchangeCreate):
"""Create a new exchange."""
try:
db = get_database()
async with db.get_session() as session:
from src.exchanges.factory import ExchangeFactory
from src.exchanges.public_data import PublicDataAdapter
# Check if this is a public data exchange
adapter_class = None
try:
if hasattr(ExchangeFactory, '_adapters'):
adapter_class = ExchangeFactory._adapters.get(exchange.name.lower())
except:
pass
is_public_data = adapter_class == PublicDataAdapter if adapter_class else False
# Only require API keys for non-public-data exchanges
if not is_public_data:
if not exchange.api_key or not exchange.api_secret:
raise HTTPException(status_code=400, detail="API key and secret are required")
new_exchange = Exchange(
name=exchange.name,
enabled=exchange.enabled
)
session.add(new_exchange)
await session.flush()
# Save credentials
key_manager = get_key_manager()
key_manager.update_exchange(
new_exchange.id,
api_key=exchange.api_key or "",
api_secret=exchange.api_secret or "",
read_only=exchange.read_only if not is_public_data else True,
sandbox=exchange.sandbox if not is_public_data else False
)
await session.commit()
return {"id": new_exchange.id, "name": new_exchange.name, "status": "created"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/exchanges/{exchange_id}")
async def update_exchange(exchange_id: int, exchange: ExchangeUpdate):
"""Update an existing exchange."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange_obj = result.scalar_one_or_none()
if not exchange_obj:
raise HTTPException(status_code=404, detail="Exchange not found")
key_manager = get_key_manager()
credentials = key_manager.get_exchange_credentials(exchange_id)
# Update credentials if provided
if exchange.api_key is not None or exchange.api_secret is not None:
key_manager.update_exchange(
exchange_id,
api_key=exchange.api_key or credentials.get('api_key', ''),
api_secret=exchange.api_secret or credentials.get('api_secret', ''),
read_only=exchange.read_only if exchange.read_only is not None else credentials.get('read_only', True),
sandbox=exchange.sandbox if exchange.sandbox is not None else credentials.get('sandbox', False)
)
if exchange.enabled is not None:
exchange_obj.enabled = exchange.enabled
await session.commit()
return {"status": "success"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/exchanges/{exchange_id}")
async def delete_exchange(exchange_id: int):
"""Delete an exchange."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange = result.scalar_one_or_none()
if not exchange:
raise HTTPException(status_code=404, detail="Exchange not found")
await session.delete(exchange)
await session.commit()
return {"status": "success"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/exchanges/{exchange_id}/test")
async def test_exchange_connection(exchange_id: int):
"""Test exchange connection."""
try:
db = get_database()
async with db.get_session() as session:
stmt = select(Exchange).where(Exchange.id == exchange_id)
result = await session.execute(stmt)
exchange = result.scalar_one_or_none()
if not exchange:
raise HTTPException(status_code=404, detail="Exchange not found")
from src.exchanges.factory import ExchangeFactory
from src.exchanges.public_data import PublicDataAdapter
# Get adapter class safely
adapter_class = None
try:
if hasattr(ExchangeFactory, '_adapters'):
adapter_class = ExchangeFactory._adapters.get(exchange.name.lower())
except:
pass
is_public_data = adapter_class == PublicDataAdapter if adapter_class else False
key_manager = get_key_manager()
if not is_public_data and not key_manager.get_exchange_credentials(exchange_id):
raise HTTPException(status_code=400, detail="No credentials found for this exchange")
adapter = ExchangeFactory.create(exchange_id)
if adapter and adapter.connect():
try:
if is_public_data:
adapter.get_ticker("BTC/USDT")
else:
adapter.get_balance()
return {"status": "success", "message": "Connection successful"}
except Exception as e:
return {"status": "error", "message": f"Connected but failed to fetch data: {str(e)}"}
else:
return {"status": "error", "message": "Failed to connect"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))