Replace .format() with f-strings to improve performance.

This commit is contained in:
Markus
2021-03-27 15:28:53 +01:00
parent cc3d11c85f
commit 8a51434ce3
44 changed files with 222 additions and 439 deletions

View File

@@ -15,7 +15,7 @@ warnings.simplefilter(action='ignore', category=FutureWarning)
if jh.python_version() < 3.7:
print(
jh.color(
'Jesse requires Python version above 3.7. Yours is {}'.format(jh.python_version()),
f'Jesse requires Python version above 3.7. Yours is {jh.python_version()}',
'red'
)
)
@@ -111,58 +111,52 @@ def register_custom_exception_handler() -> None:
exceptions.CandleNotFoundInDatabase
]:
click.clear()
print('=' * 30 + ' EXCEPTION TRACEBACK:')
print(f"{'=' * 30} EXCEPTION TRACEBACK:")
traceback.print_tb(exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
jh.color(f'{exc_type.__name__}: {exc_value}', 'yellow')
)
return
# send notifications if it's a live session
if jh.is_live():
jesse_logger.error(
'{}: {}'.format(exc_type.__name__, exc_value)
f'{exc_type.__name__}: {exc_value}'
)
if jh.is_live() or jh.is_collecting_data():
logging.error("Uncaught Exception:", exc_info=(exc_type, exc_value, exc_traceback))
else:
print('=' * 30 + ' EXCEPTION TRACEBACK:')
print(f"{'=' * 30} EXCEPTION TRACEBACK:")
traceback.print_tb(exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
jh.color(f'{exc_type.__name__}: {exc_value}', 'yellow')
)
if jh.is_paper_trading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/paper-trade.txt'
),
'An uncaught exception was raised. Check the log file at:\nstorage/logs/paper-trade.txt',
'red'
)
)
elif jh.is_livetrading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/live-trade.txt'
),
'An uncaught exception was raised. Check the log file at:\nstorage/logs/live-trade.txt',
'red'
)
)
elif jh.is_collecting_data():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/collect.txt'
),
'An uncaught exception was raised. Check the log file at:\nstorage/logs/collect.txt',
'red'
)
)
@@ -181,59 +175,53 @@ def register_custom_exception_handler() -> None:
exceptions.CandleNotFoundInDatabase
]:
click.clear()
print('=' * 30 + ' EXCEPTION TRACEBACK:')
print(f"{'=' * 30} EXCEPTION TRACEBACK:")
traceback.print_tb(args.exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
jh.color(f'{args.exc_type.__name__}: {args.exc_value}', 'yellow')
)
return
# send notifications if it's a live session
if jh.is_live():
jesse_logger.error(
'{}: {}'.format(args.exc_type.__name__, args.exc_value)
f'{args.exc_type.__name__}: { args.exc_value}'
)
if jh.is_live() or jh.is_collecting_data():
logging.error("Uncaught Exception:",
exc_info=(args.exc_type, args.exc_value, args.exc_traceback))
else:
print('=' * 30 + ' EXCEPTION TRACEBACK:')
print(f"{'=' * 30} EXCEPTION TRACEBACK:")
traceback.print_tb(args.exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
jh.color(f'{args.exc_type.__name__}: {args.exc_value}', 'yellow')
)
if jh.is_paper_trading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/paper-trade.txt'
),
'An uncaught exception was raised. Check the log file at:\nstorage/logs/paper-trade.txt',
'red'
)
)
elif jh.is_livetrading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/live-trade.txt'
),
'An uncaught exception was raised. Check the log file at:\nstorage/logs/live-trade.txt',
'red'
)
)
elif jh.is_collecting_data():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/collect.txt'
),
'An uncaught exception was raised. Check the log file at:\nstorage/logs/collect.txt',
'red'
)
)

View File

@@ -109,7 +109,7 @@ class Sandbox(Exchange):
o.cancel()
if not jh.is_unit_testing():
store.orders.storage['{}-{}'.format(self.name, symbol)].clear()
store.orders.storage[f'{self.name}-{symbol}'].clear()
def cancel_order(self, symbol, order_id):
"""

View File

@@ -90,7 +90,7 @@ def convert_number(old_max: float, old_min: float, new_max: float, new_min: floa
"""
# validation
if old_value > old_max or old_value < old_min:
raise ValueError('old_value:{} must be within the range. {}-{}'.format(old_value, old_min, old_max))
raise ValueError(f'old_value:{old_value} must be within the range. {old_min}-{old_max}')
old_range = (old_max - old_min)
new_range = (new_max - new_min)
@@ -104,7 +104,7 @@ def dashless_symbol(symbol: str) -> str:
def dashy_symbol(symbol: str) -> str:
return symbol[0:3] + '-' + symbol[3:]
return f"{symbol[0:3]}-{symbol[3:]}"
def date_diff_in_days(date1: arrow.arrow.Arrow, date2: arrow.arrow.Arrow) -> int:
@@ -275,9 +275,9 @@ def get_strategy_class(strategy_name: str):
from pydoc import locate
if is_unit_testing():
return locate('jesse.strategies.{}.{}'.format(strategy_name, strategy_name))
return locate(f'jesse.strategies.{strategy_name}.{strategy_name}')
else:
return locate('strategies.{}.{}'.format(strategy_name, strategy_name))
return locate(f'strategies.{strategy_name}.{strategy_name}')
def insecure_hash(msg: str) -> str:
@@ -357,9 +357,9 @@ def is_valid_uuid(uuid_to_test, version: int = 4) -> bool:
def key(exchange: str, symbol: str, timeframe: str = None):
if timeframe is None:
return '{}-{}'.format(exchange, symbol)
return f'{exchange}-{symbol}'
return '{}-{}-{}'.format(exchange, symbol, timeframe)
return f'{exchange}-{symbol}-{timeframe}'
def max_timeframe(timeframes_list: list) -> str:
@@ -524,7 +524,7 @@ def prepare_qty(qty: float, side: str) -> float:
def python_version() -> float:
return float('{}.{}'.format(sys.version_info[0], sys.version_info[1]))
return float(f'{sys.version_info[0]}.{sys.version_info[1]}')
def quote_asset(symbol: str) -> str:
@@ -532,7 +532,7 @@ def quote_asset(symbol: str) -> str:
return symbol.split('-')[1]
except IndexError:
from jesse.exceptions import InvalidRoutes
raise InvalidRoutes("The symbol format is incorrect. Correct example: 'BTC-USDT'. Yours is '{}'".format(symbol))
raise InvalidRoutes(f"The symbol format is incorrect. Correct example: 'BTC-USDT'. Yours is '{symbol}'")
def random_str(num_characters: int = 8) -> str:
@@ -557,7 +557,7 @@ def readable_duration(seconds: int, granularity: int = 2) -> str:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(value, name))
result.append(f"{value} {name}")
return ', '.join(result[:granularity])
@@ -695,8 +695,7 @@ def timeframe_to_one_minutes(timeframe: str) -> int:
return dic[timeframe]
except KeyError:
raise InvalidTimeframe(
'Timeframe "{}" is invalid. Supported timeframes are {}.'.format(
timeframe, ', '.join(all_timeframes)))
f'Timeframe "{timeframe}" is invalid. Supported timeframes are {", ".join(all_timeframes)}.')
def timestamp_to_arrow(timestamp: int) -> arrow.arrow.Arrow:

View File

@@ -21,7 +21,7 @@ def vwap(candles: np.ndarray, source_type: str = "hlc3", anchor: str = "D", sequ
source = get_candle_source(candles, source_type=source_type)
group_idx = candles[:, 0].astype('datetime64[ms]').astype('datetime64[{}]'.format(anchor)).astype('int')
group_idx = candles[:, 0].astype('datetime64[ms]').astype(f'datetime64[{anchor}]').astype('int')
vwap = aggregate(group_idx, candles[:, 5] * source, func='cumsum')
vwap /= aggregate(group_idx, candles[:, 5], func='cumsum')

View File

@@ -101,7 +101,7 @@ class CompletedTrade(peewee.Model):
@property
def fee(self) -> float:
trading_fee = jh.get_config('env.exchanges.{}.fee'.format(self.exchange))
trading_fee = jh.get_config(f'env.exchanges.{self.exchange}.fee')
return trading_fee * self.qty * (self.entry_price + self.exit_price)
@property

View File

@@ -69,7 +69,7 @@ class FuturesExchange(Exchange):
if asset == self.settlement_currency:
continue
position = selectors.get_position(self.name, asset + "-" + self.settlement_currency)
position = selectors.get_position(self.name, f"{asset}-{self.settlement_currency}")
if position is None:
continue
@@ -93,22 +93,13 @@ class FuturesExchange(Exchange):
fee_amount = abs(amount) * self.fee_rate
new_balance = self.assets[self.settlement_currency] - fee_amount
logger.info(
'Charged {} as fee. Balance for {} on {} changed from {} to {}'.format(
round(fee_amount, 2), self.settlement_currency, self.name,
round(self.assets[self.settlement_currency], 2),
round(new_balance, 2),
)
f'Charged {round(fee_amount, 2)} as fee. Balance for {self.settlement_currency} on {self.name} changed from {round(self.assets[self.settlement_currency], 2)} to {round(new_balance, 2)}'
)
self.assets[self.settlement_currency] = new_balance
def add_realized_pnl(self, realized_pnl: float):
new_balance = self.assets[self.settlement_currency] + realized_pnl
logger.info('Added realized PNL of {}. Balance for {} on {} changed from {} to {}'.format(
round(realized_pnl, 2),
self.settlement_currency, self.name,
round(self.assets[self.settlement_currency], 2),
round(new_balance, 2),
))
logger.info(f'Added realized PNL of {round(realized_pnl, 2)}. Balance for {self.settlement_currency} on {self.name} changed from {round(self.assets[self.settlement_currency], 2)} to {round(new_balance, 2)}')
self.assets[self.settlement_currency] = new_balance
def on_order_submission(self, order: Order, skip_market_order=True):
@@ -121,9 +112,7 @@ class FuturesExchange(Exchange):
remaining_margin = self.available_margin()
if order_size > remaining_margin:
raise InsufficientMargin(
'You cannot submit an order for ${} when your margin balance is ${}'.format(
round(order_size), round(remaining_margin)
))
f'You cannot submit an order for ${round(order_size)} when your margin balance is ${round(remaining_margin)}')
# skip market order at the time of submission because we don't have
# the exact order.price. Instead, we call on_order_submission() one

View File

@@ -53,11 +53,7 @@ class Order(Model):
self.notify_submission()
if jh.is_debuggable('order_submission'):
logger.info(
'{} order: {}, {}, {}, {}, ${}'.format(
'QUEUED' if self.is_queued else 'SUBMITTED',
self.symbol, self.type, self.side, self.qty,
round(self.price, 2)
)
f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
)
# handle exchange balance for ordered asset
@@ -66,11 +62,7 @@ class Order(Model):
def notify_submission(self) -> None:
notify(
'{} order: {}, {}, {}, {}, ${}'.format(
'QUEUED' if self.is_queued else 'SUBMITTED',
self.symbol, self.type, self.side, self.qty,
round(self.price, 2)
)
f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, { self.qty}, ${round(self.price, 2)}'
)
@property
@@ -121,15 +113,11 @@ class Order(Model):
if jh.is_debuggable('order_cancellation'):
logger.info(
'CANCELED order: {}, {}, {}, {}, ${}'.format(
self.symbol, self.type, self.side, self.qty, round(self.price, 2)
)
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, { self.qty}, ${round(self.price, 2)}'
)
if jh.is_live() and config['env']['notifications']['events']['cancelled_orders']:
notify(
'CANCELED order: {}, {}, {}, {}, {}'.format(
self.symbol, self.type, self.side, self.qty, round(self.price, 2)
)
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
)
# handle exchange balance
@@ -146,16 +134,12 @@ class Order(Model):
# log
if jh.is_debuggable('order_execution'):
logger.info(
'EXECUTED order: {}, {}, {}, {}, ${}'.format(
self.symbol, self.type, self.side, self.qty, round(self.price, 2)
)
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
)
# notify
if jh.is_live() and config['env']['notifications']['events']['executed_orders']:
notify(
'EXECUTED order: {}, {}, {}, {}, {}'.format(
self.symbol, self.type, self.side, self.qty, round(self.price, 2)
)
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
)
p = selectors.get_position(self.exchange, self.symbol)

View File

@@ -183,11 +183,7 @@ class Position:
self.closed_at = jh.now_to_timestamp()
if not jh.is_unit_testing():
info_text = 'CLOSED {} position: {}, {}, {}. PNL: ${}, Balance: ${}, entry: {}, exit: {}'.format(
trade_type, self.exchange_name, self.symbol, self.strategy.name,
round(estimated_profit, 2), jh.format_currency(round(self.exchange.wallet_balance(self.symbol), 2)),
entry, close_price
)
info_text = f'CLOSED {trade_type} position: {self.exchange_name}, {self.symbol}, {self.strategy.name}. PNL: ${round(estimated_profit, 2)}, Balance: ${jh.format_currency(round(self.exchange.wallet_balance(self.symbol), 2))}, entry: {entry}, exit: {close_price}'
if jh.is_debuggable('position_closed'):
logger.info(info_text)
@@ -214,9 +210,7 @@ class Position:
elif self.type == trade_types.SHORT:
self.qty = sum_floats(self.qty, qty)
info_text = 'REDUCED position: {}, {}, {}, {}, ${}'.format(
self.exchange_name, self.symbol, self.type, self.qty, round(self.entry_price, 2)
)
info_text = f'REDUCED position: {self.exchange_name}, {self.symbol}, {self.type}, {self.qty}, ${round(self.entry_price, 2)}'
if jh.is_debuggable('position_reduced'):
logger.info(info_text)
@@ -242,9 +236,7 @@ class Position:
elif self.type == trade_types.SHORT:
self.qty = subtract_floats(self.qty, qty)
info_text = 'INCREASED position: {}, {}, {}, {}, ${}'.format(
self.exchange_name, self.symbol, self.type, self.qty, round(self.entry_price, 2)
)
info_text = f'INCREASED position: {self.exchange_name}, {self.symbol}, {self.type}, {self.qty}, ${round(self.entry_price, 2)}'
if jh.is_debuggable('position_increased'):
logger.info(info_text)
@@ -261,9 +253,7 @@ class Position:
self.qty = qty
self.opened_at = jh.now_to_timestamp()
info_text = 'OPENED {} position: {}, {}, {}, ${}'.format(
self.type, self.exchange_name, self.symbol, self.qty, round(self.entry_price, 2)
)
info_text = f'OPENED {self.type} position: {self.exchange_name}, {self.symbol}, { self.qty}, ${round(self.entry_price, 2)}'
if jh.is_debuggable('position_opened'):
logger.info(info_text)
@@ -299,15 +289,11 @@ class Position:
if abs(qty) > abs(self.qty):
if order.is_reduce_only:
logger.info(
'Executed order is bigger than the current position size but it is a reduce_only order so it just closes it. Order QTY: {}, Position QTY: {}'.format(
qty, self.qty
))
f'Executed order is bigger than the current position size but it is a reduce_only order so it just closes it. Order QTY: {qty}, Position QTY: {self.qty}')
self._close(price)
else:
logger.info(
'Executed order is big enough to not close, but flip the position type. Order QTY: {}, Position QTY: {}'.format(
qty, self.qty
))
f'Executed order is big enough to not close, but flip the position type. Order QTY: {qty}, Position QTY: {self.qty}')
diff_qty = sum_floats(self.qty, qty)
self._close(price)
self._open(diff_qty, price)

View File

@@ -27,8 +27,7 @@ class SpotExchange(Exchange):
base_asset = jh.base_asset(route.symbol)
if base_asset not in self.available_assets:
raise InvalidConfig(
"Jesse needs to know the balance of your base asset for spot mode. Please add {} to your exchanges assets config.".format(
base_asset))
f"Jesse needs to know the balance of your base asset for spot mode. Please add {base_asset} to your exchanges assets config.")
def wallet_balance(self, symbol=''):
@@ -59,9 +58,7 @@ class SpotExchange(Exchange):
self.available_assets[quote_asset] -= (abs(order.qty) * order.price) * (1 + self.fee_rate)
if self.available_assets[quote_asset] < 0:
raise NegativeBalance(
"Balance cannot go below zero in spot market. Available capital at {} for {} is {} but you're trying to sell {}".format(
self.name, quote_asset, quote_balance, abs(order.qty * order.price)
)
f"Balance cannot go below zero in spot market. Available capital at {self.name} for {quote_asset} is {quote_balance} but you're trying to sell {abs(order.qty * order.price)}"
)
# sell order
else:
@@ -69,9 +66,7 @@ class SpotExchange(Exchange):
new_base_balance = base_balance + order.qty
if new_base_balance < 0:
raise NegativeBalance(
"Balance cannot go below zero in spot market. Available capital at {} for {} is {} but you're trying to sell {}".format(
self.name, base_asset, base_balance, abs(order.qty)
)
f"Balance cannot go below zero in spot market. Available capital at {self.name} for {base_asset} is {base_balance} but you're trying to sell {abs(order.qty)}"
)
self.available_assets[base_asset] -= abs(order.qty)
@@ -79,18 +74,12 @@ class SpotExchange(Exchange):
temp_new_quote_available_asset = self.available_assets[quote_asset]
if jh.is_debuggable('balance_update') and temp_old_quote_available_asset != temp_new_quote_available_asset:
logger.info(
'Available balance for {} on {} changed from {} to {}'.format(
quote_asset, self.name,
round(temp_old_quote_available_asset, 2), round(temp_new_quote_available_asset, 2)
)
f'Available balance for {quote_asset} on {self.name} changed from {round(temp_old_quote_available_asset, 2)} to {round(temp_new_quote_available_asset, 2)}'
)
temp_new_base_available_asset = self.available_assets[base_asset]
if jh.is_debuggable('balance_update') and temp_old_base_available_asset != temp_new_base_available_asset:
logger.info(
'Available balance for {} on {} changed from {} to {}'.format(
base_asset, self.name,
round(temp_old_base_available_asset, 2), round(temp_new_base_available_asset, 2)
)
f'Available balance for {base_asset} on {self.name} changed from {round(temp_old_base_available_asset, 2)} to {round(temp_new_base_available_asset, 2)}'
)
def on_order_execution(self, order: Order):
@@ -120,35 +109,23 @@ class SpotExchange(Exchange):
temp_new_quote_asset = self.assets[quote_asset]
if jh.is_debuggable('balance_update') and temp_old_quote_asset != temp_new_quote_asset:
logger.info(
'Balance for {} on {} changed from {} to {}'.format(
quote_asset, self.name,
round(temp_old_quote_asset, 2), round(temp_new_quote_asset, 2)
)
f'Balance for {quote_asset} on {self.name} changed from {round(temp_old_quote_asset, 2)} to { round(temp_new_quote_asset, 2)}'
)
temp_new_quote_available_asset = self.available_assets[quote_asset]
if jh.is_debuggable('balance_update') and temp_old_quote_available_asset != temp_new_quote_available_asset:
logger.info(
'Balance for {} on {} changed from {} to {}'.format(
quote_asset, self.name,
round(temp_old_quote_available_asset, 2), round(temp_new_quote_available_asset, 2)
)
f'Balance for {quote_asset} on {self.name} changed from {round(temp_old_quote_available_asset, 2)} to {round(temp_new_quote_available_asset, 2)}'
)
temp_new_base_asset = self.assets[base_asset]
if jh.is_debuggable('balance_update') and temp_old_base_asset != temp_new_base_asset:
logger.info(
'Balance for {} on {} changed from {} to {}'.format(
base_asset, self.name,
round(temp_old_base_asset, 2), round(temp_new_base_asset, 2)
)
f'Balance for {base_asset} on {self.name} changed from {round(temp_old_base_asset, 2)} to {round(temp_new_base_asset, 2)}'
)
temp_new_base_available_asset = self.available_assets[base_asset]
if jh.is_debuggable('balance_update') and temp_old_base_available_asset != temp_new_base_available_asset:
logger.info(
'Balance for {} on {} changed from {} to {}'.format(
base_asset, self.name,
round(temp_old_base_available_asset, 2), round(temp_new_base_available_asset, 2)
)
f'Balance for {base_asset} on {self.name} changed from {round(temp_old_base_available_asset, 2)} to {round(temp_new_base_available_asset, 2)}'
)
def on_order_cancellation(self, order: Order):
@@ -168,16 +145,10 @@ class SpotExchange(Exchange):
temp_new_quote_available_asset = self.available_assets[quote_asset]
if jh.is_debuggable('balance_update') and temp_old_quote_available_asset != temp_new_quote_available_asset:
logger.info(
'Available balance for {} on {} changed from {} to {}'.format(
quote_asset, self.name,
round(temp_old_quote_available_asset, 2), round(temp_new_quote_available_asset, 2)
)
f'Available balance for {quote_asset} on {self.name} changed from {round(temp_old_quote_available_asset, 2)} to {round(temp_new_quote_available_asset, 2)}'
)
temp_new_base_available_asset = self.available_assets[base_asset]
if jh.is_debuggable('balance_update') and temp_old_base_available_asset != temp_new_base_available_asset:
logger.info(
'Available balance for {} on {} changed from {} to {}'.format(
base_asset, self.name,
round(temp_old_base_available_asset, 2), round(temp_new_base_available_asset, 2)
)
f'Available balance for {base_asset} on {self.name} changed from {round(temp_old_base_available_asset, 2)} to {round(temp_new_base_available_asset, 2)}'
)

View File

@@ -30,7 +30,7 @@ def store_candle_into_db(exchange: str, symbol: str, candle: np.ndarray) -> None
Candle.insert(**d).on_conflict_ignore().execute()
print(
jh.color(
'candle: {}-{}-{}: {}'.format(jh.timestamp_to_time(d['timestamp']), exchange, symbol, candle),
f"candle: {jh.timestamp_to_time(d['timestamp'])}-{exchange}-{symbol}: {candle}",
'blue'
)
)
@@ -54,9 +54,7 @@ def store_ticker_into_db(exchange: str, symbol: str, ticker: np.ndarray) -> None
def async_save() -> None:
Ticker.insert(**d).on_conflict_ignore().execute()
print(
jh.color('ticker: {}-{}-{}: {}'.format(
jh.timestamp_to_time(d['timestamp']), exchange, symbol, ticker
), 'yellow')
jh.color(f'ticker: {jh.timestamp_to_time(d["timestamp"])}-{exchange}-{symbol}: {ticker}', 'yellow')
)
# async call
@@ -86,9 +84,7 @@ def store_completed_trade_into_db(completed_trade: CompletedTrade) -> None:
def async_save() -> None:
CompletedTrade.insert(**d).execute()
if jh.is_debugging():
logger.info('Stored the completed trade record for {}-{}-{} into database.'.format(
completed_trade.exchange, completed_trade.symbol, completed_trade.strategy_name
))
logger.info(f'Stored the completed trade record for {completed_trade.exchange}-{completed_trade.symbol}-{completed_trade.strategy_name} into database.')
# async call
threading.Thread(target=async_save).start()
@@ -117,9 +113,7 @@ def store_order_into_db(order: Order) -> None:
def async_save() -> None:
Order.insert(**d).execute()
if jh.is_debugging():
logger.info('Stored the executed order record for {}-{} into database.'.format(
order.exchange, order.symbol
))
logger.info(f'Stored the executed order record for {order.exchange}-{order.symbol} into database.')
# async call
threading.Thread(target=async_save).start()
@@ -129,9 +123,8 @@ def store_daily_balance_into_db(daily_balance: dict) -> None:
def async_save():
DailyBalance.insert(**daily_balance).execute()
if jh.is_debugging():
logger.info('Stored daily portfolio balance record into the database: {} => {}'.format(
daily_balance['asset'], jh.format_currency(round(daily_balance['balance'], 2))
))
logger.info(f'Stored daily portfolio balance record into the database: {daily_balance["asset"]} => {jh.format_currency(round(daily_balance["balance"], 2))}'
)
# async call
threading.Thread(target=async_save).start()
@@ -154,9 +147,7 @@ def store_trade_into_db(exchange: str, symbol: str, trade: np.ndarray) -> None:
Trade.insert(**d).on_conflict_ignore().execute()
print(
jh.color(
'trade: {}-{}-{}: {}'.format(
jh.timestamp_to_time(d['timestamp']), exchange, symbol, trade
),
f'trade: {jh.timestamp_to_time(d["timestamp"])}-{exchange}-{symbol}: {trade}',
'green'
)
)
@@ -178,13 +169,7 @@ def store_orderbook_into_db(exchange: str, symbol: str, orderbook: np.ndarray) -
Orderbook.insert(**d).on_conflict_ignore().execute()
print(
jh.color(
'orderbook: {}-{}-{}: [{}, {}], [{}, {}]'.format(
jh.timestamp_to_time(d['timestamp']), exchange, symbol,
# best ask
orderbook[0][0][0], orderbook[0][0][1],
# best bid
orderbook[1][0][0], orderbook[1][0][1]
),
f'orderbook: {jh.timestamp_to_time(d["timestamp"])}-{exchange}-{symbol}: [{orderbook[0][0][0]}, {orderbook[0][0][1]}], [{orderbook[1][0][0]}, {orderbook[1][0][1]}]',
'magenta'
)
)

View File

@@ -47,7 +47,7 @@ def run(start_date: str, finish_date: str, candles: Dict[str, Dict[str, Union[st
if not jh.should_execute_silently():
# print candles table
key = '{}-{}'.format(config['app']['considering_candles'][0][0], config['app']['considering_candles'][0][1])
key = f"{config['app']['considering_candles'][0][0]}-{config['app']['considering_candles'][0][1]}"
table.key_value(stats.candles(candles[key]['candles']), 'candles', alignments=('left', 'right'))
print('\n')
@@ -90,7 +90,7 @@ def run(start_date: str, finish_date: str, candles: Dict[str, Dict[str, Union[st
change.append(((last.close - first.close) / first.close) * 100.0)
data = report.portfolio_metrics()
data.append(['Market Change', str(round(np.average(change), 2)) + "%"])
data.append(['Market Change', f"{str(round(np.average(change), 2))}%"])
print('\n')
table.key_value(data, 'Metrics', alignments=('left', 'right'))
print('\n')
@@ -136,7 +136,7 @@ def load_candles(start_date_str: str, finish_date_str: str) -> Dict[str, Dict[st
key = jh.key(exchange, symbol)
cache_key = '{}-{}-'.format(start_date_str, finish_date_str) + key
cache_key = f"{start_date_str}-{finish_date_str}-{key}"
cached_value = cache.get_value(cache_key)
# if cache exists
if cached_value:
@@ -157,11 +157,9 @@ def load_candles(start_date_str: str, finish_date_str: str) -> Dict[str, Dict[st
required_candles_count = (finish_date - start_date) / 60_000
if len(candles_tuple) == 0 or candles_tuple[-1][0] != finish_date or candles_tuple[0][0] != start_date:
raise exceptions.CandleNotFoundInDatabase(
'Not enough candles for {}. Try running "jesse import-candles"'.format(symbol))
f'Not enough candles for {symbol}. Try running "jesse import-candles"')
elif len(candles_tuple) != required_candles_count + 1:
raise exceptions.CandleNotFoundInDatabase('There are missing candles between {} => {}'.format(
start_date_str, finish_date_str
))
raise exceptions.CandleNotFoundInDatabase(f'There are missing candles between {start_date_str} => {finish_date_str}')
# cache it for near future calls
cache.set_value(cache_key, tuple(candles_tuple), expire_seconds=60 * 60 * 24 * 7)
@@ -177,7 +175,7 @@ def load_candles(start_date_str: str, finish_date_str: str) -> Dict[str, Dict[st
def simulator(candles: Dict[str, Dict[str, Union[str, np.ndarray]]], hyperparameters=None) -> None:
begin_time_track = time.time()
key = '{}-{}'.format(config['app']['considering_candles'][0][0], config['app']['considering_candles'][0][1])
key = f"{config['app']['considering_candles'][0][0]}-{config['app']['considering_candles'][0][1]}"
first_candles_set = candles[key]['candles']
length = len(first_candles_set)
# to preset the array size for performance
@@ -289,7 +287,7 @@ def simulator(candles: Dict[str, Dict[str, Union[str, np.ndarray]]], hyperparame
# print executed time for the backtest session
finish_time_track = time.time()
print('Executed backtest simulation in: ', '{} seconds'.format(round(finish_time_track - begin_time_track, 2)))
print('Executed backtest simulation in: ', f'{round(finish_time_track - begin_time_track, 2)} seconds')
for r in router.routes:
r.strategy._terminate()

View File

@@ -39,14 +39,13 @@ def run(exchange: str, symbol: str, start_date_str: str, skip_confirmation: bool
try:
driver: CandleExchange = drivers[exchange]()
except KeyError:
raise ValueError('{} is not a supported exchange'.format(exchange))
raise ValueError(f'{exchange} is not a supported exchange')
loop_length = int(candles_count / driver.count) + 1
# ask for confirmation
if not skip_confirmation:
click.confirm(
'Importing {} days candles from "{}" for "{}". Duplicates will be skipped. All good?'
.format(days_count, exchange, symbol), abort=True, default=True)
f'Importing {days_count} days candles from "{exchange}" for "{symbol}". Duplicates will be skipped. All good?', abort=True, default=True)
with click.progressbar(length=loop_length, label='Importing candles...') as progressbar:
for _ in range(candles_count):
@@ -80,10 +79,8 @@ def run(exchange: str, symbol: str, start_date_str: str, skip_confirmation: bool
# if driver can't provide accurate get_starting_time()
if first_existing_timestamp is None:
raise CandleNotFoundInExchange(
'No candles exists in the market for this day: {} \n'
'Try another start_date'.format(
jh.timestamp_to_time(temp_start_timestamp)[:10],
)
f'No candles exists in the market for this day: {jh.timestamp_to_time(temp_start_timestamp)[:10]} \n'
'Try another start_date'
)
# handle when there's missing candles during the period
@@ -98,13 +95,9 @@ def run(exchange: str, symbol: str, start_date_str: str, skip_confirmation: bool
else:
if not skip_confirmation:
print(jh.color('No candle exists in the market for {}\n'.format(
jh.timestamp_to_time(temp_start_timestamp)[:10]), 'yellow'))
print(jh.color(f'No candle exists in the market for {jh.timestamp_to_time(temp_start_timestamp)[:10]}\n', 'yellow'))
click.confirm(
'First present candle is since {}. Would you like to continue?'.format(
jh.timestamp_to_time(first_existing_timestamp)[:10]
)
, abort=True, default=True)
f'First present candle is since {jh.timestamp_to_time(first_existing_timestamp)[:10]}. Would you like to continue?', abort=True, default=True)
run(exchange, symbol, jh.timestamp_to_time(first_existing_timestamp)[:10], True)
return
@@ -192,10 +185,8 @@ def _get_candles_from_backup_exchange(exchange: str, backup_driver: CandleExchan
if not len(candles):
raise CandleNotFoundInExchange(
'No candles exists in the market for this day: {} \n'
'Try another start_date'.format(
jh.timestamp_to_time(temp_start_timestamp)[:10],
)
f'No candles exists in the market for this day: {jh.timestamp_to_time(temp_start_timestamp)[:10]} \n'
'Try another start_date'
)
# fill absent candles (if there's any)
@@ -244,10 +235,8 @@ def _fill_absent_candles(temp_candles: List[Dict[str, Union[str, Any]]], start_t
List[Dict[str, Union[str, Any]]]:
if len(temp_candles) == 0:
raise CandleNotFoundInExchange(
'No candles exists in the market for this day: {} \n'
'Try another start_date'.format(
jh.timestamp_to_time(start_timestamp)[:10],
)
f'No candles exists in the market for this day: {jh.timestamp_to_time(start_timestamp)[:10]} \n'
'Try another start_date'
)
symbol = temp_candles[0]['symbol']

View File

@@ -28,7 +28,7 @@ class Bitfinex(CandleExchange):
'limit': 5000,
}
response = requests.get(self.endpoint + '/trade:1D:t{}/hist'.format(dashless_symbol), params=payload)
response = requests.get(f"{self.endpoint}/trade:1D:t{dashless_symbol}/hist", params=payload)
if response.status_code != 200:
raise Exception(response.content)
@@ -38,7 +38,7 @@ class Bitfinex(CandleExchange):
# wrong symbol entered
if not len(data):
raise exceptions.SymbolNotFound(
"No candle exists for {} in Bitfinex. You're probably misspelling the symbol name.".format(symbol)
f"No candle exists for {symbol} in Bitfinex. You're probably misspelling the symbol name."
)
first_timestamp = int(data[0][0])
@@ -61,7 +61,7 @@ class Bitfinex(CandleExchange):
dashless_symbol = jh.dashless_symbol(symbol)
response = requests.get(
self.endpoint + '/trade:1m:t{}/hist'.format(dashless_symbol),
f"{self.endpoint}/trade:1m:t{dashless_symbol}/hist",
params=payload
)

View File

@@ -45,7 +45,7 @@ class Coinbase(CandleExchange):
}
response = requests.get(
self.endpoint + '/{}/candles'.format(symbol),
f"{self.endpoint}/{symbol}/candles",
params=payload
)

View File

@@ -48,10 +48,7 @@ class Genetics(ABC):
self.options = options
os.makedirs('./storage/temp/optimize', exist_ok=True)
self.temp_path = './storage/temp/optimize/{}-{}-{}-{}.pickle'.format(
self.options['strategy_name'], self.options['exchange'],
self.options['symbol'], self.options['timeframe']
)
self.temp_path = f"./storage/temp/optimize/{self.options['strategy_name']}-{self.options['exchange']}-{self.options['symbol']}-{self.options['timeframe']}.pickle"
if fitness_goal > 1 or fitness_goal < 0:
raise ValueError('fitness scores must be between 0 and 1')
@@ -87,7 +84,7 @@ class Genetics(ABC):
dna_bucket.append((dna, fitness_score, fitness_log_training, fitness_log_testing))
except Exception as e:
proc = os.getpid()
logger.error('process failed - ID: {}'.format(str(proc)))
logger.error(f'process failed - ID: {str(proc)}')
logger.error("".join(traceback.TracebackException.from_exception(e).format()))
raise e
@@ -102,7 +99,7 @@ class Genetics(ABC):
for w in workers:
w.join()
if w.exitcode > 0:
logger.error('a process exited with exitcode: {}'.format(str(w.exitcode)))
logger.error(f'a process exited with exitcode: {str(w.exitcode)}')
except KeyboardInterrupt:
print(
jh.color('Terminating session...', 'red')
@@ -135,12 +132,9 @@ class Genetics(ABC):
table_items = [
['Started at', jh.timestamp_to_arrow(self.start_time).humanize()],
['Index', '{}/{}'.format(len(self.population), self.population_size)],
['errors/info', '{}/{}'.format(len(store.logs.errors), len(store.logs.info))],
['Trading Route', '{}, {}, {}, {}'.format(
router.routes[0].exchange, router.routes[0].symbol, router.routes[0].timeframe,
router.routes[0].strategy_name
)],
['Index', f'{len(self.population)}/{self.population_size}'],
['errors/info', f'{len(store.logs.errors)}/{len(store.logs.info)}'],
['Trading Route', f'{router.routes[0].exchange}, {router.routes[0].symbol}, {router.routes[0].timeframe}, {router.routes[0].strategy_name}'],
# TODO: add generated DNAs?
# ['-'*10, '-'*10],
# ['DNA', people[0]['dna']],
@@ -169,7 +163,7 @@ class Genetics(ABC):
def mutate(self, baby: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any]]:
replace_at = randint(0, self.solution_len - 1)
replace_with = choice(self.charset)
dna = '{}{}{}'.format(baby['dna'][:replace_at], replace_with, baby['dna'][replace_at + 1:])
dna = f"{baby['dna'][:replace_at]}{replace_with}{baby['dna'][replace_at + 1:]}"
fitness_score, fitness_log_training, fitness_log_testing = self.fitness(dna)
return {
'dna': dna,
@@ -243,7 +237,7 @@ class Genetics(ABC):
people.append(baby)
except Exception as e:
proc = os.getpid()
logger.error('process failed - ID: {}'.format(str(proc)))
logger.error(f'process failed - ID: {str(proc)}')
logger.error("".join(traceback.TracebackException.from_exception(e).format()))
raise e
@@ -256,7 +250,7 @@ class Genetics(ABC):
for w in workers:
w.join()
if w.exitcode > 0:
logger.error('a process exited with exitcode: {}'.format(str(w.exitcode)))
logger.error(f'a process exited with exitcode: {str(w.exitcode)}')
except KeyboardInterrupt:
print(
jh.color('Terminating session...', 'red')
@@ -281,18 +275,15 @@ class Genetics(ABC):
table_items = [
['Started At', jh.timestamp_to_arrow(self.start_time).humanize()],
['Index/Total', '{}/{}'.format((i + 1) * self.cpu_cores, self.iterations)],
['errors/info', '{}/{}'.format(len(store.logs.errors), len(store.logs.info))],
['Route', '{}, {}, {}, {}'.format(
router.routes[0].exchange, router.routes[0].symbol, router.routes[0].timeframe,
router.routes[0].strategy_name
)]
['Index/Total', f'{(i + 1) * self.cpu_cores}/{self.iterations}'],
['errors/info', f'{len(store.logs.errors)}/{len(store.logs.info)}'],
['Route', f'{router.routes[0].exchange}, {router.routes[0].symbol}, {router.routes[0].timeframe}, {router.routes[0].strategy_name}']
]
if jh.is_debugging():
table_items.insert(
3,
['Population Size, Solution Length',
'{}, {}'.format(self.population_size, self.solution_len)]
f'{self.population_size}, {self.solution_len}']
)
table.key_value(table_items, 'info', alignments=('left', 'right'))
@@ -321,10 +312,7 @@ class Genetics(ABC):
raise ValueError('self.population_size cannot be less than 10')
for j in range(number_of_ind_to_show):
log = 'win-rate: {}%, total: {}, PNL: {}% || win-rate: {}%, total: {}, PNL: {}%'.format(
self.population[j]['training_log']['win-rate'], self.population[j]['training_log']['total'],
self.population[j]['training_log']['PNL'], self.population[j]['testing_log']['win-rate'],
self.population[j]['testing_log']['total'], self.population[j]['testing_log']['PNL'])
log = f"win-rate: {self.population[j]['training_log']['win-rate']}%, total: {self.population[j]['training_log']['total']}, PNL: {self.population[j]['training_log']['PNL']}% || win-rate: {self.population[j]['testing_log']['win-rate']}%, total: {self.population[j]['testing_log']['total']}, PNL: {self.population[j]['testing_log']['PNL']}%"
if self.population[j]['testing_log']['PNL'] is not None and self.population[j]['training_log'][
'PNL'] > 0 and self.population[j]['testing_log'][
'PNL'] > 0:
@@ -359,8 +347,8 @@ class Genetics(ABC):
self.population[random_index] = baby
except IndexError:
print('=============')
print('self.population_size: {}'.format(self.population_size))
print('self.population length: {}'.format(len(self.population)))
print(f'self.population_size: {self.population_size}')
print(f'self.population length: {len(self.population)}')
jh.terminate_app()
self.population = list(sorted(self.population, key=lambda x: x['fitness'], reverse=True))
@@ -369,7 +357,7 @@ class Genetics(ABC):
if baby['fitness'] >= self.fitness_goal:
progressbar.update(self.iterations - i)
print('\n')
print('fitness goal reached after iteration {}'.format(i))
print(f'fitness goal reached after iteration {i}')
return baby
# save progress after every n iterations
@@ -383,7 +371,7 @@ class Genetics(ABC):
i += 1
print('\n\n')
print('Finished {} iterations.'.format(self.iterations))
print(f'Finished {self.iterations} iterations.')
return self.population
def run(self) -> List[Any]:
@@ -427,10 +415,7 @@ class Genetics(ABC):
"""
stores a snapshot of the fittest population members into a file.
"""
study_name = '{}-{}-{}-{}'.format(
self.options['strategy_name'], self.options['exchange'],
self.options['symbol'], self.options['timeframe']
)
study_name = f"{self.options['strategy_name']}-{self.options['exchange']}-{ self.options['symbol']}-{self.options['timeframe']}"
dnas_json = {'snapshot': []}
for i in range(30):
@@ -439,29 +424,24 @@ class Genetics(ABC):
'training_log': self.population[i]['training_log'], 'testing_log': self.population[i]['testing_log'],
'parameters': jh.dna_to_hp(self.options['strategy_hp'], self.population[i]['dna'])})
path = './storage/genetics/{}.txt'.format(study_name)
path = f'./storage/genetics/{study_name}.txt'
os.makedirs('./storage/genetics', exist_ok=True)
txt = ''
with open(path, 'a', encoding="utf-8") as f:
txt += '\n\n'
txt += '# iteration {}'.format(index)
txt += f'# iteration {index}'
txt += '\n'
for i in range(30):
log = 'win-rate: {} %, total: {}, PNL: {} % || win-rate: {} %, total: {}, PNL: {} %'.format(
self.population[i]['training_log']['win-rate'], self.population[i]['training_log']['total'],
self.population[i]['training_log']['PNL'], self.population[i]['testing_log']['win-rate'],
self.population[i]['testing_log']['total'], self.population[i]['testing_log']['PNL'])
log = f"win-rate: {self.population[i]['training_log']['win-rate']} %, total: {self.population[i]['training_log']['total']}, PNL: {self.population[i]['training_log']['PNL']} % || win-rate: {self.population[i]['testing_log']['win-rate']} %, total: {self.population[i]['testing_log']['total']}, PNL: {self.population[i]['testing_log']['PNL']} %"
txt += '\n'
txt += "{} == {} == {} == {}".format(
i + 1, self.population[i]['dna'], self.population[i]['fitness'], log
)
txt += f"{i + 1} == {self.population[i]['dna']} == {self.population[i]['fitness']} == {log}"
f.write(txt)
if self.options['csv']:
path = 'storage/genetics/csv/{}.csv'.format(study_name)
path = f'storage/genetics/csv/{study_name}.csv'
os.makedirs('./storage/genetics/csv', exist_ok=True)
exists = os.path.exists(path)
@@ -475,7 +455,7 @@ class Genetics(ABC):
df.to_csv(outfile, header=False, index=False, encoding='utf-8')
if self.options['json']:
path = 'storage/genetics/json/{}.json'.format(study_name)
path = f'storage/genetics/json/{study_name}.json'
os.makedirs('./storage/genetics/json', exist_ok=True)
exists = os.path.exists(path)

View File

@@ -55,9 +55,7 @@ class Optimizer(Genetics):
)
if cpu_cores > cpu_count():
raise ValueError('Entered cpu cores number is more than available on this machine which is {}'.format(
cpu_count()
))
raise ValueError(f'Entered cpu cores number is more than available on this machine which is {cpu_count()}')
elif cpu_cores == 0:
self.cpu_cores = cpu_count()
else:
@@ -128,8 +126,7 @@ class Optimizer(Genetics):
ratio_normalized = jh.normalize(ratio, -.5, 5)
else:
raise ValueError(
'The entered ratio configuration `{}` for the optimization is unknown. Choose between sharpe, calmar, sortino and omega.'.format(
ratio_config))
f'The entered ratio configuration `{ratio_config}` for the optimization is unknown. Choose between sharpe, calmar, sortino and omega.')
if ratio < 0:
score = 0.0001

View File

@@ -8,7 +8,7 @@ def run(dna: bool = False) -> None:
arr = []
if not dna:
print(
jh.color('{}{}{}'.format('#' * 25, ' Trading Routes ', '#' * 25), 'blue')
jh.color(f"{'#' * 25} Trading Routes {'#' * 25}", 'blue')
)
arr.append(('exchange', 'symbol', 'timeframe', 'strategy name', 'DNA'))
else:
@@ -36,7 +36,7 @@ def run(dna: bool = False) -> None:
# extra_candles
if not dna:
print(
jh.color('{}{}{}'.format('#' * 25, ' Extra Candles ', '#' * 25), 'blue')
jh.color(f"{'#' * 25} Extra Candles {'#' * 25}", 'blue')
)
arr = [('exchange', 'symbol', 'timeframe')]

View File

@@ -30,4 +30,4 @@ def save_daily_portfolio_balance() -> None:
total = sum(balances)
store.app.daily_balance.append(total)
logger.info('Saved daily portfolio balance: {}'.format(round(total, 2)))
logger.info(f'Saved daily portfolio balance: {round(total, 2)}')

View File

@@ -11,8 +11,7 @@ def init() -> None:
if jh.python_version() < 3.6:
print(
jh.color(
'Jesse has not beed tested with your Python version ({}), hence it may not work properly. Consider upgrading to >= 3.7'.format(
jh.python_version()),
f'Jesse has not beed tested with your Python version ({jh.python_version()}), hence it may not work properly. Consider upgrading to >= 3.7',
'red'
)
)

View File

@@ -47,7 +47,7 @@ def get_candles(exchange: str, symbol: str, timeframe: str, start_date: str, fin
# validate that there are enough candles for selected period
if len(candles) == 0 or candles[-1][0] != finish_date or candles[0][0] != start_date:
raise Breaker('Not enough candles for {}. Try running "jesse import-candles"'.format(symbol))
raise Breaker(f'Not enough candles for {symbol}. Try running "jesse import-candles"')
if timeframe == '1m':
return candles

View File

@@ -27,21 +27,20 @@ class RouterClass:
# validate strategy
strategy_name = r[3]
if jh.is_unit_testing():
exists = jh.file_exists(sys.path[0] + '/jesse/strategies/{}/__init__.py'.format(strategy_name))
exists = jh.file_exists(f"{sys.path[0]}/jesse/strategies/{strategy_name}/__init__.py")
else:
exists = jh.file_exists('strategies/{}/__init__.py'.format(strategy_name))
exists = jh.file_exists(f'strategies/{strategy_name}/__init__.py')
if not exists:
raise exceptions.InvalidRoutes(
'A strategy with the name of "{}" could not be found.'.format(r[3]))
f'A strategy with the name of "{r[3]}" could not be found.')
# validate timeframe
route_timeframe = r[2]
all_timeframes = [timeframe for timeframe in jh.class_iter(timeframes)]
if route_timeframe not in all_timeframes:
raise exceptions.InvalidRoutes(
'Timeframe "{}" is invalid. Supported timeframes are {}'.format(
route_timeframe, ', '.join(all_timeframes))
f'Timeframe "{route_timeframe}" is invalid. Supported timeframes are {", ".join(all_timeframes)}'
)
self.routes.append(Route(*r))

View File

@@ -15,7 +15,7 @@ class API:
for e in jh.get_config('app.considering_exchanges'):
if jh.is_live():
def initiate_ws(exchange_name: str) -> None:
exchange_class = jh.get_config('app.live_drivers.{}'.format(exchange_name))
exchange_class = jh.get_config(f'app.live_drivers.{exchange_name}')
self.drivers[exchange_name] = exchange_class()
threading.Thread(target=initiate_ws, args=[e]).start()

View File

@@ -137,17 +137,11 @@ class Broker:
if side == 'buy' and price < self.position.current_price:
raise OrderNotAllowed(
'A buy start_profit({}) order must have a price higher than current_price({}).'.format(
price,
self.position.current_price
)
f'A buy start_profit({price}) order must have a price higher than current_price({self.position.current_price}).'
)
if side == 'sell' and price > self.position.current_price:
raise OrderNotAllowed(
'A sell start_profit({}) order must have a price lower than current_price({}).'.format(
price,
self.position.current_price
)
f'A sell start_profit({price}) order must have a price lower than current_price({self.position.current_price}).'
)
return self.api.stop_order(
@@ -176,17 +170,11 @@ class Broker:
if side == 'buy' and price < self.position.current_price:
raise OrderNotAllowed(
'Cannot submit a buy stop at {} when current price is {}'.format(
price,
self.position.current_price
)
f'Cannot submit a buy stop at {price} when current price is {self.position.current_price}'
)
if side == 'sell' and price > self.position.current_price:
raise OrderNotAllowed(
'Cannot submit a sell stop at {} when current price is {}.'.format(
price,
self.position.current_price
)
f'Cannot submit a sell stop at {price} when current price is {self.position.current_price}.'
)
return self.api.stop_order(

View File

@@ -16,8 +16,8 @@ class Cache:
os.makedirs(path, exist_ok=True)
# if cache_database exists, load the dictionary
if os.path.isfile(self.path + "cache_database.pickle"):
with open(self.path + "cache_database.pickle", 'rb') as f:
if os.path.isfile(f"{self.path}cache_database.pickle"):
with open(f"{self.path}cache_database.pickle", 'rb') as f:
self.db = pickle.load(f)
# if not, create a dict object. We'll create the file when using set_value()
else:
@@ -29,7 +29,7 @@ class Cache:
# add record into the database
expire_at = None if expire_seconds is None else time() + expire_seconds
data_path = self.path + "{}.pickle".format(key)
data_path = f"{self.path}{key}.pickle"
self.db[key] = {
'expire_seconds': expire_seconds,
'expire_at': expire_at,
@@ -67,7 +67,7 @@ class Cache:
def _update_db(self) -> None:
# store/update database
with open(self.path + "cache_database.pickle", 'wb') as f:
with open(f"{self.path}cache_database.pickle", 'wb') as f:
pickle.dump(self.db, f, protocol=pickle.HIGHEST_PROTOCOL)
def flush(self) -> None:

View File

@@ -13,10 +13,7 @@ def generate_candle_from_one_minutes(timeframe: str,
if not accept_forming_candles and len(candles) != jh.timeframe_to_one_minutes(timeframe):
raise ValueError(
'Sent only {} candles but {} is required to create a "{}" candle.'.format(
len(candles), jh.timeframe_to_one_minutes(timeframe),
timeframe
)
f'Sent only {len(candles)} candles but {jh.timeframe_to_one_minutes(timeframe)} is required to create a "{timeframe}" candle.'
)
return np.array([
@@ -43,14 +40,10 @@ def print_candle(candle: np.ndarray, is_partial: bool, symbol: str) -> None:
candle_form = click.style('====', bg='red')
if is_bullish(candle):
candle_info = click.style(' {} | {} | {} | {} | {} | {} | {}'.format(
symbol, str(arrow.get(candle[0] / 1000))[:-9], candle[1], candle[2],
candle[3], candle[4], round(candle[5], 2)),
candle_info = click.style(f' {symbol} | {str(arrow.get(candle[0] / 1000))[:-9]} | {candle[1]} | {candle[2]} | {candle[3]} | {candle[4]} | {round(candle[5], 2)}',
fg='green')
else:
candle_info = click.style(' {} | {} | {} | {} | {} | {} | {}'.format(
symbol, str(arrow.get(candle[0] / 1000))[:-9], candle[1], candle[2],
candle[3], candle[4], round(candle[5], 2)),
candle_info = click.style(f' {symbol} | {str(arrow.get(candle[0] / 1000))[:-9]} | {candle[1]} | {candle[2]} | {candle[3]} | {candle[4]} | {round(candle[5], 2)}',
fg='red')
print(candle_form + candle_info)

View File

@@ -114,9 +114,7 @@ def portfolio_vs_asset_returns() -> None:
# make sure directories exist
os.makedirs('./storage/charts', exist_ok=True)
file_path = 'storage/charts/{}-{}.png'.format(
mode, str(arrow.utcnow())[0:19]
).replace(":", "-")
file_path = f'storage/charts/{mode}-{str(arrow.utcnow())[0:19]}.png'.replace(":", "-")
plt.savefig(file_path)
print('\nChart output saved at:\n{}'.format(file_path))
print(f'\nChart output saved at:\n{file_path}')

View File

@@ -13,8 +13,8 @@ def store_logs(export_json: bool = False, export_tradingview: bool = False, expo
mode = config['app']['trading_mode']
now = str(arrow.utcnow())[0:19]
study_name = '{}-{}'.format(mode, now).replace(":", "-")
path = 'storage/json/{}.json'.format(study_name)
study_name = f'{mode}-{now}'.replace(":", "-")
path = f'storage/json/{study_name}.json'
trades_json = {'trades': [], 'considering_timeframes': config['app']['considering_timeframes']}
for t in store.completed_trades.trades:
trades_json['trades'].append(t.toJSON())
@@ -29,7 +29,7 @@ def store_logs(export_json: bool = False, export_tradingview: bool = False, expo
json.dump(trades_json, outfile, default=set_default)
print('\nJSON output saved at: \n{}'.format(path))
print(f'\nJSON output saved at: \n{path}')
# store output for TradingView.com's pine-editor
if export_tradingview:
@@ -37,7 +37,7 @@ def store_logs(export_json: bool = False, export_tradingview: bool = False, expo
# also write a CSV file
if export_csv:
path = 'storage/csv/{}.csv'.format(study_name)
path = f'storage/csv/{study_name}.csv'
os.makedirs('./storage/csv', exist_ok=True)
with open(path, 'w', newline='') as outfile:
@@ -50,4 +50,4 @@ def store_logs(export_json: bool = False, export_tradingview: bool = False, expo
wr.writerow(t.values())
print('\nCSV output saved at: \n{}'.format(path))
print(f'\nCSV output saved at: \n{path}')

View File

@@ -8,10 +8,10 @@ def info(msg: str) -> None:
store.logs.info.append({'time': jh.now_to_timestamp(), 'message': msg})
if (jh.is_backtesting() and jh.is_debugging()) or jh.is_collecting_data():
print('[{}]: {}'.format(jh.timestamp_to_time(jh.now_to_timestamp()), msg))
print(f'[{jh.timestamp_to_time(jh.now_to_timestamp())}]: {msg}')
if jh.is_live():
msg = '[INFO | {}] '.format(jh.timestamp_to_time(jh.now_to_timestamp())[:19]) + str(msg)
msg = f"[INFO | {jh.timestamp_to_time(jh.now_to_timestamp())[:19]}] {str(msg)}"
import logging
logging.info(msg)
@@ -20,14 +20,14 @@ def error(msg: str) -> None:
from jesse.store import store
if jh.is_live() and jh.get_config('env.notifications.events.errors', True):
notify_urgently('ERROR at "{}" account:\n{}'.format(jh.get_config('env.identifier'), msg))
notify('ERROR:\n{}'.format(msg))
notify_urgently(f"ERROR at \"{jh.get_config('env.identifier')}\" account:\n{msg}")
notify(f'ERROR:\n{msg}')
if (jh.is_backtesting() and jh.is_debugging()) or jh.is_collecting_data():
print(jh.color('[{}]: {}'.format(jh.timestamp_to_time(jh.now_to_timestamp()), msg), 'red'))
print(jh.color(f'[{jh.timestamp_to_time(jh.now_to_timestamp())}]: {msg}', 'red'))
store.logs.errors.append({'time': jh.now_to_timestamp(), 'message': msg})
if jh.is_live() or jh.is_optimizing():
msg = '[ERROR | {}] '.format(jh.timestamp_to_time(jh.now_to_timestamp())[:19]) + str(msg)
msg = f"[ERROR | {jh.timestamp_to_time(jh.now_to_timestamp())[:19]}] {str(msg)}"
import logging
logging.error(msg)

View File

@@ -13,16 +13,15 @@ def candles(candles_array: np.ndarray) -> List[List[str]]:
jh.timestamp_to_arrow(candles_array[-1][0])) + 1
if period > 365:
duration = '{} days ({} years)'.format(period, round(period / 365, 2))
duration = f'{period} days ({round(period / 365, 2)} years)'
elif period > 30:
duration = '{} days ({} months)'.format(period, round(period / 30, 2))
duration = f'{period} days ({round(period / 30, 2)} months)'
else:
duration = '{} days'.format(period)
duration = f'{period} days'
return [
['period', duration],
['starting-ending date', '{} => {}'.format(jh.timestamp_to_time(candles_array[0][0])[:10],
jh.timestamp_to_time(candles_array[-1][0] + 60_000)[:10])],
['starting-ending date', f'{jh.timestamp_to_time(candles_array[0][0])[:10]} => {jh.timestamp_to_time(candles_array[-1][0] + 60_000)[:10]}'],
]

View File

@@ -29,9 +29,7 @@ def _telegram(msg: str) -> None:
for id in chat_IDs:
requests.get(
'https://api.telegram.org/bot{}/sendMessage?chat_id={}&parse_mode=Markdown&text={}'.format(
token, id, msg
)
f'https://api.telegram.org/bot{token}/sendMessage?chat_id={id}&parse_mode=Markdown&text={msg}'
)
@@ -44,9 +42,7 @@ def _telegram_errors_bot(msg: str) -> None:
for id in chat_IDs:
requests.get(
'https://api.telegram.org/bot{}/sendMessage?chat_id={}&parse_mode=Markdown&text={}'.format(
token, id, msg
)
f'https://api.telegram.org/bot{token}/sendMessage?chat_id={id}&parse_mode=Markdown&text={msg}'
)

View File

@@ -5,30 +5,30 @@ import jesse.helpers as jh
def generate(name: str) -> None:
path = '{}'.format(name)
path = f'{name}'
# validate that doesn't create if current directory is inside a Jesse project
ls = os.listdir('.')
is_jesse_project = 'strategies' in ls and 'config.py' in ls and 'storage' in ls and 'routes.py' in ls
if is_jesse_project:
print(jh.color('You are already inside a Jesse project. Check your working directory.'.format(name), 'red'))
print(jh.color('You are already inside a Jesse project. Check your working directory.', 'red'))
return
# validation for name duplication
exists = os.path.isdir(path)
if exists:
print(jh.color('Project "{}" already exists.'.format(name), 'red'))
print(jh.color(f'Project "{name}" already exists.', 'red'))
return
# generate from ExampleStrategy
dirname, filename = os.path.split(os.path.abspath(__file__))
shutil.copytree('{}/project_template'.format(dirname), path)
shutil.copytree(f'{dirname}/project_template', path)
# output the location of generated strategy directory
print(
jh.color(
'Your project is created successfully. \nRun "cd {}" to begin algo-trading!'.format(path, name),
f'Your project is created successfully. \nRun "cd {path}" to begin algo-trading!',
'green'
)
)

View File

@@ -26,12 +26,10 @@ def quantstats_tearsheet() -> None:
os.makedirs('./storage/full-reports', exist_ok=True)
file_path = 'storage/full-reports/{}-{}.html'.format(
modes[mode][0], str(arrow.utcnow())[0:19]
).replace(":", "-")
file_path = f'storage/full-reports/{modes[mode][0]}-{str(arrow.utcnow())[0:19]}.html'.replace(":", "-")
title = '{}{}'.format(modes[mode][1], arrow.utcnow().strftime("%d %b, %Y %H:%M:%S"))
title = f"{modes[mode][1]}{arrow.utcnow().strftime('%d %b, %Y %H:%M:%S')}"
qs.reports.html(returns=returns_time_series, title=title, output=file_path)
print('\nFull report output saved at:\n{}'.format(file_path))
print(f'\nFull report output saved at:\n{file_path}')

View File

@@ -46,15 +46,11 @@ def positions() -> List[Union[List[str], List[Union[Union[str, int, None], Any]]
pos.strategy.name,
pos.symbol,
pos.leverage,
'' if pos.is_close else '{} ago'.format(
jh.readable_duration((jh.now_to_timestamp() - pos.opened_at) / 1000, 3)),
'' if pos.is_close else f'{jh.readable_duration((jh.now_to_timestamp() - pos.opened_at) / 1000, 3)} ago',
pos.qty if abs(pos.qty) > 0 else None,
pos.entry_price,
pos.current_price,
'' if pos.is_close else '{} ({}%)'.format(
jh.color(str(round(pos.pnl, 2)), pnl_color),
jh.color(str(round(pos.pnl_percentage, 4)), pnl_color)
),
'' if pos.is_close else f'{jh.color(str(round(pos.pnl, 2)), pnl_color)} ({jh.color(str(round(pos.pnl_percentage, 4)), pnl_color)}%)',
]
)
return array
@@ -126,7 +122,7 @@ def livetrade() -> List[Union[List[Union[str, Any]], List[str], List[Union[str,
arr = [
['started at', jh.timestamp_to_arrow(store.app.starting_time).humanize()],
['current time', jh.timestamp_to_time(jh.now_to_timestamp())[:19]],
['errors/info', '{}/{}'.format(len(store.logs.errors), len(store.logs.info))],
['errors/info', f'{len(store.logs.errors)}/{len(store.logs.info)}'],
['active orders', store.orders.count_all_active_orders()],
['open positions', store.positions.count_open_positions()]
]
@@ -135,7 +131,7 @@ def livetrade() -> List[Union[List[Union[str, Any]], List[str], List[Union[str,
first_exchange = selectors.get_exchange(router.routes[0].exchange)
if first_exchange.type == 'futures':
arr.append(['started/current balance', '{}/{}'.format(starting_balance, current_balance)])
arr.append(['started/current balance', f'{starting_balance}/{current_balance}'])
else:
# loop all trading exchanges
for exchange in selectors.get_all_exchanges():
@@ -145,23 +141,15 @@ def livetrade() -> List[Union[List[Union[str, Any]], List[str], List[Union[str,
current_price = selectors.get_current_price(router.routes[0].exchange, router.routes[0].symbol)
arr.append(
[
'{}'.format(asset_name),
'{}/{} ({} {})'.format(
round(exchange.available_assets[asset_name], 5),
round(asset_balance, 5),
jh.format_currency(round(asset_balance * current_price, 2)),
jh.quote_asset(router.routes[0].symbol)
)
f'{asset_name}',
f'{round(exchange.available_assets[asset_name], 5)}/{round(asset_balance, 5)} ({jh.format_currency(round(asset_balance * current_price, 2))} { jh.quote_asset(router.routes[0].symbol)})'
]
)
else:
arr.append(
[
'{}'.format(asset_name),
'{}/{}'.format(
round(exchange.available_assets[asset_name], 5),
round(asset_balance, 5),
)
f'{asset_name}',
f'{round(exchange.available_assets[asset_name], 5)}/{round(asset_balance, 5)}'
]
)
@@ -174,8 +162,8 @@ def livetrade() -> List[Union[List[Union[str, Any]], List[str], List[Union[str,
pnl = round(df['PNL'].sum(), 2)
pnl_percentage = round((pnl / starting_balance) * 100, 2)
arr.append(['total/winning/losing trades', '{}/{}/{}'.format(total, len(winning_trades), len(losing_trades))])
arr.append(['PNL (%)', '${} ({}%)'.format(pnl, pnl_percentage)])
arr.append(['total/winning/losing trades', f'{total}/{len(winning_trades)}/{len(losing_trades)}'])
arr.append(['PNL (%)', f'${pnl} ({pnl_percentage}%)'])
if config['app']['debug_mode']:
arr.append(['debug mode', config['app']['debug_mode']])
@@ -192,24 +180,20 @@ def portfolio_metrics() -> List[
metrics = [
['Total Closed Trades', data['total']],
['Total Net Profit',
'{} ({})'.format(jh.format_currency(round(data['net_profit'], 4)),
str(round(data['net_profit_percentage'], 2)) + '%')],
f"{jh.format_currency(round(data['net_profit'], 4))} ({str(round(data['net_profit_percentage'], 2))}%)"],
['Starting => Finishing Balance',
'{} => {}'.format(jh.format_currency(round(data['starting_balance'], 2)),
jh.format_currency(round(data['finishing_balance'], 2)))],
f"{jh.format_currency(round(data['starting_balance'], 2))} => {jh.format_currency(round(data['finishing_balance'], 2))}"],
['Total Open Trades', data['total_open_trades']],
['Open PL', jh.format_currency(round(data['open_pl'], 2))],
['Total Paid Fees', jh.format_currency(round(data['fee'], 2))],
['Max Drawdown', '{}%'.format(round(data['max_drawdown'], 2))],
['Annual Return', '{}%'.format(round(data['annual_return'], 2))],
['Max Drawdown', f"{round(data['max_drawdown'], 2)}%"],
['Annual Return', f"{round(data['annual_return'], 2)}%"],
['Expectancy',
'{} ({})'.format(jh.format_currency(round(data['expectancy'], 2)),
str(round(data['expectancy_percentage'], 2)) + '%')],
['Avg Win | Avg Loss', '{} | {}'.format(jh.format_currency(round(data['average_win'], 2)),
jh.format_currency(round(data['average_loss'], 2)))],
f"{jh.format_currency(round(data['expectancy'], 2))} ({str(round(data['expectancy_percentage'], 2))}%)"],
['Avg Win | Avg Loss', f"{jh.format_currency(round(data['average_win'], 2))} | {jh.format_currency(round(data['average_loss'], 2))}"],
['Ratio Avg Win / Avg Loss', round(data['ratio_avg_win_loss'], 2)],
['Percent Profitable', str(round(data['win_rate'] * 100)) + '%'],
['Longs | Shorts', '{}% | {}%'.format(round(data['longs_percentage']), round(data['short_percentage']))],
['Percent Profitable', f"{str(round(data['win_rate'] * 100))}%"],
['Longs | Shorts', f"{round(data['longs_percentage'])}% | {round(data['short_percentage'])}%"],
['Avg Holding Time', jh.readable_duration(data['average_holding_period'], 3)],
['Winning Trades Avg Holding Time',
np.nan if np.isnan(data['average_winning_holding_period']) else jh.readable_duration(
@@ -249,7 +233,7 @@ def info() -> List[List[Union[str, Any]]]:
for w in store.logs.info[::-1][0:5]:
array.append(
[jh.timestamp_to_time(w['time'])[11:19],
(w['message'][:70] + '..') if len(w['message']) > 70 else w['message']])
f"{w['message'][:70]}.." if len(w['message']) > 70 else w['message']])
return array
@@ -274,7 +258,7 @@ def errors() -> List[List[Union[str, Any]]]:
for w in store.logs.errors[::-1][0:5]:
array.append([jh.timestamp_to_time(w['time'])[11:19],
(w['message'][:70] + '..') if len(w['message']) > 70 else w['message']])
f"{w['message'][:70]}.." if len(w['message']) > 70 else w['message']])
return array

View File

@@ -36,7 +36,7 @@ def load_required_candles(exchange: str, symbol: str, start_date_str: str, finis
short_candles_count = int((pre_finish_date - pre_start_date) / 60_000)
key = jh.key(exchange, symbol)
cache_key = '{}-{}-{}'.format(jh.timestamp_to_date(pre_start_date), jh.timestamp_to_date(pre_finish_date), key)
cache_key = f'{jh.timestamp_to_date(pre_start_date)}-{jh.timestamp_to_date(pre_finish_date)}-{key}'
cached_value = cache.get_value(cache_key)
# if cache exists
@@ -71,9 +71,7 @@ def load_required_candles(exchange: str, symbol: str, start_date_str: str, finis
if not len(first_existing_candle):
raise CandleNotFoundInDatabase(
'No candle for {} {} is present in the database. Try importing candles.'.format(
exchange, symbol
)
f'No candle for {exchange} {symbol} is present in the database. Try importing candles.'
)
first_existing_candle = first_existing_candle[0][0]
@@ -90,20 +88,14 @@ def load_required_candles(exchange: str, symbol: str, start_date_str: str, finis
# if first backtestable timestamp is in the future, that means we have some but not enough candles
if first_backtestable_timestamp > jh.today_to_timestamp():
raise CandleNotFoundInDatabase(
'Not enough candle for {} {} is present in the database. Jesse requires "210 * biggest_timeframe" warm-up candles. '
'Try importing more candles from an earlier date.'.format(
exchange, symbol
)
f'Not enough candle for {exchange} {symbol} is present in the database. Jesse requires "210 * biggest_timeframe" warm-up candles. '
'Try importing more candles from an earlier date.'
)
raise CandleNotFoundInDatabase(
'Not enough candles for {} {} exists to run backtest from {} => {}. \n'
'First available date is {}\n'
'Last available date is {}'.format(
exchange, symbol, start_date_str, finish_date_str,
jh.timestamp_to_date(first_backtestable_timestamp),
jh.timestamp_to_date(last_existing_candle),
)
f'Not enough candles for {exchange} {symbol} exists to run backtest from {start_date_str} => {finish_date_str}. \n'
f'First available date is {jh.timestamp_to_date(first_backtestable_timestamp)}\n'
f'Last available date is {jh.timestamp_to_date(last_existing_candle)}'
)
return candles

View File

@@ -8,7 +8,7 @@ def get_current_price(exchange: str, symbol: str) -> float:
def get_position(exchange: str, symbol: str) -> Any:
from jesse.store import store
key = '{}-{}'.format(exchange, symbol)
key = f'{exchange}-{symbol}'
return store.positions.storage.get(key, None)

View File

@@ -10,27 +10,27 @@ def generate(name: str) -> None:
:param name:
:return:
"""
path = 'strategies/{}'.format(name)
path = f'strategies/{name}'
# validation for name duplication
exists = os.path.isdir(path)
if exists:
print(jh.color('Strategy "{}" already exists.'.format(name), 'red'))
print(jh.color(f'Strategy "{name}" already exists.', 'red'))
return
# generate from ExampleStrategy
dirname, filename = os.path.split(os.path.abspath(__file__))
shutil.copytree('{}/ExampleStrategy'.format(dirname), path)
shutil.copytree(f'{dirname}/ExampleStrategy', path)
# replace 'ExampleStrategy' with the name of the new strategy
fin = open("{}/__init__.py".format(path), "rt")
fin = open(f"{path}/__init__.py", "rt")
data = fin.read()
data = data.replace('ExampleStrategy', name)
fin.close()
fin = open("{}/__init__.py".format(path), "wt")
fin = open(f"{path}/__init__.py", "wt")
fin.write(data)
fin.close()
# output the location of generated strategy directory
print(jh.color('Strategy created at: {}'.format(path), 'green'))
print(jh.color(f'Strategy created at: {path}', 'green'))

View File

@@ -5,28 +5,21 @@ from jesse.store import store
def tradingview_logs(study_name: str = None, mode=None, now=None) -> None:
tv_text = '//@version=4\nstrategy("{}", overlay=true, initial_capital=10000, commission_type=strategy.commission.percent, commission_value=0.2)\n'.format(
study_name)
tv_text = f'//@version=4\nstrategy("{study_name}", overlay=true, initial_capital=10000, commission_type=strategy.commission.percent, commission_value=0.2)\n'
for i, t in enumerate(store.completed_trades.trades[::-1][:]):
tv_text += '\n'
for j, o in enumerate(t.orders):
when = "time_close == {}".format(int(o.executed_at))
when = f"time_close == {int(o.executed_at)}"
if int(o.executed_at) % (jh.timeframe_to_one_minutes(t.timeframe) * 60_000) != 0:
when = "time_close >= {} and time_close - {} < {}" \
.format(int(o.executed_at),
int(o.executed_at) + jh.timeframe_to_one_minutes(t.timeframe) * 60_000,
jh.timeframe_to_one_minutes(t.timeframe) * 60_000)
when = f"time_close >= {int(o.executed_at)} and time_close - {int(o.executed_at) + jh.timeframe_to_one_minutes(t.timeframe) * 60_000} < {jh.timeframe_to_one_minutes(t.timeframe) * 60_000}"
if j == len(t.orders) - 1:
tv_text += 'strategy.close("{}", when = {})\n'.format(i, when)
tv_text += f'strategy.close("{i}", when = {when})\n'
else:
tv_text += 'strategy.order("{}", {}, {}, {}, when = {})\n'.format(
i, 1 if t.type == 'long' else 0, abs(o.qty), o.price, when
)
tv_text += f'strategy.order("{i}", {1 if t.type == "long" else 0}, {abs(o.qty)}, {o.price}, when = {when})\n'
path = 'storage/trading-view-pine-editor/{}-{}.txt'.format(mode, now).replace(":", "-")
path = f'storage/trading-view-pine-editor/{mode}-{now}.txt'.replace(":", "-")
os.makedirs('./storage/trading-view-pine-editor', exist_ok=True)
with open(path, 'w+') as outfile:
outfile.write(tv_text)
print('\nPine-editor output saved at: \n{}'.format(path))
print(f'\nPine-editor output saved at: \n{path}')

View File

@@ -22,9 +22,7 @@ class CandlesState:
return self.storage[key]
except KeyError:
raise RouteNotFound(
"Bellow route is required but missing in your routes:\n('{}', '{}', '{}')".format(
exchange, symbol, timeframe
)
f"Bellow route is required but missing in your routes:\n('{exchange}', '{symbol}', '{timeframe}')"
)
def init_storage(self, bucket_size: int = 1000) -> None:

View File

@@ -18,9 +18,9 @@ class ExchangesState:
elif exchange_type == 'futures':
self.storage[name] = FuturesExchange(
name, starting_assets, fee,
settlement_currency=jh.get_config('env.exchanges.{}.settlement_currency'.format(name)),
futures_leverage_mode=jh.get_config('env.exchanges.{}.futures_leverage_mode'.format(name)),
futures_leverage=jh.get_config('env.exchanges.{}.futures_leverage'.format(name)),
settlement_currency=jh.get_config(f'env.exchanges.{name}.settlement_currency'),
futures_leverage_mode=jh.get_config(f'env.exchanges.{name}.futures_leverage_mode'),
futures_leverage=jh.get_config(f'env.exchanges.{name}.futures_leverage'),
)
else:
raise InvalidConfig(

View File

@@ -124,9 +124,7 @@ def _fix_array_len(arr: np.ndarray, target_len: int) -> np.ndarray:
missing_len = target_len - len(arr)
if missing_len < 0:
raise ValueError("len cannot be smaller than array's length. {} sent, while array has {} items".format(
target_len, len(arr)
))
raise ValueError(f"len cannot be smaller than array's length. {target_len} sent, while array has {len(arr)} items")
if not missing_len:
return arr

View File

@@ -15,7 +15,7 @@ class OrdersState:
for exchange in config['app']['trading_exchanges']:
for symbol in config['app']['trading_symbols']:
key = '{}-{}'.format(exchange, symbol)
key = f'{exchange}-{symbol}'
self.storage[key] = []
def reset(self) -> None:
@@ -23,12 +23,12 @@ class OrdersState:
self.storage[key].clear()
def add_order(self, order: Order) -> None:
key = '{}-{}'.format(order.exchange, order.symbol)
key = f'{order.exchange}-{order.symbol}'
self.storage[key].append(order)
# getters
def get_orders(self, exchange, symbol) -> List[Order]:
key = '{}-{}'.format(exchange, symbol)
key = f'{exchange}-{symbol}'
return self.storage.get(key, [])
def count_all_active_orders(self) -> int:
@@ -53,7 +53,7 @@ class OrdersState:
return len(self.get_orders(exchange, symbol))
def get_order_by_id(self, exchange: str, symbol: str, id: str, use_exchange_id: bool = False) -> Order:
key = '{}-{}'.format(exchange, symbol)
key = f'{exchange}-{symbol}'
if use_exchange_id:
return pydash.find(self.storage[key], lambda o: o.exchange_id == id)

View File

@@ -8,7 +8,7 @@ class PositionsState:
for exchange in config['app']['trading_exchanges']:
for symbol in config['app']['trading_symbols']:
key = '{}-{}'.format(exchange, symbol)
key = f'{exchange}-{symbol}'
self.storage[key] = Position(exchange, symbol)
def count_open_positions(self) -> int:

View File

@@ -261,10 +261,8 @@ class Strategy(ABC):
return arr
except ValueError:
raise exceptions.InvalidShape(
'The format of {} is invalid. \n'
'It must be (qty, price) or [(qty, price), (qty, price)] for multiple points; but {} was given'.format(
name, arr
)
f'The format of {name} is invalid. \n'
f'It must be (qty, price) or [(qty, price), (qty, price)] for multiple points; but {arr} was given'
)
def _validate_stop_loss(self) -> None:
@@ -373,7 +371,7 @@ class Strategy(ABC):
self.on_cancel()
if not jh.is_unit_testing() and not jh.is_live():
store.orders.storage['{}-{}'.format(self.exchange, self.symbol)].clear()
store.orders.storage[f'{self.exchange}-{self.symbol}'].clear()
def _reset(self) -> None:
self.buy = None
@@ -587,7 +585,7 @@ class Strategy(ABC):
self._is_initiated = True
if jh.is_live() and jh.is_debugging():
logger.info('Executing {}-{}-{}-{}'.format(self.name, self.exchange, self.symbol, self.timeframe))
logger.info(f'Executing {self.name}-{self.exchange}-{self.symbol}-{self.timeframe}')
# for caution to make sure testing on livetrade won't bleed your account
if jh.is_test_driving() and store.completed_trades.count >= 2:
@@ -645,18 +643,12 @@ class Strategy(ABC):
if self.is_long:
if o[1] <= self.position.entry_price:
raise exceptions.InvalidStrategy(
'take-profit({}) must be above entry-price({}) in a long position'.format(
o[1],
self.position.entry_price
)
f'take-profit({o[1]}) must be above entry-price({self.position.entry_price}) in a long position'
)
elif self.is_short:
if o[1] >= self.position.entry_price:
raise exceptions.InvalidStrategy(
'take-profit({}) must be below entry-price({}) in a short position'.format(
o[1],
self.position.entry_price
)
f'take-profit({o[1]}) must be below entry-price({self.position.entry_price}) in a short position'
)
# submit take-profit
@@ -674,18 +666,12 @@ class Strategy(ABC):
if self.is_long:
if o[1] >= self.position.entry_price:
raise exceptions.InvalidStrategy(
'stop-loss({}) must be below entry-price({}) in a long position'.format(
o[1],
self.position.entry_price
)
f'stop-loss({o[1]}) must be below entry-price({self.position.entry_price}) in a long position'
)
elif self.is_short:
if o[1] <= self.position.entry_price:
raise exceptions.InvalidStrategy(
'stop-loss({}) must be above entry-price({}) in a short position'.format(
o[1],
self.position.entry_price
)
f'stop-loss({o[1]}) must be above entry-price({self.position.entry_price}) in a short position'
)
# submit stop-loss
@@ -864,11 +850,7 @@ class Strategy(ABC):
store.app.total_open_trades += 1
store.app.total_open_pl += self.position.pnl
logger.info(
"Closed open {}-{} position at {} with PNL: {}({}%) because we reached the end of the backtest session.".format(
self.exchange, self.symbol, self.position.current_price,
round(self.position.pnl, 4),
round(self.position.pnl_percentage, 2)
)
f"Closed open {self.exchange}-{self.symbol} position at {self.position.current_price} with PNL: {round(self.position.pnl, 4)}({round(self.position.pnl_percentage, 2)}%) because we reached the end of the backtest session."
)
# fake a closing (market) order so that the calculations would be correct
self.broker.reduce_position_at(self.position.qty, self.position.current_price, order_roles.CLOSE_POSITION)

View File

@@ -402,7 +402,7 @@ def test_prepare_qty():
def test_python_version():
import sys
assert jh.python_version() == float('{}.{}'.format(sys.version_info[0], sys.version_info[1]))
assert jh.python_version() == float(f'{sys.version_info[0]}.{sys.version_info[1]}')
def test_quote_asset():