|
|
```python |
|
|
from fastapi import FastAPI, HTTPException, Depends, Security, Request, Response |
|
|
from fastapi.security import APIKeyHeader |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from pydantic import BaseModel |
|
|
from typing import Optional, List |
|
|
import logging |
|
|
import uvicorn |
|
|
import os |
|
|
|
|
|
|
|
|
API_KEY = os.getenv("API_KEY", "default-secret-key") |
|
|
BINANCE_API_KEY = os.getenv("BINANCE_API_KEY") |
|
|
BINANCE_SECRET_KEY = os.getenv("BINANCE_SECRET_KEY") |
|
|
app = FastAPI(title="CryptoPilot Pro API", version="0.1.0") |
|
|
|
|
|
@app.middleware("http") |
|
|
async def add_security_headers(request: Request, call_next): |
|
|
response = await call_next(request) |
|
|
|
|
|
|
|
|
response.headers["X-Frame-Options"] = "DENY" |
|
|
response.headers["X-Content-Type-Options"] = "nosniff" |
|
|
response.headers["X-XSS-Protection"] = "1; mode=block" |
|
|
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" |
|
|
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" |
|
|
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https:; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self' https: wss:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" |
|
|
response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()" |
|
|
|
|
|
|
|
|
if request.url.path.endswith(('.js', '.css', '.png', '.jpg', '.jpeg', '.gif', '.ico')): |
|
|
response.headers["Cache-Control"] = "public, max-age=31536000, immutable" |
|
|
else: |
|
|
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate" |
|
|
|
|
|
|
|
|
response.headers["X-Copyright"] = "© 2024 BIZRAH ELECTRONICS. All rights reserved." |
|
|
response.headers["X-Powered-By"] = "BIZRAH ELECTRONICS" |
|
|
|
|
|
return response |
|
|
|
|
|
from backend.services.model_server import router as model_router |
|
|
app.include_router(model_router) |
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["GET", "POST", "PUT", "DELETE"], |
|
|
allow_headers=["*"], |
|
|
expose_headers=["Content-Disposition"] |
|
|
) |
|
|
|
|
|
api_key_header = APIKeyHeader(name="X-API-KEY") |
|
|
|
|
|
async def get_api_key(api_key: str = Security(api_key_header)): |
|
|
if api_key != API_KEY: |
|
|
raise HTTPException(status_code=403, detail="Invalid API Key") |
|
|
return api_key |
|
|
|
|
|
|
|
|
class AccountBalance(BaseModel): |
|
|
asset: str |
|
|
free: float |
|
|
locked: float |
|
|
|
|
|
class Position(BaseModel): |
|
|
symbol: str |
|
|
positionAmt: float |
|
|
entryPrice: float |
|
|
markPrice: float |
|
|
unRealizedProfit: float |
|
|
leverage: int |
|
|
|
|
|
class Order(BaseModel): |
|
|
symbol: str |
|
|
orderId: int |
|
|
side: str |
|
|
positionSide: str |
|
|
type: str |
|
|
price: float |
|
|
qty: float |
|
|
status: str |
|
|
|
|
|
class Signal(BaseModel): |
|
|
symbol: str |
|
|
timeframe: str |
|
|
prob_up: float |
|
|
prob_down: float |
|
|
confidence: float |
|
|
advice: str |
|
|
|
|
|
|
|
|
@app.get("/api/account", |
|
|
dependencies=[Depends(get_api_key)], |
|
|
response_model=List[AccountBalance]) |
|
|
async def get_account(): |
|
|
"""Fetch account balance and open positions""" |
|
|
|
|
|
return { |
|
|
"balances": [ |
|
|
{"asset": "USDT", "free": 10000.0, "locked": 0.0}, |
|
|
{"asset": "BTC", "free": 0.5, "locked": 0.0} |
|
|
], |
|
|
"positions": [ |
|
|
{ |
|
|
"symbol": "BTCUSDT", |
|
|
"positionAmt": 0.1, |
|
|
"entryPrice": 50000.0, |
|
|
"markPrice": 51000.0, |
|
|
"unRealizedProfit": 100.0, |
|
|
"leverage": 10 |
|
|
} |
|
|
] |
|
|
} |
|
|
@app.get("/api/orders", |
|
|
dependencies=[Depends(get_api_key)], |
|
|
response_model=List[Order]) |
|
|
async def get_orders(symbol: Optional[str] = None): |
|
|
"""List all orders (optionally filtered by symbol)""" |
|
|
|
|
|
return [ |
|
|
{ |
|
|
"symbol": "BTCUSDT", |
|
|
"orderId": 123456, |
|
|
"side": "BUY", |
|
|
"positionSide": "LONG", |
|
|
"type": "LIMIT", |
|
|
"price": 50000.0, |
|
|
"qty": 0.1, |
|
|
"status": "FILLED" |
|
|
} |
|
|
] |
|
|
@app.post("/api/orders", |
|
|
dependencies=[Depends(get_api_key)], |
|
|
response_model=Dict[str, str]) |
|
|
async def create_order(): |
|
|
"""Place new order""" |
|
|
|
|
|
return {"status": "success", "message": "Order placed"} |
|
|
@app.get("/api/signal/{symbol}", |
|
|
dependencies=[Depends(get_api_key)], |
|
|
response_model=Signal) |
|
|
async def get_signal(symbol: str, timeframe: str = "5m"): |
|
|
"""Get model prediction for symbol""" |
|
|
try: |
|
|
|
|
|
binance = BinanceAdapter() |
|
|
klines = binance.get_klines( |
|
|
symbol=symbol, |
|
|
interval=timeframe, |
|
|
limit=100 |
|
|
) |
|
|
|
|
|
|
|
|
candles = [{ |
|
|
"open": float(k[1]), |
|
|
"high": float(k[2]), |
|
|
"low": float(k[3]), |
|
|
"close": float(k[4]), |
|
|
"volume": float(k[5]), |
|
|
"timestamp": k[0] |
|
|
} for k in klines] |
|
|
|
|
|
|
|
|
return await model_router.get_signal(symbol, candles) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error generating signal: {e}") |
|
|
return { |
|
|
"symbol": symbol, |
|
|
"timeframe": timeframe, |
|
|
"prob_up": 0.5, |
|
|
"prob_down": 0.5, |
|
|
"confidence": 0, |
|
|
"advice": "neutral" |
|
|
} |
|
|
@app.get("/api/risk/check", |
|
|
dependencies=[Depends(get_api_key)], |
|
|
response_model=Dict[str, Any]) |
|
|
async def risk_check(): |
|
|
"""Check if trade passes risk parameters""" |
|
|
|
|
|
return { |
|
|
"approved": True, |
|
|
"max_leverage": 10, |
|
|
"max_position_size": 0.5, |
|
|
"required_stop_loss": 49000.0 |
|
|
} |
|
|
|
|
|
if __name__ == "__main__": |
|
|
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True) |
|
|
``` |