Skip to content

Commit

Permalink
Merge pull request #15 from jrmeier/better-cli
Browse files Browse the repository at this point in the history
Moving CLI to argparse
  • Loading branch information
jrmeier authored Mar 1, 2022
2 parents 7dc9ba8 + 4a1db98 commit 742767c
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 207 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.fast_trade/
venv/*
*.csv
.vscode
__pycache__/
Expand Down
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [email protected].

## Install
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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
```
Expand Down
228 changes: 116 additions & 112 deletions fast_trade/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
Loading

0 comments on commit 742767c

Please sign in to comment.