def on_order_submission(self, order: Order, skip_market_order: bool = True) -> None: base_asset = jh.base_asset(order.symbol) # make sure we don't spend more than we're allowed considering current allowed leverage if order.type != order_types.MARKET or skip_market_order: if not order.is_reduce_only: order_size = abs(order.qty * order.price) 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) )) # skip market order at the time of submission because we don't have # the exact order.price. Instead, we call on_order_submission() one # more time at time of execution without "skip_market_order=False". if order.type == order_types.MARKET and skip_market_order: return self.available_assets[base_asset] += order.qty if order.side == sides.BUY: self.buy_orders[base_asset].append(np.array([order.qty, order.price])) else: self.sell_orders[base_asset].append(np.array([order.qty, order.price]))
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 _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 __init__(self, name: str, starting_assets: list, fee_rate: float, settlement_currency: str, futures_leverage_mode: str, futures_leverage: int): super().__init__(name, starting_assets, fee_rate, 'futures') self.futures_leverage_mode = futures_leverage_mode self.futures_leverage = futures_leverage for item in starting_assets: self.buy_orders[item['asset']] = DynamicNumpyArray((10, 2)) self.sell_orders[item['asset']] = DynamicNumpyArray((10, 2)) # make sure trading routes exist in starting_assets from jesse.routes import router for r in router.routes: base = jh.base_asset(r.symbol) if base not in self.assets: self.assets[base] = 0 self.temp_reduced_amount[base] = 0 if base not in self.buy_orders: self.buy_orders[base] = DynamicNumpyArray((10, 2)) if base not in self.sell_orders: self.sell_orders[base] = DynamicNumpyArray((10, 2)) self.starting_assets = self.assets.copy() self.available_assets = self.assets.copy() # start from 0 balance for self.available_assets which acts as a temp variable for k in self.available_assets: self.available_assets[k] = 0 self.settlement_currency = settlement_currency.upper()
def __init__(self, name: str, starting_assets: list, fee_rate: float, exchange_type: str, settlement_currency: str): self.name = name self.type = exchange_type.lower() for item in starting_assets: self.assets[item['asset']] = item['balance'] self.buy_orders[item['asset']] = DynamicNumpyArray((10, 2)) self.sell_orders[item['asset']] = DynamicNumpyArray((10, 2)) self.temp_reduced_amount[item['asset']] = 0 # margin only: make sure trading routes exist in starting_assets if self.type == 'margin': from jesse.routes import router for r in router.routes: base = jh.base_asset(r.symbol) if base not in self.assets: self.assets[base] = 0 if base not in self.buy_orders: self.buy_orders[base] = DynamicNumpyArray((10, 2)) if base not in self.sell_orders: self.sell_orders[base] = DynamicNumpyArray((10, 2)) if base not in self.temp_reduced_amount: self.temp_reduced_amount[base] = 0 self.starting_assets = self.assets.copy() self.available_assets = self.assets.copy() # in margin mode, start from 0 balance for self.available_assets # which acts as a temp variable if self.type == 'margin': for k in self.available_assets: self.available_assets[k] = 0 self.fee_rate = fee_rate self.settlement_currency = settlement_currency.upper()
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 _on_canceled_order(self, order): qty = order.qty price = order.price size = ju.qty_to_size(qty, price) available_qty = self.exchange.available_assets[jh.base_asset(self.symbol)] if order.is_reduce_only: return
def on_order_execution(self, order: Order): base_asset = jh.base_asset(order.symbol) quote_asset = jh.quote_asset(order.symbol) if order.type == order_types.MARKET: self.on_order_submission(order, skip_market_order=False) # used for logging balance change temp_old_quote_asset = self.assets[quote_asset] temp_old_quote_available_asset = self.available_assets[quote_asset] temp_old_base_asset = self.assets[base_asset] temp_old_base_available_asset = self.available_assets[base_asset] # works for both buy and sell orders (sell order's qty < 0) self.assets[base_asset] += order.qty if order.side == sides.BUY: self.available_assets[base_asset] += order.qty self.assets[quote_asset] -= (abs(order.qty) * order.price) * (1 + self.fee_rate) # sell order else: self.available_assets[quote_asset] += abs( order.qty) * order.price * (1 - self.fee_rate) self.assets[quote_asset] += abs( order.qty) * order.price * (1 - self.fee_rate) 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))) 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))) 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))) 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)))
def on_order_submission(self, order: Order, skip_market_order: bool = True) -> None: base_asset = jh.base_asset(order.symbol) quote_asset = jh.quote_asset(order.symbol) # skip market order at the time of submission because we don't have # the exact order.price. Instead, we call on_order_submission() one # more time at time of execution without "skip_market_order=False". if order.type == order_types.MARKET and skip_market_order: return # 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: quote_balance = self.available_assets[quote_asset] 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))) # sell order else: base_balance = self.available_assets[base_asset] 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))) 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 __init__(self, name: str, starting_assets: list, fee_rate: float): super().__init__(name, starting_assets, fee_rate, 'spot') from jesse.routes import router # check if base assets are configured for route in router.routes: base_asset = jh.base_asset(route.symbol) if base_asset not in self.available_assets: raise InvalidConfig( f"Jesse needs to know the balance of your base asset for spot mode. Please add {base_asset} to your exchanges assets config." )
def on_order_cancellation(self, order: Order): base_asset = jh.base_asset(order.symbol) quote_asset = jh.quote_asset(order.symbol) # in margin, we only update available_asset's value which is used for detecting reduce_only orders if self.type == 'margin': self.available_assets[base_asset] -= order.qty # self.available_assets[quote_asset] += order.qty * order.price if order.side == sides.BUY: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.buy_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.buy_orders[base_asset][index] = np.array([0, 0]) break else: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.sell_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.sell_orders[base_asset][index] = np.array([0, 0]) break return # 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 on_order_cancellation(self, order: Order) -> None: base_asset = jh.base_asset(order.symbol) self.available_assets[base_asset] -= order.qty # self.available_assets[quote_asset] += order.qty * order.price if order.side == sides.BUY: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.buy_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.buy_orders[base_asset][index] = np.array([0, 0]) break else: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.sell_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.sell_orders[base_asset][index] = np.array([0, 0]) break return
def on_order_execution(self, order: Order) -> None: base_asset = jh.base_asset(order.symbol) if order.type == order_types.MARKET: self.on_order_submission(order, skip_market_order=False) if order.side == sides.BUY: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.buy_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.buy_orders[base_asset][index] = np.array([0, 0]) break else: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.sell_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.sell_orders[base_asset][index] = np.array([0, 0]) break return
def test_base_asset(): assert jh.base_asset('BTC-USDT') == 'BTC' assert jh.base_asset('BTC-USD') == 'BTC' assert jh.base_asset('DEFI-USDT') == 'DEFI' assert jh.base_asset('DEFI-USD') == 'DEFI'
def livetrade() -> List[Union[List[Union[str, Any]], List[str], List[Union[ str, int]], List[Union[str, Dict[str, Union[str, int]], Dict[ str, str], Dict[str, bool], Dict[str, Union[Dict[str, Union[ int, str, List[Dict[str, Union[str, int]]]]], Dict[str, Union[ float, str, int, List[Dict[str, Union[str, int]]]]]]], Dict[ str, int]]]]]: # sum up balance of all trading exchanges starting_balance = 0 current_balance = 0 for e in store.exchanges.storage: starting_balance += store.exchanges.storage[e].starting_assets[ jh.app_currency()] current_balance += store.exchanges.storage[e].assets[jh.app_currency()] starting_balance = round(starting_balance, 2) current_balance = round(current_balance, 2) 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', f'{len(store.logs.errors)}/{len(store.logs.info)}'], ['active orders', store.orders.count_all_active_orders()], ['open positions', store.positions.count_open_positions()]] # TODO: for now, we assume that we trade on one exchange only. Later, we need to support for more than one exchange at a time first_exchange = selectors.get_exchange(router.routes[0].exchange) if first_exchange.type == 'futures': arr.append([ 'started/current balance', f'{starting_balance}/{current_balance}' ]) else: # loop all trading exchanges for exchange in selectors.get_all_exchanges(): # loop all assets for asset_name, asset_balance in exchange.assets.items(): if asset_name == jh.base_asset(router.routes[0].symbol): current_price = selectors.get_current_price( router.routes[0].exchange, router.routes[0].symbol) arr.append([ 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([ f'{asset_name}', f'{round(exchange.available_assets[asset_name], 5)}/{round(asset_balance, 5)}' ]) # short trades summary if len(store.completed_trades.trades): df = pd.DataFrame.from_records( [t.to_dict() for t in store.completed_trades.trades]) total = len(df) winning_trades = df.loc[df['PNL'] > 0] losing_trades = df.loc[df['PNL'] < 0] pnl = round(df['PNL'].sum(), 2) pnl_percentage = round((pnl / starting_balance) * 100, 2) 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']]) if config['app']['is_test_driving']: arr.append(['Test Drive', config['app']['is_test_driving']]) return arr
def on_order_execution(self, order: Order): base_asset = jh.base_asset(order.symbol) quote_asset = jh.quote_asset(order.symbol) if order.type == order_types.MARKET: self.on_order_submission(order, skip_market_order=False) if self.type == 'margin': if order.side == sides.BUY: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.buy_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.buy_orders[base_asset][index] = np.array([0, 0]) break else: # find and set order to [0, 0] (same as removing it) for index, item in enumerate(self.sell_orders[base_asset]): if item[0] == order.qty and item[1] == order.price: self.sell_orders[base_asset][index] = np.array([0, 0]) break return # used for logging balance change temp_old_quote_asset = self.assets[quote_asset] temp_old_quote_available_asset = self.available_assets[quote_asset] temp_old_base_asset = self.assets[base_asset] temp_old_base_available_asset = self.available_assets[base_asset] # works for both buy and sell orders (sell order's qty < 0) self.assets[base_asset] += order.qty if order.side == sides.BUY: self.available_assets[base_asset] += order.qty self.assets[quote_asset] -= (abs(order.qty) * order.price) * (1 + self.fee_rate) # sell order else: self.available_assets[quote_asset] += abs( order.qty) * order.price * (1 - self.fee_rate) self.assets[quote_asset] += abs( order.qty) * order.price * (1 - self.fee_rate) 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))) 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))) 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))) 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)))