Skip to content

Refactor f-string formatting. #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
18 changes: 9 additions & 9 deletions backtesting/_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 ', '('))

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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():
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions backtesting/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -125,9 +125,9 @@ def _update(self):

def __repr__(self):
i = min(self.__i, len(self.__df) - 1)
return '<Data i={} ({}) {}>'.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'<Data i={i} ({index}) {items}>'

def __len__(self):
return self.__i
Expand Down
70 changes: 35 additions & 35 deletions backtesting/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,19 @@ def __repr__(self):
return '<Strategy ' + str(self) + '>'

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

Expand Down Expand Up @@ -116,15 +116,15 @@ 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()))))

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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -346,7 +346,7 @@ def close(self, portion: float = 1.):
trade.close(portion)

def __repr__(self):
return '<Position: {} ({} trades)>'.format(self.size, len(self.__broker.trades))
return f'<Position: {self.size} ({len(self.__broker.trades)} trades)>'


class _OutOfMoneyError(Exception):
Expand Down Expand Up @@ -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 '<Order {}>'.format(', '.join('{}={}'.format(param, round(value, 5))
return '<Order {}>'.format(', '.join(f'{param}={round(value, 5)}'
for param, value in (
('size', self.__size),
('limit', self.__limit_price),
Expand Down Expand Up @@ -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 '<Trade size={} time={}-{} price={}-{} pl={:.0f}>'.format(
self.__size, self.__entry_bar, self.__exit_bar or '',
self.__entry_price, self.__exit_price or '', self.pl)
return f'<Trade size={self.__size} time={self.__entry_bar}-{self.__exit_bar or ""} ' \
f'price={self.__entry_price}-{self.__exit_price or ""} pl={self.pl:.0f}>'

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):
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -678,8 +677,7 @@ def __init__(self, *, data, cash, commission, margin,
self.closed_trades: List[Trade] = []

def __repr__(self):
return '<Broker: {:.0f}{:+.1f} ({} trades)>'.format(
self._cash, self.position.pl, len(self.trades))
return f'<Broker: {self._cash:.0f}{self.position.pl:+.1f} ({len(self.trades)} trades)>'

def new_order(self,
size: float,
Expand All @@ -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,
Expand Down Expand Up @@ -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):
Expand All @@ -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,
Expand Down