Merge branch 'develop' into feat/dataprovider
This commit is contained in:
commit
02d13645b0
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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__':
|
||||||
|
@ -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']
|
||||||
|
Loading…
Reference in New Issue
Block a user