def _execute_filters(self): for f in self.filters(): try: passed = f() except TypeError: raise exceptions.InvalidStrategy( "Invalid filter format. You need to pass filter methods WITHOUT calling them " "(no parentheses must be present at the end)" "\n\n" u"\u274C " + "Incorrect Example:\n" "return [\n" " self.filter_1()\n" "]\n\n" u"\u2705 " + "Correct Example:\n" "return [\n" " self.filter_1\n" "]\n" ) if passed == False: logger.info(f.__name__) self._reset() return False return True
def _check_for_liquidations(candle: np.ndarray, exchange: str, symbol: str) -> None: p: Position = selectors.get_position(exchange, symbol) if not p: return # for now, we only support the isolated mode: if p.mode != 'isolated': return if candle_includes_price(candle, p.liquidation_price): closing_order_side = jh.closing_side(p.type) # create the market order that is used as the liquidation order order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': exchange, 'side': closing_order_side, 'type': order_types.MARKET, 'flag': order_flags.REDUCE_ONLY, 'qty': jh.prepare_qty(p.qty, closing_order_side), 'price': p.bankruptcy_price, 'role': order_roles.CLOSE_POSITION }) store.orders.add_order(order) store.app.total_liquidations += 1 logger.info(f'{p.symbol} liquidated at {p.liquidation_price}') order.execute()
def __init__(self, attributes: dict = None, **kwargs) -> None: Model.__init__(self, attributes=attributes, **kwargs) if attributes is None: attributes = {} for a, value in attributes.items(): setattr(self, a, value) if self.created_at is None: self.created_at = jh.now_to_timestamp() if jh.is_live(): from jesse.store import store self.session_id = store.app.session_id self.save(force_insert=True) if jh.is_live(): self.notify_submission() if jh.is_debuggable('order_submission'): 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)}' logger.info(txt) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(self)
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 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 _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 __init__(self, attributes=None, **kwargs)-> None: Model.__init__(self, attributes=attributes, **kwargs) if attributes is None: attributes = {} for a in attributes: setattr(self, a, attributes[a]) if self.created_at is None: self.created_at = jh.now_to_timestamp() if jh.is_live() and config['env']['notifications']['events']['submitted_orders']: 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) ) ) # handle exchange balance for ordered asset e = selectors.get_exchange(self.exchange) e.on_order_submission(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 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 _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 charge_fee(self, amount): fee_amount = abs(amount) * self.fee_rate new_balance = self.assets[self.settlement_currency] - fee_amount logger.info( 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 on_order_cancellation(self, order: Order): base_asset = jh.base_asset(order.symbol) quote_asset = jh.quote_asset(order.symbol) # used for logging balance change temp_old_quote_available_asset = self.available_assets[quote_asset] temp_old_base_available_asset = self.available_assets[base_asset] if order.side == sides.BUY: self.available_assets[quote_asset] += ( abs(order.qty) * order.price) * (1 + self.fee_rate) # sell order else: self.available_assets[base_asset] += abs(order.qty) 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))) 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)))
def save_daily_portfolio_balance() -> None: balances = [] # add exchange balances for key, e in store.exchanges.storage.items(): balances.append(e.assets[jh.app_currency()]) # # store daily_balance of assets into database # if jh.is_livetrading(): # for asset_key, asset_value in e.assets.items(): # store_daily_balance_into_db({ # 'id': jh.generate_unique_id(), # 'timestamp': jh.now(), # 'identifier': jh.get_config('env.identifier', 'main'), # 'exchange': e.name, # 'asset': asset_key, # 'balance': asset_value, # }) # add open position values for key, pos in store.positions.storage.items(): if pos.is_open: balances.append(pos.pnl) total = sum(balances) store.app.daily_balance.append(total) # TEMP: disable storing in database for now if not jh.is_livetrading(): logger.info(f'Saved daily portfolio balance: {round(total, 2)}')
def cancel_order(self, exchange: str, symbol: str, order_id: str) -> bool: if exchange not in self.drivers: logger.info( f'Exchange "{exchange}" driver not initiated yet. Trying again in the next candle' ) return False return self.drivers[exchange].cancel_order(symbol, order_id)
def _on_executed_order(self, order: Order): qty = order.qty price = order.price # TODO: detect reduce_only order, and if so, see if you need to adjust qty and price (above variables) self.exchange.charge_fee(qty * price) # order opens position if self.qty == 0: change_balance = order.type == order_types.MARKET self._open(qty, price, change_balance) # order closes position elif (sum_floats(self.qty, qty)) == 0: self._close(price) # order increases the size of the position elif self.qty * qty > 0: self._increase(qty, price) # order reduces the size of the position elif self.qty * qty < 0: # if size of the order is big enough to both close the # position AND open it on the opposite side if abs(qty) > abs(self.qty): logger.info( 'Executed order is big enough to not close, but flip the position type. Order QTY: {}, Position QTY: {}' .format(qty, self.qty)) diff_qty = sum_floats(self.qty, qty) self._close(price) self._open(diff_qty, price) else: self._reduce(qty, price) if self.strategy: self.strategy._on_updated_position(order)
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 _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 _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 _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 save_daily_portfolio_balance() -> None: balances = [] # add exchange balances for key, e in store.exchanges.storage.items(): balances.append(e.assets[jh.app_currency()]) # store daily_balance of assets into database if jh.is_livetrading(): for asset_key, asset_value in e.assets.items(): store_daily_balance_into_db({ 'id': jh.generate_unique_id(), 'timestamp': jh.now(), 'identifier': jh.get_config('env.identifier', 'main'), 'exchange': e.name, 'asset': asset_key, 'balance': asset_value, }) # add open position values for key, pos in store.positions.storage.items(): if pos.is_open: balances.append(pos.pnl) total = sum(balances) store.app.daily_balance.append(total) logger.info('Saved daily portfolio balance: {}'.format(round(total, 2)))
def add_realized_pnl(self, realized_pnl: float) -> None: 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), )) self.assets[self.settlement_currency] = new_balance
def _on_take_profit(self, order: Order) -> None: if not jh.should_execute_silently() or jh.is_debugging(): logger.info("Take-profit order has been executed.") self._broadcast('route-take-profit') self._execute_cancel() self.on_take_profit(order) self._detect_and_handle_entry_and_exit_modifications()
def _on_stop_loss(self, order: Order) -> None: if not jh.should_execute_silently() or jh.is_debugging(): logger.info('Stop-loss has been executed.') self._broadcast('route-stop-loss') self._execute_cancel() self.on_stop_loss(order) self._detect_and_handle_entry_and_exit_modifications()
def log(self, msg, log_type='info'): msg = str(msg) if log_type == 'info': logger.info(msg) elif log_type == 'error': logger.error(msg) else: raise ValueError(f'log_type should be either "info" or "error". You passed {log_type}')
def stop_order(self, exchange: str, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Union[Order, None]: if exchange not in self.drivers: logger.info( f'Exchange "{exchange}" driver not initiated yet. Trying again in the next candle' ) return None return self.drivers[exchange].stop_order(symbol, qty, price, side, role, flags)
def _on_increased_position(self): if not jh.should_execute_silently() or jh.is_debugging(): logger.info("Position size increased.") self._broadcast('route-increased-position') self.on_increased_position() self._detect_and_handle_entry_and_exit_modifications()
def _on_close_position(self, order: Order): if not jh.should_execute_silently() or jh.is_debugging(): logger.info("A closing order has been executed") self._broadcast('route-close-position') self._execute_cancel() self.on_close_position(order) self._detect_and_handle_entry_and_exit_modifications()
def decrease_balance(self, position, balance): old_balance = self.balance self.balance -= abs(balance) * (1 + self.fee) new_balance = self.balance if jh.is_debuggable('balance_update'): logger.info('balance changed from {} to {}'.format( old_balance, new_balance))
def terminate(self): # log, so we can check this block was executed in the first place logger.info('executed terminate successfully') # assert open position assert self.position.is_open assert self.position.pnl == 97 # close it manually self.liquidate()