diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 5f84713f2..081dcd623 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -212,6 +212,8 @@ class ApiServer(RPC): view_func=self._trades, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/trades/', 'trades_delete', view_func=self._trades_delete, methods=['DELETE']) + self.app.add_url_rule(f'{BASE_URI}/pair_candles', 'pair_candles', + view_func=self._analysed_candles, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/pair_history', 'pair_history', view_func=self._analysed_history, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/plot_config', 'plot_config', @@ -507,9 +509,10 @@ class ApiServer(RPC): @require_login @rpc_catch_errors - def _analysed_history(self): + def _analysed_candles(self): """ Handler for /pair_history. + Returns the dataframe the bot is using during live/dry operations. Takes the following get arguments: get: parameters: @@ -521,9 +524,31 @@ class ApiServer(RPC): timeframe = request.args.get("timeframe") limit = request.args.get("limit", type=int) - results = self._rpc_analysed_history(pair, timeframe, limit) + results = self._analysed_dataframe(pair, timeframe, limit) return self.rest_dump(results) + @require_login + @rpc_catch_errors + def _analysed_history(self): + """ + Handler for /pair_history. + Returns the dataframe of a given timerange + Takes the following get arguments: + get: + parameters: + - pair: Pair + - timeframe: Timeframe to get data for (should be aligned to strategy.timeframe) + - timerange: timerange in the format YYYYMMDD-YYYYMMDD (YYYYMMDD- or (-YYYYMMDD)) + are als possible. If omitted uses all available data. + """ + pair = request.args.get("pair") + timeframe = request.args.get("timeframe") + timerange = request.args.get("timerange") + + results = self._rpc_analysed_history_full(pair, timeframe, timerange) + return self.rest_dump(results) + + @require_login @rpc_catch_errors def _plot_config(self): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index e8ec099e5..c9d589c31 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -9,9 +9,11 @@ from math import isnan from typing import Any, Dict, List, Optional, Tuple, Union import arrow -from numpy import NAN, mean, int64 +from numpy import NAN, int64, mean +from freqtrade.configuration.timerange import TimeRange from freqtrade.constants import CANCEL_REASON +from freqtrade.data.history import load_data from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.loggers import bufferHandler @@ -654,19 +656,41 @@ class RPC: raise RPCException('Edge is not enabled.') return self._freqtrade.edge.accepted_pairs() - def _rpc_analysed_history(self, pair, timeframe, limit) -> Dict[str, Any]: + def _convert_dataframe_to_dict(self, pair, dataframe, last_analyzed): + dataframe = dataframe.replace({NAN: None}) + dataframe['date'] = dataframe['date'].astype(int64) // 1000 // 1000 + return { + 'pair': pair, + 'columns': list(dataframe.columns), + 'data': dataframe.values.tolist(), + 'length': len(dataframe), + 'last_analyzed': last_analyzed, + } + + def _analysed_dataframe(self, pair: str, timeframe: str, limit: int) -> Dict[str, Any]: _data, last_analyzed = self._freqtrade.dataprovider.get_analyzed_dataframe(pair, timeframe) if limit: _data = _data.iloc[-limit:] - _data = _data.replace({NAN: None}) - _data['date'] = _data['date'].astype(int64) // 1000 // 1000 - return { - 'columns': list(_data.columns), - 'data': _data.values.tolist(), - 'length': len(_data), - 'last_analyzed': last_analyzed, - } + return self._convert_dataframe_to_dict(pair, _data, last_analyzed) + + def _rpc_analysed_history_full(self, pair: str, timeframe: str, + timerange: str) -> Dict[str, Any]: + timerange = TimeRange.parse_timerange(None if self.config.get( + 'timerange') is None else str(self.config.get('timerange'))) + + _data = load_data( + datadir=self._freqtrade.config.get("datadir"), + pairs=[pair], + timeframe=self._freqtrade.config.get('timeframe', '5m'), + timerange=timerange, + data_format=self._freqtrade.config.get('dataformat_ohlcv', 'json'), + ) + from freqtrade.resolvers.strategy_resolver import StrategyResolver + strategy = StrategyResolver.load_strategy(self._freqtrade.config) + df_analyzed = strategy.analyze_ticker(_data, {'pair': pair}) + + return self._convert_dataframe_to_dict(pair, df_analyzed, arrow.Arrow.utcnow().datetime) def _rpc_plot_config(self) -> Dict[str, Any]: diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 9c814e1ac..91870aa6b 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -813,7 +813,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): assert rc.json == {'result': 'Created sell order for trade 1.'} -def test_api_pair_history(botclient, ohlcv_history): +def test_api_pair_candles(botclient, ohlcv_history): ftbot, client = botclient timeframe = '5m' amount = 2 @@ -821,11 +821,13 @@ def test_api_pair_history(botclient, ohlcv_history): ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) rc = client_get(client, - f"{BASE_URI}/pair_history?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") + f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") assert_response(rc) assert 'columns' in rc.json assert isinstance(rc.json['columns'], list) assert rc.json['columns'] == ['date', 'open', 'high', 'low', 'close', 'volume', 'sma'] + assert 'pair' in rc.json + assert rc.json['pair'] == 'XRP/BTC' assert 'data' in rc.json assert len(rc.json['data']) == amount