A library built with backtest portability and performance in mind for backtest trading strategies. There is also an Archive, which can be used to download compatible kline data from Binance (.com or .us) and Coinbase into sqlite databases..
If backests are fast, strategies are cheap.
I'm using this library to build a whole platform (data management, backesting, scanners, signals, etc) via Jupyter Notebooks and a discord bot, and I'm looking for beta testers. If you like this library, send me an email at fasttrade@jedm.dev or join the discord https://discord.gg/Y8ypD3dcgs
If you'd like to add a feature, fix a bug, or something else, please clone the repo and fork it. When you're ready, open a PR into this main repo.
To get started with local dev, clone the repo, set up a virtual env, source it, then install the dev requirements.
git clone git@github.com:<YOUR GIT USERNAME>/fast-trade.git
cd ./fast-trade
python -m venv .fast_trade
source .fast_trade/bin/activate
pip install -r dev_requirements.txtTo generate testing coverage, run
coverage run -m pytest
coverage report -mpip install fast-tradestrategy.json for an example strategy. The basic idea is you describe the "datapoints" then compare them in the "logics". The "datapoints" describe the technical analysis functions to run, and the "logics" describe the logic to use to determine when to enter and exit trades.
Example backtest script
from fast_trade import run_backtest, validate_backtest
backtest = {
"base_balance": 1000, # start with a balance of 1000
"freq": "5Min", # time period selected on the chard
"chart_start": "2021-08-30 18:00:00", # when to start the chart
"chart_stop": "2021-09-06 16:39:00", # when to stop the chart
"comission": 0.01, # a comission to pay per transaction
"datapoints": [ # describes the data to use in the logic
{
"args": [ # args are passed to the transformer function
30
],
"transformer": "sma", # technical analysis function to run
"name": "sma_short" # reference point for use in logic
},
{
"args": [
90
],
"transformer": "sma",
"name": "sma_long"
},
],
"enter": [
[
"close", # field to reference, by default this is any column in the data file. Could also be a float or int
">", # operator to compare these to
"sma_long" # name of datapoint that was prevously defined
],
[
"close",
">",
"sma_short"
]
],
"exit": [
[
"close",
"<",
"sma_short"
]
],
"rules": [["sharpe_ratio", ">", 0.5]], # use rules to filter out backtests that didnt perform well
"trailing_stop_loss": 0.05, # optional trailing stop loss
"exit_on_end": False, # at then end of the backtest, if true, the trade will exit
}
# backtests can also come from urls
# backtest = "https://raw.githubusercontent.com/jrmeier/fast-trade/master/sma_strategy.json"
# returns a mirror of the object, with errors if any
print(validate_backtest(backtest))
# returns the summary object and the dataframe
result = run_backtest(backtest)
summary = result["summary"]
df = result["df"]
trade_log_df = result["trade_df"]
print(summary)
print(df.head())You can also use the package from the command line. Each command's specific help feature can be viewed by running ft <command> -h.
List the commands and their help.
ft -h
This will download the last month of data for BTCUSD from binance.us and store in a ft_archive/ directory as a sqlite database.
ft download BTCUSD binanceus
This will backtest a file with a strategy. By default, it will only show a summary of the backtest. However, if you want to save the results, add the --save flag and it will go the saved_backtests/ directory.
ft backtest ./strategy.json
You can validate a backtest before you run it. This doesn't help with the data, but does help with the logic.
ft validate stategy.json
Modifying the freq
ft backtest ./strategy.json --mods freq 1H
Modifying the freq and the trailing_stop_loss
ft backtest ./strategy.json --mods freq 1H trailing_stop_loss .05
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.
ft backtest ./strategy.json --save
You can download data directly from the CoinbaseAPI and BinanceAPI without registering for an API key.
Get a list of assets available for download from the given exchange. Defaults to binanceus.
ft assets --exchange=EXCHANGE
Download a single asset from the given exchange. Defaults to binanceus.
ft download SYMBOL EXCHANGE
Download the last 30 days of BTCUSDT from binance.us
ft download BTCUSDT binanceus
ft download SYMBOL --archive ARCHIVE_PATH --start START_DATE --end END_DATE --exchange=EXCHANGE
Update the archive. Brings the archive up to date with the latest data for each symbol.
ft update_archive
This update all the existing items in the archive, downloading the latest data for each symbol.
python -m pytestcoverage run -m pytest
coverage report -mThe output its a dictionary. The summary is a summary all the inputs and of the performace of the model. The df is a Pandas Dataframe, which contains all of the data used in the simulation. And the trade_df is a subset of the df frame which just has all the rows when there was an event. The backtest object is also returned, with the details of how the backtest was run.
Example output:
{
"return_perc": 10.093,
"sharpe_ratio": 0.893,
"buy_and_hold_perc": 2.086,
"median_trade_len": 4200.0,
"mean_trade_len": 7341.7,
"max_trade_held": 54300.0,
"min_trade_len": 300.0,
"total_num_winning_trades": 136.0,
"total_num_losing_trades": 371.0,
"avg_win_perc": 0.142,
"avg_loss_perc": -0.021,
"best_trade_perc": 0.012,
"min_trade_perc": -0.0025,
"median_trade_perc": -0.0001,
"mean_trade_perc": 0.0002,
"num_trades": 507,
"win_perc": 26.824,
"loss_perc": 73.176,
"equity_peak": 1127.147,
"equity_final": 1112.254,
"max_drawdown": 985.676,
"total_fees": 26.662,
"first_tic": "2024-11-27 01:15:00",
"last_tic": "2025-01-09 03:10:00",
"total_tics": 12408,
"perc_missing": 0.0,
"total_missing": 0,
"test_duration": 0.302,
"num_of_enter_signals": 718,
"num_of_exit_signals": 5564,
"num_of_hold_signals": 6126,
"market_adjusted_return": 8.007,
"position_metrics": {
"avg_position_size": 0.011,
"max_position_size": 0.012,
"avg_position_duration": 18.327,
"total_commission_impact": 2.397
},
"trade_quality": {
"profit_factor": 2.522,
"avg_win_loss_ratio": 6.881,
"largest_winning_trade": 0.012,
"largest_losing_trade": -0.003
},
"market_exposure": {
"time_in_market_pct": 37.516,
"avg_trade_duration": 18.327
},
"effective_trades": {
"num_profitable_after_commission": 0,
"num_unprofitable_after_commission": 507,
"commission_drag_pct": 2.397
},
"drawdown_metrics": {
"max_drawdown_pct": -11.578,
"avg_drawdown_pct": -3.07,
"max_drawdown_duration": 6178.0,
"avg_drawdown_duration": 137.977,
"current_drawdown": -1.457
},
"risk_metrics": {
"sortino_ratio": 0.006,
"calmar_ratio": 0.0,
"value_at_risk_95": -0.002,
"annualized_volatility": 0.018,
"downside_deviation": 0.001
},
"trade_streaks": {
"current_streak": 371,
"max_win_streak": 1,
"max_loss_streak": 19,
"avg_win_streak": 1.0,
"avg_loss_streak": 2.708
},
"time_analysis": {
"best_day": 0.0,
"worst_day": 0.0,
"avg_daily_return": 0.0,
"daily_return_std": 0.0,
"profitable_days_pct": 0.0,
"best_month": 0.0,
"worst_month": 0.0,
"avg_monthly_return": 0.0,
"monthly_return_std": 0.0,
"profitable_months_pct": 0.0
},
"rules": {
"all": true,
"any": true,
"results": [
true
]
},
"strategy": {
"any_enter": [],
"any_exit": [],
"freq": "5Min",
"comission": 0.01,
"symbol": "BTCUSDT",
"exchange": "binanceus",
"datapoints": [
{
"args": [
9
],
"name": "zlema",
"transformer": "zlema"
},
{
"args": [
99
],
"name": "zlema_1",
"transformer": "zlema"
}
],
"enter": [
[
"zlema",
">",
"close",
4
]
],
"exit": [
[
"zlema_1",
"<",
"close",
2
]
],
"start_date": "2024-11-01",
"exit_on_end": false,
"trailing_stop_loss": null,
"rules": [
[
"return_perc",
">",
0.05
]
],
"base_balance": 1000,
"lot_size_perc": 1.0,
"max_lot_size": 0
}
}