freqtrade_origin/freqtrade/rpc/api_server/api_v1.py

384 lines
15 KiB
Python
Raw Normal View History

2021-01-02 14:13:32 +00:00
import logging
2020-12-26 19:05:27 +00:00
from copy import deepcopy
2021-01-02 14:11:40 +00:00
from typing import List, Optional
2020-12-26 15:43:15 +00:00
from fastapi import APIRouter, Depends, Query
2020-12-26 19:05:27 +00:00
from fastapi.exceptions import HTTPException
2020-12-25 12:11:01 +00:00
from freqtrade import __version__
2020-12-26 19:05:27 +00:00
from freqtrade.data.history import get_datahandler
2022-03-03 06:06:13 +00:00
from freqtrade.enums import CandleType, TradingMode
2020-12-26 19:05:27 +00:00
from freqtrade.exceptions import OperationalException
2020-12-26 14:54:22 +00:00
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload,
BlacklistResponse, Count, Daily,
DeleteLockRequest, DeleteTrade,
ExchangeListResponse, ForceEnterPayload,
ForceEnterResponse, ForceExitPayload,
FreqAIModelListResponse, Health, Locks, Logs,
OpenTradeSchema, PairHistory, PerformanceEntry,
Ping, PlotConfig, Profit, ResultMsg, ShowConfig,
Stats, StatusMsg, StrategyListResponse,
StrategyResponse, SysInfo, Version,
WhitelistResponse)
2022-01-22 06:11:59 +00:00
from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional
from freqtrade.rpc.rpc import RPCException
2020-12-25 12:11:01 +00:00
2021-01-02 14:13:32 +00:00
logger = logging.getLogger(__name__)
2021-11-23 06:06:53 +00:00
# API version
# Pre-1.1, no version was provided
# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen.
2021-11-27 08:10:18 +00:00
# 1.11: forcebuy and forcesell accept ordertype
# 1.12: add blacklist delete endpoint
# 1.13: forcebuy supports stake_amount
2022-01-26 06:10:38 +00:00
# versions 2.xx -> futures/short branch
2022-03-03 19:51:52 +00:00
# 2.14: Add entry/exit orders to trade response
# 2.15: Add backtest history endpoints
2022-06-11 09:28:45 +00:00
# 2.16: Additional daily metrics
2022-08-07 08:13:22 +00:00
# 2.17: Forceentry - leverage, partial force_exit
2022-09-13 20:07:59 +00:00
# 2.20: Add websocket endpoints
2022-12-05 18:56:33 +00:00
# 2.21: Add new_candle messagetype
2022-12-20 06:21:52 +00:00
# 2.22: Add FreqAI to backtesting
2023-01-18 17:15:14 +00:00
# 2.23: Allow plot config request in webserver mode
2023-01-31 06:09:07 +00:00
# 2.24: Add cancel_open_order endpoint
2023-03-06 06:10:02 +00:00
# 2.25: Add several profit values to /status endpoint
2023-04-22 12:57:13 +00:00
# 2.26: increase /balance output
# 2.27: Add /trades/<id>/reload endpoint
2023-05-16 18:27:07 +00:00
# 2.28: Switch reload endpoint to Post
2023-06-03 07:20:36 +00:00
# 2.29: Add /exchanges endpoint
# 2.30: new /pairlists endpoint
2023-07-25 18:51:33 +00:00
# 2.31: new /backtest/history/ delete endpoint
2023-08-01 04:30:23 +00:00
# 2.32: new /backtest/history/ patch endpoint
API_VERSION = 2.32
2021-11-23 06:06:53 +00:00
# Public API, requires no auth.
router_public = APIRouter()
2020-12-25 12:11:01 +00:00
# Private API, protected by authentication
router = APIRouter()
@router_public.get('/ping', response_model=Ping)
def ping():
2020-12-26 16:48:19 +00:00
"""simple ping"""
return {"status": "pong"}
2020-12-25 19:07:12 +00:00
@router.get('/version', response_model=Version, tags=['info'])
2020-12-25 14:57:05 +00:00
def version():
2020-12-26 16:48:19 +00:00
""" Bot Version info"""
2020-12-25 14:57:05 +00:00
return {"version": __version__}
2020-12-25 19:07:12 +00:00
@router.get('/balance', response_model=Balances, tags=['info'])
def balance(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
2020-12-26 16:48:19 +00:00
"""Account Balances"""
return rpc._rpc_balance(config['stake_currency'], config.get('fiat_display_currency', ''),)
2020-12-25 12:11:01 +00:00
2020-12-26 14:54:22 +00:00
@router.get('/count', response_model=Count, tags=['info'])
def count(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_count()
2020-12-26 15:43:15 +00:00
@router.get('/performance', response_model=List[PerformanceEntry], tags=['info'])
def performance(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_performance()
@router.get('/profit', response_model=Profit, tags=['info'])
def profit(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_trade_statistics(config['stake_currency'],
config.get('fiat_display_currency')
)
@router.get('/stats', response_model=Stats, tags=['info'])
def stats(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_stats()
2020-12-26 16:33:27 +00:00
@router.get('/daily', response_model=Daily, tags=['info'])
def daily(timescale: int = 7, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_timeunit_profit(timescale, config['stake_currency'],
config.get('fiat_display_currency', ''))
2020-12-26 16:33:27 +00:00
2021-01-01 18:38:28 +00:00
@router.get('/status', response_model=List[OpenTradeSchema], tags=['info'])
def status(rpc: RPC = Depends(get_rpc)):
try:
return rpc._rpc_trade_status()
except RPCException:
return []
# Using the responsemodel here will cause a ~100% increase in response time (from 1s to 2s)
# on big databases. Correct response model: response_model=TradeResponse,
@router.get('/trades', tags=['info', 'trading'])
def trades(limit: int = 500, offset: int = 0, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_trade_history(limit, offset=offset, order_by_id=True)
2020-12-26 16:33:27 +00:00
@router.get('/trade/{tradeid}', response_model=OpenTradeSchema, tags=['info', 'trading'])
def trade(tradeid: int = 0, rpc: RPC = Depends(get_rpc)):
try:
return rpc._rpc_trade_status([tradeid])[0]
except (RPCException, KeyError):
raise HTTPException(status_code=404, detail='Trade not found.')
2020-12-26 16:33:27 +00:00
@router.delete('/trades/{tradeid}', response_model=DeleteTrade, tags=['info', 'trading'])
def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete(tradeid)
2023-01-31 06:09:03 +00:00
@router.delete('/trades/{tradeid}/open-order', response_model=OpenTradeSchema, tags=['trading'])
def trade_cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)):
2023-01-31 06:09:03 +00:00
rpc._rpc_cancel_open_order(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
2023-05-16 18:27:07 +00:00
@router.post('/trades/{tradeid}/reload', response_model=OpenTradeSchema, tags=['trading'])
def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)):
rpc._rpc_reload_trade_from_exchange(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
2020-12-26 16:33:27 +00:00
# TODO: Missing response model
@router.get('/edge', tags=['info'])
def edge(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_edge()
2021-01-02 14:48:33 +00:00
@router.get('/show_config', response_model=ShowConfig, tags=['info'])
2021-01-02 12:12:49 +00:00
def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(get_config)):
state = ''
2021-12-04 13:49:45 +00:00
strategy_version = None
2021-01-02 12:12:49 +00:00
if rpc:
state = rpc._freqtrade.state
2021-12-04 13:49:45 +00:00
strategy_version = rpc._freqtrade.strategy.version()
resp = RPC._rpc_show_config(config, state, strategy_version)
2021-11-23 06:06:53 +00:00
resp['api_version'] = API_VERSION
return resp
2020-12-26 14:54:22 +00:00
2020-12-26 16:33:27 +00:00
2022-04-08 05:15:05 +00:00
# /forcebuy is deprecated with short addition. use /forceentry instead
2022-04-07 18:33:54 +00:00
@router.post('/forceenter', response_model=ForceEnterResponse, tags=['trading'])
@router.post('/forcebuy', response_model=ForceEnterResponse, tags=['trading'])
2022-04-05 10:31:53 +00:00
def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)):
2021-11-27 08:26:14 +00:00
ordertype = payload.ordertype.value if payload.ordertype else None
2022-01-26 18:08:37 +00:00
trade = rpc._rpc_force_entry(payload.pair, payload.price, order_side=payload.side,
2022-08-02 18:15:47 +00:00
order_type=ordertype, stake_amount=payload.stakeamount,
enter_tag=payload.entry_tag or 'force_entry',
leverage=payload.leverage)
if trade:
2023-07-18 05:05:30 +00:00
return ForceEnterResponse.model_validate(trade.to_json())
else:
2023-07-18 05:05:30 +00:00
return ForceEnterResponse.model_validate(
2022-01-26 18:08:37 +00:00
{"status": f"Error entering {payload.side} trade for pair {payload.pair}."})
2022-04-08 05:15:05 +00:00
# /forcesell is deprecated with short addition. use /forceexit instead
2022-04-07 18:33:54 +00:00
@router.post('/forceexit', response_model=ResultMsg, tags=['trading'])
2020-12-26 16:33:27 +00:00
@router.post('/forcesell', response_model=ResultMsg, tags=['trading'])
2022-04-10 13:56:29 +00:00
def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)):
2021-11-27 08:26:14 +00:00
ordertype = payload.ordertype.value if payload.ordertype else None
2022-08-02 17:53:10 +00:00
return rpc._rpc_force_exit(payload.tradeid, ordertype, amount=payload.amount)
2020-12-26 14:54:22 +00:00
2020-12-26 16:33:27 +00:00
@router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
def blacklist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist()
@router.post('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist(payload.blacklist)
@router.delete('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
def blacklist_delete(pairs_to_delete: List[str] = Query([]), rpc: RPC = Depends(get_rpc)):
"""Provide a list of pairs to delete from the blacklist"""
return rpc._rpc_blacklist_delete(pairs_to_delete)
2020-12-26 16:33:27 +00:00
@router.get('/whitelist', response_model=WhitelistResponse, tags=['info', 'pairlist'])
def whitelist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_whitelist()
2021-03-01 18:50:39 +00:00
@router.get('/locks', response_model=Locks, tags=['info', 'locks'])
2020-12-26 16:33:27 +00:00
def locks(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_locks()
2021-03-01 18:50:39 +00:00
@router.delete('/locks/{lockid}', response_model=Locks, tags=['info', 'locks'])
def delete_lock(lockid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=lockid)
@router.post('/locks/delete', response_model=Locks, tags=['info', 'locks'])
def delete_lock_pair(payload: DeleteLockRequest, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=payload.lockid, pair=payload.pair)
2020-12-26 16:33:27 +00:00
@router.get('/logs', response_model=Logs, tags=['info'])
def logs(limit: Optional[int] = None):
return RPC._rpc_get_logs(limit)
2020-12-26 16:33:27 +00:00
2020-12-25 19:07:12 +00:00
@router.post('/start', response_model=StatusMsg, tags=['botcontrol'])
def start(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_start()
@router.post('/stop', response_model=StatusMsg, tags=['botcontrol'])
def stop(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_stop()
2020-12-26 14:54:22 +00:00
2022-08-28 09:32:53 +00:00
@router.post('/stopentry', response_model=StatusMsg, tags=['botcontrol'])
2020-12-26 14:54:22 +00:00
@router.post('/stopbuy', response_model=StatusMsg, tags=['botcontrol'])
def stop_buy(rpc: RPC = Depends(get_rpc)):
2022-08-28 09:32:53 +00:00
return rpc._rpc_stopentry()
2020-12-26 14:54:22 +00:00
@router.post('/reload_config', response_model=StatusMsg, tags=['botcontrol'])
def reload_config(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_reload_config()
2020-12-26 19:05:27 +00:00
2020-12-27 08:02:35 +00:00
@router.get('/pair_candles', response_model=PairHistory, tags=['candle data'])
def pair_candles(
pair: str, timeframe: str, limit: Optional[int] = None, rpc: RPC = Depends(get_rpc)):
2020-12-26 19:05:27 +00:00
return rpc._rpc_analysed_dataframe(pair, timeframe, limit)
2020-12-27 08:02:35 +00:00
@router.get('/pair_history', response_model=PairHistory, tags=['candle data'])
2020-12-26 19:05:27 +00:00
def pair_history(pair: str, timeframe: str, timerange: str, strategy: str,
freqaimodel: Optional[str] = None,
2022-01-22 06:11:59 +00:00
config=Depends(get_config), exchange=Depends(get_exchange)):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
2020-12-26 19:05:27 +00:00
config = deepcopy(config)
config.update({
2021-08-06 22:19:36 +00:00
'strategy': strategy,
'timerange': timerange,
'freqaimodel': freqaimodel if freqaimodel else config.get('freqaimodel'),
2021-08-06 22:19:36 +00:00
})
try:
return RPC._rpc_analysed_history_full(config, pair, timeframe, exchange)
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
2020-12-26 19:05:27 +00:00
2021-01-02 14:11:40 +00:00
@router.get('/plot_config', response_model=PlotConfig, tags=['candle data'])
def plot_config(strategy: Optional[str] = None, config=Depends(get_config),
rpc: Optional[RPC] = Depends(get_rpc_optional)):
if not strategy:
if not rpc:
raise RPCException("Strategy is mandatory in webserver mode.")
2023-07-18 05:05:30 +00:00
return PlotConfig.model_validate(rpc._rpc_plot_config())
else:
config1 = deepcopy(config)
config1.update({
'strategy': strategy
})
try:
return PlotConfig.parse_obj(RPC._rpc_plot_config_with_strategy(config1))
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
2020-12-26 19:05:27 +00:00
@router.get('/strategies', response_model=StrategyListResponse, tags=['strategy'])
def list_strategies(config=Depends(get_config)):
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategies = StrategyResolver.search_all_objects(
2022-10-14 14:41:25 +00:00
config, False, config.get('recursive_strategy_search', False))
2020-12-26 19:05:27 +00:00
strategies = sorted(strategies, key=lambda x: x['name'])
return {'strategies': [x['name'] for x in strategies]}
@router.get('/strategy/{strategy}', response_model=StrategyResponse, tags=['strategy'])
2021-01-02 14:11:40 +00:00
def get_strategy(strategy: str, config=Depends(get_config)):
if ":" in strategy:
raise HTTPException(status_code=500, detail="base64 encoded strategies are not allowed.")
2020-12-26 19:05:27 +00:00
config_ = deepcopy(config)
2020-12-26 19:05:27 +00:00
from freqtrade.resolvers.strategy_resolver import StrategyResolver
try:
strategy_obj = StrategyResolver._load_strategy(strategy, config_,
extra_dir=config_.get('strategy_path'))
2020-12-26 19:05:27 +00:00
except OperationalException:
raise HTTPException(status_code=404, detail='Strategy not found')
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
2020-12-26 19:05:27 +00:00
return {
'strategy': strategy_obj.get_strategy_name(),
'code': strategy_obj.__source__,
}
@router.get('/exchanges', response_model=ExchangeListResponse, tags=[])
def list_exchanges(config=Depends(get_config)):
from freqtrade.exchange import list_available_exchanges
exchanges = list_available_exchanges(config)
return {
'exchanges': exchanges,
}
2022-12-20 06:21:52 +00:00
@router.get('/freqaimodels', response_model=FreqAIModelListResponse, tags=['freqai'])
def list_freqaimodels(config=Depends(get_config)):
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
models = FreqaiModelResolver.search_all_objects(
2022-12-20 06:21:52 +00:00
config, False)
models = sorted(models, key=lambda x: x['name'])
2022-12-20 06:21:52 +00:00
return {'freqaimodels': [x['name'] for x in models]}
2022-12-20 06:21:52 +00:00
2020-12-26 19:05:27 +00:00
@router.get('/available_pairs', response_model=AvailablePairs, tags=['candle data'])
def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Optional[str] = None,
2021-12-03 13:11:24 +00:00
candletype: Optional[CandleType] = None, config=Depends(get_config)):
2020-12-26 19:05:27 +00:00
dh = get_datahandler(config['datadir'], config.get('dataformat_ohlcv'))
2022-03-03 06:06:13 +00:00
trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
2021-12-08 13:35:15 +00:00
pair_interval = dh.ohlcv_get_available_data(config['datadir'], trading_mode)
2020-12-26 19:05:27 +00:00
if timeframe:
pair_interval = [pair for pair in pair_interval if pair[1] == timeframe]
if stake_currency:
pair_interval = [pair for pair in pair_interval if pair[0].endswith(stake_currency)]
if candletype:
pair_interval = [pair for pair in pair_interval if pair[2] == candletype]
else:
2021-12-08 13:35:15 +00:00
candle_type = CandleType.get_default(trading_mode)
pair_interval = [pair for pair in pair_interval if pair[2] == candle_type]
2020-12-26 19:05:27 +00:00
pair_interval = sorted(pair_interval, key=lambda x: x[0])
pairs = list({x[0] for x in pair_interval})
2021-05-24 05:47:38 +00:00
pairs.sort()
2020-12-26 19:05:27 +00:00
result = {
'length': len(pairs),
'pairs': pairs,
'pair_interval': pair_interval,
}
return result
2021-10-06 17:36:28 +00:00
@router.get('/sysinfo', response_model=SysInfo, tags=['info'])
def sysinfo():
return RPC._rpc_sysinfo()
@router.get('/health', response_model=Health, tags=['info'])
def health(rpc: RPC = Depends(get_rpc)):
return rpc.health()