diff --git a/app/main.py b/app/main.py index ba90ea9..045ed14 100644 --- a/app/main.py +++ b/app/main.py @@ -9,6 +9,7 @@ from app.coinex import CoinExClient, CoinExLiveStream from app.core import get_settings from app.db import SessionLocal, get_db, init_db from app.demo_account import DemoAccountService +from app.legacy_bot import LegacyBotService from app.models import DemoTradeRecord settings = get_settings() @@ -16,6 +17,7 @@ coinex = CoinExClient(settings.coinex_api_base) live_stream = CoinExLiveStream(settings.coinex_ws_spot, coinex) demo_service = DemoAccountService(settings.demo_initial_balance, settings.demo_quote_asset) ai_bot = AiTradingBot(coinex, settings.markets) +legacy_bot = LegacyBotService() app = FastAPI(title=settings.app_name) static_dir = Path(__file__).parent / 'static' @@ -25,6 +27,8 @@ app.mount('/static', StaticFiles(directory=static_dir), name='static') @app.on_event('startup') def on_startup() -> None: init_db() + with SessionLocal() as db: + legacy_bot.ensure_defaults(db) @app.get('/') @@ -34,24 +38,21 @@ async def dashboard() -> FileResponse: @app.get('/health') async def health() -> dict: - return { - 'status': 'ok', - 'app': settings.app_name, - 'env': settings.app_env, - 'default_market': settings.default_market, - } + return {'status': 'ok', 'app': settings.app_name, 'env': settings.app_env, 'default_market': settings.default_market} @app.get('/api/v1/app/bootstrap') async def app_bootstrap(db: Session = Depends(get_db)) -> dict: state = ai_bot.get_or_create_state(db) account = demo_service.snapshot(db) + legacy_bot.ensure_defaults(db) return { 'app': settings.app_name, 'default_market': settings.default_market, 'markets': settings.markets, 'bot': ai_bot.state_to_dict(state), 'account': account.model_dump(mode='json'), + 'legacy': legacy_bot.dashboard(db), } @@ -66,16 +67,10 @@ async def reset_demo_account(db: Session = Depends(get_db)) -> dict: @app.post('/api/v1/demo/trades') -async def create_demo_trade( - market: str, - side: str, - price: float, - amount: float, - reason: str = 'manual demo trade', - db: Session = Depends(get_db), -) -> dict: +async def create_demo_trade(market: str, side: str, price: float, amount: float, reason: str = 'manual demo trade', db: Session = Depends(get_db)) -> dict: try: trade = demo_service.add_demo_trade(db=db, market=market, side=side, price=price, amount=amount, reason=reason) + legacy_bot.snapshot_history(db) return trade.model_dump(mode='json') except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @@ -100,28 +95,10 @@ async def bot_settings(db: Session = Depends(get_db)) -> dict: @app.post('/api/v1/bot/settings') -async def update_bot_settings( - enabled: bool | None = None, - trade_mode: str | None = None, - trade_style_mode: str | None = None, - min_signal_score: float | None = None, - max_open_positions: int | None = None, - max_quote_per_trade: float | None = None, - emergency_stop: bool | None = None, - db: Session = Depends(get_db), -) -> dict: +async def update_bot_settings(enabled: bool | None = None, trade_mode: str | None = None, trade_style_mode: str | None = None, min_signal_score: float | None = None, max_open_positions: int | None = None, max_quote_per_trade: float | None = None, emergency_stop: bool | None = None, db: Session = Depends(get_db)) -> dict: if trade_mode is not None and trade_mode not in {'demo', 'live'}: raise HTTPException(status_code=400, detail='trade_mode must be demo or live') - state = ai_bot.update_state( - db, - enabled=enabled, - trade_mode=trade_mode, - trade_style_mode=trade_style_mode, - min_signal_score=min_signal_score, - max_open_positions=max_open_positions, - max_quote_per_trade=max_quote_per_trade, - emergency_stop=emergency_stop, - ) + state = ai_bot.update_state(db, enabled=enabled, trade_mode=trade_mode, trade_style_mode=trade_style_mode, min_signal_score=min_signal_score, max_open_positions=max_open_positions, max_quote_per_trade=max_quote_per_trade, emergency_stop=emergency_stop) return ai_bot.state_to_dict(state) @@ -174,12 +151,8 @@ def _force_demo_trade(db: Session, decision) -> dict: decision.executed = True db.add(decision) db.commit() - return { - 'executed': True, - 'mode': 'demo', - 'decision': decision_to_dict(decision), - 'trade': trade.model_dump(mode='json'), - } + legacy_bot.snapshot_history(db) + return {'executed': True, 'mode': 'demo', 'decision': decision_to_dict(decision), 'trade': trade.model_dump(mode='json')} @app.post('/api/v1/bot/auto-demo-trade') @@ -196,25 +169,12 @@ async def auto_trade(market: str | None = None, db: Session = Depends(get_db)) - state = ai_bot.get_or_create_state(db) decision = await ai_bot.make_decision(db, market) if state.trade_mode == 'live': - return { - 'executed': False, - 'mode': 'live', - 'decision': decision_to_dict(decision), - 'message': 'Live mode is selected. Real CoinEx execution adapter is not connected yet; demo execution is stable.', - } + return {'executed': False, 'mode': 'live', 'decision': decision_to_dict(decision), 'message': 'Live mode is selected. CoinEx execution adapter is the next implementation step.'} return _force_demo_trade(db, decision) @app.post('/api/v1/signals/manual') -async def create_manual_signal( - title: str, - market: str, - sentiment: str = 'neutral', - score: float = 50.0, - source: str = 'manual', - url: str = '', - db: Session = Depends(get_db), -) -> dict: +async def create_manual_signal(title: str, market: str, sentiment: str = 'neutral', score: float = 50.0, source: str = 'manual', url: str = '', db: Session = Depends(get_db)) -> dict: signal = ai_bot.record_manual_signal(db, title=title, market=market, sentiment=sentiment, score=score, source=source, url=url) return signal_to_dict(signal) @@ -238,6 +198,70 @@ async def dashboard_markers(market: str | None = None, limit: int = 100, db: Ses return {'items': [trade_marker_from_record(item) for item in reversed(trades)]} +@app.get('/api/v1/legacy/dashboard') +async def legacy_dashboard(db: Session = Depends(get_db)) -> dict: + return legacy_bot.dashboard(db) + + +@app.get('/api/v1/legacy/open-trades') +async def legacy_open_trades(db: Session = Depends(get_db)) -> dict: + return {'items': legacy_bot.open_positions(db)} + + +@app.get('/api/v1/legacy/closed-trades') +async def legacy_closed_trades(limit: int = 50, db: Session = Depends(get_db)) -> dict: + return {'items': legacy_bot.closed_positions(db, limit)} + + +@app.get('/api/v1/legacy/chart-history') +async def legacy_chart_history(limit: int = 100, db: Session = Depends(get_db)) -> dict: + return {'items': legacy_bot.chart_history(db, limit)} + + +@app.get('/api/v1/legacy/strategies') +async def legacy_strategies(db: Session = Depends(get_db)) -> dict: + legacy_bot.ensure_defaults(db) + return {'items': legacy_bot.dashboard(db)['strategies']} + + +@app.post('/api/v1/legacy/strategies/{strategy_id}') +async def legacy_update_strategy(strategy_id: int, enabled: bool | None = None, dry_run: bool | None = None, db: Session = Depends(get_db)) -> dict: + try: + return legacy_bot.update_strategy(db, strategy_id, enabled, dry_run) + except ValueError as exc: + raise HTTPException(status_code=404, detail=str(exc)) from exc + + +@app.get('/api/v1/legacy/coinex') +async def legacy_coinex(db: Session = Depends(get_db)) -> dict: + return legacy_bot.dashboard(db)['coinex'] + + +@app.post('/api/v1/legacy/coinex') +async def legacy_update_coinex(enabled: bool | None = None, access_id: str | None = None, secret_key: str | None = None, mode: str | None = None, db: Session = Depends(get_db)) -> dict: + return legacy_bot.update_coinex(db, enabled, access_id, secret_key, mode) + + +@app.post('/api/v1/legacy/telegram') +async def legacy_update_telegram(enabled: bool | None = None, chat_id: str | None = None, message_format: str | None = None, db: Session = Depends(get_db)) -> dict: + return legacy_bot.update_telegram(db, enabled, chat_id, message_format) + + +@app.get('/api/v1/legacy/updater/status') +async def legacy_updater_status(db: Session = Depends(get_db)) -> dict: + return legacy_bot.updater_status(db) + + +@app.post('/api/v1/legacy/terminal') +async def legacy_terminal(command: str) -> dict: + return legacy_bot.terminal_command(command) + + +@app.get('/api/v1/android/status') +async def android_status(db: Session = Depends(get_db)) -> dict: + return legacy_bot.android_status(db) + + @app.websocket('/ws/market/{market}') async def market_stream(websocket: WebSocket, market: str) -> None: await websocket.accept()