From a0e7e86de18d750feba72eca19d0ea68f77ad8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Fri, 8 May 2026 03:26:33 +0900 Subject: [PATCH] Add FastAPI application --- app/main.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 app/main.py diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..6415fb5 --- /dev/null +++ b/app/main.py @@ -0,0 +1,80 @@ +import asyncio +from pathlib import Path +from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles + +from app.coinex import CoinExClient +from app.core import get_settings +from app.demo_account import DemoAccountService + +settings = get_settings() +coinex = CoinExClient(settings.coinex_api_base) +demo_service = DemoAccountService(settings.demo_initial_balance, settings.demo_quote_asset) + +app = FastAPI(title=settings.app_name) +static_dir = Path(__file__).parent / 'static' +app.mount('/static', StaticFiles(directory=static_dir), name='static') + + +@app.get('/') +async def dashboard() -> FileResponse: + return FileResponse(static_dir / 'index.html') + + +@app.get('/health') +async def health() -> dict: + return { + 'status': 'ok', + 'app': settings.app_name, + 'env': settings.app_env, + 'trade_mode': settings.trade_mode, + 'live_enabled': settings.live_enabled, + 'default_market': settings.default_market, + } + + +@app.get('/api/v1/demo/account') +async def demo_account() -> dict: + return demo_service.snapshot().model_dump(mode='json') + + +@app.post('/api/v1/demo/reset') +async def reset_demo_account() -> dict: + return demo_service.reset().model_dump(mode='json') + + +@app.post('/api/v1/demo/trades') +async def create_demo_trade(market: str, side: str, price: float, amount: float, reason: str = 'manual demo trade') -> dict: + try: + trade = demo_service.add_demo_trade(market=market, side=side, price=price, amount=amount, reason=reason) + return trade.model_dump(mode='json') + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + + +@app.get('/api/v1/market/kline') +async def market_kline(market: str | None = None, period: str = '1min', limit: int = 100) -> dict: + return await coinex.get_kline(market or settings.default_market, period, limit) + + +@app.get('/api/v1/market/ticker') +async def market_ticker(market: str | None = None) -> dict: + return await coinex.get_ticker(market or settings.default_market) + + +@app.websocket('/ws/market/{market}') +async def market_stream(websocket: WebSocket, market: str) -> None: + await websocket.accept() + try: + while True: + ticker = await coinex.get_ticker(market) + await websocket.send_json({ + 'type': 'ticker', + 'market': market.upper(), + 'data': ticker, + 'demo_account': demo_service.snapshot().model_dump(mode='json'), + }) + await asyncio.sleep(3) + except WebSocketDisconnect: + return