Repository: freqtrade/freqtrade

Issue:
Show open trades when plot-profit
<!-- 
Have you searched for similar issues before posting it?
Did you have a VERY good look at the [documentation](https://www.freqtrade.io/en/latest/) and are sure that the question is not explained there

Please do not use the question template to report bugs or to request new features.
-->

## Describe your environment

  * Operating system: Windows7
  * Python Version: 3.7 (`python -V`)
  * CCXT version: 1.59.2 (`pip freeze | grep ccxt`)
  * Freqtrade Version: 2021.10 (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
  
## Your question
*Ask the question you have not been able to find an answer in the [Documentation](https://www.freqtrade.io/en/latest/)*


Hi,

Is there any settings to show open trades on ,,every moment" when plot-profit or elsewhere? Because I can't find any.
I'm curious to see if I have to set the max-open trades to a higher level or to a lower level(if there is no enough buy signals).

Thank you



Show open trades when plot-profit
<!-- 
Have you searched for similar issues before posting it?
Did you have a VERY good look at the [documentation](https://www.freqtrade.io/en/latest/) and are sure that the question is not explained there

Please do not use the question template to report bugs or to request new features.
-->

## Describe your environment

  * Operating system: Windows7
  * Python Version: 3.7 (`python -V`)
  * CCXT version: 1.59.2 (`pip freeze | grep ccxt`)
  * Freqtrade Version: 2021.10 (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
  
## Your question
*Ask the question you have not been able to find an answer in the [Documentation](https://www.freqtrade.io/en/latest/)*


Hi,

Is there any settings to show open trades on ,,every moment" when plot-profit or elsewhere? Because I can't find any.
I'm curious to see if I have to set the max-open trades to a higher level or to a lower level(if there is no enough buy signals).

Thank you





Discussion:
You can't really at the moment - although the functionality / code for it would be in btanalysis (`analyze_trade_parallelism()`), so i guess it might be pretty simple to throw that onto a chart ...
You can't really at the moment - although the functionality / code for it would be in btanalysis (`analyze_trade_parallelism()`), so i guess it might be pretty simple to throw that onto a chart ...

Resolving patch:
diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py
--- a/freqtrade/data/btanalysis.py
+++ b/freqtrade/data/btanalysis.py
@@ -361,6 +361,36 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
     return df
 
 
+def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str
+                          ) -> pd.DataFrame:
+    max_drawdown_df = pd.DataFrame()
+    max_drawdown_df['cumulative'] = profit_results[value_col].cumsum()
+    max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax()
+    max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value']
+    max_drawdown_df['date'] = profit_results.loc[:, date_col]
+    return max_drawdown_df
+
+
+def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date',
+                         value_col: str = 'profit_ratio'
+                         ):
+    """
+    Calculate max drawdown and the corresponding close dates
+    :param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
+    :param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
+    :param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio')
+    :return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown,
+             high and low time and high and low value.
+    :raise: ValueError if trade-dataframe was found empty.
+    """
+    if len(trades) == 0:
+        raise ValueError("Trade dataframe empty.")
+    profit_results = trades.sort_values(date_col).reset_index(drop=True)
+    max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col)
+
+    return max_drawdown_df
+
+
 def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date',
                            value_col: str = 'profit_ratio'
                            ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]:
@@ -376,10 +406,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
     if len(trades) == 0:
         raise ValueError("Trade dataframe empty.")
     profit_results = trades.sort_values(date_col).reset_index(drop=True)
-    max_drawdown_df = pd.DataFrame()
-    max_drawdown_df['cumulative'] = profit_results[value_col].cumsum()
-    max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax()
-    max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value']
+    max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col)
 
     idxmin = max_drawdown_df['drawdown'].idxmin()
     if idxmin == 0:
diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py
--- a/freqtrade/plot/plotting.py
+++ b/freqtrade/plot/plotting.py
@@ -5,7 +5,8 @@
 import pandas as pd
 
 from freqtrade.configuration import TimeRange
-from freqtrade.data.btanalysis import (calculate_max_drawdown, combine_dataframes_with_mean,
+from freqtrade.data.btanalysis import (analyze_trade_parallelism, calculate_max_drawdown,
+                                       calculate_underwater, combine_dataframes_with_mean,
                                        create_cum_profit, extract_trades_of_period, load_trades)
 from freqtrade.data.converter import trim_dataframe
 from freqtrade.data.dataprovider import DataProvider
@@ -185,6 +186,48 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame,
     return fig
 
 
+def add_underwater(fig, row, trades: pd.DataFrame) -> make_subplots:
+    """
+    Add underwater plot
+    """
+    try:
+        underwater = calculate_underwater(trades, value_col="profit_abs")
+
+        underwater = go.Scatter(
+            x=underwater['date'],
+            y=underwater['drawdown'],
+            name="Underwater Plot",
+            fill='tozeroy',
+            fillcolor='#cc362b',
+            line={'color': '#cc362b'},
+        )
+        fig.add_trace(underwater, row, 1)
+    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)
+
+        drawdown = go.Scatter(
+            x=result.index,
+            y=result['open_trades'],
+            name="Parallel trades",
+            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:
     """
     Add trades to "fig"
@@ -482,20 +525,30 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
         name='Avg close price',
     )
 
-    fig = make_subplots(rows=3, cols=1, shared_xaxes=True,
-                        row_width=[1, 1, 1],
+    fig = make_subplots(rows=5, cols=1, shared_xaxes=True,
+                        row_heights=[1, 1, 1, 0.5, 1],
                         vertical_spacing=0.05,
-                        subplot_titles=["AVG Close Price", "Combined Profit", "Profit per pair"])
+                        subplot_titles=[
+                            "AVG Close Price",
+                            "Combined Profit",
+                            "Profit per pair",
+                            "Parallelism",
+                            "Underwater",
+                            ])
     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']['xaxis']['rangeslider'].update(visible=False)
     fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"])
 
     fig.add_trace(avgclose, 1, 1)
     fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit')
     fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe)
+    fig = add_parallelism(fig, 4, trades, timeframe)
+    fig = add_underwater(fig, 5, trades)
 
     for pair in pairs:
         profit_col = f'cum_profit_{pair}'


Test patch:
diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py
--- a/tests/data/test_btanalysis.py
+++ b/tests/data/test_btanalysis.py
@@ -11,10 +11,10 @@
 from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD,
                                        analyze_trade_parallelism, calculate_csum,
                                        calculate_market_change, calculate_max_drawdown,
-                                       combine_dataframes_with_mean, create_cum_profit,
-                                       extract_trades_of_period, get_latest_backtest_filename,
-                                       get_latest_hyperopt_file, load_backtest_data, load_trades,
-                                       load_trades_from_db)
+                                       calculate_underwater, combine_dataframes_with_mean,
+                                       create_cum_profit, extract_trades_of_period,
+                                       get_latest_backtest_filename, get_latest_hyperopt_file,
+                                       load_backtest_data, load_trades, load_trades_from_db)
 from freqtrade.data.history import load_data, load_pair_history
 from tests.conftest import create_mock_trades
 from tests.conftest_trades import MOCK_TRADE_COUNT
@@ -291,9 +291,16 @@ def test_calculate_max_drawdown(testdatadir):
     assert isinstance(lval, float)
     assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC')
     assert lowdate == Timestamp('2018-01-30 04:45:00', tz='UTC')
+
+    underwater = calculate_underwater(bt_data)
+    assert isinstance(underwater, DataFrame)
+
     with pytest.raises(ValueError, match='Trade dataframe empty.'):
         drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame())
 
+    with pytest.raises(ValueError, match='Trade dataframe empty.'):
+        calculate_underwater(DataFrame())
+
 
 def test_calculate_csum(testdatadir):
     filename = testdatadir / "backtest-result_test.json"
diff --git a/tests/test_plotting.py b/tests/test_plotting.py
--- a/tests/test_plotting.py
+++ b/tests/test_plotting.py
@@ -336,15 +336,20 @@ def test_generate_profit_graph(testdatadir):
     assert fig.layout.yaxis3.title.text == "Profit BTC"
 
     figure = fig.layout.figure
-    assert len(figure.data) == 5
+    assert len(figure.data) == 7
 
     avgclose = find_trace_in_fig_data(figure.data, "Avg close price")
     assert isinstance(avgclose, go.Scatter)
 
     profit = find_trace_in_fig_data(figure.data, "Profit")
     assert isinstance(profit, go.Scatter)
-    profit = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%")
-    assert isinstance(profit, go.Scatter)
+    drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%")
+    assert isinstance(drawdown, go.Scatter)
+    parallel = find_trace_in_fig_data(figure.data, "Parallel trades")
+    assert isinstance(parallel, go.Scatter)
+
+    underwater = find_trace_in_fig_data(figure.data, "Underwater Plot")
+    assert isinstance(underwater, go.Scatter)
 
     for pair in pairs:
         profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}")