From 6bd749296ddc9e5202c7765dde0a79323eda2e9b Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 31 Oct 2020 16:29:38 +0100 Subject: [PATCH 01/11] Refactor f-string formating. --- backtesting/_plotting.py | 16 ++++++------- backtesting/backtesting.py | 47 ++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 6494506f..8572b365 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}' (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,7 +517,7 @@ 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) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 83da833d..8dd396fa 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -66,15 +66,15 @@ def __str__(self): 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 " + 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.".format(self.__class__.__name__, k)) + "can be optimized or run with.") setattr(self, k, v) return params @@ -121,7 +121,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())))) @@ -129,7 +129,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 @@ -145,8 +145,7 @@ 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}" 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 @@ -293,10 +292,9 @@ 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: @@ -351,7 +349,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): @@ -518,9 +516,7 @@ def __init__(self, broker: '_Broker', size: int, entry_price: float, entry_bar): self.__tp_order = None # type: Optional[Order] 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(): @@ -665,9 +661,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 shosuld 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 # type: _Data self._cash = cash self._commission = commission @@ -683,8 +679,7 @@ def __init__(self, *, data, cash, commission, margin, self.closed_trades = [] # type: List[Trade] def __repr__(self): - return ''.format( - self._cash, self.position.pl, len(self.trades)) + return f'' def new_order(self, size: float, @@ -707,12 +702,10 @@ def new_order(self, if is_long: if not (sl or -np.inf) <= (limit or stop or self.last_price) <= (tp or np.inf): - raise ValueError("Long orders require: SL ({}) < LIMIT ({}) < TP ({})".format( - sl, limit or stop or self.last_price, tp)) + raise ValueError(f"Long orders require: SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") else: if not (tp or -np.inf) <= (limit or stop or self.last_price) <= (sl or np.inf): - raise ValueError("Short orders require: TP ({}) < LIMIT ({}) < SL ({})".format( - tp, limit or stop or self.last_price, sl)) + raise ValueError(f"Short orders require: TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") order = Order(self, size, limit, stop, sl, tp, trade) # Put the new order in the order queue, @@ -1237,8 +1230,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): @@ -1253,7 +1246,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, From e38b25f3e7afdfccd5e9aa5fdf5241b95f9cc78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:42:43 +0100 Subject: [PATCH 02/11] Update backtesting.py --- backtesting/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 8dd396fa..8a5892fe 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -145,7 +145,7 @@ 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 ' - f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}" shape: {getattr(value, 'shape', '')}, returned value: {value})') + f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}" 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 From 696669a71f90a355d44d61c4f0e53510b2e68163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 18:02:10 +0100 Subject: [PATCH 03/11] Update backtesting.py --- backtesting/backtesting.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 8a5892fe..b63689a3 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -72,8 +72,8 @@ def _check_params(self, params): for k, v in params.items(): if not hasattr(self, k): raise AttributeError( - f"Strategy '{self.__class__.__name__}' is missing parameter '{k}'. Strategy class " - "should define parameters as class variables before they " + 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 @@ -145,7 +145,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 ' - f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}" shape: {getattr(value, "shape" , "")}, returned value: {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 @@ -292,7 +293,8 @@ 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(f'Strategy.orders.{"/.".join(removed_attrs)} were removed in Backtesting 0.2.0. ' + 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}") @@ -516,7 +518,8 @@ def __init__(self, broker: '_Broker', size: int, entry_price: float, entry_bar): self.__tp_order = None # type: Optional[Order] def __repr__(self): - return f'' + return f'' def _replace(self, **kwargs): for k, v in kwargs.items(): @@ -702,10 +705,12 @@ def new_order(self, if is_long: if not (sl or -np.inf) <= (limit or stop or self.last_price) <= (tp or np.inf): - raise ValueError(f"Long orders require: SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") + raise ValueError(f"Long orders require:' f + "SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") else: if not (tp or -np.inf) <= (limit or stop or self.last_price) <= (sl or np.inf): - raise ValueError(f"Short orders require: TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") + raise ValueError(f"Short orders require: " + f"TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") order = Order(self, size, limit, stop, sl, tp, trade) # Put the new order in the order queue, From e0b929e308996f41df81ff8339bce94adf4a53aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 18:49:30 +0100 Subject: [PATCH 04/11] Update backtesting.py --- backtesting/backtesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index b63689a3..c1e3f7ff 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -145,7 +145,7 @@ 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 ' - f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}"' + 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): @@ -518,7 +518,7 @@ def __init__(self, broker: '_Broker', size: int, entry_price: float, entry_bar): self.__tp_order = None # type: Optional[Order] def __repr__(self): - return f'' def _replace(self, **kwargs): From 21fed39e3e323cc399194f9e3d4485fb01182346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 19:19:25 +0100 Subject: [PATCH 05/11] Update backtesting.py --- backtesting/backtesting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index c1e3f7ff..25749a0e 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -705,8 +705,8 @@ def new_order(self, if is_long: if not (sl or -np.inf) <= (limit or stop or self.last_price) <= (tp or np.inf): - raise ValueError(f"Long orders require:' f - "SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") + raise ValueError(f"Long orders require:' + f"SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") else: if not (tp or -np.inf) <= (limit or stop or self.last_price) <= (sl or np.inf): raise ValueError(f"Short orders require: " From 732068a6a5a3d152584ae32179a355b3611bb6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 19:26:55 +0100 Subject: [PATCH 06/11] Update backtesting.py --- backtesting/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 25749a0e..ae9e708e 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -705,7 +705,7 @@ def new_order(self, if is_long: if not (sl or -np.inf) <= (limit or stop or self.last_price) <= (tp or np.inf): - raise ValueError(f"Long orders require:' + raise ValueError(f"Long orders require:" f"SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") else: if not (tp or -np.inf) <= (limit or stop or self.last_price) <= (sl or np.inf): From 6c633bcbccd750960d5e5219a6d09b890f3c1639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 19:35:55 +0100 Subject: [PATCH 07/11] Update backtesting.py --- backtesting/backtesting.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index ae9e708e..5e9039e9 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -145,7 +145,7 @@ 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 ' - f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}"' \ + 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): @@ -424,8 +424,8 @@ def size(self) -> float: """ Order size (negative for short orders). - If size is a value between 0 and 1, it is interpreted as a fraction of current - available liquidity (cash plus `Position.pl` minus used margin). + If size is a value between 0 and 1, it is interpreted as a fraction + of current available liquidity (cash plus `Position.pl` minus used margin). A value greater than or equal to 1 indicates an absolute number of units. """ return self.__size @@ -705,12 +705,12 @@ def new_order(self, if is_long: if not (sl or -np.inf) <= (limit or stop or self.last_price) <= (tp or np.inf): - raise ValueError(f"Long orders require:" - f"SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") + raise ValueError(f"Long orders require: " + f"SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") else: if not (tp or -np.inf) <= (limit or stop or self.last_price) <= (sl or np.inf): - raise ValueError(f"Short orders require: " - f"TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") + raise ValueError(f"Short orders require: " + f"TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") order = Order(self, size, limit, stop, sl, tp, trade) # Put the new order in the order queue, From 00bb39e5d24f0fd1e47e94062d87ce268834c8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 19:40:02 +0100 Subject: [PATCH 08/11] Update backtesting.py --- backtesting/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 5e9039e9..fffe0af9 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -424,7 +424,7 @@ def size(self) -> float: """ Order size (negative for short orders). - If size is a value between 0 and 1, it is interpreted as a fraction + If size is a value between 0 and 1, it is interpreted as a fraction of current available liquidity (cash plus `Position.pl` minus used margin). A value greater than or equal to 1 indicates an absolute number of units. """ From 5ab6cb343d768a3874407b85b7bf7c8dd2c6f743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Sat, 31 Oct 2020 19:40:57 +0100 Subject: [PATCH 09/11] Update _plotting.py --- backtesting/_plotting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 8572b365..b75d9d78 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -424,7 +424,8 @@ def _plot_superimposed_ohlc(): millisecond='S').get(time_resolution)) if not resample_rule: warnings.warn( - f"'Can't superimpose OHLC data with rule '{resample_rule}' (index datetime resolution: '{time_resolution}'). " + f"'Can't superimpose OHLC data with rule '{resample_rule}'" + f"(index datetime resolution: '{time_resolution}'). " "Skipping.", stacklevel=4) return From 4fd6ac51b54a924791fee34c49301fe5618d3769 Mon Sep 17 00:00:00 2001 From: Kernc Date: Thu, 12 Nov 2020 20:24:08 +0100 Subject: [PATCH 10/11] address comments --- backtesting/_plotting.py | 3 +-- backtesting/backtesting.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index b75d9d78..29a65c64 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -425,8 +425,7 @@ def _plot_superimposed_ohlc(): if not resample_rule: warnings.warn( f"'Can't superimpose OHLC data with rule '{resample_rule}'" - f"(index datetime resolution: '{time_resolution}'). " - "Skipping.", + f"(index datetime resolution: '{time_resolution}'). Skipping.", stacklevel=4) return diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index fffe0af9..787f3762 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -424,8 +424,8 @@ def size(self) -> float: """ Order size (negative for short orders). - If size is a value between 0 and 1, it is interpreted as a fraction - of current available liquidity (cash plus `Position.pl` minus used margin). + If size is a value between 0 and 1, it is interpreted as a fraction of current + available liquidity (cash plus `Position.pl` minus used margin). A value greater than or equal to 1 indicates an absolute number of units. """ return self.__size @@ -664,7 +664,7 @@ 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, f"cash shosuld be >0, is {cash}" + 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 # type: _Data @@ -705,12 +705,14 @@ def new_order(self, if is_long: if not (sl or -np.inf) <= (limit or stop or self.last_price) <= (tp or np.inf): - raise ValueError(f"Long orders require: " - f"SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") + raise ValueError( + "Long orders require: " + f"SL ({sl}) < LIMIT ({limit or stop or self.last_price}) < TP ({tp})") else: if not (tp or -np.inf) <= (limit or stop or self.last_price) <= (sl or np.inf): - raise ValueError(f"Short orders require: " - f"TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") + raise ValueError( + "Short orders require: " + f"TP ({tp}) < LIMIT ({limit or stop or self.last_price}) < SL ({sl})") order = Order(self, size, limit, stop, sl, tp, trade) # Put the new order in the order queue, From aad278cb1fe3f206a011817ac9047afc737eb70d Mon Sep 17 00:00:00 2001 From: Kernc Date: Thu, 12 Nov 2020 20:42:30 +0100 Subject: [PATCH 11/11] add some more f-strings where useful --- backtesting/_plotting.py | 2 +- backtesting/_util.py | 8 ++++---- backtesting/backtesting.py | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 29a65c64..6749a6cf 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -521,7 +521,7 @@ def __eq__(self, other): 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 b441b53b..a77915c0 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 787f3762..b1f18c40 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -62,8 +62,8 @@ 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 f'{self.__class__.__name__}{params}' @@ -391,11 +391,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), @@ -523,7 +523,7 @@ def __repr__(self): 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): @@ -651,7 +651,7 @@ 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) + attr = f'_{self.__class__.__qualname__}__{type}_order' order = getattr(self, attr) # type: Order if order: order.cancel()