Ejemplo n.º 1
0
    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]))
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
    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()
Ejemplo n.º 6
0
    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)))
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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)))
Ejemplo n.º 9
0
    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)))
Ejemplo n.º 10
0
    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."
                )
Ejemplo n.º 11
0
    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)))
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
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'
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
    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)))