Merge pull request #2176 from freqtrade/plot_commands

Move Plot scripts to freqtrade subcommands
This commit is contained in:
hroff-1902 2019-09-02 08:08:51 +03:00 committed by GitHub
commit 08b090c707
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 415 additions and 255 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
docs/assets/plot-profit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -2,9 +2,9 @@
This page explains how to plot prices, indicators and profits.
## Installation
## Installation / Setup
Plotting scripts use Plotly library. Install/upgrade it with:
Plotting modules use the Plotly library. You can install / upgrade this by running the following command:
``` bash
pip install -U -r requirements-plot.txt
@ -12,90 +12,172 @@ pip install -U -r requirements-plot.txt
## Plot price and indicators
Usage for the price plotter:
The `freqtrade plot-dataframe` subcommand shows an interactive graph with three subplots:
* Main plot with candlestics and indicators following price (sma/ema)
* Volume bars
* Additional indicators as specified by `--indicators2`
![plot-dataframe](assets/plot-dataframe.png)
Possible arguments:
```
usage: freqtrade plot-dataframe [-h] [-p PAIRS [PAIRS ...]]
[--indicators1 INDICATORS1 [INDICATORS1 ...]]
[--indicators2 INDICATORS2 [INDICATORS2 ...]]
[--plot-limit INT] [--db-url PATH]
[--trade-source {DB,file}] [--export EXPORT]
[--export-filename PATH]
[--timerange TIMERANGE]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
separated.
--indicators1 INDICATORS1 [INDICATORS1 ...]
Set indicators from your strategy you want in the
first row of the graph. Space-separated list. Example:
`ema3 ema5`. Default: `['sma', 'ema3', 'ema5']`.
--indicators2 INDICATORS2 [INDICATORS2 ...]
Set indicators from your strategy you want in the
third row of the graph. Space-separated list. Example:
`fastd fastk`. Default: `['macd', 'macdsignal']`.
--plot-limit INT Specify tick limit for plotting. Notice: too high
values cause huge files. Default: 750.
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite://` for Dry Run).
--trade-source {DB,file}
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
--export-filename PATH
Save backtest results to the file with this filename
(default: `user_data/backtest_results/backtest-
result.json`). Requires `--export` to be set as well.
Example: `--export-filename=user_data/backtest_results
/backtest_today.json`
--timerange TIMERANGE
Specify what timerange of data to use.
``` bash
python3 script/plot_dataframe.py [-h] [-p pairs]
```
Example
Example:
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH
freqtrade plot-dataframe -p BTC/ETH
```
The `-p` pairs argument can be used to specify pairs you would like to plot.
The `-p/--pairs` argument can be used to specify pairs you would like to plot.
!!! Note
The `freqtrade plot-dataframe` subcommand generates one plot-file per pair.
Specify custom indicators.
Use `--indicators1` for the main plot and `--indicators2` for the subplot below (if values are in a different range than prices).
!!! tip
You will almost certainly want to specify a custom strategy! This can be done by adding `-s Classname` / `--strategy ClassName` to the command.
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH --indicators1 sma,ema --indicators2 macd
freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --indicators1 sma ema --indicators2 macd
```
### Advanced use
### Further usage examples
To plot multiple pairs, separate them with a comma:
To plot multiple pairs, separate them with a space:
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH XRP/ETH
```
To plot a timerange (to zoom in):
To plot a timerange (to zoom in)
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH --timerange=20180801-20180805
freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --timerange=20180801-20180805
```
To plot trades stored in a database use `--db-url` argument:
To plot trades stored in a database use `--db-url` in combination with `--trade-source DB`:
``` bash
python3 scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB
freqtrade --strategy AwesomeStrategy plot-dataframe --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB
```
To plot trades from a backtesting result, use `--export-filename <filename>`
``` bash
python3 scripts/plot_dataframe.py --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH
```
To plot a custom strategy the strategy should have first be backtested.
The results may then be plotted with the -s argument:
``` bash
python3 scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
freqtrade --strategy AwesomeStrategy plot-dataframe --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH
```
## Plot profit
The profit plotter shows a picture with three plots:
![plot-profit](assets/plot-profit.png)
The `freqtrade plot-profit` subcommand shows an interactive graph with three plots:
1) Average closing price for all pairs
2) The summarized profit made by backtesting.
Note that this is not the real-world profit, but
more of an estimate.
3) Each pair individually profit
Note that this is not the real-world profit, but more of an estimate.
3) Profit for each individual pair
The first graph is good to get a grip of how the overall market
progresses.
The first graph is good to get a grip of how the overall market progresses.
The second graph will show how your algorithm works or doesn't.
Perhaps you want an algorithm that steadily makes small profits,
or one that acts less seldom, but makes big swings.
The second graph will show if your algorithm works or doesn't.
Perhaps you want an algorithm that steadily makes small profits, or one that acts less often, but makes big swings.
The third graph can be useful to spot outliers, events in pairs
that makes profit spikes.
The third graph can be useful to spot outliers, events in pairs that cause profit spikes.
Usage for the profit plotter:
Possible options for the `freqtrade plot-profit` subcommand:
```
usage: freqtrade plot-profit [-h] [-p PAIRS [PAIRS ...]]
[--timerange TIMERANGE] [--export EXPORT]
[--export-filename PATH] [--db-url PATH]
[--trade-source {DB,file}]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
separated.
--timerange TIMERANGE
Specify what timerange of data to use.
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
--export-filename PATH
Save backtest results to the file with this filename
(default: `user_data/backtest_results/backtest-
result.json`). Requires `--export` to be set as well.
Example: `--export-filename=user_data/backtest_results
/backtest_today.json`
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite://` for Dry Run).
--trade-source {DB,file}
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
``` bash
python3 script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
```
The `-p` pair argument, can be used to plot a single pair
The `-p/--pairs` argument, can be used to limit the pairs that are considered for this calculation.
Example
Examples:
Use custom backtest-export file
``` bash
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
freqtrade plot-profit -p LTC/BTC --export-filename user_data/backtest_results/backtest-result-Strategy005.json
```
Use custom database
``` bash
freqtrade plot-profit -p LTC/BTC --db-url sqlite:///tradesv3.sqlite --trade-source DB
```
``` bash
freqtrade plot-profit --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
```

View File

@ -34,15 +34,13 @@ ARGS_CREATE_USERDIR = ["user_data_dir"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"]
ARGS_PLOT_DATAFRAME = (ARGS_COMMON + ARGS_STRATEGY +
["pairs", "indicators1", "indicators2", "plot_limit", "db_url",
"trade_source", "export", "exportfilename", "timerange",
"refresh_pairs"])
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url",
"trade_source", "export", "exportfilename", "timerange", "ticker_interval"]
ARGS_PLOT_PROFIT = (ARGS_COMMON + ARGS_STRATEGY +
["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"])
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
"trade_source", "ticker_interval"]
NO_CONF_REQURIED = ["start_download_data"]
NO_CONF_REQURIED = ["download-data", "plot-dataframe", "plot-profit"]
class Arguments(object):
@ -81,8 +79,7 @@ class Arguments(object):
# (see https://bugs.python.org/issue16399)
# Allow no-config for certain commands (like downloading / plotting)
if (not self._no_default_config and parsed_arg.config is None
and not (hasattr(parsed_arg, 'func')
and parsed_arg.func.__name__ in NO_CONF_REQURIED)):
and not ('subparser' in parsed_arg and parsed_arg.subparser in NO_CONF_REQURIED)):
parsed_arg.config = [constants.DEFAULT_CONFIG]
return parsed_arg
@ -119,6 +116,7 @@ class Arguments(object):
hyperopt_cmd.set_defaults(func=start_hyperopt)
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
# add create-userdir subcommand
create_userdir_cmd = subparsers.add_parser('create-userdir',
help="Create user-data directory.")
create_userdir_cmd.set_defaults(func=start_create_userdir)
@ -139,3 +137,20 @@ class Arguments(object):
)
download_data_cmd.set_defaults(func=start_download_data)
self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd)
# Add Plotting subcommand
from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit
plot_dataframe_cmd = subparsers.add_parser(
'plot-dataframe',
help='Plot candles with indicators.'
)
plot_dataframe_cmd.set_defaults(func=start_plot_dataframe)
self._build_args(optionlist=ARGS_PLOT_DATAFRAME, parser=plot_dataframe_cmd)
# Plot profit
plot_profit_cmd = subparsers.add_parser(
'plot-profit',
help='Generate plot showing profits.'
)
plot_profit_cmd.set_defaults(func=start_plot_profit)
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)

View File

@ -5,6 +5,7 @@ from freqtrade import OperationalException
from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason,
is_exchange_available, is_exchange_bad,
is_exchange_officially_supported)
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -19,6 +20,10 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
raises an exception if the exchange if not supported by ccxt
and thus is not known for the Freqtrade at all.
"""
if config['runmode'] in [RunMode.PLOT] and not config.get('exchange', {}).get('name'):
# Skip checking exchange in plot mode, since it requires no exchange
return True
logger.info("Checking exchange...")
exchange = config.get('exchange', {}).get('name').lower()

View File

@ -292,14 +292,16 @@ AVAILABLE_CLI_OPTIONS = {
"indicators1": Arg(
'--indicators1',
help='Set indicators from your strategy you want in the first row of the graph. '
'Comma-separated list. Example: `ema3,ema5`. Default: `%(default)s`.',
default='sma,ema3,ema5',
'Space-separated list. Example: `ema3 ema5`. Default: `%(default)s`.',
default=['sma', 'ema3', 'ema5'],
nargs='+',
),
"indicators2": Arg(
'--indicators2',
help='Set indicators from your strategy you want in the third row of the graph. '
'Comma-separated list. Example: `fastd,fastk`. Default: `%(default)s`.',
default='macd,macdsignal',
'Space-separated list. Example: `fastd fastk`. Default: `%(default)s`.',
default=['macd', 'macdsignal'],
nargs='+',
),
"plot_limit": Arg(
'--plot-limit',

View File

@ -112,16 +112,16 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
return trades
def load_trades(config) -> pd.DataFrame:
def load_trades(source: str, db_url: str, exportfilename: str) -> pd.DataFrame:
"""
Based on configuration option "trade_source":
* loads data from DB (using `db_url`)
* loads data from backtestfile (using `exportfilename`)
"""
if config["trade_source"] == "DB":
return load_trades_from_db(config["db_url"])
elif config["trade_source"] == "file":
return load_backtest_data(Path(config["exportfilename"]))
if source == "DB":
return load_trades_from_db(db_url)
elif source == "file":
return load_backtest_data(Path(exportfilename))
def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame) -> pd.DataFrame:
@ -157,7 +157,8 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str) ->
:param trades: DataFrame containing trades (requires columns close_time and profitperc)
:return: Returns df with one additional column, col_name, containing the cumulative profit.
"""
df[col_name] = trades.set_index('close_time')['profitperc'].cumsum()
# Use groupby/sum().cumsum() to avoid errors when multiple trades sold at the same candle.
df[col_name] = trades.groupby('close_time')['profitperc'].sum().cumsum()
# Set first value to 0
df.loc[df.iloc[0].name, col_name] = 0
# FFill to get continuous

View File

@ -0,0 +1,26 @@
from argparse import Namespace
from freqtrade.state import RunMode
from freqtrade.utils import setup_utils_configuration
def start_plot_dataframe(args: Namespace) -> None:
"""
Entrypoint for dataframe plotting
"""
# Import here to avoid errors if plot-dependencies are not installed.
from freqtrade.plot.plotting import analyse_and_plot_pairs
config = setup_utils_configuration(args, RunMode.PLOT)
analyse_and_plot_pairs(config)
def start_plot_profit(args: Namespace) -> None:
"""
Entrypoint for plot_profit
"""
# Import here to avoid errors if plot-dependencies are not installed.
from freqtrade.plot.plotting import plot_profit
config = setup_utils_configuration(args, RunMode.PLOT)
plot_profit(config)

View File

@ -1,15 +1,15 @@
import logging
from pathlib import Path
from typing import Dict, List, Optional
from typing import Any, Dict, List
import pandas as pd
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import (combine_tickers_with_mean,
create_cum_profit, load_trades)
from freqtrade.exchange import Exchange
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
create_cum_profit,
extract_trades_of_period, load_trades)
from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)
@ -19,23 +19,16 @@ try:
from plotly.offline import plot
import plotly.graph_objects as go
except ImportError:
logger.exception("Module plotly not found \n Please install using `pip install plotly`")
logger.exception("Module plotly not found \n Please install using `pip3 install plotly`")
exit(1)
def init_plotscript(config):
"""
Initialize objects needed for plotting
:return: Dict with tickers, trades, pairs and strategy
:return: Dict with tickers, trades and pairs
"""
exchange: Optional[Exchange] = None
# Exchange is only needed when downloading data!
if config.get("refresh_pairs", False):
exchange = ExchangeResolver(config.get('exchange', {}).get('name'),
config).exchange
strategy = StrategyResolver(config).strategy
if "pairs" in config:
pairs = config["pairs"]
else:
@ -47,17 +40,18 @@ def init_plotscript(config):
tickers = history.load_data(
datadir=Path(str(config.get("datadir"))),
pairs=pairs,
ticker_interval=config['ticker_interval'],
refresh_pairs=config.get('refresh_pairs', False),
ticker_interval=config.get('ticker_interval', '5m'),
timerange=timerange,
exchange=exchange,
)
trades = load_trades(config)
trades = load_trades(config['trade_source'],
db_url=config.get('db_url'),
exportfilename=config.get('exportfilename'),
)
return {"tickers": tickers,
"trades": trades,
"pairs": pairs,
"strategy": strategy,
}
@ -280,8 +274,15 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
name='Avg close price',
)
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 1])
fig['layout'].update(title="Profit plot")
fig = make_subplots(rows=3, cols=1, shared_xaxes=True,
row_width=[1, 1, 1],
vertical_spacing=0.05,
subplot_titles=["AVG Close Price", "Combined Profit", "Profit per pair"])
fig['layout'].update(title="Freqtrade Profit plot")
fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title='Profit')
fig['layout']['yaxis3'].update(title='Profit')
fig['layout']['xaxis']['rangeslider'].update(visible=False)
fig.add_trace(avgclose, 1, 1)
fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit')
@ -321,3 +322,67 @@ def store_plot_file(fig, filename: str, directory: Path, auto_open: bool = False
plot(fig, filename=str(_filename),
auto_open=auto_open)
logger.info(f"Stored plot as {_filename}")
def analyse_and_plot_pairs(config: Dict[str, Any]):
"""
From configuration provided
- Initializes plot-script
- Get tickers data
- Generate Dafaframes populated with indicators and signals based on configured strategy
- Load trades excecuted during the selected period
- Generate Plotly plot objects
- Generate plot files
:return: None
"""
strategy = StrategyResolver(config).strategy
plot_elements = init_plotscript(config)
trades = plot_elements['trades']
pair_counter = 0
for pair, data in plot_elements["tickers"].items():
pair_counter += 1
logger.info("analyse pair %s", pair)
tickers = {}
tickers[pair] = data
dataframe = strategy.analyze_ticker(tickers[pair], {'pair': pair})
trades_pair = trades.loc[trades['pair'] == pair]
trades_pair = extract_trades_of_period(dataframe, trades_pair)
fig = generate_candlestick_graph(
pair=pair,
data=dataframe,
trades=trades_pair,
indicators1=config["indicators1"],
indicators2=config["indicators2"],
)
store_plot_file(fig, filename=generate_plot_filename(pair, config['ticker_interval']),
directory=config['user_data_dir'] / "plot")
logger.info('End of plotting process. %s plots generated', pair_counter)
def plot_profit(config: Dict[str, Any]) -> None:
"""
Plots the total profit for all pairs.
Note, the profit calculation isn't realistic.
But should be somewhat proportional, and therefor useful
in helping out to find a good algorithm.
"""
plot_elements = init_plotscript(config)
trades = load_trades(config['trade_source'],
db_url=str(config.get('db_url')),
exportfilename=str(config.get('exportfilename')),
)
# Filter trades to relevant pairs
trades = trades[trades['pair'].isin(plot_elements["pairs"])]
# Create an average close price of all the pairs that were involved.
# this could be useful to gauge the overall market trend
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"], trades)
store_plot_file(fig, filename='freqtrade-profit-plot.html',
directory=config['user_data_dir'] / "plot", auto_open=True)

View File

@ -25,4 +25,5 @@ class RunMode(Enum):
BACKTEST = "backtest"
EDGE = "edge"
HYPEROPT = "hyperopt"
PLOT = "plot"
OTHER = "other" # Used for plotting scripts and test

View File

@ -89,17 +89,20 @@ def test_load_trades(default_conf, mocker):
db_mock = mocker.patch("freqtrade.data.btanalysis.load_trades_from_db", MagicMock())
bt_mock = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock())
default_conf['trade_source'] = "DB"
load_trades(default_conf)
load_trades("DB",
db_url=default_conf.get('db_url'),
exportfilename=default_conf.get('exportfilename'),
)
assert db_mock.call_count == 1
assert bt_mock.call_count == 0
db_mock.reset_mock()
bt_mock.reset_mock()
default_conf['trade_source'] = "file"
default_conf['exportfilename'] = "testfile.json"
load_trades(default_conf)
load_trades("file",
db_url=default_conf.get('db_url'),
exportfilename=default_conf.get('exportfilename'),)
assert db_mock.call_count == 0
assert bt_mock.call_count == 1

View File

@ -4,7 +4,6 @@ import argparse
import pytest
from freqtrade.configuration import Arguments
from freqtrade.configuration.arguments import ARGS_PLOT_DATAFRAME
from freqtrade.configuration.cli_options import check_int_positive
@ -149,20 +148,35 @@ def test_download_data_options() -> None:
def test_plot_dataframe_options() -> None:
args = [
'--indicators1', 'sma10,sma100',
'--indicators2', 'macd,fastd,fastk',
'-c', 'config.json.example',
'plot-dataframe',
'--indicators1', 'sma10', 'sma100',
'--indicators2', 'macd', 'fastd', 'fastk',
'--plot-limit', '30',
'-p', 'UNITTEST/BTC',
]
arguments = Arguments(args, '')
arguments._build_args(ARGS_PLOT_DATAFRAME)
pargs = arguments._parse_args()
assert pargs.indicators1 == "sma10,sma100"
assert pargs.indicators2 == "macd,fastd,fastk"
pargs = Arguments(args, '').get_parsed_arg()
assert pargs.indicators1 == ["sma10", "sma100"]
assert pargs.indicators2 == ["macd", "fastd", "fastk"]
assert pargs.plot_limit == 30
assert pargs.pairs == ["UNITTEST/BTC"]
def test_plot_profit_options() -> None:
args = [
'plot-profit',
'-p', 'UNITTEST/BTC',
'--trade-source', 'DB',
"--db-url", "sqlite:///whatever.sqlite",
]
pargs = Arguments(args, '').get_parsed_arg()
assert pargs.trade_source == "DB"
assert pargs.pairs == ["UNITTEST/BTC"]
assert pargs.db_url == "sqlite:///whatever.sqlite"
def test_check_int_positive() -> None:
assert check_int_positive("3") == 3
assert check_int_positive("1") == 1

View File

@ -479,6 +479,7 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
def test_check_exchange(default_conf, caplog) -> None:
# Test an officially supported by Freqtrade team exchange
default_conf['runmode'] = RunMode.DRY_RUN
default_conf.get('exchange').update({'name': 'BITTREX'})
assert check_exchange(default_conf)
assert log_has_re(r"Exchange .* is officially supported by the Freqtrade development team\.",
@ -523,6 +524,11 @@ def test_check_exchange(default_conf, caplog) -> None:
):
check_exchange(default_conf)
# Test no exchange...
default_conf.get('exchange').update({'name': ''})
default_conf['runmode'] = RunMode.PLOT
assert check_exchange(default_conf)
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)

View File

@ -9,13 +9,15 @@ from plotly.subplots import make_subplots
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit
from freqtrade.plot.plotting import (add_indicators, add_profit,
analyse_and_plot_pairs,
generate_candlestick_graph,
generate_plot_filename,
generate_profit_graph, init_plotscript,
plot_trades, store_plot_file)
plot_profit, plot_trades, store_plot_file)
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.tests.conftest import log_has, log_has_re
from freqtrade.tests.conftest import get_args, log_has, log_has_re
def fig_generating_mock(fig, *args, **kwargs):
@ -49,7 +51,6 @@ def test_init_plotscript(default_conf, mocker):
assert "tickers" in ret
assert "trades" in ret
assert "pairs" in ret
assert "strategy" in ret
default_conf['pairs'] = ["POWR/BTC", "XLM/BTC"]
ret = init_plotscript(default_conf)
@ -257,7 +258,11 @@ def test_generate_profit_graph():
fig = generate_profit_graph(pairs, tickers, trades)
assert isinstance(fig, go.Figure)
assert fig.layout.title.text == "Profit plot"
assert fig.layout.title.text == "Freqtrade Profit plot"
assert fig.layout.yaxis.title.text == "Price"
assert fig.layout.yaxis2.title.text == "Profit"
assert fig.layout.yaxis3.title.text == "Profit"
figure = fig.layout.figure
assert len(figure.data) == 4
@ -270,3 +275,85 @@ def test_generate_profit_graph():
for pair in pairs:
profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}")
assert isinstance(profit_pair, go.Scattergl)
def test_start_plot_dataframe(mocker):
aup = mocker.patch("freqtrade.plot.plotting.analyse_and_plot_pairs", MagicMock())
args = [
"--config", "config.json.example",
"plot-dataframe",
"--pairs", "ETH/BTC"
]
start_plot_dataframe(get_args(args))
assert aup.call_count == 1
called_config = aup.call_args_list[0][0][0]
assert "pairs" in called_config
assert called_config['pairs'] == ["ETH/BTC"]
def test_analyse_and_plot_pairs(default_conf, mocker, caplog):
default_conf['trade_source'] = 'file'
default_conf["datadir"] = history.make_testdata_path(None)
default_conf['exportfilename'] = str(
history.make_testdata_path(None) / "backtest-result_test.json")
default_conf['indicators1'] = ["sma5", "ema10"]
default_conf['indicators2'] = ["macd"]
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"]
candle_mock = MagicMock()
store_mock = MagicMock()
mocker.patch.multiple(
"freqtrade.plot.plotting",
generate_candlestick_graph=candle_mock,
store_plot_file=store_mock
)
analyse_and_plot_pairs(default_conf)
# Both mocks should be called once per pair
assert candle_mock.call_count == 2
assert store_mock.call_count == 2
assert candle_mock.call_args_list[0][1]['indicators1'] == ['sma5', 'ema10']
assert candle_mock.call_args_list[0][1]['indicators2'] == ['macd']
assert log_has("End of plotting process. 2 plots generated", caplog)
def test_start_plot_profit(mocker):
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
args = [
"--config", "config.json.example",
"plot-profit",
"--pairs", "ETH/BTC"
]
start_plot_profit(get_args(args))
assert aup.call_count == 1
called_config = aup.call_args_list[0][0][0]
assert "pairs" in called_config
assert called_config['pairs'] == ["ETH/BTC"]
def test_plot_profit(default_conf, mocker, caplog):
default_conf['trade_source'] = 'file'
default_conf["datadir"] = history.make_testdata_path(None)
default_conf['exportfilename'] = str(
history.make_testdata_path(None) / "backtest-result_test.json")
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"]
profit_mock = MagicMock()
store_mock = MagicMock()
mocker.patch.multiple(
"freqtrade.plot.plotting",
generate_profit_graph=profit_mock,
store_plot_file=store_mock
)
plot_profit(default_conf)
# Plot-profit generates one combined plot
assert profit_mock.call_count == 1
assert store_mock.call_count == 1
assert profit_mock.call_args_list[0][0][0] == default_conf['pairs']
assert store_mock.call_args_list[0][1]['auto_open'] is True

View File

@ -92,6 +92,3 @@ def start_download_data(args: Namespace) -> None:
if pairs_not_available:
logger.info(f"Pairs [{','.join(pairs_not_available)}] not available "
f"on exchange {config['exchange']['name']}.")
# configuration.resolve_pairs_list()
print(config)

View File

@ -14,12 +14,12 @@ nav:
- Backtesting: backtesting.md
- Hyperopt: hyperopt.md
- Edge positioning: edge.md
- Plotting: plotting.md
- Deprecated features: deprecated.md
- FAQ: faq.md
- Data Analysis: data-analysis.md
- Plotting: plotting.md
- SQL Cheatsheet: sql_cheatsheet.md
- Sandbox testing: sandbox-testing.md
- Deprecated features: deprecated.md
- Contributors guide: developer.md
theme:
name: material

View File

@ -1,100 +1,11 @@
#!/usr/bin/env python3
"""
Script to display when the bot will buy on specific pair(s)
Use `python plot_dataframe.py --help` to display the command line arguments
Indicators recommended
Row 1: sma, ema3, ema5, ema10, ema50
Row 3: macd, rsi, fisher_rsi, mfi, slowd, slowk, fastd, fastk
Example of usage:
> python3 scripts/plot_dataframe.py --pairs BTC/EUR,XRP/BTC -d user_data/data/
--indicators1 sma,ema3 --indicators2 fastk,fastd
"""
import logging
import sys
from typing import Any, Dict, List
from freqtrade.configuration import Arguments
from freqtrade.configuration.arguments import ARGS_PLOT_DATAFRAME
from freqtrade.data.btanalysis import extract_trades_of_period
from freqtrade.optimize import setup_configuration
from freqtrade.plot.plotting import (init_plotscript, generate_candlestick_graph,
store_plot_file,
generate_plot_filename)
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
def analyse_and_plot_pairs(config: Dict[str, Any]):
"""
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
"""
plot_elements = init_plotscript(config)
trades = plot_elements['trades']
strategy = plot_elements["strategy"]
print("This script has been integrated into freqtrade "
"and its functionality is available by calling `freqtrade plot-dataframe`.")
print("Please check the documentation on https://www.freqtrade.io/en/latest/plotting/ "
"for details.")
pair_counter = 0
for pair, data in plot_elements["tickers"].items():
pair_counter += 1
logger.info("analyse pair %s", pair)
tickers = {}
tickers[pair] = data
dataframe = strategy.analyze_ticker(tickers[pair], {'pair': pair})
trades_pair = trades.loc[trades['pair'] == pair]
trades_pair = extract_trades_of_period(dataframe, trades_pair)
fig = generate_candlestick_graph(
pair=pair,
data=dataframe,
trades=trades_pair,
indicators1=config["indicators1"].split(","),
indicators2=config["indicators2"].split(",")
)
store_plot_file(fig, filename=generate_plot_filename(pair, config['ticker_interval']),
directory=config['user_data_dir'] / "plot")
logger.info('End of ploting process %s plots generated', pair_counter)
def plot_parse_args(args: List[str]) -> Dict[str, Any]:
"""
Parse args passed to the script
:param args: Cli arguments
:return: args: Array with all arguments
"""
arguments = Arguments(args, 'Graph dataframe')
arguments._build_args(optionlist=ARGS_PLOT_DATAFRAME)
parsed_args = arguments._parse_args()
# Load the configuration
config = setup_configuration(parsed_args, RunMode.OTHER)
return config
def main(sysargv: List[str]) -> None:
"""
This function will initiate the bot and start the trading loop.
:return: None
"""
logger.info('Starting Plot Dataframe')
analyse_and_plot_pairs(
plot_parse_args(sysargv)
)
exit()
if __name__ == '__main__':
main(sys.argv[1:])
sys.exit(1)

View File

@ -1,66 +1,11 @@
#!/usr/bin/env python3
"""
Script to display profits
Use `python plot_profit.py --help` to display the command line arguments
"""
import logging
import sys
from typing import Any, Dict, List
from freqtrade.configuration import Arguments
from freqtrade.configuration.arguments import ARGS_PLOT_PROFIT
from freqtrade.optimize import setup_configuration
from freqtrade.plot.plotting import init_plotscript, generate_profit_graph, store_plot_file
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
def plot_profit(config: Dict[str, Any]) -> None:
"""
Plots the total profit for all pairs.
Note, the profit calculation isn't realistic.
But should be somewhat proportional, and therefor useful
in helping out to find a good algorithm.
"""
plot_elements = init_plotscript(config)
trades = plot_elements['trades']
# Filter trades to relevant pairs
trades = trades[trades['pair'].isin(plot_elements["pairs"])]
print("This script has been integrated into freqtrade "
"and its functionality is available by calling `freqtrade plot-profit`.")
print("Please check the documentation on https://www.freqtrade.io/en/latest/plotting/ "
"for details.")
# Create an average close price of all the pairs that were involved.
# this could be useful to gauge the overall market trend
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"], trades)
store_plot_file(fig, filename='freqtrade-profit-plot.html',
directory=config['user_data_dir'] / "plot", auto_open=True)
def plot_parse_args(args: List[str]) -> Dict[str, Any]:
"""
Parse args passed to the script
:param args: Cli arguments
:return: args: Array with all arguments
"""
arguments = Arguments(args, 'Graph profits')
arguments._build_args(optionlist=ARGS_PLOT_PROFIT)
parsed_args = arguments._parse_args()
# Load the configuration
config = setup_configuration(parsed_args, RunMode.OTHER)
return config
def main(sysargv: List[str]) -> None:
"""
This function will initiate the bot and start the trading loop.
:return: None
"""
logger.info('Starting Plot Dataframe')
plot_profit(
plot_parse_args(sysargv)
)
if __name__ == '__main__':
main(sys.argv[1:])
sys.exit(1)