fix crash when backtest-result.json not exist

This commit is contained in:
AxelCh 2019-01-23 14:11:05 -04:00 committed by Matthias
parent 06e0616fb0
commit eec7276393
5 changed files with 473 additions and 389 deletions

View File

@ -15,7 +15,7 @@ At least version 2.3.0 is required.
Usage for the price plotter: Usage for the price plotter:
``` ```
script/plot_dataframe.py [-h] [-p pair] [--live] script/plot_dataframe.py [-h] [-p pairs] [--live]
``` ```
Example Example
@ -23,11 +23,16 @@ Example
python scripts/plot_dataframe.py -p BTC/ETH python scripts/plot_dataframe.py -p BTC/ETH
``` ```
The `-p` pair argument, can be used to specify what The `-p` pairs argument, can be used to specify
pair you would like to plot. pairs you would like to plot.
**Advanced use** **Advanced use**
To plot multiple pairs, separate them with a comma:
```
python scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
```
To plot the current live price use the `--live` flag: To plot the current live price use the `--live` flag:
``` ```
python scripts/plot_dataframe.py -p BTC/ETH --live python scripts/plot_dataframe.py -p BTC/ETH --live

View File

@ -352,9 +352,9 @@ class Arguments(object):
Parses given arguments for scripts. Parses given arguments for scripts.
""" """
self.parser.add_argument( self.parser.add_argument(
'-p', '--pair', '-p', '--pairs',
help='Show profits for only this pairs. Pairs are comma-separated.', help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pair', dest='pairs',
default=None default=None
) )

View File

@ -47,7 +47,7 @@ def test_scripts_options() -> None:
arguments = Arguments(['-p', 'ETH/BTC'], '') arguments = Arguments(['-p', 'ETH/BTC'], '')
arguments.scripts_options() arguments.scripts_options()
args = arguments.get_parsed_arg() args = arguments.get_parsed_arg()
assert args.pair == 'ETH/BTC' assert args.pairs == 'ETH/BTC'
def test_parse_args_version() -> None: def test_parse_args_version() -> None:

View File

@ -1,18 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Script to display when the bot will buy a specific pair Script to display when the bot will buy on specific pair(s)
Mandatory Cli parameters: Mandatory Cli parameters:
-p / --pair: pair to examine -p / --pairs: pair(s) to examine
Option but recommended Option but recommended
-s / --strategy: strategy to use -s / --strategy: strategy to use
Optional Cli parameters Optional Cli parameters
-d / --datadir: path to pair backtest data -d / --datadir: path to pair(s) backtest data
--timerange: specify what timerange of data to use. --timerange: specify what timerange of data to use.
-l / --live: Live, to download the latest ticker for the pair -l / --live: Live, to download the latest ticker for the pair(s)
-db / --db-url: Show trades stored in database -db / --db-url: Show trades stored in database
@ -21,12 +21,13 @@ Row 1: sma, ema3, ema5, ema10, ema50
Row 3: macd, rsi, fisher_rsi, mfi, slowd, slowk, fastd, fastk Row 3: macd, rsi, fisher_rsi, mfi, slowd, slowk, fastd, fastk
Example of usage: Example of usage:
> python3 scripts/plot_dataframe.py --pair BTC/EUR -d user_data/data/ --indicators1 sma,ema3 > python3 scripts/plot_dataframe.py --pairs BTC/EUR,XRP/BTC -d user_data/data/ --indicators1 sma,ema3
--indicators2 fastk,fastd --indicators2 fastk,fastd
""" """
import json import json
import logging import logging
import sys import sys
import os
from argparse import Namespace from argparse import Namespace
from pathlib import Path from pathlib import Path
from typing import Dict, List, Any from typing import Dict, List, Any
@ -65,7 +66,8 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
t.open_date.replace(tzinfo=timeZone), t.open_date.replace(tzinfo=timeZone),
t.close_date.replace(tzinfo=timeZone) if t.close_date else None, t.close_date.replace(tzinfo=timeZone) if t.close_date else None,
t.open_rate, t.close_rate, t.open_rate, t.close_rate,
t.close_date.timestamp() - t.open_date.timestamp() if t.close_date else None) t.close_date.timestamp() - t.open_date.timestamp()
if t.close_date else None)
for t in Trade.query.filter(Trade.pair.is_(pair)).all()], for t in Trade.query.filter(Trade.pair.is_(pair)).all()],
columns=columns) columns=columns)
@ -74,6 +76,7 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
# must align with columns in backtest.py # must align with columns in backtest.py
columns = ["pair", "profit", "opents", "closets", "index", "duration", columns = ["pair", "profit", "opents", "closets", "index", "duration",
"open_rate", "close_rate", "open_at_end", "sell_reason"] "open_rate", "close_rate", "open_at_end", "sell_reason"]
if os.path.exists(file):
with file.open() as f: with file.open() as f:
data = json.load(f) data = json.load(f)
trades = pd.DataFrame(data, columns=columns) trades = pd.DataFrame(data, columns=columns)
@ -84,42 +87,59 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
if timerange.stoptype == 'date': if timerange.stoptype == 'date':
trades = trades.loc[trades["opents"] <= timerange.stopts] trades = trades.loc[trades["opents"] <= timerange.stopts]
trades['opents'] = pd.to_datetime(trades['opents'], trades['opents'] = pd.to_datetime(
trades['opents'],
unit='s', unit='s',
utc=True, utc=True,
infer_datetime_format=True) infer_datetime_format=True)
trades['closets'] = pd.to_datetime(trades['closets'], trades['closets'] = pd.to_datetime(
trades['closets'],
unit='s', unit='s',
utc=True, utc=True,
infer_datetime_format=True) infer_datetime_format=True)
else:
trades = pd.DataFrame([], columns=columns)
return trades return trades
def plot_analyzed_dataframe(args: Namespace) -> None: def generate_plot_file(fig, pair, tick_interval, is_last) -> None:
""" """
Calls analyze() and plots the returned dataframe Generate a plot html file from pre populated fig plotly object
:return: None :return: None
""" """
logger.info('Generate plot file for %s', pair)
pair_name = pair.replace("/", "_")
file_name = 'freqtrade-plot-' + pair_name + '-' + tick_interval + '.html'
if not os.path.exists('user_data/plots'):
try:
os.makedirs('user_data/plots')
except OSError as e:
raise
plot(fig, filename=str(Path('user_data/plots').joinpath(file_name)), auto_open=False)
if is_last:
plot(fig, filename=str(Path('user_data').joinpath('freqtrade-plot.html')), auto_open=False)
def get_trading_env(args: Namespace):
"""
Initalize freqtrade Exchange and Strategy, split pairs recieved in parameter
:return: Strategy
"""
global _CONF global _CONF
# Load the configuration # Load the configuration
_CONF.update(setup_configuration(args)) _CONF.update(setup_configuration(args))
print(_CONF) print(_CONF)
# Set the pair to audit
pair = args.pair
if pair is None: pairs = args.pairs.split(',')
logger.critical('Parameter --pair mandatory;. E.g --pair ETH/BTC') if pairs is None:
logger.critical('Parameter --pairs mandatory;. E.g --pairs ETH/BTC,XRP/BTC')
exit() exit()
if '/' not in pair:
logger.critical('--pair format must be XXX/YYY')
exit()
# Set timerange to use
timerange = Arguments.parse_timerange(args.timerange)
# Load the strategy # Load the strategy
try: try:
strategy = StrategyResolver(_CONF).strategy strategy = StrategyResolver(_CONF).strategy
@ -131,52 +151,68 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
) )
exit() exit()
# Set the ticker to use return [strategy, exchange, pairs]
tick_interval = strategy.ticker_interval
def get_tickers_data(strategy, exchange, pairs: List[str], args):
"""
Get tickers data for each pairs on live or local, option defined in args
:return: dictinnary of tickers. output format: {'pair': tickersdata}
"""
tick_interval = strategy.ticker_interval
timerange = Arguments.parse_timerange(args.timerange)
# Load pair tickers
tickers = {} tickers = {}
if args.live: if args.live:
logger.info('Downloading pair.') logger.info('Downloading pairs.')
exchange.refresh_tickers([pair], tick_interval) exchange.refresh_tickers(pairs, tick_interval)
for pair in pairs:
tickers[pair] = exchange.klines(pair) tickers[pair] = exchange.klines(pair)
else: else:
tickers = history.load_data( tickers = history.load_data(
datadir=Path(_CONF.get("datadir")), datadir=Path(_CONF.get("datadir")),
pairs=[pair], pairs=pairs,
ticker_interval=tick_interval, ticker_interval=tick_interval,
refresh_pairs=_CONF.get('refresh_pairs', False), refresh_pairs=_CONF.get('refresh_pairs', False),
timerange=timerange, timerange=timerange,
exchange=Exchange(_CONF) exchange=Exchange(_CONF)
) )
# No ticker found, or impossible to download # No ticker found, impossible to download, len mismatch
if tickers == {}: for pair, data in tickers.copy().items():
exit() logger.debug("checking tickers data of pair: %s", pair)
logger.debug("data.empty: %s", data.empty)
logger.debug("len(data): %s", len(data))
if data.empty:
del tickers[pair]
logger.info(
'An issue occured while retreiving datas of %s pair, please retry '
'using -l option for live or --refresh-pairs-cached', pair)
return tickers
# Get trades already made from the DB
trades = load_trades(args, pair, timerange) def generate_dataframe(strategy, tickers, pair) -> pd.DataFrame:
"""
Get tickers then Populate strategy indicators and signals, then return the full dataframe
:return: the DataFrame of a pair
"""
dataframes = strategy.tickerdata_to_dataframe(tickers) dataframes = strategy.tickerdata_to_dataframe(tickers)
dataframe = dataframes[pair] dataframe = dataframes[pair]
dataframe = strategy.advise_buy(dataframe, {'pair': pair}) dataframe = strategy.advise_buy(dataframe, {'pair': pair})
dataframe = strategy.advise_sell(dataframe, {'pair': pair}) dataframe = strategy.advise_sell(dataframe, {'pair': pair})
if len(dataframe.index) > args.plot_limit: return dataframe
logger.warning('Ticker contained more than %s candles as defined '
'with --plot-limit, clipping.', args.plot_limit)
dataframe = dataframe.tail(args.plot_limit)
def extract_trades_of_period(dataframe, trades) -> pd.DataFrame:
"""
Compare trades and backtested pair DataFrames to get trades performed on backtested period
:return: the DataFrame of a trades of period
"""
trades = trades.loc[trades['opents'] >= dataframe.iloc[0]['date']] trades = trades.loc[trades['opents'] >= dataframe.iloc[0]['date']]
fig = generate_graph( return trades
pair=pair,
trades=trades,
data=dataframe,
args=args
)
plot(fig, filename=str(Path('user_data').joinpath('freqtrade-plot.html')))
def generate_graph(pair, trades: pd.DataFrame, data: pd.DataFrame, args) -> tools.make_subplots: def generate_graph(pair, trades: pd.DataFrame, data: pd.DataFrame, args) -> tools.make_subplots:
@ -201,6 +237,7 @@ def generate_graph(pair, trades: pd.DataFrame, data: pd.DataFrame, args) -> tool
fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title='Volume') fig['layout']['yaxis2'].update(title='Volume')
fig['layout']['yaxis3'].update(title='Other') fig['layout']['yaxis3'].update(title='Other')
fig['layout']['xaxis']['rangeslider'].update(visible=False)
# Common information # Common information
candles = go.Candlestick( candles = go.Candlestick(
@ -366,15 +403,57 @@ def plot_parse_args(args: List[str]) -> Namespace:
return arguments.parse_args() return arguments.parse_args()
def analyse_and_plot_pairs(args: Namespace):
"""
From arguments provided in cli:
-Initialise backtest env
-Get tickers data
-Generate Dafaframes populated with indicators and signals
-Load trades excecuted on same periods
-Generate Plotly plot objects
-Generate plot files
:return: None
"""
strategy, exchange, pairs = get_trading_env(args)
# Set timerange to use
timerange = Arguments.parse_timerange(args.timerange)
tick_interval = strategy.ticker_interval
tickers = get_tickers_data(strategy, exchange, pairs, args)
pair_counter = 0
for pair, data in tickers.items():
pair_counter += 1
logger.info("analyse pair %s", pair)
tickers = {}
tickers[pair] = data
dataframe = generate_dataframe(strategy, tickers, pair)
trades = load_trades(args, pair, timerange)
trades = extract_trades_of_period(dataframe, trades)
fig = generate_graph(
pair=pair,
trades=trades,
data=dataframe,
args=args
)
is_last = (False, True)[pair_counter == len(tickers)]
generate_plot_file(fig, pair, tick_interval, is_last)
logger.info('End of ploting process %s plots generated', pair_counter)
def main(sysargv: List[str]) -> None: def main(sysargv: List[str]) -> None:
""" """
This function will initiate the bot and start the trading loop. This function will initiate the bot and start the trading loop.
:return: None :return: None
""" """
logger.info('Starting Plot Dataframe') logger.info('Starting Plot Dataframe')
plot_analyzed_dataframe( analyse_and_plot_pairs(
plot_parse_args(sysargv) plot_parse_args(sysargv)
) )
exit()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -107,8 +107,8 @@ def plot_profit(args: Namespace) -> None:
exit(1) exit(1)
# Take pairs from the cli otherwise switch to the pair in the config file # Take pairs from the cli otherwise switch to the pair in the config file
if args.pair: if args.pairs:
filter_pairs = args.pair filter_pairs = args.pairs
filter_pairs = filter_pairs.split(',') filter_pairs = filter_pairs.split(',')
else: else:
filter_pairs = config['exchange']['pair_whitelist'] filter_pairs = config['exchange']['pair_whitelist']