From 9d2b7c1fc0b1209ce9491930e61485ef98d25010 Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Mon, 26 Mar 2018 20:18:14 +0200 Subject: [PATCH 1/8] Add convert script --- scripts/convert_backtestdata.py | 181 ++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100755 scripts/convert_backtestdata.py diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py new file mode 100755 index 000000000..c96579e46 --- /dev/null +++ b/scripts/convert_backtestdata.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +""" +Script to display when the bot will buy a specific pair + +Mandatory Cli parameters: +-p / --pair: pair to examine + +Optional Cli parameters +-d / --datadir: path to pair backtest data +--timerange: specify what timerange of data to use. +-l / --live: Live, to download the latest ticker for the pair +""" + +import sys +from argparse import Namespace +from os import path +import glob +import json +import re +from typing import List, Dict + +from freqtrade.arguments import Arguments +from freqtrade import misc +from freqtrade.logger import Logger +from pandas import DataFrame + +import dateutil.parser + +logger = Logger(name="Convert data").get_logger() + + +def load_old_file(filename) -> List[Dict]: + if not path.isfile(filename): + logger.warning("filename %s does not exist", filename) + return None + logger.debug('Loading ticker data from file %s', filename) + # as in optimize/__init__.py::load_tickerdata_file + # if os.path.isfile(gzipfile): + # logger.debug('Loading ticker data from file %s', gzipfile) + # with gzip.open(gzipfile) as tickerdata: + # pairdata = json.load(tickerdata) + + pairdata = None + with open(filename) as tickerdata: + pairdata = json.load(tickerdata) + return pairdata + + +def parse_old_backtest_data(ticker) -> DataFrame: + """ + Reads old backtest data + Format: "O": 8.794e-05, + "H": 8.948e-05, + "L": 8.794e-05, + "C": 8.88e-05, + "V": 991.09056638, + "T": "2017-11-26T08:50:00", + "BV": 0.0877869 + """ + + columns = {'C': 'close', 'V': 'volume', 'O': 'open', + 'H': 'high', 'L': 'low', 'T': 'date'} + + frame = DataFrame(ticker) \ + .rename(columns=columns) + if 'BV' in frame: + frame.drop('BV', 1, inplace=True) + + frame.sort_values('date', inplace=True) + return frame + + +def convert_dataframe(frame: DataFrame): + """Convert dataframe to new format""" + # reorder columns: + cols = ['date', 'open', 'high', 'low', 'close', 'volume'] + frame = frame[cols] + + frame['date'] = frame['date'].apply( + lambda d: int(dateutil.parser.parse(d).timestamp()) * 1000) + frame['date'] = frame['date'].astype(int) + # Convert columns one by one to preserve type. + by_column = [frame[x].values.tolist() for x in frame.columns] + return list(list(x) for x in zip(*by_column)) + + + +def convert_file(filename: str, filename_new: str): + """Converts a file following the old format to the new format""" + pairdata = load_old_file(filename) + if pairdata and type(pairdata) is list and len(pairdata) > 0: + if type(pairdata[0]) is list: + logger.error("pairdata for %s already in new format", filename) + return 1 + + frame = parse_old_backtest_data(pairdata) + # Convert frame to new format + frame1 = convert_dataframe(frame) + + misc.file_dump_json(filename_new, frame1) + + +def convert_main(args: Namespace) -> None: + """ + converts a folder given in --datadir from old to new format to support ccxt + """ + + workdir = args.datadir if args.datadir.endswith("/") else args.datadir + "/" + print(workdir) + + for filename in glob.glob(workdir + "*.json"): + # swap currency names + ret = re.search(r'[A-Z_]{7,}', path.basename(filename)) + if args.norename: + filename_new = filename + else: + if not ret: + logger.warning("file %s could not be converted, could not extract currencies", + filename) + continue + pair = ret.group(0) + currencies = pair.split("_") + if len(currencies) != 2: + logger.warning("file %s could not be converted, could not extract currencies", + filename) + continue + + ret = re.search(r'\d+(?=\.json)', path.basename(filename)) + if not ret: + logger.warning("file %s could not be converted, interval not found", filename) + continue + interval = ret.group(0) + # filename = "user_data/data/BTC_ADA-5.json" + # filename_new = "user_data/data/ADA_BTC-5.json" + filename_new = "{}/{}_{}-{}.json".format(path.dirname(filename), + currencies[1], currencies[0], interval) + logger.debug("Converting and renaming %s to %s", filename, filename_new) + convert_file(filename, filename_new) + + +def convert_parse_args(args: List[str]) -> Namespace: + """ + Parse args passed to the script + :param args: Cli arguments + :return: args: Array with all arguments + """ + arguments = Arguments(args, 'Convert datafiles') + arguments.parser.add_argument( + '-d', '--datadir', + help='path to backtest data (default: %(default)s', + dest='datadir', + default=path.join('freqtrade', 'tests', 'testdata'), + type=str, + metavar='PATH', + ) + arguments.parser.add_argument( + '-n', '--norename', + help='don''t rename files from BTC_ to _BTC - ' + 'Note that not renaming will overwrite source files', + dest='norename', + default=False, + action='store_true' + ) + + return arguments.parse_args() + + +def main(sysargv: List[str]) -> None: + """ + This function will initiate the bot and start the trading loop. + :return: None + """ + logger.info('Starting Dataframe conversation') + convert_main(convert_parse_args(sysargv)) + + + + + +if __name__ == '__main__': + main(sys.argv[1:]) From 756bd63e1de1f9039d51493f52e272dce724e0ae Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Mon, 26 Mar 2018 23:16:41 +0200 Subject: [PATCH 2/8] whitespace fix --- scripts/convert_backtestdata.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py index c96579e46..051ff24cc 100755 --- a/scripts/convert_backtestdata.py +++ b/scripts/convert_backtestdata.py @@ -84,9 +84,8 @@ def convert_dataframe(frame: DataFrame): return list(list(x) for x in zip(*by_column)) - def convert_file(filename: str, filename_new: str): - """Converts a file following the old format to the new format""" + """Converts a file from old format to ccxt format""" pairdata = load_old_file(filename) if pairdata and type(pairdata) is list and len(pairdata) > 0: if type(pairdata[0]) is list: From a4906c477edf44db9070e731ebb3476c6291ba8e Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Fri, 30 Mar 2018 23:30:23 +0200 Subject: [PATCH 3/8] Add handling for gzip files --- freqtrade/misc.py | 13 +++++++++--- freqtrade/tests/test_misc.py | 5 +++++ scripts/convert_backtestdata.py | 36 ++++++++++++++++----------------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index bc04d6b88..fac379c18 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -5,6 +5,7 @@ Various tool function for Freqtrade and scripts import json import logging import re +import gzip from datetime import datetime from typing import Dict @@ -63,15 +64,21 @@ def common_datearray(dfs: Dict[str, DataFrame]) -> np.ndarray: return np.sort(arr, axis=0) -def file_dump_json(filename, data) -> None: +def file_dump_json(filename, data, is_zip=False) -> None: """ Dump JSON data into a file :param filename: file to create :param data: JSON Data to save :return: """ - with open(filename, 'w') as fp: - json.dump(data, fp, default=str) + if not is_zip: + with open(filename, 'w') as fp: + json.dump(data, fp, default=str) + else: + if not filename.endswith('.gz'): + filename = filename + '.gz' + with gzip.open(filename, 'w') as fp: + json.dump(data, fp, default=str) def format_ms_time(date: str) -> str: diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 3560b2db1..91c34b620 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -71,3 +71,8 @@ def test_file_dump_json(mocker) -> None: file_dump_json('somefile', [1, 2, 3]) assert file_open.call_count == 1 assert json_dump.call_count == 1 + file_open = mocker.patch('freqtrade.misc.gzip.open', MagicMock()) + json_dump = mocker.patch('json.dump', MagicMock()) + file_dump_json('somefile', [1, 2, 3], True) + assert file_open.call_count == 1 + assert json_dump.call_count == 1 diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py index 051ff24cc..1ec553666 100755 --- a/scripts/convert_backtestdata.py +++ b/scripts/convert_backtestdata.py @@ -18,6 +18,7 @@ import glob import json import re from typing import List, Dict +import gzip from freqtrade.arguments import Arguments from freqtrade import misc @@ -26,24 +27,27 @@ from pandas import DataFrame import dateutil.parser -logger = Logger(name="Convert data").get_logger() +logger = Logger(name="Convert data", level=10).get_logger() -def load_old_file(filename) -> List[Dict]: +def load_old_file(filename) -> (List[Dict], bool): if not path.isfile(filename): logger.warning("filename %s does not exist", filename) - return None + return (None, False) logger.debug('Loading ticker data from file %s', filename) - # as in optimize/__init__.py::load_tickerdata_file - # if os.path.isfile(gzipfile): - # logger.debug('Loading ticker data from file %s', gzipfile) - # with gzip.open(gzipfile) as tickerdata: - # pairdata = json.load(tickerdata) pairdata = None - with open(filename) as tickerdata: - pairdata = json.load(tickerdata) - return pairdata + + if filename.endswith('.gz'): + logger.debug('Loading ticker data from file %s', filename) + is_zip = True + with gzip.open(filename) as tickerdata: + pairdata = json.load(tickerdata) + else: + is_zip = False + with open(filename) as tickerdata: + pairdata = json.load(tickerdata) + return (pairdata, is_zip) def parse_old_backtest_data(ticker) -> DataFrame: @@ -86,7 +90,7 @@ def convert_dataframe(frame: DataFrame): def convert_file(filename: str, filename_new: str): """Converts a file from old format to ccxt format""" - pairdata = load_old_file(filename) + (pairdata, is_zip) = load_old_file(filename) if pairdata and type(pairdata) is list and len(pairdata) > 0: if type(pairdata[0]) is list: logger.error("pairdata for %s already in new format", filename) @@ -95,8 +99,7 @@ def convert_file(filename: str, filename_new: str): frame = parse_old_backtest_data(pairdata) # Convert frame to new format frame1 = convert_dataframe(frame) - - misc.file_dump_json(filename_new, frame1) + misc.file_dump_json(filename_new, frame1, is_zip) def convert_main(args: Namespace) -> None: @@ -105,7 +108,7 @@ def convert_main(args: Namespace) -> None: """ workdir = args.datadir if args.datadir.endswith("/") else args.datadir + "/" - print(workdir) + logger.info("Workdir: %s", workdir) for filename in glob.glob(workdir + "*.json"): # swap currency names @@ -173,8 +176,5 @@ def main(sysargv: List[str]) -> None: convert_main(convert_parse_args(sysargv)) - - - if __name__ == '__main__': main(sys.argv[1:]) From a972b8768d9ab55a5cd451113d60485a2387bc3f Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Fri, 30 Mar 2018 23:34:22 +0200 Subject: [PATCH 4/8] Improve errorhandling for json files which are not ticker data --- scripts/convert_backtestdata.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py index 1ec553666..42bc0b4ad 100755 --- a/scripts/convert_backtestdata.py +++ b/scripts/convert_backtestdata.py @@ -69,7 +69,9 @@ def parse_old_backtest_data(ticker) -> DataFrame: .rename(columns=columns) if 'BV' in frame: frame.drop('BV', 1, inplace=True) - + if not 'date' in frame: + logger.warning("Date not in frame - probably not a Ticker file") + return None frame.sort_values('date', inplace=True) return frame @@ -98,8 +100,9 @@ def convert_file(filename: str, filename_new: str): frame = parse_old_backtest_data(pairdata) # Convert frame to new format - frame1 = convert_dataframe(frame) - misc.file_dump_json(filename_new, frame1, is_zip) + if frame is not None: + frame1 = convert_dataframe(frame) + misc.file_dump_json(filename_new, frame1, is_zip) def convert_main(args: Namespace) -> None: From 8a83e050d0b23ac0347440e12089e4a6d6e71afa Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Sat, 31 Mar 2018 17:24:25 +0200 Subject: [PATCH 5/8] use path to handle filenames --- scripts/convert_backtestdata.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py index 42bc0b4ad..3c6c47f0d 100755 --- a/scripts/convert_backtestdata.py +++ b/scripts/convert_backtestdata.py @@ -110,7 +110,7 @@ def convert_main(args: Namespace) -> None: converts a folder given in --datadir from old to new format to support ccxt """ - workdir = args.datadir if args.datadir.endswith("/") else args.datadir + "/" + workdir = path.join(args.datadir, "") logger.info("Workdir: %s", workdir) for filename in glob.glob(workdir + "*.json"): @@ -135,10 +135,10 @@ def convert_main(args: Namespace) -> None: logger.warning("file %s could not be converted, interval not found", filename) continue interval = ret.group(0) - # filename = "user_data/data/BTC_ADA-5.json" - # filename_new = "user_data/data/ADA_BTC-5.json" - filename_new = "{}/{}_{}-{}.json".format(path.dirname(filename), - currencies[1], currencies[0], interval) + + filename_new = path.join(path.dirname(filename), + "{}_{}-{}.json".format(currencies[1], + currencies[0], interval)) logger.debug("Converting and renaming %s to %s", filename, filename_new) convert_file(filename, filename_new) From 2f40e23dcc870dbbe309f606c731bd88694b5d56 Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Sat, 31 Mar 2018 17:28:54 +0200 Subject: [PATCH 6/8] don't check negated if both trees are handled --- freqtrade/misc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index fac379c18..7546dba8f 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -71,14 +71,14 @@ def file_dump_json(filename, data, is_zip=False) -> None: :param data: JSON Data to save :return: """ - if not is_zip: - with open(filename, 'w') as fp: - json.dump(data, fp, default=str) - else: + if is_zip: if not filename.endswith('.gz'): filename = filename + '.gz' with gzip.open(filename, 'w') as fp: json.dump(data, fp, default=str) + else: + with open(filename, 'w') as fp: + json.dump(data, fp, default=str) def format_ms_time(date: str) -> str: From 18f8686cdbf8dd3a3abfb2b81d1512478c3ee598 Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Sat, 31 Mar 2018 17:29:52 +0200 Subject: [PATCH 7/8] fix returncode for convert_file --- scripts/convert_backtestdata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py index 3c6c47f0d..024b3c9af 100755 --- a/scripts/convert_backtestdata.py +++ b/scripts/convert_backtestdata.py @@ -90,13 +90,13 @@ def convert_dataframe(frame: DataFrame): return list(list(x) for x in zip(*by_column)) -def convert_file(filename: str, filename_new: str): +def convert_file(filename: str, filename_new: str) -> None: """Converts a file from old format to ccxt format""" (pairdata, is_zip) = load_old_file(filename) - if pairdata and type(pairdata) is list and len(pairdata) > 0: + if pairdata and type(pairdata) is list: if type(pairdata[0]) is list: logger.error("pairdata for %s already in new format", filename) - return 1 + return frame = parse_old_backtest_data(pairdata) # Convert frame to new format From 4ac591b0763325c5b06004b40c7acc19f4abd45b Mon Sep 17 00:00:00 2001 From: Matthias Voppichler Date: Sat, 31 Mar 2018 17:30:11 +0200 Subject: [PATCH 8/8] rename logging to freqtrade --- scripts/convert_backtestdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/convert_backtestdata.py b/scripts/convert_backtestdata.py index 024b3c9af..a6f6ccc60 100755 --- a/scripts/convert_backtestdata.py +++ b/scripts/convert_backtestdata.py @@ -27,7 +27,7 @@ from pandas import DataFrame import dateutil.parser -logger = Logger(name="Convert data", level=10).get_logger() +logger = Logger(name="freqtrade").get_logger() def load_old_file(filename) -> (List[Dict], bool):