diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index b65fcb2d..fef05cfa 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -101,8 +101,8 @@ def _maybe_resample_data(resample_rule, df, indicators, equity_data, trades): FREQS = ('1T', '5T', '10T', '15T', '30T', '1H', '2H', '4H', '8H', '1D', '1W', '1M') freq = next((f for f in FREQS[from_index:] if len(df.resample(f)) <= _MAX_CANDLES), FREQS[-1]) - warnings.warn("Data contains too many candlesticks to plot; downsampling to {!r}. " - "See `Backtest.plot(resample=...)`".format(freq)) + warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. " + "See `Backtest.plot(resample=...)`") from .lib import OHLCV_AGG, TRADES_AGG, _EQUITY_AGG df = df.resample(freq, label='right').agg(OHLCV_AGG).dropna() @@ -354,7 +354,7 @@ def _plot_equity_section(): dd_timedelta_label = df['datetime'].iloc[int(round(dd_end))] - df['datetime'].iloc[dd_start] fig.line([dd_start, dd_end], equity.iloc[dd_start], line_color='red', line_width=2, - legend_label='Max Dd Dur. ({})'.format(dd_timedelta_label) + legend_label=f'Max Dd Dur. ({dd_timedelta_label})' .replace(' 00:00:00', '') .replace('(0 days ', '(')) @@ -424,8 +424,8 @@ def _plot_superimposed_ohlc(): millisecond='S').get(time_resolution)) if not resample_rule: warnings.warn( - "'Can't superimpose OHLC data with rule '{}' (index datetime resolution: '{}'). " - "Skipping.".format(resample_rule, time_resolution), + f"'Can't superimpose OHLC data with rule '{resample_rule}'" + f"(index datetime resolution: '{time_resolution}'). Skipping.", stacklevel=4) return @@ -469,7 +469,7 @@ def _plot_ohlc_trades(): trade_source.add(trades[['EntryPrice', 'ExitPrice']].values.tolist(), 'position_lines_ys') fig_ohlc.multi_line(xs='position_lines_xs', ys='position_lines_ys', source=trade_source, line_color=trades_cmap, - legend_label='Trades ({})'.format(len(trades)), + legend_label=f'Trades ({len(trades)})', line_width=8, line_alpha=1, line_dash='dotted') def _plot_indicators(): @@ -478,7 +478,7 @@ def _plot_indicators(): def _too_many_dims(value): assert value.ndim >= 2 if value.ndim > 2: - warnings.warn("Can't plot indicators with >2D ('{}')".format(value.name), + warnings.warn(f"Can't plot indicators with >2D ('{value.name}')", stacklevel=5) return True return False @@ -517,11 +517,11 @@ def __eq__(self, other): legend_label = LegendStr(value.name) for j, arr in enumerate(value, 1): color = next(colors) - source_name = '{}_{}_{}'.format(legend_label, i, j) + source_name = f'{legend_label}_{i}_{j}' if arr.dtype == bool: arr = arr.astype(int) source.add(arr, source_name) - tooltips.append('@{{{}}}{{0,0.0[0000]}}'.format(source_name)) + tooltips.append(f'@{{{source_name}}}{{0,0.0[0000]}}') if is_overlay: ohlc_extreme_values[source_name] = arr if is_scatter: diff --git a/backtesting/_util.py b/backtesting/_util.py index 2f708cef..2b8a2eb5 100644 --- a/backtesting/_util.py +++ b/backtesting/_util.py @@ -111,7 +111,7 @@ def __getattr__(self, item): try: return self.__get_array(item) except KeyError: - raise AttributeError("Column '{}' not in data".format(item)) from None + raise AttributeError(f"Column '{item}' not in data") from None def _set_length(self, i): self.__i = i @@ -125,9 +125,9 @@ def _update(self): def __repr__(self): i = min(self.__i, len(self.__df) - 1) - return ''.format(i, self.__arrays['__index'][i], - ', '.join('{}={}'.format(k, v) - for k, v in self.__df.iloc[i].items())) + index = self.__arrays['__index'][i] + items = ', '.join(f'{k}={v}' for k, v in self.__df.iloc[i].items()) + return f'' def __len__(self): return self.__i diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 9b2053fa..23bb9317 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -57,19 +57,19 @@ def __repr__(self): return '' def __str__(self): - params = ','.join('{}={}'.format(*p) for p in zip(self._params.keys(), - map(_as_str, self._params.values()))) + params = ','.join(f'{i[0]}={i[1]}' for i in zip(self._params.keys(), + map(_as_str, self._params.values()))) if params: params = '(' + params + ')' - return '{}{}'.format(self.__class__.__name__, params) + return f'{self.__class__.__name__}{params}' def _check_params(self, params): for k, v in params.items(): if not hasattr(self, k): raise AttributeError( - "Strategy '{}' is missing parameter '{}'. Strategy class " - "should define parameters as class variables before they " - "can be optimized or run with.".format(self.__class__.__name__, k)) + f"Strategy '{self.__class__.__name__}' is missing parameter '{k}'." + "Strategy class should define parameters as class variables before they " + "can be optimized or run with.") setattr(self, k, v) return params @@ -116,7 +116,7 @@ def init(): if name is None: params = ','.join(filter(None, map(_as_str, chain(args, kwargs.values())))) func_name = _as_str(func) - name = ('{}({})' if params else '{}').format(func_name, params) + name = (f'{func_name}({params})' if params else f'{func_name}') else: name = name.format(*map(_as_str, args), **dict(zip(kwargs.keys(), map(_as_str, kwargs.values())))) @@ -124,7 +124,7 @@ def init(): try: value = func(*args, **kwargs) except Exception as e: - raise RuntimeError('Indicator "{}" errored with exception: {}'.format(name, e)) + raise RuntimeError(f'Indicator "{name}" errored with exception: {e}') if isinstance(value, pd.DataFrame): value = value.values.T @@ -140,8 +140,8 @@ def init(): if not is_arraylike or not 1 <= value.ndim <= 2 or value.shape[-1] != len(self._data.Close): raise ValueError( 'Indicators must return (optionally a tuple of) numpy.arrays of same ' - 'length as `data` (data shape: {}; indicator "{}" shape: {}, returned value: {})' - .format(self._data.Close.shape, name, getattr(value, 'shape', ''), value)) + f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}"' + f'shape: {getattr(value, "shape" , "")}, returned value: {value})') if plot and overlay is None and np.issubdtype(value.dtype, np.number): x = value / self._data.Close @@ -288,10 +288,10 @@ def __getattr__(self, item): removed_attrs = ('entry', 'set_entry', 'is_long', 'is_short', 'sl', 'tp', 'set_sl', 'set_tp') if item in removed_attrs: - raise AttributeError('Strategy.orders.{} were removed in Backtesting 0.2.0. ' - 'Use `Order` API instead. See docs.' - .format('/.'.join(removed_attrs))) - raise AttributeError("'tuple' object has no attribute {!r}".format(item)) + raise AttributeError(f'Strategy.orders.{"/.".join(removed_attrs)} were removed in' + 'Backtesting 0.2.0. ' + 'Use `Order` API instead. See docs.') + raise AttributeError(f"'tuple' object has no attribute {item!r}") class Position: @@ -346,7 +346,7 @@ def close(self, portion: float = 1.): trade.close(portion) def __repr__(self): - return ''.format(self.size, len(self.__broker.trades)) + return f'' class _OutOfMoneyError(Exception): @@ -386,11 +386,11 @@ def __init__(self, broker: '_Broker', def _replace(self, **kwargs): for k, v in kwargs.items(): - setattr(self, '_{}__{}'.format(self.__class__.__qualname__, k), v) + setattr(self, f'_{self.__class__.__qualname__}__{k}', v) return self def __repr__(self): - return ''.format(', '.join('{}={}'.format(param, round(value, 5)) + return ''.format(', '.join(f'{param}={round(value, 5)}' for param, value in ( ('size', self.__size), ('limit', self.__limit_price), @@ -513,13 +513,12 @@ def __init__(self, broker: '_Broker', size: int, entry_price: float, entry_bar): self.__tp_order: Optional[Order] = None def __repr__(self): - return ''.format( - self.__size, self.__entry_bar, self.__exit_bar or '', - self.__entry_price, self.__exit_price or '', self.pl) + return f'' def _replace(self, **kwargs): for k, v in kwargs.items(): - setattr(self, '_{}__{}'.format(self.__class__.__qualname__, k), v) + setattr(self, f'_{self.__class__.__qualname__}__{k}', v) return self def _copy(self, **kwargs): @@ -647,8 +646,8 @@ def tp(self, price: float): def __set_contingent(self, type, price): assert type in ('sl', 'tp') assert price is None or 0 < price < np.inf - attr = '_{}__{}_order'.format(self.__class__.__qualname__, type) - order: Order = getattr(self, attr) + attr = f'_{self.__class__.__qualname__}__{type}_order' + order: Order = getattr(self, attr) # type: Order if order: order.cancel() if price: @@ -660,9 +659,9 @@ def __set_contingent(self, type, price): class _Broker: def __init__(self, *, data, cash, commission, margin, trade_on_close, hedging, exclusive_orders, index): - assert 0 < cash, "cash shosuld be >0, is {}".format(cash) - assert 0 <= commission < .1, "commission should be between 0-10%, is {}".format(commission) - assert 0 < margin <= 1, "margin should be between 0 and 1, is {}".format(margin) + assert 0 < cash, f"cash should be >0, is {cash}" + assert 0 <= commission < .1, f"commission should be between 0-10%, is {commission}" + assert 0 < margin <= 1, f"margin should be between 0 and 1, is {margin}" self._data: _Data = data self._cash = cash self._commission = commission @@ -678,8 +677,7 @@ def __init__(self, *, data, cash, commission, margin, self.closed_trades: List[Trade] = [] def __repr__(self): - return ''.format( - self._cash, self.position.pl, len(self.trades)) + return f'' def new_order(self, size: float, @@ -703,12 +701,14 @@ def new_order(self, if is_long: if not (sl or -np.inf) < (limit or stop or adjusted_price) < (tp or np.inf): - raise ValueError("Long orders require: SL ({}) < LIMIT ({}) < TP ({})".format( - sl, limit or stop or adjusted_price, tp)) + raise ValueError( + "Long orders require: " + f"SL ({sl}) < LIMIT ({limit or stop or adjusted_price}) < TP ({tp})") else: if not (tp or -np.inf) < (limit or stop or adjusted_price) < (sl or np.inf): - raise ValueError("Short orders require: TP ({}) < LIMIT ({}) < SL ({})".format( - tp, limit or stop or adjusted_price, sl)) + raise ValueError( + "Short orders require: " + f"TP ({tp}) < LIMIT ({limit or stop or adjusted_price}) < SL ({sl})") order = Order(self, size, limit, stop, sl, tp, trade) # Put the new order in the order queue, @@ -1243,8 +1243,8 @@ def _tuple(x): for k, v in kwargs.items(): if len(_tuple(v)) == 0: - raise ValueError("Optimization variable '{0}' is passed no " - "optimization values: {0}={1}".format(k, v)) + raise ValueError(f"Optimization variable '{k}' is passed no " + f"optimization values: {k}={v}") class AttrDict(dict): def __getattr__(self, item): @@ -1259,7 +1259,7 @@ def __getattr__(self, item): raise ValueError('No admissible parameter combinations to test') if len(param_combos) > 300: - warnings.warn('Searching for best of {} configurations.'.format(len(param_combos)), + warnings.warn(f'Searching for best of {len(param_combos)} configurations.', stacklevel=2) heatmap = pd.Series(np.nan,