File size: 8,744 Bytes
590a501 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | 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()
# ββ Market Data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@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))
# ββ Orders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@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
# ββ Positions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@router.get("/positions")
async def get_positions():
return order_manager.get_positions()
# ββ Account ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@router.get("/account")
async def get_account():
return order_manager.account
# ββ Trades βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@router.get("/trades")
async def get_trades(limit: int = 100):
return order_manager.get_trades(limit)
# ββ Strategies βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@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
# ββ Risk Management ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@router.get("/risk")
async def get_risk_metrics():
return risk_manager.get_risk_metrics()
# ββ Pattern Detection (ε―»ζΎζΊδΌ) βββββββββββββββββββββββββββββββββββββββββββββ
@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)
# ββ Backtesting ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@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
|