diff --git a/.gitignore b/.gitignore index b298e0c..d39e0f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.fast_trade/ +venv/* *.csv .vscode __pycache__/ diff --git a/README.md b/README.md index f6cd91f..701b762 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A library built with backtest portability and performance in mind for backtest trading strategies. There is also a [DataDownloader](#DataDownloader), which can be used to download compatible kline data from Binance (.com or .us) ## Beta Testing + I'm using this library to build a platform (live data, charting, paper trading, etc.) and I'm looking for beta testers. If you like this library, send me an email at fasttrade@jedm.dev. ## Install @@ -58,15 +59,14 @@ Custom datapoints can be added by setting a function name in the [datapoints](/f Note: -If a transfomer function returns multiple values, the will be datapoints with the `datapointName_respective column. +If a transfomer function returns multiple series, fast-trade will name concate the name of the series with the name of the transfomer function. -Example +Example: -Say we have a datapoint that uses the transformer `bbands`. +The `bbands` function returns two series, one for the upper band and one for the lower band. The name of the series will be `bbands_upper` and `bbands_lower`. `bbands` returns 3 columns `upper_bb, middle_band, lower_bb` -So when we want to reference these in our logic, the names we use in the logics are `bbands_upper_bb`, `bbands_middle_bb`,`bbands_lower_bb` ## Data @@ -205,7 +205,7 @@ Using an archive item managed by the DataDownloader. Just pass in the symbol ins Modifying the `chart_period` -`ft backtest --data=./datafile.csv --backtest=./backtest.json --chart_period=1h` +`ft backtest --data=./datafile.csv --backtest=./backtest.json --mods chart_period 1h` Saving a test result This generates creates the `saved_backtest` directory (if it doesn't exist), then inside of there, is another directory with a timestamp, with a chart, the backtest file, the summary, and the raw dataframe as a csv. @@ -222,15 +222,18 @@ Download 1 minute kline/ohlcv from Binance and store them in CSVs in the `archiv It may take awhile to download all of the data the first time, so be patient. It only need to download all of it once, then it will be updated from the most recent date. Check out the file [update_symbol_data.py](/fast_trade/update_symbol_data.py) if you want to see how it works. -`ft download --symbol=SYMBOL --exchange=EXCHANGE --start=START --end=END --archive=ARCHIVE` +Basic example +Download the last 30 days of BTCUSDT from binance.com +`python -m fast_trade download BTCUSDT` + +`ft download SYMBOL --archive ARCHIVE_PATH --start START_DATE --end END_DATE --exchange=EXCHANGE` Defaults are: ```python - -SYMBOL="BTCUSDT" # any symbol on the exchange EXCHANGE="binance.com" # can be binance.us -START="2017-01-01" # the start date of when you want date. This default is the oldest for binance.com. +START= "2020-01-01" # start date +END= "2020-01-31" # end date END='current date' # you'll probably never need this ARCHIVE='./archive' # path the archive folder, which is where the CSVs are stored ``` diff --git a/fast_trade/cli.py b/fast_trade/cli.py index 2315f8a..4833864 100644 --- a/fast_trade/cli.py +++ b/fast_trade/cli.py @@ -2,140 +2,144 @@ from fast_trade.validate_backtest import validate_backtest import sys import json -from .build_data_frame import prepare_df - from .cli_helpers import ( open_strat_file, - format_all_help_text, save, create_plot, - format_command, ) -from fast_trade.update_symbol_data import load_archive_to_df, update_symbol_data +from fast_trade.update_symbol_data import update_symbol_data from .run_backtest import run_backtest import matplotlib.pyplot as plt import datetime +import argparse +import os -def parse_args(raw_args): - """ - args: raw args after the command line - - returns: dict of args as key value pairs - """ - arg_dict = {} - for raw_arg in raw_args: - arg = raw_arg.split("=") - arg_key = arg[0].split("--").pop() - if len(arg) > 1: - if arg[1] == "false": - arg_dict[arg_key] = False - elif arg[1] == "true": - arg_dict[arg_key] == True - # flake8: noqa - elif arg[1] != "false" or arg[1] != "true": - arg_dict[arg_key] = arg[1] - else: - arg_dict[arg_key] = True +def main(): + parser = argparse.ArgumentParser( + description="Fast Trade CLI", + prog="ft", + ) + + sub_parsers = parser.add_subparsers() + + # build the argement parser downloading stuff + default_archive_path = os.path.join(os.getcwd(), "archive") + default_end_date = datetime.datetime.utcnow() + default_start_date = default_end_date - datetime.timedelta(days=30) + + download_parser = sub_parsers.add_parser("download", help="download data") + download_parser.add_argument("symbol", help="symbol to download", type=str) + download_parser.add_argument( + "--archive", + help="path to directory to serve as the 'archive'", + type=str, + default=default_archive_path, + ) + + download_parser.add_argument( + "--start", + help="first date to start downloading data from. Recently Listed coins might not have a lot of data.", + type=str, + default=default_start_date.strftime("%Y-%m-%d"), + ) + + download_parser.add_argument( + "--end", + help="Last date to download data. Defaults to today.", + type=str, + default=default_end_date.strftime("%Y-%m-%d"), + ) + + download_parser.add_argument( + "--exchange", + help="Which exchange to download data from. Defaults to binance.com", + type=str, + default="binance.com", + choices=["binance.com", "binance.us"], + ) + + backtest_parser = sub_parsers.add_parser("backtest", help="backtest a strategy") + backtest_parser.add_argument( + "strategy", + help="path to strategy file", + type=str, + ) + + backtest_parser.add_argument("data", help="path to data file for kline data.") + backtest_parser.add_argument( + "--mods", help="Modifiers for strategy/backtest", nargs="*" + ) + backtest_parser.add_argument( + "--save", + help="save the backtest results to a directory", + action="store_true", + default=False, + ) + backtest_parser.add_argument( + "--plot", + help="plot the backtest results", + action="store_true", + default=False, + ) + + validate_backtest_parser = sub_parsers.add_parser( + "validate", help="validate a strategy file" + ) + + validate_backtest_parser.add_argument( + "strategy", + help="path to strategy file", + type=str, + ) + validate_backtest_parser.add_argument( + "--mods", help="Modifiers for strategy/backtest", nargs="*" + ) + + args = parser.parse_args() + command = sys.argv[1] - return arg_dict + if command == "download": + update_symbol_data( + args.symbol, args.start, args.end, args.archive, args.exchange + ) + if command == "backtest": + # match the mods to the kwargs + strat_obj = open_strat_file(args.strategy) + if args.mods: + mods = {} + i = 0 + while i < len(args.mods): + mods[args.mods[i]] = args.mods[i + 1] + i += 2 -def main(): - if len(sys.argv) < 2: - print(format_all_help_text()) - return + strat_obj = {**strat_obj, **mods} - command = sys.argv[1] + backtest = run_backtest(strat_obj, ohlcv_path=args.data) - args = parse_args(sys.argv[2:]) + if args.save: + save(backtest, backtest["backtest"]) - if command == "backtest": - # check for help - if "help" in args.keys(): - print(format_command(command)) - return - - strat_obj = open_strat_file(args["backtest"]) - strat_obj = {**strat_obj, **args} - if args.get("data", "").endswith(".csv"): - # use a csv file - data = args["data"] - try: - res = run_backtest(strat_obj, ohlcv_path=data) - except Exception as e: - print("Error running backtest: ", e) - return - else: - # load from the archive - archive = args.get("archive", "./archive") - try: - archive_df = load_archive_to_df(strat_obj["symbol"], archive) - except Exception as e: - print("Error loading archive file: ", e) - return - - try: - - archive_df = prepare_df(archive_df, strat_obj) - except Exception as e: - print("Error preparing the dataframe: ", e) - return - try: - res = run_backtest(strat_obj, df=archive_df) - except Exception as e: - print("Error running backtest: ", e) - return - - if res["summary"]: - print(json.dumps((res["summary"]), indent=2)) - else: - print("There was an error:") - print(json.dumps((res["backtest_validation"]), indent=2)) - - if args.get("save"): - save(res, strat_obj) - - if args.get("plot"): - create_plot(res["df"]) + if args.plot: + create_plot(backtest["df"]) plt.show() - return + if command == "validate": + strat_obj = open_strat_file(args.strategy) + if args.mods: + mods = {} + i = 0 + while i < len(args.mods): + mods[args.mods[i]] = args.mods[i + 1] + i += 2 - if command == "help": - print(format_all_help_text()) - return + strat_obj = {**strat_obj, **mods} - if command == "download": - default_end = ( - datetime.datetime.utcnow() + datetime.timedelta(days=1) - ).strftime("%Y-%m-%d") - symbol = args.get("symbol", "BTCUSDT") - arc_path = args.get("archive", "./archive/") - start_date = args.get("start", "2017-01-01") - end_date = args.get("end", default_end) - exchange = args.get("exchange", "binance.com") - update_symbol_data(symbol, start_date, end_date, arc_path, exchange) - - print("Done downloading ", symbol) - return + backtest = validate_backtest(strat_obj) - if command == "validate": - print("args: ", args) - backtest = open_strat_file(args["backtest"]) - if not backtest: - print("backtest not found! ") - return - print("backtest: ", backtest) - backtest = {**backtest, **args} - - res = validate_backtest(backtest) - - print(json.dumps(res, indent=2)) - return - print("Command not found") - print(format_all_help_text()) + print(json.dumps(backtest, indent=2)) if __name__ == "__main__": diff --git a/fast_trade/cli_helpers.py b/fast_trade/cli_helpers.py index b483c80..42a44fb 100644 --- a/fast_trade/cli_helpers.py +++ b/fast_trade/cli_helpers.py @@ -17,37 +17,6 @@ def open_strat_file(fp): print("error: ", e) -def format_command(command): - - command_obj = help_dict[command] - ret_str = f"\n\t{command}" - ret_str += f"\n\t\t\t{command_obj['description']}" - - ret_str += f"\n\t\tusage" - for ex in command_obj["examples"]: - ret_str += f"\n\t\t\t{ex}" - - ret_str += f"\n\t\targs" - for arg in command_obj["args"].keys(): - ret_str += f"\n\t\t\t{arg}," - if command_obj["args"][arg]["required"]: - ret_str += " required," - - ret_str += f" {command_obj['args'][arg]['desc']}" - - return ret_str - - -def format_all_help_text(): - - formated_string = "\nFast Trade Help\n\nCommands\n\n" - - for command in help_dict.keys(): - formated_string = formated_string + format_command(command) - - return formated_string - - def create_plot(df): plot_df = pd.DataFrame( data={ @@ -93,55 +62,3 @@ def save(result, strat_obj): create_plot(result["df"]) plt.savefig(f"{new_save_dir}/plot.png") - - -help_dict = { - "validate": { - "description": """Takes in a backtest object and returns - a mirror object with errors if they exist - """, - "examples": ["ft validate --backtest./backtest.json"], - "args": {"--backtest": {"desc": "path to the backtest file", "required": True}}, - }, - "download": { - "description": "download data from Binance", - "examples": ["ft download --symbol=BTCUSDT --start=2019-01-01"], - "args": { - "--symbol": {"desc": "Symbol to download", "required": False}, - "--archive": { - "desc": "Path to the archive where the CSVs live", - "required": False, - }, - "--start": {"desc": "date to start downloading from", "required": False}, - "--end": {"desc": "date to stop downloading_from", "required": False}, - "--exchange": { - "desc": "Exchange to use. Either binance.us or binance.com. binance.com is the default.", - "required": False, - }, - }, - }, - "backtest": { - "description": """Runs a backtest with the given parameters. - Any backtest modifications can be passed at the end of the command. - """, - "examples": ["ft backtest --csv=./datafile.csv/ --backtest=./backtest.json"], - "args": { - "--csv": { - "desc": "path or paths to csv_file seperated by commands", - "required": True, - }, - "--backtest": { - "desc": "path to backtest file, must json format", - "required": True, - }, - "--plot": { - "desc": "opens a basic plot using matlib.plot", - "required": False, - }, - "--save": { - "desc": "saves the dataframe, backtest, and plot in to the path", - "required": False, - }, - }, - }, -} diff --git a/fast_trade/update_symbol_data.py b/fast_trade/update_symbol_data.py index aef958c..042fbbf 100644 --- a/fast_trade/update_symbol_data.py +++ b/fast_trade/update_symbol_data.py @@ -50,9 +50,12 @@ # START_DATE = int(datetime.datetime.fromisoformat(tmp_start_date).timestamp()) # EXCHANGE = "binance.com" # binance.com or binance.us +default_end_date = datetime.datetime.utcnow() +default_start_date = default_end_date - datetime.timedelta(days=30) # by default is the last + def update_symbol_data( - symbol, start_date, end_date="", arc_path="./archive", exchange="binance.us" + symbol, start_date=default_start_date, end_date=default_end_date, arc_path="./archive", exchange="binance.us" ): print(f"updating: {symbol}") diff --git a/setup.py b/setup.py index 274c329..19f9649 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="fast-trade", - version="0.3.1", + version="0.3.2", description="About low code backtesting library utilizing pandas and technical analysis indicators", long_description=README, long_description_content_type="text/markdown", diff --git a/wtf.json b/strategy.json similarity index 100% rename from wtf.json rename to strategy.json