Skip to content

Commit

Permalink
Merge branch 'develop' into feat/short
Browse files Browse the repository at this point in the history
  • Loading branch information
xmatthias committed Jan 7, 2022
2 parents 173524e + 7f20f68 commit 46809f0
Show file tree
Hide file tree
Showing 27 changed files with 212 additions and 217 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ jobs:
uses: rjstone/discord-webhook-notify@v1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
severity: error
severity: info
details: Test Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}

Expand Down
7 changes: 4 additions & 3 deletions docs/backtesting.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ A backtesting result will look like that:
| | |
| Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC |
| Drawdown | 50.63% |
| Drawdown (Account) | 13.33% |
| Drawdown | 0.0015 BTC |
| Drawdown high | 0.0013 BTC |
| Drawdown low | -0.0002 BTC |
Expand Down Expand Up @@ -405,7 +405,7 @@ It contains some useful key metrics about performance of your strategy on backte
| | |
| Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC |
| Drawdown | 50.63% |
| Drawdown (Account) | 13.33% |
| Drawdown | 0.0015 BTC |
| Drawdown high | 0.0013 BTC |
| Drawdown low | -0.0002 BTC |
Expand All @@ -432,7 +432,8 @@ It contains some useful key metrics about performance of your strategy on backte
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached.
- `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period.
- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
- `Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as $(Absolute Drawdown) / (DrawdownHigh + startingBalance)$.
- `Drawdown`: Maximum, absolute drawdown experienced. Difference between Drawdown High and Subsequent Low point.
- `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost.
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
Expand Down
53 changes: 22 additions & 31 deletions freqtrade/data/btanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,13 @@
import pandas as pd

from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.exceptions import OperationalException
from freqtrade.misc import json_load
from freqtrade.persistence import LocalTrade, Trade, init_db


logger = logging.getLogger(__name__)

# Old format - maybe remove?
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]

# Mid-term format, created by BacktestResult Named Tuple
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
'fee_close', 'amount', 'profit_abs', 'profit_ratio']

# Newest format
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'open_rate', 'close_rate',
Expand Down Expand Up @@ -170,23 +162,9 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
)
else:
# old format - only with lists.
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS_OLD)
if not df.empty:
df['open_date'] = pd.to_datetime(df['open_date'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['close_date'] = pd.to_datetime(df['close_date'],
unit='s',
utc=True,
infer_datetime_format=True
)
# Create compatibility with new format
df['profit_abs'] = df['close_rate'] - df['open_rate']
raise OperationalException(
"Backtest-results with only trades data are no longer supported.")
if not df.empty:
if 'profit_ratio' not in df.columns:
df['profit_ratio'] = df['profit_percent']
df = df.sort_values("open_date").reset_index(drop=True)
return df

Expand Down Expand Up @@ -395,15 +373,17 @@ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date',


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]:
value_col: str = 'profit_abs', starting_balance: float = 0
) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]:
"""
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.
:param value_col: Column in DataFrame to use for values (defaults to 'profit_abs')
:param starting_balance: Portfolio starting balance - properly calculate relative drawdown.
:return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown)
with absolute max drawdown, high and low time and high and low value,
and the relative account drawdown
:raise: ValueError if trade-dataframe was found empty.
"""
if len(trades) == 0:
Expand All @@ -419,7 +399,18 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin]
['high_value'].idxmax(), 'cumulative']
low_val = max_drawdown_df.loc[idxmin, 'cumulative']
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val
max_drawdown_rel = 0.0
if high_val + starting_balance != 0:
max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance)

return (
abs(min(max_drawdown_df['drawdown'])),
high_date,
low_date,
high_val,
low_val,
max_drawdown_rel
)


def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]:
Expand Down
5 changes: 4 additions & 1 deletion freqtrade/exchange/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class Exchange:
"ohlcv_params": {},
"ohlcv_candle_limit": 500,
"ohlcv_partial_candle": True,
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
"ohlcv_volume_currency": "base", # "base" or "quote"
"trades_pagination": "time", # Possible are "time" or "id"
"trades_pagination_arg": "since",
"l2_limit_range": None,
Expand Down Expand Up @@ -797,7 +799,8 @@ def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: f
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))

remaining_amount = amount
filled_amount = 0
filled_amount = 0.0
book_entry_price = 0.0
for book_entry in ob[ob_type]:
book_entry_price = book_entry[0]
book_entry_coin_volume = book_entry[1]
Expand Down
1 change: 1 addition & 0 deletions freqtrade/exchange/ftx.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Ftx(Exchange):
_ft_has: Dict = {
"stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500,
"ohlcv_volume_currency": "quote",
"mark_ohlcv_price": "index",
"mark_ohlcv_timeframe": "1h",
}
Expand Down
1 change: 1 addition & 0 deletions freqtrade/exchange/gateio.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Gateio(Exchange):

_ft_has: Dict = {
"ohlcv_candle_limit": 1000,
"ohlcv_volume_currency": "quote",
}

_headers = {'X-Gate-Channel-Id': 'freqtrade'}
Expand Down
2 changes: 1 addition & 1 deletion freqtrade/exchange/okex.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Okex(Exchange):
"""

_ft_has: Dict = {
"ohlcv_candle_limit": 100,
"ohlcv_candle_limit": 300,
"mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h",
}
Expand Down
4 changes: 3 additions & 1 deletion freqtrade/optimize/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,10 @@ def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[
detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX]
detail_data.loc[:, 'enter_long'] = sell_row[LONG_IDX]
detail_data.loc[:, 'exit_long'] = sell_row[ELONG_IDX]
detail_data.loc[:, 'enter_tag'] = sell_row[ENTER_TAG_IDX]
detail_data.loc[:, 'exit_tag'] = sell_row[EXIT_TAG_IDX]
headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short']
'enter_short', 'exit_short', 'enter_tag', 'exit_tag']
for det_row in detail_data[headers].values.tolist():
res = self._get_sell_trade_entry_for_candle(trade, det_row)
if res:
Expand Down
3 changes: 2 additions & 1 deletion freqtrade/optimize/hyperopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(self, config: Dict[str, Any]) -> None:
self.config = config

self.backtesting = Backtesting(self.config)
self.pairlist = self.backtesting.pairlists.whitelist

if not self.config.get('hyperopt'):
self.custom_hyperopt = HyperOptAuto(self.config)
Expand Down Expand Up @@ -332,7 +333,7 @@ def _get_results_dict(self, backtesting_results, min_date, max_date,
params_details = self._get_params_details(params_dict)

strat_stats = generate_strategy_stats(
processed, self.backtesting.strategy.get_strategy_name(),
self.pairlist, self.backtesting.strategy.get_strategy_name(),
backtesting_results, min_date, max_date, market_change=0
)
results_explanation = HyperoptTools.format_results_explanation_string(
Expand Down
3 changes: 1 addition & 2 deletions freqtrade/optimize/hyperopt_loss_calmar.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ def hyperopt_loss_function(

# calculate max drawdown
try:
_, _, _, high_val, low_val = calculate_max_drawdown(
_, _, _, _, _, max_drawdown = calculate_max_drawdown(
results, value_col="profit_abs"
)
max_drawdown = (high_val - low_val) / high_val
except ValueError:
max_drawdown = 0

Expand Down
43 changes: 23 additions & 20 deletions freqtrade/optimize/hyperopt_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,7 @@ def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataF

if not has_drawdown:
# Ensure compatibility with older versions of hyperopt results
trials['results_metrics.max_drawdown_abs'] = None
trials['results_metrics.max_drawdown'] = None
trials['results_metrics.max_drawdown_account'] = None

# New mode, using backtest result for metrics
trials['results_metrics.winsdrawslosses'] = trials.apply(
Expand All @@ -320,12 +319,15 @@ def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataF
'results_metrics.winsdrawslosses',
'results_metrics.profit_mean', 'results_metrics.profit_total_abs',
'results_metrics.profit_total', 'results_metrics.holding_avg',
'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs',
'results_metrics.max_drawdown',
'results_metrics.max_drawdown_account', 'results_metrics.max_drawdown_abs',
'loss', 'is_initial_point', 'is_best']]

trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit',
'Total profit', 'Profit', 'Avg duration', 'Max Drawdown',
'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best']
trials.columns = [
'Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit',
'Total profit', 'Profit', 'Avg duration', 'max_drawdown', 'max_drawdown_account',
'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best'
]

return trials

Expand All @@ -341,9 +343,9 @@ def get_result_table(config: dict, results: list, total_epochs: int, highlight_b
tabulate.PRESERVE_WHITESPACE = True
trials = json_normalize(results, max_level=1)

has_drawdown = 'results_metrics.max_drawdown_abs' in trials.columns
has_account_drawdown = 'results_metrics.max_drawdown_account' in trials.columns

trials = HyperoptTools.prepare_trials_columns(trials, has_drawdown)
trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown)

trials['is_profit'] = False
trials.loc[trials['is_initial_point'], 'Best'] = '* '
Expand All @@ -368,19 +370,20 @@ def get_result_table(config: dict, results: list, total_epochs: int, highlight_b

stake_currency = config['stake_currency']

if has_drawdown:
trials['Max Drawdown'] = trials.apply(
lambda x: '{} {}'.format(
round_coin_value(x['max_drawdown_abs'], stake_currency),
f"({x['Max Drawdown']:,.2%})".rjust(10, ' ')
).rjust(25 + len(stake_currency))
if x['Max Drawdown'] != 0.0 else '--'.rjust(25 + len(stake_currency)),
axis=1
)
else:
trials = trials.drop(columns=['Max Drawdown'])
trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply(
lambda x: "{} {}".format(
round_coin_value(x['max_drawdown_abs'], stake_currency),
(f"({x['max_drawdown_account']:,.2%})"
if has_account_drawdown
else f"({x['max_drawdown']:,.2%})"
).rjust(10, ' ')
).rjust(25 + len(stake_currency))
if x['max_drawdown'] != 0.0 or x['max_drawdown_account'] != 0.0
else '--'.rjust(25 + len(stake_currency)),
axis=1
)

trials = trials.drop(columns=['max_drawdown_abs'])
trials = trials.drop(columns=['max_drawdown_abs', 'max_drawdown', 'max_drawdown_account'])

trials['Profit'] = trials.apply(
lambda x: '{} {}'.format(
Expand Down
Loading

0 comments on commit 46809f0

Please sign in to comment.