Merge branch 'develop' into feat/dataprovider

This commit is contained in:
Matthias 2019-01-26 19:28:49 +01:00
commit 02d13645b0
9 changed files with 245 additions and 137 deletions

View File

@ -166,53 +166,65 @@ The most important in the backtesting is to understand the result.
A backtesting result will look like that: A backtesting result will look like that:
``` ```
======================================== BACKTESTING REPORT ========================================= ========================================================= BACKTESTING REPORT ========================================================
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss | | pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:| |:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ETH/BTC | 44 | 0.18 | 0.00159118 | 50.9 | 44 | 0 | | ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 21 |
| LTC/BTC | 27 | 0.10 | 0.00051931 | 103.1 | 26 | 1 | | ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 8 |
| ETC/BTC | 24 | 0.05 | 0.00022434 | 166.0 | 22 | 2 | | BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 14 |
| DASH/BTC | 29 | 0.18 | 0.00103223 | 192.2 | 29 | 0 | | DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 7 |
| ZEC/BTC | 65 | -0.02 | -0.00020621 | 202.7 | 62 | 3 | | ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 10 |
| XLM/BTC | 35 | 0.02 | 0.00012877 | 242.4 | 32 | 3 | | EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 20 |
| BCH/BTC | 12 | 0.62 | 0.00149284 | 50.0 | 12 | 0 | | ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 15 |
| POWR/BTC | 21 | 0.26 | 0.00108215 | 134.8 | 21 | 0 | | ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 17 |
| ADA/BTC | 54 | -0.19 | -0.00205202 | 191.3 | 47 | 7 | | IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 18 |
| XMR/BTC | 24 | -0.43 | -0.00206013 | 120.6 | 20 | 4 | | LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 9 |
| TOTAL | 335 | 0.03 | 0.00175246 | 157.9 | 315 | 20 | | LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 21 |
2018-06-13 06:57:27,347 - freqtrade.optimize.backtesting - INFO - | NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 7 |
====================================== LEFT OPEN TRADES REPORT ====================================== | NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 13 |
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss | | REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 5 |
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:| | XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 9 |
| ETH/BTC | 3 | 0.16 | 0.00009619 | 25.0 | 3 | 0 | | XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 11 |
| LTC/BTC | 1 | -1.00 | -0.00020118 | 1085.0 | 0 | 1 | | XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 23 |
| ETC/BTC | 2 | -1.80 | -0.00071933 | 1092.5 | 0 | 2 | | ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 15 |
| DASH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 | | TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| ZEC/BTC | 3 | -4.27 | -0.00256826 | 1301.7 | 0 | 3 | ========================================================= SELL REASON STATS =========================================================
| XLM/BTC | 3 | -1.11 | -0.00066744 | 965.0 | 0 | 3 | | Sell Reason | Count |
| BCH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 | |:-------------------|--------:|
| POWR/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 | | trailing_stop_loss | 205 |
| ADA/BTC | 7 | -3.58 | -0.00503604 | 850.0 | 0 | 7 | | stop_loss | 166 |
| XMR/BTC | 4 | -3.79 | -0.00303456 | 291.2 | 0 | 4 | | sell_signal | 56 |
| TOTAL | 23 | -2.63 | -0.01213062 | 750.4 | 3 | 20 | | force_sell | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 |
``` ```
The 1st table will contain all trades the bot made. The 1st table will contain all trades the bot made.
The 2nd table will contain all trades the bot had to `forcesell` at the end of the backtest period to present a full picture. The 2nd table will contain a recap of sell reasons.
The 3rd table will contain all trades the bot had to `forcesell` at the end of the backtest period to present a full picture.
These trades are also included in the first table, but are extracted separately for clarity. These trades are also included in the first table, but are extracted separately for clarity.
The last line will give you the overall performance of your strategy, The last line will give you the overall performance of your strategy,
here: here:
``` ```
TOTAL 419 -0.41 -0.00348593 52.9 | TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
``` ```
We understand the bot has made `419` trades for an average duration of We understand the bot has made `429` trades for an average duration of
`52.9` min, with a performance of `-0.41%` (loss), that means it has `4:12:00`, with a performance of `76.20%` (profit), that means it has
lost a total of `-0.00348593 BTC`. earned a total of `0.00762792 BTC` starting with a capital of 0.01 BTC.
The column `avg profit %` shows the average profit for all trades made while the column `cum profit %` sums all the profits/losses.
The column `tot profit %` shows instead the total profit % in relation to allocated capital
(`max_open_trades * stake_amount`). In the above results we have `max_open_trades=2 stake_amount=0.005` in config
so `(76.20/100) * (0.005 * 2) =~ 0.00762792 BTC`.
As you will see your strategy performance will be influenced by your buy As you will see your strategy performance will be influenced by your buy
strategy, your sell strategy, and also by the `minimal_roi` and strategy, your sell strategy, and also by the `minimal_roi` and
@ -251,11 +263,11 @@ There will be an additional table comparing win/losses of the different strategi
Detailed output for all strategies one after the other will be available, so make sure to scroll up. Detailed output for all strategies one after the other will be available, so make sure to scroll up.
``` ```
=================================================== Strategy Summary ==================================================== =========================================================== Strategy Summary ===========================================================
| Strategy | buy count | avg profit % | cum profit % | total profit ETH | avg duration | profit | loss | | Strategy | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:-----------|------------:|---------------:|---------------:|-------------------:|:----------------|---------:|-------:| |:------------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| Strategy1 | 19 | -0.76 | -14.39 | -0.01440287 | 15:48:00 | 15 | 4 | | Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| Strategy2 | 6 | -2.73 | -16.40 | -0.01641299 | 1 day, 14:12:00 | 3 | 3 | | Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 825 |
``` ```
## Next step ## Next step

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

@ -101,11 +101,13 @@ class Backtesting(object):
:return: pretty printed table with tabulate as str :return: pretty printed table with tabulate as str
""" """
stake_currency = str(self.config.get('stake_currency')) stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f') floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = [] tabular_data = []
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %', headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss'] 'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for pair in data: for pair in data:
result = results[results.pair == pair] result = results[results.pair == pair]
if skip_nan and result.profit_abs.isnull().all(): if skip_nan and result.profit_abs.isnull().all():
@ -117,6 +119,7 @@ class Backtesting(object):
result.profit_percent.mean() * 100.0, result.profit_percent.mean() * 100.0,
result.profit_percent.sum() * 100.0, result.profit_percent.sum() * 100.0,
result.profit_abs.sum(), result.profit_abs.sum(),
result.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta( str(timedelta(
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00', minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
len(result[result.profit_abs > 0]), len(result[result.profit_abs > 0]),
@ -130,6 +133,7 @@ class Backtesting(object):
results.profit_percent.mean() * 100.0, results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0, results.profit_percent.sum() * 100.0,
results.profit_abs.sum(), results.profit_abs.sum(),
results.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta( str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00', minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]), len(results[results.profit_abs > 0]),
@ -154,11 +158,13 @@ class Backtesting(object):
Generate summary table per strategy Generate summary table per strategy
""" """
stake_currency = str(self.config.get('stake_currency')) stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f') floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = [] tabular_data = []
headers = ['Strategy', 'buy count', 'avg profit %', 'cum profit %', headers = ['Strategy', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss'] 'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for strategy, results in all_results.items(): for strategy, results in all_results.items():
tabular_data.append([ tabular_data.append([
strategy, strategy,
@ -166,6 +172,7 @@ class Backtesting(object):
results.profit_percent.mean() * 100.0, results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0, results.profit_percent.sum() * 100.0,
results.profit_abs.sum(), results.profit_abs.sum(),
results.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta( str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00', minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]), len(results[results.profit_abs > 0]),
@ -432,18 +439,18 @@ class Backtesting(object):
strategy if len(self.strategylist) > 1 else None) strategy if len(self.strategylist) > 1 else None)
print(f"Result for strategy {strategy}") print(f"Result for strategy {strategy}")
print(' BACKTESTING REPORT '.center(119, '=')) print(' BACKTESTING REPORT '.center(133, '='))
print(self._generate_text_table(data, results)) print(self._generate_text_table(data, results))
print(' SELL REASON STATS '.center(119, '=')) print(' SELL REASON STATS '.center(133, '='))
print(self._generate_text_table_sell_reason(data, results)) print(self._generate_text_table_sell_reason(data, results))
print(' LEFT OPEN TRADES REPORT '.center(119, '=')) print(' LEFT OPEN TRADES REPORT '.center(133, '='))
print(self._generate_text_table(data, results.loc[results.open_at_end], True)) print(self._generate_text_table(data, results.loc[results.open_at_end], True))
print() print()
if len(all_results) > 1: if len(all_results) > 1:
# Print Strategy summary table # Print Strategy summary table
print(' Strategy Summary '.center(119, '=')) print(' Strategy Summary '.center(133, '='))
print(self._generate_text_table_strategy(all_results)) print(self._generate_text_table_strategy(all_results))
print('\nFor more details, please look at the detail tables above') print('\nFor more details, please look at the detail tables above')

View File

@ -346,6 +346,7 @@ def test_tickerdata_to_dataframe_bt(default_conf, mocker) -> None:
def test_generate_text_table(default_conf, mocker): def test_generate_text_table(default_conf, mocker):
patch_exchange(mocker) patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
results = pd.DataFrame( results = pd.DataFrame(
@ -361,13 +362,13 @@ def test_generate_text_table(default_conf, mocker):
result_str = ( result_str = (
'| pair | buy count | avg profit % | cum profit % | ' '| pair | buy count | avg profit % | cum profit % | '
'total profit BTC | avg duration | profit | loss |\n' 'tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:--------|------------:|---------------:|---------------:|' '|:--------|------------:|---------------:|---------------:|'
'-------------------:|:---------------|---------:|-------:|\n' '-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | ' '| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 0:20:00 | 2 | 0 |\n' '0.60000000 | 15.00 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | ' '| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 0:20:00 | 2 | 0 |' '0.60000000 | 15.00 | 0:20:00 | 2 | 0 |'
) )
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
@ -403,6 +404,7 @@ def test_generate_text_table_strategyn(default_conf, mocker):
Test Backtesting.generate_text_table_sell_reason() method Test Backtesting.generate_text_table_sell_reason() method
""" """
patch_exchange(mocker) patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
results = {} results = {}
results['ETH/BTC'] = pd.DataFrame( results['ETH/BTC'] = pd.DataFrame(
@ -430,13 +432,13 @@ def test_generate_text_table_strategyn(default_conf, mocker):
result_str = ( result_str = (
'| Strategy | buy count | avg profit % | cum profit % ' '| Strategy | buy count | avg profit % | cum profit % '
'| total profit BTC | avg duration | profit | loss |\n' '| tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:-----------|------------:|---------------:|---------------:' '|:-----------|------------:|---------------:|---------------:'
'|-------------------:|:---------------|---------:|-------:|\n' '|-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 3 | 20.00 | 60.00 ' '| ETH/BTC | 3 | 20.00 | 60.00 '
'| 1.10000000 | 0:17:00 | 3 | 0 |\n' '| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
'| LTC/BTC | 3 | 30.00 | 90.00 ' '| LTC/BTC | 3 | 30.00 | 90.00 '
'| 1.30000000 | 0:20:00 | 3 | 0 |' '| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
) )
print(backtesting._generate_text_table_strategy(all_results=results)) print(backtesting._generate_text_table_strategy(all_results=results))
assert backtesting._generate_text_table_strategy(all_results=results) == result_str assert backtesting._generate_text_table_strategy(all_results=results) == result_str

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,4 +1,4 @@
ccxt==1.18.144 ccxt==1.18.152
SQLAlchemy==1.2.16 SQLAlchemy==1.2.16
python-telegram-bot==11.1.0 python-telegram-bot==11.1.0
arrow==0.13.0 arrow==0.13.0
@ -13,7 +13,7 @@ scipy==1.2.0
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.16.0 numpy==1.16.0
TA-Lib==0.4.17 TA-Lib==0.4.17
tabulate==0.8.2 tabulate==0.8.3
coinmarketcap==5.0.3 coinmarketcap==5.0.3
# Required for hyperopt # Required for hyperopt

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,8 +21,8 @@ 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/
--indicators2 fastk,fastd --indicators1 sma,ema3 --indicators2 fastk,fastd
""" """
import json import json
import logging import logging
@ -65,7 +65,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 +75,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 file.exists():
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 +86,55 @@ 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'
Path("user_data/plots").mkdir(parents=True, exist_ok=True)
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,61 +146,84 @@ 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_latest_ohlcv([(pair, tick_interval)]) exchange.refresh_latest_ohlcv([(pair, tick_interval) for pair in pairs])
for pair in pairs:
tickers[pair] = exchange.klines((pair, tick_interval)) tickers[pair] = exchange.klines((pair, tick_interval))
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: str,
trades: pd.DataFrame,
data: pd.DataFrame,
indicators1: str,
indicators2: str
) -> tools.make_subplots:
""" """
Generate the graph from the data generated by Backtesting or from DB Generate the graph from the data generated by Backtesting or from DB
:param pair: Pair to Display on the graph :param pair: Pair to Display on the graph
:param trades: All trades created :param trades: All trades created
:param data: Dataframe :param data: Dataframe
:param args: sys.argv that contrains the two params indicators1, and indicators2 :indicators1: String Main plot indicators
:indicators2: String Sub plot indicators
:return: None :return: None
""" """
@ -201,6 +239,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(
@ -285,7 +324,7 @@ def generate_graph(pair, trades: pd.DataFrame, data: pd.DataFrame, args) -> tool
fig.append_trace(bb_lower, 1, 1) fig.append_trace(bb_lower, 1, 1)
fig.append_trace(bb_upper, 1, 1) fig.append_trace(bb_upper, 1, 1)
fig = generate_row(fig=fig, row=1, raw_indicators=args.indicators1, data=data) fig = generate_row(fig=fig, row=1, raw_indicators=indicators1, data=data)
fig.append_trace(buys, 1, 1) fig.append_trace(buys, 1, 1)
fig.append_trace(sells, 1, 1) fig.append_trace(sells, 1, 1)
fig.append_trace(trade_buys, 1, 1) fig.append_trace(trade_buys, 1, 1)
@ -300,7 +339,7 @@ def generate_graph(pair, trades: pd.DataFrame, data: pd.DataFrame, args) -> tool
fig.append_trace(volume, 2, 1) fig.append_trace(volume, 2, 1)
# Row 3 # Row 3
fig = generate_row(fig=fig, row=3, raw_indicators=args.indicators2, data=data) fig = generate_row(fig=fig, row=3, raw_indicators=indicators2, data=data)
return fig return fig
@ -349,7 +388,7 @@ def plot_parse_args(args: List[str]) -> Namespace:
help='Set indicators from your strategy you want in the third row of the graph. Separate ' help='Set indicators from your strategy you want in the third row of the graph. Separate '
'them with a coma. E.g: fastd,fastk (default: %(default)s)', 'them with a coma. E.g: fastd,fastk (default: %(default)s)',
type=str, type=str,
default='macd', default='macd,macdsignal',
dest='indicators2', dest='indicators2',
) )
arguments.parser.add_argument( arguments.parser.add_argument(
@ -366,15 +405,58 @@ 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,
indicators1=args.indicators1,
indicators2=args.indicators2
)
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

@ -108,8 +108,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']