Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    def execute(self) -> None:
        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)
Exemplo n.º 3
0
    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)
Exemplo n.º 4
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 = 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)

            if jh.is_live(
            ) and config['env']['notifications']['events']['updated_position']:
                notifier.notify(info_text)
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
    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_margin_balance(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)
Exemplo n.º 8
0
    def _increase(self, qty, price):
        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_margin_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 = '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)
Exemplo n.º 9
0
    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_margin_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)
Exemplo n.º 10
0
    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.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()

        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)
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
    def on_order_submission(self, order: Order, skip_market_order=True):
        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(
                    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:
            base_balance = self.available_assets[base_asset]
            new_base_balance = base_balance + order.qty
            if new_base_balance < 0:
                raise NegativeBalance(
                    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)

        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(
                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(
                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)}'
            )
Exemplo n.º 13
0
    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))
Exemplo n.º 14
0
    def increase_balance(self, position, balance, is_refund=False):
        old_balance = self.balance
        if is_refund:
            self.balance += abs(balance) * (1 + self.fee)
        else:
            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))
Exemplo n.º 15
0
    def __init__(self, attributes=None):
        # id generated by Jesse for database usage
        self.id = ''

        # id generated by market, used in live-trade mode
        self.exchange_id = ''
        # some exchanges might require even further info
        self.vars = {}

        self.symbol = ''
        self.exchange = ''
        self.side = ''
        self.type = ''
        self.flag = ''
        self.qty = 0
        self.price = 0
        self.status = order_statuses.ACTIVE
        self.created_at = None
        self.executed_at = None
        self.canceled_at = None
        self.role = None

        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()

        p = selectors.get_position(self.exchange, self.symbol)
        if p:
            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)
                    )
                )

            p._on_opened_order(self)

        # handle exchange balance for ordered asset
        e = selectors.get_exchange(self.exchange)
        e.on_order_submission(self)
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    def decrease_balance(self, position, delta_balance):
        old_balance = self.balance

        to_spend = abs(delta_balance) * (1 + self.fee_rate)

        self.balance -= to_spend

        new_balance = self.balance

        if new_balance < 0:
            raise NegativeBalance(
                "Balance cannot go below zero. Available capital at {} is {} but you're trying to spend {}"
                .format(self.name, old_balance, to_spend))

        if jh.is_debuggable('balance_update'):
            logger.info('balance changed from {} to {}'.format(
                old_balance, new_balance))
Exemplo n.º 18
0
    def increase_balance(self, position, delta_balance, is_refund=False):
        """

        :param position:
        :param delta_balance:
        :param is_refund:
        """
        old_balance = self.balance

        if is_refund:
            self.balance += abs(delta_balance) * (1 + self.fee_rate)
        else:
            self.balance += abs(delta_balance) * (1 - self.fee_rate)

        new_balance = self.balance

        if jh.is_debuggable('balance_update'):
            logger.info('balance changed from {} to {}'.format(old_balance, new_balance))
Exemplo n.º 19
0
    def __init__(self, attributes=None):
        # id generated by Jesse for database usage
        self.id = ''
        # id generated by market
        self.exchange_id = ''
        self.symbol = ''
        self.exchange = ''
        self.side = ''
        self.type = ''
        self.flag = ''
        self.qty = 0
        self.price = 0
        self.status = order_statuses.ACTIVE
        self.created_at = None
        self.executed_at = None
        self.canceled_at = None
        self.role = None

        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()

        p = selectors.get_position(self.exchange, self.symbol)
        if p:
            p._on_opened_order(self)

        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)
                )
            )
Exemplo n.º 20
0
    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)
Exemplo n.º 21
0
    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(
        ) and config['env']['notifications']['events']['submitted_orders']:
            self.notify_submission()
        if jh.is_debuggable('order_submission'):
            logger.info(
                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
        e = selectors.get_exchange(self.exchange)
        e.on_order_submission(self)
Exemplo n.º 22
0
def run(start_date: str,
        finish_date: str,
        candles: Dict[str, Dict[str, Union[str, np.ndarray]]] = None,
        chart: bool = False,
        tradingview: bool = False,
        full_reports: bool = False,
        csv: bool = False,
        json: bool = False) -> None:
    # clear the screen
    if not jh.should_execute_silently():
        click.clear()

    # validate routes
    validate_routes(router)

    # initiate candle store
    store.candles.init_storage(5000)

    # load historical candles
    if candles is None:
        print('loading candles...')
        candles = load_candles(start_date, finish_date)
        click.clear()

    if not jh.should_execute_silently():
        # print candles table
        key = '{}-{}'.format(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')

        # print routes table
        table.multi_value(stats.routes(router.routes))
        print('\n')

        # print guidance for debugging candles
        if jh.is_debuggable('trading_candles') or jh.is_debuggable(
                'shorter_period_candles'):
            print(
                '     Symbol  |     timestamp    | open | close | high | low | volume'
            )

    # run backtest simulation
    simulator(candles)

    if not jh.should_execute_silently():
        # print trades metrics
        if store.completed_trades.count > 0:

            change = []
            # calcualte market change
            for e in router.routes:
                if e.strategy is None:
                    return

                first = Candle.select(Candle.close).where(
                    Candle.timestamp == jh.date_to_timestamp(start_date),
                    Candle.exchange == e.exchange,
                    Candle.symbol == e.symbol).first()
                last = Candle.select(Candle.close).where(
                    Candle.timestamp == jh.date_to_timestamp(finish_date) -
                    60000, Candle.exchange == e.exchange,
                    Candle.symbol == e.symbol).first()

                change.append(
                    ((last.close - first.close) / first.close) * 100.0)

            data = report.portfolio_metrics()
            data.append(
                ['Market Change',
                 str(round(np.average(change), 2)) + "%"])
            print('\n')
            table.key_value(data, 'Metrics', alignments=('left', 'right'))
            print('\n')

            # save logs
            store_logs(json, tradingview, csv)

            if chart:
                charts.portfolio_vs_asset_returns()

            # QuantStats' report
            if full_reports:
                quantstats.quantstats_tearsheet()
        else:
            print(jh.color('No trades were made.', 'yellow'))
Exemplo n.º 23
0
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])
    first_candles_set = candles[key]['candles']
    length = len(first_candles_set)
    # to preset the array size for performance
    store.app.starting_time = first_candles_set[0][0]
    store.app.time = first_candles_set[0][0]

    # initiate strategies
    for r in router.routes:
        StrategyClass = jh.get_strategy_class(r.strategy_name)

        try:
            r.strategy = StrategyClass()
        except TypeError:
            raise exceptions.InvalidStrategy(
                "Looks like the structure of your strategy directory is incorrect. Make sure to include the strategy INSIDE the __init__.py file."
                "\nIf you need working examples, check out: https://github.com/jesse-ai/example-strategies"
            )
        except:
            raise

        r.strategy.name = r.strategy_name
        r.strategy.exchange = r.exchange
        r.strategy.symbol = r.symbol
        r.strategy.timeframe = r.timeframe

        # inject hyper parameters (used for optimize_mode)
        # convert DNS string into hyperparameters
        if r.dna and hyperparameters is None:
            hyperparameters = jh.dna_to_hp(r.strategy.hyperparameters(), r.dna)

        # inject hyperparameters sent within the optimize mode
        if hyperparameters is not None:
            r.strategy.hp = hyperparameters

        # init few objects that couldn't be initiated in Strategy __init__
        # it also injects hyperparameters into self.hp in case the route does not uses any DNAs
        r.strategy._init_objects()

        selectors.get_position(r.exchange, r.symbol).strategy = r.strategy

    # add initial balance
    save_daily_portfolio_balance()

    with click.progressbar(length=length,
                           label='Executing simulation...') as progressbar:
        for i in range(length):
            # update time
            store.app.time = first_candles_set[i][0] + 60_000

            # add candles
            for j in candles:
                short_candle = candles[j]['candles'][i]
                if i != 0:
                    previous_short_candle = candles[j]['candles'][i - 1]
                    short_candle = _get_fixed_jumped_candle(
                        previous_short_candle, short_candle)
                exchange = candles[j]['exchange']
                symbol = candles[j]['symbol']

                store.candles.add_candle(short_candle,
                                         exchange,
                                         symbol,
                                         '1m',
                                         with_execution=False,
                                         with_generation=False)

                # print short candle
                if jh.is_debuggable('shorter_period_candles'):
                    print_candle(short_candle, True, symbol)

                _simulate_price_change_effect(short_candle, exchange, symbol)

                # generate and add candles for bigger timeframes
                for timeframe in config['app']['considering_timeframes']:
                    # for 1m, no work is needed
                    if timeframe == '1m':
                        continue

                    count = jh.timeframe_to_one_minutes(timeframe)
                    until = count - ((i + 1) % count)

                    if (i + 1) % count == 0:
                        generated_candle = generate_candle_from_one_minutes(
                            timeframe,
                            candles[j]['candles'][(i - (count - 1)):(i + 1)])
                        store.candles.add_candle(generated_candle,
                                                 exchange,
                                                 symbol,
                                                 timeframe,
                                                 with_execution=False,
                                                 with_generation=False)

            # update progressbar
            if not jh.is_debugging() and not jh.should_execute_silently(
            ) and i % 60 == 0:
                progressbar.update(60)

            # now that all new generated candles are ready, execute
            for r in router.routes:
                count = jh.timeframe_to_one_minutes(r.timeframe)
                # 1m timeframe
                if r.timeframe == timeframes.MINUTE_1:
                    r.strategy._execute()
                elif (i + 1) % count == 0:
                    # print candle
                    if jh.is_debuggable('trading_candles'):
                        print_candle(
                            store.candles.get_current_candle(
                                r.exchange, r.symbol, r.timeframe), False,
                            r.symbol)
                    r.strategy._execute()

            # now check to see if there's any MARKET orders waiting to be executed
            store.orders.execute_pending_market_orders()

            if i != 0 and i % 1440 == 0:
                save_daily_portfolio_balance()

    if not jh.should_execute_silently():
        if jh.is_debuggable('trading_candles') or jh.is_debuggable(
                'shorter_period_candles'):
            print('\n')

        # 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)))

    for r in router.routes:
        r.strategy._terminate()
        store.orders.execute_pending_market_orders()

    # now that backtest is finished, add finishing balance
    save_daily_portfolio_balance()
Exemplo n.º 24
0
def simulator(
        candles: dict, run_silently: bool, hyperparameters: dict = None
) -> None:
    begin_time_track = time.time()
    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
    try:
        store.app.starting_time = first_candles_set[0][0]
    except IndexError:
        raise IndexError('Check your "warm_up_candles" config value')
    store.app.time = first_candles_set[0][0]

    # initiate strategies
    for r in router.routes:
        # if the r.strategy is str read it from file
        if isinstance(r.strategy_name, str):
            StrategyClass = jh.get_strategy_class(r.strategy_name)
        # else it is a class object so just use it
        else:
            StrategyClass = r.strategy_name

        try:
            r.strategy = StrategyClass()
        except TypeError:
            raise exceptions.InvalidStrategy(
                "Looks like the structure of your strategy directory is incorrect. Make sure to include the strategy INSIDE the __init__.py file."
                "\nIf you need working examples, check out: https://github.com/jesse-ai/example-strategies"
            )
        except:
            raise

        r.strategy.name = r.strategy_name
        r.strategy.exchange = r.exchange
        r.strategy.symbol = r.symbol
        r.strategy.timeframe = r.timeframe

        # read the dna from strategy's dna() and use it for injecting inject hyperparameters
        # first convert DNS string into hyperparameters
        if len(r.strategy.dna()) > 0 and hyperparameters is None:
            hyperparameters = jh.dna_to_hp(r.strategy.hyperparameters(), r.strategy.dna())

        # inject hyperparameters sent within the optimize mode
        if hyperparameters is not None:
            r.strategy.hp = hyperparameters

        # init few objects that couldn't be initiated in Strategy __init__
        # it also injects hyperparameters into self.hp in case the route does not uses any DNAs
        r.strategy._init_objects()

        selectors.get_position(r.exchange, r.symbol).strategy = r.strategy

    # add initial balance
    save_daily_portfolio_balance()

    progressbar = Progressbar(length, step=60)
    for i in range(length):
        # update time
        store.app.time = first_candles_set[i][0] + 60_000

        # add candles
        for j in candles:
            short_candle = candles[j]['candles'][i]
            if i != 0:
                previous_short_candle = candles[j]['candles'][i - 1]
                short_candle = _get_fixed_jumped_candle(previous_short_candle, short_candle)
            exchange = candles[j]['exchange']
            symbol = candles[j]['symbol']

            store.candles.add_candle(short_candle, exchange, symbol, '1m', with_execution=False,
                                     with_generation=False)

            # print short candle
            if jh.is_debuggable('shorter_period_candles'):
                print_candle(short_candle, True, symbol)

            _simulate_price_change_effect(short_candle, exchange, symbol)

            # generate and add candles for bigger timeframes
            for timeframe in config['app']['considering_timeframes']:
                # for 1m, no work is needed
                if timeframe == '1m':
                    continue

                count = jh.timeframe_to_one_minutes(timeframe)
                # until = count - ((i + 1) % count)

                if (i + 1) % count == 0:
                    generated_candle = generate_candle_from_one_minutes(
                        timeframe,
                        candles[j]['candles'][(i - (count - 1)):(i + 1)])
                    store.candles.add_candle(generated_candle, exchange, symbol, timeframe, with_execution=False,
                                             with_generation=False)

        # update progressbar
        if not run_silently and i % 60 == 0:
            progressbar.update()
            sync_publish('progressbar', {
                'current': progressbar.current,
                'estimated_remaining_seconds': progressbar.estimated_remaining_seconds
            })

        # now that all new generated candles are ready, execute
        for r in router.routes:
            count = jh.timeframe_to_one_minutes(r.timeframe)
            # 1m timeframe
            if r.timeframe == timeframes.MINUTE_1:
                r.strategy._execute()
            elif (i + 1) % count == 0:
                # print candle
                if jh.is_debuggable('trading_candles'):
                    print_candle(store.candles.get_current_candle(r.exchange, r.symbol, r.timeframe), False,
                                 r.symbol)
                r.strategy._execute()

        # now check to see if there's any MARKET orders waiting to be executed
        store.orders.execute_pending_market_orders()

        if i != 0 and i % 1440 == 0:
            save_daily_portfolio_balance()

    if not run_silently:
        # print executed time for the backtest session
        finish_time_track = time.time()
        sync_publish('alert', {
            'message': f'Successfully executed backtest simulation in: {round(finish_time_track - begin_time_track, 2)} seconds',
            'type': 'success'
        })

    for r in router.routes:
        r.strategy._terminate()
        store.orders.execute_pending_market_orders()

    # now that backtest is finished, add finishing balance
    save_daily_portfolio_balance()
Exemplo n.º 25
0
def test_is_debuggable():
    debug_item = 'order_submission'
    assert jh.is_debuggable(debug_item) is False
Exemplo n.º 26
0
def generate_signal(start_date: str,
                    finish_date: str,
                    candles=None,
                    chart=False,
                    tradingview=False,
                    csv=False,
                    json=False):
    # clear the screen
    if not jh.should_execute_silently():
        click.clear()

    # validate routes
    validate_routes(router)

    # initiate candle store
    store.candles.init_storage(5000)

    # load historical candles
    if candles is None:
        print('loading candles...')
        candles = load_candles(start_date, finish_date)
        click.clear()

    if not jh.should_execute_silently():
        # print candles table
        key = '{}-{}'.format(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')

        # print routes table
        table.multi_value(stats.routes(router.routes))
        print('\n')

        # print guidance for debugging candles
        if jh.is_debuggable('trading_candles') or jh.is_debuggable(
                'shorter_period_candles'):
            print(
                '     Symbol  |     timestamp    | open | close | high | low | volume'
            )

    # run backtest simulation
    signal_simulator(candles)

    if not jh.should_execute_silently():
        # print trades statistics
        if store.completed_trades.count > 0:
            print('\n')
            table.key_value(report.portfolio_metrics(),
                            'Metrics',
                            alignments=('left', 'right'))
            print('\n')

            # save logs
            store_logs(json, tradingview, csv)

            if chart:
                charts.portfolio_vs_asset_returns()

            # # clone the trades so that the original is not mutated
            # completed_trades = copy.deepcopy(store.completed_trades.trades)
            #
            # for trade in completed_trades:
            #     print(trade.to_dict())
            #
            # # filter trades which were generated for current day
            # completed_trades = filter(lambda x: x.opened_at == jh.get_current_time_in_epoch(), completed_trades)
            #
            # # TODO: add a slack notification here instead of print to notify the user
            # print("Trades to execute today: ")
            # for trade in completed_trades:
            #     print(trade.to_dict())
        else:
            print(jh.color('No trades were completed/closed.', 'yellow'))

        open_positions = store.positions.get_open_positions()
        open_orders = store.orders.get_all_active_orders()

        current_report = jh.generate_signals_report(open_positions,
                                                    open_orders)

        return current_report
Exemplo n.º 27
0
def run(start_date: str,
        finish_date: str,
        candles=None,
        chart=False,
        tradingview=False,
        csv=False,
        json=False):
    # clear the screen
    if not jh.should_execute_silently():
        click.clear()

    # validate routes
    validate_routes(router)

    # initiate candle store
    store.candles.init_storage(5000)

    # load historical candles
    if candles is None:
        print('loading candles...')
        candles = load_candles(start_date, finish_date)
        click.clear()

    if not jh.should_execute_silently():
        # print candles table
        key = '{}-{}'.format(config['app']['trading_exchanges'][0],
                             config['app']['trading_symbols'][0])
        table.key_value(stats.candles(candles[key]['candles']),
                        'candles',
                        alignments=('left', 'right'))
        print('\n')

        # print routes table
        table.multi_value(stats.routes(router.routes))
        print('\n')

        # print guidance for debugging candles
        if jh.is_debuggable('trading_candles') or jh.is_debuggable(
                'shorter_period_candles'):
            print(
                '     Symbol  |     timestamp    | open | close | high | low | volume'
            )

    # run backtest simulation
    simulator(candles)

    if not jh.should_execute_silently():
        # print trades statistics
        if store.completed_trades.count > 0:
            print('\n')
            table.key_value(report.portfolio_metrics(),
                            'Metrics',
                            alignments=('left', 'right'))
            print('\n')

            # save logs
            store_logs(json, tradingview, csv)

            if chart:
                charts.portfolio_vs_asset_returns()
        else:
            print(jh.color('No trades were made.', 'yellow'))
Exemplo n.º 28
0
def run(start_date: str,
        finish_date: str,
        candles: Dict[str, Dict[str, Union[str, np.ndarray]]] = None,
        chart: bool = False,
        tradingview: bool = False,
        full_reports: bool = False,
        csv: bool = False,
        json: bool = False) -> None:
    # clear the screen
    if not jh.should_execute_silently():
        click.clear()

    # validate routes
    validate_routes(router)

    # initiate candle store
    store.candles.init_storage(5000)

    # load historical candles
    if candles is None:
        print('loading candles...')
        candles = load_candles(start_date, finish_date)
        click.clear()

    if not jh.should_execute_silently():
        # print candles table
        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')

        # print routes table
        table.multi_value(stats.routes(router.routes))
        print('\n')

        # print guidance for debugging candles
        if jh.is_debuggable('trading_candles') or jh.is_debuggable(
                'shorter_period_candles'):
            print(
                '     Symbol  |     timestamp    | open | close | high | low | volume'
            )

    # run backtest simulation
    simulator(candles)

    if not jh.should_execute_silently():
        # print trades metrics
        if store.completed_trades.count > 0:

            change = []
            # calcualte market change
            for e in router.routes:
                if e.strategy is None:
                    return

                first = Candle.select(Candle.close).where(
                    Candle.timestamp == jh.date_to_timestamp(start_date),
                    Candle.exchange == e.exchange,
                    Candle.symbol == e.symbol).first()
                last = Candle.select(Candle.close).where(
                    Candle.timestamp == jh.date_to_timestamp(finish_date) -
                    60000, Candle.exchange == e.exchange,
                    Candle.symbol == e.symbol).first()

                change.append(
                    ((last.close - first.close) / first.close) * 100.0)

            data = report.portfolio_metrics()
            data.append(
                ['Market Change', f"{str(round(np.average(change), 2))}%"])
            print('\n')
            table.key_value(data, 'Metrics', alignments=('left', 'right'))
            print('\n')

            # save logs
            more = ""
            routes_count = len(router.routes)
            if routes_count > 1:
                more = f"-and-{routes_count-1}-more"

            study_name = f"{router.routes[0].strategy_name}-{router.routes[0].exchange}-{router.routes[0].symbol}-{router.routes[0].timeframe}{more}-{start_date}-{finish_date}"
            store_logs(study_name, json, tradingview, csv)

            if chart:
                charts.portfolio_vs_asset_returns(study_name)

            # QuantStats' report
            if full_reports:

                price_data = []

                # load close candles for Buy and hold and calculate pct_change
                for index, c in enumerate(
                        config['app']['considering_candles']):
                    exchange, symbol = c[0], c[1]
                    if exchange in config['app'][
                            'trading_exchanges'] and symbol in config['app'][
                                'trading_symbols']:
                        # fetch from database
                        candles_tuple = Candle.select(
                            Candle.timestamp, Candle.close).where(
                                Candle.timestamp.between(
                                    jh.date_to_timestamp(start_date),
                                    jh.date_to_timestamp(finish_date) - 60000),
                                Candle.exchange == exchange,
                                Candle.symbol == symbol).order_by(
                                    Candle.timestamp.asc()).tuples()

                        candles = np.array(candles_tuple)

                        timestamps = candles[:, 0]
                        price_data.append(candles[:, 1])

                price_data = np.transpose(price_data)
                price_df = pd.DataFrame(price_data,
                                        index=pd.to_datetime(timestamps,
                                                             unit="ms"),
                                        dtype=float).resample('D').mean()
                price_pct_change = price_df.pct_change(1).fillna(0)
                bh_daily_returns_all_routes = price_pct_change.mean(1)
                quantstats.quantstats_tearsheet(bh_daily_returns_all_routes,
                                                study_name)
        else:
            print(jh.color('No trades were made.', 'yellow'))
Exemplo n.º 29
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)))
Exemplo n.º 30
0
    def on_order_submission(self, order: Order, skip_market_order=True):
        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

        # 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
            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]))
            # self.available_assets[quote_asset] -= order.qty * order.price
            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)))