diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 17abae3b6..0f5d395ff 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -150,15 +150,21 @@ def combine_tickers_with_mean(tickers: Dict[str, pd.DataFrame], column: str = "c return df_comb -def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str) -> pd.DataFrame: +def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, + timeframe: str) -> pd.DataFrame: """ Adds a column `col_name` with the cumulative profit for the given trades array. :param df: DataFrame with date index :param trades: DataFrame containing trades (requires columns close_time and profitperc) + :param col_name: Column name that will be assigned the results + :param timeframe: Timeframe used during the operations :return: Returns df with one additional column, col_name, containing the cumulative profit. """ - # 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() + from freqtrade.exchange import timeframe_to_minutes + ticker_minutes = timeframe_to_minutes(timeframe) + # Resample to ticker_interval to make sure trades match candles + _trades_sum = trades.resample(f'{ticker_minutes}min', on='close_time')[['profitperc']].sum() + df.loc[:, col_name] = _trades_sum.cumsum() # Set first value to 0 df.loc[df.iloc[0].name, col_name] = 0 # FFill to get continuous diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 6bd5993b6..bbdb52ca1 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -264,12 +264,12 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame], - trades: pd.DataFrame) -> go.Figure: + trades: pd.DataFrame, timeframe: str) -> go.Figure: # Combine close-values for all pairs, rename columns to "pair" df_comb = combine_tickers_with_mean(tickers, "close") # Add combined cumulative profit - df_comb = create_cum_profit(df_comb, trades, 'cum_profit') + df_comb = create_cum_profit(df_comb, trades, 'cum_profit', timeframe) # Plot the pairs average close prices, and total profit growth avgclose = go.Scatter( @@ -293,7 +293,7 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame], for pair in pairs: profit_col = f'cum_profit_{pair}' - df_comb = create_cum_profit(df_comb, trades[trades['pair'] == pair], profit_col) + 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}") @@ -382,9 +382,9 @@ def plot_profit(config: Dict[str, Any]) -> None: ) # 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) + fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"], + trades, config.get('ticker_interval', '5m')) store_plot_file(fig, filename='freqtrade-profit-plot.html', directory=config['user_data_dir'] / "plot", auto_open=True) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 4068e00e4..a04a2c529 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock import pytest from arrow import Arrow -from pandas import DataFrame, to_datetime +from pandas import DataFrame, DateOffset, to_datetime from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, @@ -130,7 +130,25 @@ def test_create_cum_profit(testdatadir): cum_profits = create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'POWR/BTC'], - "cum_profits") + "cum_profits", timeframe="5m") + assert "cum_profits" in cum_profits.columns + assert cum_profits.iloc[0]['cum_profits'] == 0 + assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005 + + +def test_create_cum_profit1(testdatadir): + filename = testdatadir / "backtest-result_test.json" + bt_data = load_backtest_data(filename) + # Move close-time to "off" the candle, to make sure the logic still works + bt_data.loc[:, 'close_time'] = bt_data.loc[:, 'close_time'] + DateOffset(seconds=20) + timerange = TimeRange.parse_timerange("20180110-20180112") + + df = load_pair_history(pair="POWR/BTC", ticker_interval='5m', + datadir=testdatadir, timerange=timerange) + + cum_profits = create_cum_profit(df.set_index('date'), + bt_data[bt_data["pair"] == 'POWR/BTC'], + "cum_profits", timeframe="5m") assert "cum_profits" in cum_profits.columns assert cum_profits.iloc[0]['cum_profits'] == 0 assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005 diff --git a/tests/test_plotting.py b/tests/test_plotting.py index a39b2b76e..1c7d1b392 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -234,7 +234,7 @@ def test_add_profit(testdatadir): cum_profits = create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'POWR/BTC'], - "cum_profits") + "cum_profits", timeframe="5m") fig1 = add_profit(fig, row=2, data=cum_profits, column='cum_profits', name='Profits') figure = fig1.layout.figure @@ -256,7 +256,7 @@ def test_generate_profit_graph(testdatadir): ) trades = trades[trades['pair'].isin(pairs)] - fig = generate_profit_graph(pairs, tickers, trades) + fig = generate_profit_graph(pairs, tickers, trades, timeframe="5m") assert isinstance(fig, go.Figure) assert fig.layout.title.text == "Freqtrade Profit plot"