Files
crypto_trader/backend/api/settings.py

360 lines
13 KiB
Python

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))