freqtrade_origin/freqtrade/rpc/api_server/api_v1.py

413 lines
16 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,
2023-09-02 15:06:23 +00:00
BlacklistResponse, Count, DailyWeeklyMonthly,
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
2023-09-02 15:06:23 +00:00
# 2.33: Additional weekly/monthly metrics
2023-11-11 06:33:15 +00:00
# 2.34: new entries/exits/mix_tags endpoints
API_VERSION = 2.34
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()
2023-11-11 06:23:20 +00:00
@router.get('/entries', tags=['info', 'trading'])
def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_enter_tag_performance(pair)
2023-11-11 06:23:20 +00:00
@router.get('/exits', tags=['info', 'trading'])
def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_exit_reason_performance(pair)
2023-11-11 06:23:20 +00:00
@router.get('/mix_tags', tags=['info', 'trading'])
def mix_tags(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_mix_tag_performance(pair)
2020-12-26 14:54:22 +00:00
2023-11-11 06:23:20 +00:00
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()
2023-09-02 15:06:23 +00:00
@router.get('/daily', response_model=DailyWeeklyMonthly, tags=['info'])
2020-12-26 16:33:27 +00:00
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
2023-09-02 15:06:23 +00:00
@router.get('/weekly', response_model=DailyWeeklyMonthly, tags=['info'])
def weekly(timescale: int = 4, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_timeunit_profit(timescale, config['stake_currency'],
config.get('fiat_display_currency', ''), 'weeks')
2023-09-02 15:06:23 +00:00
@router.get('/monthly', response_model=DailyWeeklyMonthly, tags=['info'])
def monthly(timescale: int = 3, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_timeunit_profit(timescale, config['stake_currency'],
config.get('fiat_display_currency', ''), 'months')
2023-09-02 15:06:23 +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.model_validate(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()