diff --git a/freqtrade/rpc/api_server2/api_models.py b/freqtrade/rpc/api_server2/api_models.py index 293b1e97f..aa9dfcc33 100644 --- a/freqtrade/rpc/api_server2/api_models.py +++ b/freqtrade/rpc/api_server2/api_models.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Union +from typing import Dict, List, Optional, Union from pydantic import BaseModel @@ -90,3 +90,12 @@ class SellReason(BaseModel): class Stats(BaseModel): sell_reasons: Dict[str, SellReason] durations: Dict[str, Union[str, float]] + + +class ForceBuyPayload(BaseModel): + pair: str + price: Optional[float] + + +class ForceSellPayload(BaseModel): + tradeid: str diff --git a/freqtrade/rpc/api_server2/api_v1.py b/freqtrade/rpc/api_server2/api_v1.py index 78a7ebbf8..961eb2c78 100644 --- a/freqtrade/rpc/api_server2/api_v1.py +++ b/freqtrade/rpc/api_server2/api_v1.py @@ -4,8 +4,10 @@ from fastapi import APIRouter, Depends from freqtrade import __version__ from freqtrade.rpc import RPC +from freqtrade.rpc.rpc import RPCException -from .api_models import Balances, Count, PerformanceEntry, Ping, Profit, Stats, StatusMsg, Version +from .api_models import (Balances, Count, ForceBuyPayload, ForceSellPayload, PerformanceEntry, Ping, Profit, Stats, + StatusMsg, Version) from .deps import get_config, get_rpc @@ -53,10 +55,33 @@ def stats(rpc: RPC = Depends(get_rpc)): return rpc._rpc_stats() +# TODO: Missing response model +@router.get('/status', tags=['info']) +def status(rpc: RPC = Depends(get_rpc)): + try: + return rpc._rpc_trade_status() + except RPCException: + return [] + + @router.get('/show_config', tags=['info']) def show_config(rpc: RPC = Depends(get_rpc), config=Depends(get_config)): return RPC._rpc_show_config(config, rpc._freqtrade.state) +@router.post('/forcebuy', tags=['trading']) +def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): + trade = rpc._rpc_forcebuy(payload.pair, payload.price) + + if trade: + return trade.to_json() + else: + return {"status": f"Error buying pair {payload.pair}."} + + +@router.post('/forcesell', tags=['trading']) +def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)): + return rpc._rpc_forcesell(payload.tradeid) + @router.post('/start', response_model=StatusMsg, tags=['botcontrol']) def start(rpc: RPC = Depends(get_rpc)): diff --git a/freqtrade/rpc/api_server2/webserver.py b/freqtrade/rpc/api_server2/webserver.py index 755b43127..45e695151 100644 --- a/freqtrade/rpc/api_server2/webserver.py +++ b/freqtrade/rpc/api_server2/webserver.py @@ -1,14 +1,20 @@ +import logging from typing import Any, Dict, Optional +from starlette.responses import JSONResponse import uvicorn from fastapi import Depends, FastAPI +from fastapi.exceptions import HTTPException from fastapi.middleware.cors import CORSMiddleware -from freqtrade.rpc.rpc import RPC, RPCHandler +from freqtrade.rpc.rpc import RPC, RPCException, RPCHandler from .uvicorn_threaded import UvicornServer +logger = logging.getLogger(__name__) + + class ApiServer(RPCHandler): _rpc: Optional[RPC] = None @@ -34,10 +40,17 @@ class ApiServer(RPCHandler): def send_msg(self, msg: Dict[str, str]) -> None: pass + def handle_rpc_exception(self, request, exc): + logger.exception(f"API Error calling: {exc}") + return JSONResponse( + status_code=502, + content={'error': f"Error querying {request.url.path}: {exc.message}"} + ) + def configure_app(self, app: FastAPI, config): + from .api_auth import http_basic_or_jwt_token, router_login from .api_v1 import router as api_v1 from .api_v1 import router_public as api_v1_public - from .api_auth import http_basic_or_jwt_token, router_login app.include_router(api_v1_public, prefix="/api/v1") app.include_router(api_v1, prefix="/api/v1", @@ -53,6 +66,8 @@ class ApiServer(RPCHandler): allow_headers=["*"], ) + app.add_exception_handler(RPCException, self.handle_rpc_exception) + def start_api(self): """ Start API ... should be run in thread.