freqtrade_origin/freqtrade/plot/plotting.py

717 lines
25 KiB
Python
Raw Normal View History

2018-06-23 12:18:30 +00:00
import logging
from datetime import datetime, timezone
from pathlib import Path
2019-05-29 05:19:21 +00:00
2019-05-28 05:00:57 +00:00
import pandas as pd
2020-01-04 02:07:51 +00:00
from freqtrade.configuration import TimeRange
2022-09-18 11:31:52 +00:00
from freqtrade.constants import Config
from freqtrade.data.btanalysis import (
analyze_trade_parallelism,
extract_trades_of_period,
load_trades,
)
2020-01-04 02:07:51 +00:00
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange, load_data
from freqtrade.data.metrics import (
calculate_max_drawdown,
calculate_underwater,
combine_dataframes_with_mean,
create_cum_profit,
)
from freqtrade.enums import CandleType
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds
2020-01-04 02:07:51 +00:00
from freqtrade.misc import pair_to_filename
2021-01-12 00:13:58 +00:00
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.strategy import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
2018-06-23 12:18:30 +00:00
2020-09-28 17:39:41 +00:00
2018-06-23 12:18:30 +00:00
logger = logging.getLogger(__name__)
try:
import plotly.graph_objects as go
2020-09-28 17:39:41 +00:00
from plotly.offline import plot
from plotly.subplots import make_subplots
2018-06-23 12:18:30 +00:00
except ImportError:
2019-08-22 14:51:00 +00:00
logger.exception("Module plotly not found \n Please install using `pip3 install plotly`")
2019-05-29 05:19:21 +00:00
exit(1)
2019-05-28 05:00:57 +00:00
def init_plotscript(config, markets: list, startup_candles: int = 0):
"""
Initialize objects needed for plotting
:return: Dict with candle (OHLCV) data, trades and pairs
"""
if "pairs" in config:
2024-05-12 14:32:47 +00:00
pairs = expand_pairlist(config["pairs"], markets)
else:
2024-05-12 14:32:47 +00:00
pairs = expand_pairlist(config["exchange"]["pair_whitelist"], markets)
# Set timerange to use
2024-05-12 14:32:47 +00:00
timerange = TimeRange.parse_timerange(config.get("timerange"))
data = load_data(
2024-05-12 14:32:47 +00:00
datadir=config.get("datadir"),
pairs=pairs,
2024-05-12 14:32:47 +00:00
timeframe=config["timeframe"],
timerange=timerange,
startup_candles=startup_candles,
2024-05-12 14:32:47 +00:00
data_format=config["dataformat_ohlcv"],
candle_type=config.get("candle_type_def", CandleType.SPOT),
)
if startup_candles and data:
min_date, max_date = get_timerange(data)
logger.info(f"Loading data from {min_date} to {max_date}")
2024-05-12 14:32:47 +00:00
timerange.adjust_start_if_necessary(
timeframe_to_seconds(config["timeframe"]), startup_candles, min_date
)
2020-03-15 20:20:32 +00:00
no_trades = False
filename = config.get("exportfilename")
if config.get("no_trades", False):
2020-03-15 20:20:32 +00:00
no_trades = True
2024-05-12 14:32:47 +00:00
elif config["trade_source"] == "file":
if not filename.is_dir() and not filename.is_file():
logger.warning("Backtest file is missing skipping trades.")
no_trades = True
try:
trades = load_trades(
2024-05-12 14:32:47 +00:00
config["trade_source"],
db_url=config.get("db_url"),
exportfilename=filename,
no_trades=no_trades,
2024-05-12 14:32:47 +00:00
strategy=config.get("strategy"),
)
except ValueError as e:
raise OperationalException(e) from e
if not trades.empty:
2024-05-12 14:32:47 +00:00
trades = trim_dataframe(trades, timerange, df_date_col="open_date")
2020-03-14 21:15:03 +00:00
2024-05-12 14:32:47 +00:00
return {
"ohlcv": data,
"trades": trades,
"pairs": pairs,
"timerange": timerange,
}
def add_indicators(fig, row, indicators: dict[str, dict], data: pd.DataFrame) -> make_subplots:
2019-05-28 05:00:57 +00:00
"""
2020-01-03 12:27:22 +00:00
Generate all the indicators selected by the user for a specific row, based on the configuration
2019-05-28 05:00:57 +00:00
:param fig: Plot figure to append to
:param row: row number for this plot
2020-01-03 12:27:22 +00:00
:param indicators: Dict of Indicators with configuration options.
Dict key must correspond to dataframe column.
2019-05-28 05:00:57 +00:00
:param data: candlestick DataFrame
"""
plot_kinds = {
2024-05-12 14:32:47 +00:00
"scatter": go.Scatter,
"bar": go.Bar,
}
2020-01-03 12:27:22 +00:00
for indicator, conf in indicators.items():
2020-01-04 10:13:45 +00:00
logger.debug(f"indicator {indicator} with config {conf}")
2019-05-28 05:00:57 +00:00
if indicator in data:
2024-05-12 14:32:47 +00:00
kwargs = {"x": data["date"], "y": data[indicator].values, "name": indicator}
plot_type = conf.get("type", "scatter")
color = conf.get("color")
if plot_type == "bar":
kwargs.update(
{
"marker_color": color or "DarkSlateGrey",
"marker_line_color": color or "DarkSlateGrey",
}
)
else:
if color:
2024-05-12 14:32:47 +00:00
kwargs.update({"line": {"color": color}})
kwargs["mode"] = "lines"
if plot_type != "scatter":
logger.warning(
f"Indicator {indicator} has unknown plot trace kind {plot_type}"
f', assuming "scatter".'
)
kwargs.update(conf.get("plotly", {}))
trace = plot_kinds[plot_type](**kwargs)
fig.add_trace(trace, row, 1)
2019-05-28 05:00:57 +00:00
else:
logger.info(
2024-05-12 14:32:47 +00:00
'Indicator "%s" ignored. Reason: This indicator is not found ' "in your strategy.",
indicator,
2019-05-28 05:00:57 +00:00
)
return fig
def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_subplots:
2019-06-30 08:14:33 +00:00
"""
Add profit-plot
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param column: Column to use for plot
:param name: Name to use
:return: fig with added profit plot
"""
2019-10-05 08:32:42 +00:00
profit = go.Scatter(
2019-06-30 08:14:33 +00:00
x=data.index,
y=data[column],
name=name,
)
fig.add_trace(profit, row, 1)
2019-06-30 08:14:33 +00:00
return fig
2024-05-12 14:32:47 +00:00
def add_max_drawdown(
fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, timeframe: str, starting_balance: float
) -> make_subplots:
2020-03-03 06:21:14 +00:00
"""
Add scatter points indicating max drawdown
"""
try:
drawdown = calculate_max_drawdown(trades, starting_balance=starting_balance)
2020-03-03 06:21:14 +00:00
drawdown = go.Scatter(
x=[drawdown.high_date, drawdown.low_date],
2020-03-03 06:21:14 +00:00
y=[
df_comb.loc[timeframe_to_prev_date(timeframe, drawdown.high_date), "cum_profit"],
df_comb.loc[timeframe_to_prev_date(timeframe, drawdown.low_date), "cum_profit"],
2020-03-03 06:21:14 +00:00
],
2024-05-12 14:32:47 +00:00
mode="markers",
name=f"Max drawdown {drawdown.relative_account_drawdown:.2%}",
text=f"Max drawdown {drawdown.relative_account_drawdown:.2%}",
2024-05-12 14:32:47 +00:00
marker=dict(symbol="square-open", size=9, line=dict(width=2), color="green"),
2020-03-03 06:21:14 +00:00
)
fig.add_trace(drawdown, row, 1)
except ValueError:
logger.warning("No trades found - not plotting max drawdown.")
return fig
def add_underwater(fig, row, trades: pd.DataFrame, starting_balance: float) -> make_subplots:
2022-01-01 13:40:20 +00:00
"""
2022-04-11 19:41:48 +00:00
Add underwater plots
2022-01-01 13:40:20 +00:00
"""
try:
2022-04-23 16:15:14 +00:00
underwater = calculate_underwater(
2024-05-12 14:32:47 +00:00
trades, value_col="profit_abs", starting_balance=starting_balance
2022-04-23 16:15:14 +00:00
)
2022-01-01 13:40:20 +00:00
2022-04-11 19:41:48 +00:00
underwater_plot = go.Scatter(
2024-05-12 14:32:47 +00:00
x=underwater["date"],
y=underwater["drawdown"],
2022-01-01 13:40:20 +00:00
name="Underwater Plot",
2024-05-12 14:32:47 +00:00
fill="tozeroy",
fillcolor="#cc362b",
line={"color": "#cc362b"},
2022-01-01 13:40:20 +00:00
)
2022-04-11 19:41:48 +00:00
underwater_plot_relative = go.Scatter(
2024-05-12 14:32:47 +00:00
x=underwater["date"],
y=(-underwater["drawdown_relative"]),
2022-04-11 19:41:48 +00:00
name="Underwater Plot (%)",
2024-05-12 14:32:47 +00:00
fill="tozeroy",
fillcolor="green",
line={"color": "green"},
2022-04-11 19:41:48 +00:00
)
2022-04-23 16:15:14 +00:00
2022-04-11 19:41:48 +00:00
fig.add_trace(underwater_plot, row, 1)
2022-04-23 16:15:14 +00:00
fig.add_trace(underwater_plot_relative, row + 1, 1)
2022-01-01 13:40:20 +00:00
except ValueError:
logger.warning("No trades found - not plotting underwater plot")
return fig
def add_parallelism(fig, row, trades: pd.DataFrame, timeframe: str) -> make_subplots:
"""
Add Chart showing trade parallelism
"""
try:
result = analyze_trade_parallelism(trades, timeframe)
2022-01-01 15:40:18 +00:00
drawdown = go.Scatter(
x=result.index,
2024-05-12 14:32:47 +00:00
y=result["open_trades"],
name="Parallel trades",
2024-05-12 14:32:47 +00:00
fill="tozeroy",
fillcolor="#242222",
line={"color": "#242222"},
)
fig.add_trace(drawdown, row, 1)
except ValueError:
logger.warning("No trades found - not plotting Parallelism.")
return fig
def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
2019-05-28 05:00:57 +00:00
"""
2019-06-30 07:47:07 +00:00
Add trades to "fig"
2019-05-28 05:00:57 +00:00
"""
# Trades can be empty
2019-05-28 18:23:16 +00:00
if trades is not None and len(trades) > 0:
# Create description for exit summarizing the trade
2024-05-12 14:32:47 +00:00
trades["desc"] = trades.apply(
lambda row: f"{row['profit_ratio']:.2%}, "
+ (f"{row['enter_tag']}, " if row["enter_tag"] is not None else "")
+ f"{row['exit_reason']}, "
+ f"{row['trade_duration']} min",
axis=1,
)
trade_entries = go.Scatter(
x=trades["open_date"],
2019-05-28 05:00:57 +00:00
y=trades["open_rate"],
2024-05-12 14:32:47 +00:00
mode="markers",
name="Trade entry",
text=trades["desc"],
2024-05-12 14:32:47 +00:00
marker=dict(symbol="circle-open", size=11, line=dict(width=2), color="cyan"),
)
trade_exits = go.Scatter(
2024-05-12 14:32:47 +00:00
x=trades.loc[trades["profit_ratio"] > 0, "close_date"],
y=trades.loc[trades["profit_ratio"] > 0, "close_rate"],
text=trades.loc[trades["profit_ratio"] > 0, "desc"],
mode="markers",
name="Exit - Profit",
marker=dict(symbol="square-open", size=11, line=dict(width=2), color="green"),
2019-05-28 05:00:57 +00:00
)
trade_exits_loss = go.Scatter(
2024-05-12 14:32:47 +00:00
x=trades.loc[trades["profit_ratio"] <= 0, "close_date"],
y=trades.loc[trades["profit_ratio"] <= 0, "close_rate"],
text=trades.loc[trades["profit_ratio"] <= 0, "desc"],
mode="markers",
name="Exit - Loss",
marker=dict(symbol="square-open", size=11, line=dict(width=2), color="red"),
2019-05-28 05:00:57 +00:00
)
fig.add_trace(trade_entries, 1, 1)
fig.add_trace(trade_exits, 1, 1)
fig.add_trace(trade_exits_loss, 1, 1)
2019-06-22 13:45:20 +00:00
else:
logger.warning("No trades found.")
2019-05-28 05:00:57 +00:00
return fig
2024-05-12 14:32:47 +00:00
def create_plotconfig(
indicators1: list[str], indicators2: list[str], plot_config: dict[str, dict]
) -> dict[str, dict]:
2020-01-04 10:13:45 +00:00
"""
Combines indicators 1 and indicators 2 into plot_config if necessary
:param indicators1: List containing Main plot indicators
:param indicators2: List containing Sub plot indicators
:param plot_config: Dict of Dicts containing advanced plot configuration
:return: plot_config - eventually with indicators 1 and 2
"""
if plot_config:
if indicators1:
2024-05-12 14:32:47 +00:00
plot_config["main_plot"] = {ind: {} for ind in indicators1}
if indicators2:
2024-05-12 14:32:47 +00:00
plot_config["subplots"] = {"Other": {ind: {} for ind in indicators2}}
2020-01-04 10:13:45 +00:00
if not plot_config:
# If no indicators and no plot-config given, use defaults.
if not indicators1:
2024-05-12 14:32:47 +00:00
indicators1 = ["sma", "ema3", "ema5"]
2020-01-04 10:13:45 +00:00
if not indicators2:
2024-05-12 14:32:47 +00:00
indicators2 = ["macd", "macdsignal"]
2020-01-04 10:13:45 +00:00
# Create subplot configuration if plot_config is not available.
plot_config = {
2024-05-12 14:32:47 +00:00
"main_plot": {ind: {} for ind in indicators1},
"subplots": {"Other": {ind: {} for ind in indicators2}},
2020-01-04 10:13:45 +00:00
}
2024-05-12 14:32:47 +00:00
if "main_plot" not in plot_config:
plot_config["main_plot"] = {}
2024-05-12 14:32:47 +00:00
if "subplots" not in plot_config:
plot_config["subplots"] = {}
return plot_config
2020-01-04 10:13:45 +00:00
2024-05-12 14:32:47 +00:00
def plot_area(
fig,
row: int,
data: pd.DataFrame,
indicator_a: str,
indicator_b: str,
label: str = "",
fill_color: str = "rgba(0,176,246,0.2)",
) -> make_subplots:
"""Creates a plot for the area between two traces and adds it to fig.
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
2021-06-25 13:45:49 +00:00
:param indicator_a: indicator name as populated in strategy
:param indicator_b: indicator name as populated in strategy
:param label: label for the filled area
:param fill_color: color to be used for the filled area
:return: fig with added filled_traces plot
"""
if indicator_a in data and indicator_b in data:
2020-12-19 20:37:52 +00:00
# make lines invisible to get the area plotted, only.
2024-05-12 14:32:47 +00:00
line = {"color": "rgba(255,255,255,0)"}
# TODO: Figure out why scattergl causes problems plotly/plotly.js#2284
2024-05-12 14:32:47 +00:00
trace_a = go.Scatter(x=data.date, y=data[indicator_a], showlegend=False, line=line)
trace_b = go.Scatter(
x=data.date,
y=data[indicator_b],
name=label,
fill="tonexty",
fillcolor=fill_color,
line=line,
)
fig.add_trace(trace_a, row, 1)
fig.add_trace(trace_b, row, 1)
return fig
2020-12-19 16:42:22 +00:00
def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots:
2024-05-12 14:32:47 +00:00
"""Adds all area plots (specified in plot_config) to fig.
2020-12-19 16:42:22 +00:00
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param indicators: dict with indicators. ie.: plot_config['main_plot'] or
plot_config['subplots'][subplot_label]
:return: fig with added filled_traces plot
"""
for indicator, ind_conf in indicators.items():
2024-05-12 14:32:47 +00:00
if "fill_to" in ind_conf:
indicator_b = ind_conf["fill_to"]
2020-12-19 19:32:13 +00:00
if indicator in data and indicator_b in data:
2024-05-12 14:32:47 +00:00
label = ind_conf.get("fill_label", f"{indicator}<>{indicator_b}")
fill_color = ind_conf.get("fill_color", "rgba(0,176,246,0.2)")
fig = plot_area(
fig, row, data, indicator, indicator_b, label=label, fill_color=fill_color
)
2020-12-19 19:32:13 +00:00
elif indicator not in data:
logger.info(
'Indicator "%s" ignored. Reason: This indicator is not '
2024-05-12 14:32:47 +00:00
"found in your strategy.",
indicator,
2020-12-19 19:32:13 +00:00
)
elif indicator_b not in data:
logger.info(
2024-05-12 14:32:47 +00:00
'fill_to: "%s" ignored. Reason: This indicator is not ' "in your strategy.",
indicator_b,
2020-12-19 19:32:13 +00:00
)
2020-12-19 16:42:22 +00:00
return fig
def create_scatter(data, column_name, color, direction) -> go.Scatter | None:
if column_name in data.columns:
df_short = data[data[column_name] == 1]
if len(df_short) > 0:
shorts = go.Scatter(
x=df_short.date,
y=df_short.close,
2024-05-12 14:32:47 +00:00
mode="markers",
name=column_name,
marker=dict(
symbol=f"triangle-{direction}-dot",
size=9,
line=dict(width=1),
color=color,
2024-05-12 14:32:47 +00:00
),
)
return shorts
else:
logger.warning(f"No {column_name}-signals found.")
return None
2023-01-21 14:01:56 +00:00
def generate_candlestick_graph(
2024-05-12 14:32:47 +00:00
pair: str,
data: pd.DataFrame,
trades: pd.DataFrame | None = None,
2024-05-12 14:32:47 +00:00
*,
indicators1: list[str] | None = None,
indicators2: list[str] | None = None,
plot_config: dict[str, dict] | None = None,
2024-05-12 14:32:47 +00:00
) -> go.Figure:
2019-05-28 05:00:57 +00:00
"""
Generate the graph from the data generated by Backtesting or from DB
2024-04-18 20:51:25 +00:00
Volume will always be plotted in row2, so Row 1 and 3 are to our disposal for custom indicators
2019-05-28 05:00:57 +00:00
:param pair: Pair to Display on the graph
:param data: OHLCV DataFrame containing indicators and entry/exit signals
2019-05-28 05:00:57 +00:00
:param trades: All trades created
:param indicators1: List containing Main plot indicators
:param indicators2: List containing Sub plot indicators
2020-01-04 10:13:45 +00:00
:param plot_config: Dict of Dicts containing advanced plot configuration
:return: Plotly figure
2019-05-28 05:00:57 +00:00
"""
plot_config = create_plotconfig(
indicators1 or [],
indicators2 or [],
plot_config or {},
)
2024-05-12 14:32:47 +00:00
rows = 2 + len(plot_config["subplots"])
row_widths = [1 for _ in plot_config["subplots"]]
2019-05-28 05:00:57 +00:00
# Define the graph
fig = make_subplots(
2020-01-04 10:13:45 +00:00
rows=rows,
2019-05-28 05:00:57 +00:00
cols=1,
shared_xaxes=True,
2020-01-04 10:13:45 +00:00
row_width=row_widths + [1, 4],
2019-05-28 05:00:57 +00:00
vertical_spacing=0.0001,
)
2024-05-12 14:32:47 +00:00
fig["layout"].update(title=pair)
fig["layout"]["yaxis1"].update(title="Price")
fig["layout"]["yaxis2"].update(title="Volume")
for i, name in enumerate(plot_config["subplots"]):
fig["layout"][f"yaxis{3 + i}"].update(title=name)
fig["layout"]["xaxis"]["rangeslider"].update(visible=False)
fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"])
2019-05-28 05:00:57 +00:00
# Common information
candles = go.Candlestick(
2024-05-12 14:32:47 +00:00
x=data.date, open=data.open, high=data.high, low=data.low, close=data.close, name="Price"
2019-05-28 05:00:57 +00:00
)
fig.add_trace(candles, 1, 1)
2019-05-28 05:00:57 +00:00
2024-05-12 14:32:47 +00:00
longs = create_scatter(data, "enter_long", "green", "up")
exit_longs = create_scatter(data, "exit_long", "red", "down")
shorts = create_scatter(data, "enter_short", "blue", "down")
exit_shorts = create_scatter(data, "exit_short", "violet", "up")
for scatter in [longs, exit_longs, shorts, exit_shorts]:
if scatter:
fig.add_trace(scatter, 1, 1)
2020-12-19 16:42:22 +00:00
# Add Bollinger Bands
2024-05-12 14:32:47 +00:00
fig = plot_area(fig, 1, data, "bb_lowerband", "bb_upperband", label="Bollinger Band")
# prevent bb_lower and bb_upper from plotting
try:
2024-05-12 14:32:47 +00:00
del plot_config["main_plot"]["bb_lowerband"]
del plot_config["main_plot"]["bb_upperband"]
except KeyError:
pass
2020-12-19 20:37:52 +00:00
# main plot goes to row 1
2024-05-12 14:32:47 +00:00
fig = add_indicators(fig=fig, row=1, indicators=plot_config["main_plot"], data=data)
fig = add_areas(fig, 1, data, plot_config["main_plot"])
2019-05-28 05:00:57 +00:00
fig = plot_trades(fig, trades)
2020-12-19 20:37:52 +00:00
# sub plot: Volume goes to row 2
2019-05-28 05:00:57 +00:00
volume = go.Bar(
2024-05-12 14:32:47 +00:00
x=data["date"],
y=data["volume"],
name="Volume",
marker_color="DarkSlateGrey",
marker_line_color="DarkSlateGrey",
2020-01-03 12:27:22 +00:00
)
fig.add_trace(volume, 2, 1)
2020-12-19 21:01:33 +00:00
# add each sub plot to a separate row
2024-05-12 14:32:47 +00:00
for i, label in enumerate(plot_config["subplots"]):
sub_config = plot_config["subplots"][label]
row = 3 + i
2024-05-12 14:32:47 +00:00
fig = add_indicators(fig=fig, row=row, indicators=sub_config, data=data)
# fill area between indicators ( 'fill_to': 'other_indicator')
2020-12-19 16:42:22 +00:00
fig = add_areas(fig, row, data, sub_config)
2019-05-28 05:00:57 +00:00
return fig
2019-05-31 04:41:55 +00:00
2024-05-12 14:32:47 +00:00
def generate_profit_graph(
pairs: str,
data: dict[str, pd.DataFrame],
2024-05-12 14:32:47 +00:00
trades: pd.DataFrame,
timeframe: str,
stake_currency: str,
starting_balance: float,
) -> go.Figure:
2019-06-30 08:31:36 +00:00
# Combine close-values for all pairs, rename columns to "pair"
try:
df_comb = combine_dataframes_with_mean(data, "close")
except ValueError:
raise OperationalException(
"No data found. Please make sure that data is available for "
2024-05-12 14:32:47 +00:00
"the timerange and pairs selected."
)
2019-06-30 08:31:36 +00:00
# Trim trades to available OHLCV data
trades = extract_trades_of_period(df_comb, trades, date_index=True)
if len(trades) == 0:
2024-05-12 14:32:47 +00:00
raise OperationalException("No trades found in selected timerange.")
2019-06-30 08:31:36 +00:00
# Add combined cumulative profit
2024-05-12 14:32:47 +00:00
df_comb = create_cum_profit(df_comb, trades, "cum_profit", timeframe)
2019-06-30 08:31:36 +00:00
# Plot the pairs average close prices, and total profit growth
2019-10-05 08:32:42 +00:00
avgclose = go.Scatter(
2019-06-30 08:31:36 +00:00
x=df_comb.index,
2024-05-12 14:32:47 +00:00
y=df_comb["mean"],
name="Avg close price",
2019-06-30 08:31:36 +00:00
)
2024-05-12 14:32:47 +00:00
fig = make_subplots(
rows=6,
cols=1,
shared_xaxes=True,
row_heights=[1, 1, 1, 0.5, 0.75, 0.75],
vertical_spacing=0.05,
subplot_titles=[
"AVG Close Price",
"Combined Profit",
"Profit per pair",
"Parallelism",
"Underwater",
"Relative Drawdown",
],
)
fig["layout"].update(title="Freqtrade Profit plot")
fig["layout"]["yaxis1"].update(title="Price")
fig["layout"]["yaxis2"].update(title=f"Profit {stake_currency}")
fig["layout"]["yaxis3"].update(title=f"Profit {stake_currency}")
fig["layout"]["yaxis4"].update(title="Trade count")
fig["layout"]["yaxis5"].update(title="Underwater Plot")
fig["layout"]["yaxis6"].update(title="Underwater Plot Relative (%)", tickformat=",.2%")
fig["layout"]["xaxis"]["rangeslider"].update(visible=False)
fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"])
2019-06-30 08:31:36 +00:00
fig.add_trace(avgclose, 1, 1)
2024-05-12 14:32:47 +00:00
fig = add_profit(fig, 2, df_comb, "cum_profit", "Profit")
2022-04-11 19:41:48 +00:00
fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe, starting_balance)
fig = add_parallelism(fig, 4, trades, timeframe)
2022-04-11 19:41:48 +00:00
# Two rows consumed
fig = add_underwater(fig, 5, trades, starting_balance)
2019-06-30 08:31:36 +00:00
for pair in pairs:
2024-05-12 14:32:47 +00:00
profit_col = f"cum_profit_{pair}"
try:
2024-05-12 14:32:47 +00:00
df_comb = create_cum_profit(
df_comb, trades[trades["pair"] == pair], profit_col, timeframe
)
fig = add_profit(fig, 3, df_comb, profit_col, f"Profit {pair}")
except ValueError:
pass
2019-06-30 08:47:55 +00:00
return fig
2019-06-30 08:31:36 +00:00
2020-02-02 04:00:40 +00:00
def generate_plot_filename(pair: str, timeframe: str) -> str:
2019-06-30 07:47:07 +00:00
"""
Generate filenames per pair/timeframe to be used for storing plots
2019-06-30 07:47:07 +00:00
"""
2020-01-04 02:07:51 +00:00
pair_s = pair_to_filename(pair)
2024-05-12 14:32:47 +00:00
file_name = "freqtrade-plot-" + pair_s + "-" + timeframe + ".html"
2024-05-12 14:32:47 +00:00
logger.info("Generate plot file for %s", pair)
return file_name
2019-07-31 04:54:45 +00:00
def store_plot_file(fig, filename: str, directory: Path, auto_open: bool = False) -> None:
2019-05-31 04:41:55 +00:00
"""
Generate a plot html file from pre populated fig plotly object
:param fig: Plotly Figure to plot
:param filename: Name to store the file as
:param directory: Directory to store the file in
:param auto_open: Automatically open files saved
2019-05-31 04:41:55 +00:00
:return: None
"""
2019-07-31 04:54:45 +00:00
directory.mkdir(parents=True, exist_ok=True)
2019-05-31 04:41:55 +00:00
_filename = directory.joinpath(filename)
2024-05-12 14:32:47 +00:00
plot(fig, filename=str(_filename), auto_open=auto_open)
logger.info(f"Stored plot as {_filename}")
2022-09-18 11:31:52 +00:00
def load_and_plot_trades(config: Config):
"""
From configuration provided
- Initializes plot-script
- Get candle (OHLCV) data
- Generate Dafaframes populated with indicators and signals based on configured strategy
2021-08-16 12:16:24 +00:00
- Load trades executed during the selected period
- Generate Plotly plot objects
- Generate plot files
:return: None
"""
strategy = StrategyResolver.load_strategy(config)
2023-05-13 06:27:27 +00:00
exchange = ExchangeResolver.load_exchange(config)
IStrategy.dp = DataProvider(config, exchange)
strategy.ft_bot_start()
strategy_safe_wrapper(strategy.bot_loop_start)(current_time=datetime.now(timezone.utc))
2021-01-12 00:13:58 +00:00
plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count)
2024-05-12 14:32:47 +00:00
timerange = plot_elements["timerange"]
trades = plot_elements["trades"]
pair_counter = 0
for pair, data in plot_elements["ohlcv"].items():
pair_counter += 1
logger.info("analyse pair %s", pair)
2024-05-12 14:32:47 +00:00
df_analyzed = strategy.analyze_ticker(data, {"pair": pair})
df_analyzed = trim_dataframe(df_analyzed, timerange)
if not trades.empty:
2024-05-12 14:32:47 +00:00
trades_pair = trades.loc[trades["pair"] == pair]
trades_pair = extract_trades_of_period(df_analyzed, trades_pair)
else:
trades_pair = trades
fig = generate_candlestick_graph(
pair=pair,
2020-03-13 01:00:24 +00:00
data=df_analyzed,
trades=trades_pair,
2024-05-12 14:32:47 +00:00
indicators1=config.get("indicators1", []),
indicators2=config.get("indicators2", []),
plot_config=strategy.plot_config if hasattr(strategy, "plot_config") else {},
)
2024-05-12 14:32:47 +00:00
store_plot_file(
fig,
filename=generate_plot_filename(pair, config["timeframe"]),
directory=config["user_data_dir"] / "plot",
)
2024-05-12 14:32:47 +00:00
logger.info("End of plotting process. %s plots generated", pair_counter)
2019-08-22 14:51:00 +00:00
2022-09-18 11:31:52 +00:00
def plot_profit(config: Config) -> None:
2019-08-22 14:51:00 +00:00
"""
Plots the total profit for all pairs.
Note, the profit calculation isn't realistic.
2024-04-18 20:51:25 +00:00
But should be somewhat proportional, and therefore useful
2019-08-22 14:51:00 +00:00
in helping out to find a good algorithm.
"""
2024-05-12 14:32:47 +00:00
if "timeframe" not in config:
raise OperationalException("Timeframe must be set in either config or via --timeframe.")
2023-05-13 06:27:27 +00:00
exchange = ExchangeResolver.load_exchange(config)
2021-01-12 00:13:58 +00:00
plot_elements = init_plotscript(config, list(exchange.markets))
2024-05-12 14:32:47 +00:00
trades = plot_elements["trades"]
2019-08-22 14:51:00 +00:00
# Filter trades to relevant pairs
# Remove open pairs - we don't know the profit yet so can't calculate profit for these.
# Also, If only one open pair is left, then the profit-generation would fail.
2024-05-12 14:32:47 +00:00
trades = trades[
(trades["pair"].isin(plot_elements["pairs"])) & (~trades["close_date"].isnull())
]
if len(trades) == 0:
2024-05-12 14:32:47 +00:00
raise OperationalException(
"No trades found, cannot generate Profit-plot without "
"trades from either Backtest result or database."
)
2019-08-22 14:51:00 +00:00
# Create an average close price of all the pairs that were involved.
# this could be useful to gauge the overall market trend
2024-05-12 14:32:47 +00:00
fig = generate_profit_graph(
plot_elements["pairs"],
plot_elements["ohlcv"],
trades,
config["timeframe"],
config.get("stake_currency", ""),
config.get("available_capital", config["dry_run_wallet"]),
)
store_plot_file(
fig,
filename="freqtrade-profit-plot.html",
directory=config["user_data_dir"] / "plot",
auto_open=config.get("plot_auto_open", False),
)