| import uuid |
|
|
| from fastapi import APIRouter, HTTPException |
|
|
| from app.models.schemas import OrderRequest, StrategyConfig |
| from app.services.backtest_engine import STRATEGY_TEMPLATES, run_backtest |
| from app.services.market_data import market_data_service |
| from app.services.order_manager import order_manager |
| from app.services.pattern_detector import detect_patterns, get_pattern_registry |
| from app.services.risk_manager import risk_manager |
| from app.services.strategy_engine import strategy_engine |
|
|
| router = APIRouter() |
|
|
|
|
| |
|
|
| @router.get("/market/quotes") |
| async def get_quotes(category: str | None = None, exchange: str | None = None): |
| quotes = market_data_service.get_all_market_data() |
| if category: |
| quotes = [q for q in quotes if q.category == category] |
| if exchange: |
| quotes = [q for q in quotes if q.exchange == exchange] |
| return quotes |
|
|
|
|
| @router.get("/market/klines/{symbol}") |
| async def get_klines(symbol: str, interval: str = "1m", limit: int = 200): |
| klines = market_data_service.get_kline_history(symbol, interval, limit) |
| if not klines: |
| raise HTTPException(status_code=404, detail=f"No data for symbol {symbol}") |
| return klines |
|
|
|
|
| @router.get("/market/intervals") |
| async def get_intervals(): |
| return market_data_service.INTERVALS |
|
|
|
|
| @router.get("/market/contracts") |
| async def get_contracts(category: str | None = None, exchange: str | None = None): |
| return market_data_service.get_contracts(category=category, exchange=exchange) |
|
|
|
|
| @router.get("/market/exchanges") |
| async def get_exchanges(): |
| return market_data_service.get_exchanges() |
|
|
|
|
| @router.get("/market/categories") |
| async def get_categories(): |
| return market_data_service.get_categories() |
|
|
|
|
| @router.get("/market/contract/{symbol}") |
| async def get_contract_detail(symbol: str): |
| detail = market_data_service.get_contract_details(symbol) |
| if detail is None: |
| quote = market_data_service.get_quote(symbol) |
| if quote: |
| return {"quote": quote, "contracts": None} |
| raise HTTPException(status_code=404, detail="Contract not found") |
| return detail |
|
|
|
|
| @router.get("/market/mode") |
| async def get_market_mode(): |
| return {"mode": market_data_service.mode} |
|
|
|
|
| @router.post("/market/mode") |
| async def set_market_mode(mode: str): |
| try: |
| market_data_service.set_mode(mode) |
| return {"mode": market_data_service.mode} |
| except ValueError as e: |
| raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
| |
|
|
| @router.post("/orders") |
| async def place_order(req: OrderRequest): |
| ok, msg = risk_manager.check_order(req) |
| if not ok: |
| raise HTTPException(status_code=400, detail=msg) |
| return order_manager.place_order(req) |
|
|
|
|
| @router.get("/orders") |
| async def get_orders(symbol: str | None = None): |
| return order_manager.get_orders(symbol) |
|
|
|
|
| @router.delete("/orders/{order_id}") |
| async def cancel_order(order_id: str): |
| result = order_manager.cancel_order(order_id) |
| if result is None: |
| raise HTTPException(status_code=404, detail="Order not found or cannot be cancelled") |
| return result |
|
|
|
|
| |
|
|
| @router.get("/positions") |
| async def get_positions(): |
| return order_manager.get_positions() |
|
|
|
|
| |
|
|
| @router.get("/account") |
| async def get_account(): |
| return order_manager.account |
|
|
|
|
| |
|
|
| @router.get("/trades") |
| async def get_trades(limit: int = 100): |
| return order_manager.get_trades(limit) |
|
|
|
|
| |
|
|
| @router.get("/strategies/available") |
| async def get_available_strategies(): |
| return strategy_engine.get_available_strategies() |
|
|
|
|
| @router.get("/strategies") |
| async def get_strategies(): |
| return strategy_engine.get_running_strategies() |
|
|
|
|
| @router.post("/strategies") |
| async def add_strategy(config: StrategyConfig): |
| if not config.strategy_id: |
| config.strategy_id = f"STR-{uuid.uuid4().hex[:8].upper()}" |
| try: |
| return strategy_engine.add_strategy(config) |
| except ValueError as e: |
| raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
| @router.post("/strategies/{strategy_id}/start") |
| async def start_strategy(strategy_id: str): |
| result = strategy_engine.start_strategy(strategy_id) |
| if result is None: |
| raise HTTPException(status_code=404, detail="Strategy not found") |
| return result |
|
|
|
|
| @router.post("/strategies/{strategy_id}/stop") |
| async def stop_strategy(strategy_id: str): |
| result = strategy_engine.stop_strategy(strategy_id) |
| if result is None: |
| raise HTTPException(status_code=404, detail="Strategy not found") |
| return result |
|
|
|
|
| @router.delete("/strategies/{strategy_id}") |
| async def remove_strategy(strategy_id: str): |
| strategy_engine.remove_strategy(strategy_id) |
| return {"status": "removed"} |
|
|
|
|
| @router.get("/strategies/{strategy_id}/signals") |
| async def get_strategy_signals(strategy_id: str): |
| return strategy_engine.get_strategy_signals(strategy_id) |
|
|
|
|
| @router.get("/strategies/{strategy_id}/performance") |
| async def get_strategy_performance(strategy_id: str): |
| perf = strategy_engine.get_strategy_performance(strategy_id) |
| if perf is None: |
| raise HTTPException(status_code=404, detail="Strategy not found") |
| return perf |
|
|
|
|
| |
|
|
| @router.get("/risk") |
| async def get_risk_metrics(): |
| return risk_manager.get_risk_metrics() |
|
|
|
|
| |
|
|
| @router.get("/patterns/registry") |
| async def get_pattern_types(): |
| return get_pattern_registry() |
|
|
|
|
| @router.get("/patterns/detect/{symbol}") |
| async def detect_chart_patterns( |
| symbol: str, |
| interval: str = "1m", |
| limit: int = 200, |
| patterns: str | None = None, |
| ): |
| klines = market_data_service.get_kline_history(symbol, interval, limit) |
| if not klines: |
| raise HTTPException(status_code=404, detail=f"No kline data for {symbol}") |
| enabled = patterns.split(",") if patterns else None |
| return detect_patterns(klines, enabled) |
|
|
|
|
| |
|
|
| @router.get("/backtest/templates") |
| async def get_backtest_templates(): |
| return {k: {"name": v["name"], "description": v["description"], "code": v["code"]} for k, v in STRATEGY_TEMPLATES.items()} |
|
|
|
|
| @router.post("/backtest/run") |
| async def run_backtest_api(payload: dict): |
| code = payload.get("code", "") |
| symbol = payload.get("symbol", "θΊηΊΉι’") |
| interval = payload.get("interval", "1d") |
| limit = payload.get("limit", 200) |
| initial_capital = payload.get("initial_capital", 1_000_000) |
| commission = payload.get("commission", 0.0003) |
| slippage = payload.get("slippage", 0.0001) |
|
|
| if not code.strip(): |
| raise HTTPException(status_code=400, detail="Strategy code is required") |
|
|
| klines = market_data_service.get_kline_history(symbol, interval, limit) |
| if not klines or len(klines) < 30: |
| raise HTTPException(status_code=400, detail=f"Insufficient kline data for {symbol} ({interval}): need >= 30 bars, got {len(klines)}") |
|
|
| import asyncio |
| loop = asyncio.get_event_loop() |
| result = await loop.run_in_executor(None, run_backtest, code, klines, initial_capital, commission, slippage) |
| return result |
|
|