def execute(self, silent=False) -> None: if self.is_canceled or self.is_executed: return self.executed_at = jh.now_to_timestamp() self.status = order_statuses.EXECUTED if jh.is_live(): self.save() if not silent: txt = f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' # log if jh.is_debuggable('order_execution'): logger.info(txt) # notify if jh.is_live(): self.broadcast() if config['env']['notifications']['events']['executed_orders']: notify(txt) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_executed_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_execution(self)
def cancel(self, silent=False) -> None: if self.is_canceled or self.is_executed: return self.canceled_at = jh.now_to_timestamp() self.status = order_statuses.CANCELED if jh.is_live(): self.save() if not silent: txt = f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' if jh.is_debuggable('order_cancellation'): logger.info(txt) if jh.is_live(): self.broadcast() if config['env']['notifications']['events'][ 'cancelled_orders']: notify(txt) # handle exchange balance e = selectors.get_exchange(self.exchange) e.on_order_cancellation(self)
def _close(self, close_price: float) -> None: if self.is_open is False: raise EmptyPosition('The position is already closed.') # just to prevent confusion close_qty = abs(self.qty) estimated_profit = jh.estimate_PNL(close_qty, self.entry_price, close_price, self.type) entry = self.entry_price trade_type = self.type self.exit_price = close_price if self.exchange: self.exchange.add_realized_pnl(estimated_profit) self.exchange.temp_reduced_amount[jh.base_asset( self.symbol)] += abs(close_qty * close_price) self.qty = 0 self.entry_price = None 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) if jh.is_debuggable('position_closed'): logger.info(info_text) if jh.is_live( ) and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def error(msg: str) -> None: if jh.app_mode() not in LOGGERS: _init_main_logger() # error logs should be logged as info logs as well info(msg) msg = str(msg) from jesse.store import store log_id = jh.generate_unique_id() log_dict = { 'id': log_id, 'timestamp': jh.now_to_timestamp(), 'message': msg } if jh.is_live() and jh.get_config('env.notifications.events.errors', True): # notify_urgently(f"ERROR at \"{jh.get_config('env.identifier')}\" account:\n{msg}") notify_urgently(f"ERROR:\n{msg}") notify(f'ERROR:\n{msg}') if (jh.is_backtesting() and jh.is_debugging()) or jh.is_collecting_data() or jh.is_live(): sync_publish('error_log', log_dict) store.logs.errors.append(log_dict) if jh.is_live() or jh.is_optimizing(): msg = f"[ERROR | {jh.timestamp_to_time(jh.now_to_timestamp())[:19]}] {msg}" logger = LOGGERS[jh.app_mode()] logger.error(msg) if jh.is_live(): from jesse.models.utils import store_log_into_db store_log_into_db(log_dict, 'error')
def _reduce(self, qty, price): if self.is_open is False: raise EmptyPosition('The position is closed.') # just to prevent confusion qty = abs(qty) estimated_profit = jh.estimate_PNL(qty, self.entry_price, price, self.type) if self.exchange: self.exchange.increase_balance( self, qty * self.entry_price + estimated_profit) if self.type == trade_types.LONG: self.qty -= qty elif self.type == trade_types.SHORT: self.qty += qty info_text = 'REDUCED position: {}, {}, {}, {}, ${}'.format( self.exchange_name, self.symbol, self.type, self.qty, round(self.entry_price, 2)) if jh.is_debuggable('position_reduced'): logger.info(info_text) if jh.is_live( ) and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def _reduce(self, qty: float, price: float) -> None: if self.is_open is False: raise EmptyPosition('The position is closed.') # just to prevent confusion qty = abs(qty) estimated_profit = jh.estimate_PNL(qty, self.entry_price, price, self.type) if self.exchange: # self.exchange.increase_futures_balance(qty * self.entry_price + estimated_profit) self.exchange.add_realized_pnl(estimated_profit) self.exchange.temp_reduced_amount[jh.base_asset( self.symbol)] += abs(qty * price) if self.type == trade_types.LONG: self.qty = subtract_floats(self.qty, qty) 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)) if jh.is_debuggable('position_reduced'): logger.info(info_text) if jh.is_live( ) and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def execute(self): if self.is_canceled or self.is_executed: return self.executed_at = jh.now_to_timestamp() self.status = order_statuses.EXECUTED # log if jh.is_debuggable('order_execution'): logger.info('EXECUTED order: {}, {}, {}, {}, ${}'.format( 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))) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_executed_order(self) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_execution(self)
def _open(self, qty, price, change_balance=True): if self.is_open: raise OpenPositionError( 'an already open position cannot be opened') if change_balance: size = abs(qty) * price if self.exchange: self.exchange.decrease_balance(self, size) self.entry_price = price self.exit_price = None self.qty = qty self.opened_at = jh.now() info_text = 'OPENED {} position: {}, {}, {}, ${}'.format( self.type, self.exchange_name, self.symbol, self.qty, round(self.entry_price, 2)) if jh.is_debuggable('position_opened'): logger.info(info_text) if jh.is_live( ) and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def _increase(self, qty: float, price: float) -> None: if not self.is_open: raise OpenPositionError('position must be already open in order to increase its size') qty = abs(qty) # size = qty * price # if self.exchange: # self.exchange.decrease_futures_balance(size) self.entry_price = jh.estimate_average_price(qty, price, self.qty, self.entry_price) if self.type == trade_types.LONG: self.qty = sum_floats(self.qty, qty) elif self.type == trade_types.SHORT: self.qty = subtract_floats(self.qty, qty) 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) if jh.is_live() and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def info(msg: str, send_notification=False) -> None: if jh.app_mode() not in LOGGERS: _init_main_logger() msg = str(msg) from jesse.store import store log_id = jh.generate_unique_id() log_dict = { 'id': log_id, 'timestamp': jh.now_to_timestamp(), 'message': msg } store.logs.info.append(log_dict) if jh.is_collecting_data() or jh.is_live(): sync_publish('info_log', log_dict) if jh.is_live() or (jh.is_backtesting() and jh.is_debugging()): msg = f"[INFO | {jh.timestamp_to_time(jh.now_to_timestamp())[:19]}] {msg}" logger = LOGGERS[jh.app_mode()] logger.info(msg) if jh.is_live(): from jesse.models.utils import store_log_into_db store_log_into_db(log_dict, 'info') if send_notification: notify(msg)
def cancel(self): """ :return: """ if self.is_canceled or self.is_executed: return self.canceled_at = jh.now() self.status = order_statuses.CANCELED if jh.is_debuggable('order_cancellation'): logger.info( 'CANCELED order: {}, {}, {}, {}, ${}'.format( self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) ) # notify 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) ) ) p = selectors.get_position(self.exchange, self.symbol) if p: p._on_canceled_order(self)
def _increase(self, qty, price): if not self.is_open: raise OpenPositionError( 'position must be already open in order to incrase its size') qty = abs(qty) size = qty * price if self.exchange: self.exchange.decrease_balance(self, size) self.entry_price = jh.estimate_average_price(qty, price, self.qty, self.entry_price) if self.type == trade_types.LONG: self.qty += qty elif self.type == trade_types.SHORT: self.qty -= qty info_text = 'INCREASED position: {}, {}, {}, {}, ${}'.format( self.exchange_name, self.symbol, self.type, self.qty, round(self.entry_price, 2)) if jh.is_debuggable('position_increased'): logger.info(info_text) if jh.is_live( ) and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def _close(self, close_price): if self.is_open is False: raise EmptyPosition('The position is already closed.') # just to prevent confusion close_qty = abs(self.qty) estimated_profit = jh.estimate_PNL(close_qty, self.entry_price, close_price, self.type) entry = self.entry_price trade_type = self.type self.exit_price = close_price if self.exchange: self.exchange.increase_balance( self, close_qty * self.entry_price + estimated_profit) self.qty = 0 self.entry_price = None self.closed_at = jh.now() info_text = 'CLOSED {} position: {}, {}. PNL: ${}, entry: {}, exit: {}'.format( trade_type, self.exchange_name, self.symbol, round(estimated_profit, 2), entry, close_price) if jh.is_debuggable('position_closed'): logger.info(info_text) if jh.is_live( ) and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def notify_submission(self): notify( '{} order: {}, {}, {}, {}, ${}'.format( 'QUEUED' if self.is_queued else 'SUBMITTED', self.symbol, self.type, self.side, self.qty, round(self.price, 2) ) )
def notify_submission(self) -> None: self.broadcast() if config['env']['notifications']['events']['submitted_orders']: txt = f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}' if self.price: txt += f', ${round(self.price, 2)}' notify(txt)
def error(msg): from jesse.store import store if jh.is_live() and jh.get_config('env.notifications.events.errors', True): notify('ERROR:\n{}'.format(msg)) if (jh.is_backtesting() and jh.is_debugging()) or jh.is_collecting_data(): print(jh.color('[{}]: {}'.format(jh.timestamp_to_time(jh.now()), msg), 'red')) store.logs.errors.append({'time': jh.now(), 'message': msg}) if jh.is_live(): msg = '[ERROR | {}] '.format(jh.timestamp_to_time(jh.now())[:19]) + str(msg) import logging logging.error(msg)
def error(msg: str) -> None: msg = str(msg) from jesse.store import store if jh.is_live() and jh.get_config('env.notifications.events.errors', True): 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(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 = f"[ERROR | {jh.timestamp_to_time(jh.now_to_timestamp())[:19]}] {msg}" logging.error(msg)
def log(msg: str, log_type: str = 'info') -> None: msg = str(msg) if log_type == 'info': logger.info(msg) if jh.is_live(): notifier.notify(msg) elif log_type == 'error': logger.error(msg) if jh.is_live(): notifier.notify(msg) notifier.notify_urgently(msg) else: raise ValueError(f'log_type should be either "info" or "error". You passed {log_type}')
def _open(self, qty: float, price: float, change_balance: bool = True) -> None: if self.is_open: raise OpenPositionError('an already open position cannot be opened') self.entry_price = price self.exit_price = None self.qty = qty self.opened_at = jh.now_to_timestamp() 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) if jh.is_live() and config['env']['notifications']['events']['updated_position']: notifier.notify(info_text)
def cancel(self): if self.is_canceled or self.is_executed: return self.canceled_at = jh.now_to_timestamp() self.status = order_statuses.CANCELED if jh.is_debuggable('order_cancellation'): logger.info('CANCELED order: {}, {}, {}, {}, ${}'.format( 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))) # handle exchange balance e = selectors.get_exchange(self.exchange) e.on_order_cancellation(self)
def notify_submission(self) -> None: notify( f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, { self.qty}, ${round(self.price, 2)}' )