コード例 #1
0
def backtest(trades_list: [dict], settings: dict):
    # trades format is [{price: float, buyer_maker: bool}]

    # no static mode
    grid_spacing = settings['grid_spacing']
    grid_coefficient = settings['grid_coefficient']
    price_step = settings['price_step']
    qty_step = settings['qty_step']
    inverse = settings['inverse']
    liq_diff_threshold = settings['liq_diff_threshold']
    stop_loss_pos_reduction = settings['stop_loss_pos_reduction']
    min_qty = settings['min_qty']
    ddown_factor = settings['ddown_factor']
    leverage = settings['leverage']
    starting_balance = settings['balance']
    maker_fee = settings['maker_fee']
    taker_fee = settings['taker_fee']
    min_markup = settings['min_markup']
    max_markup = settings['max_markup']
    n_close_orders = settings['n_close_orders']
    break_on_loss = settings['break_on_loss']

    min_notional = settings[
        'min_notional'] if 'min_notional' in settings else 0.0

    if inverse:
        calc_cost = lambda qty_, price_: qty_ / price_
        calc_liq_price = lambda balance_, pos_size_, pos_price_: \
            bybit_calc_cross_shrt_liq_price(balance_, pos_size_, pos_price_, leverage=leverage) \
                if pos_size_ < 0.0 else \
                bybit_calc_cross_long_liq_price(balance_, pos_size_, pos_price_, leverage=leverage)
        if settings['default_qty'] <= 0.0:
            calc_default_qty_ = lambda balance_, last_price: \
                calc_default_qty(min_qty, qty_step, balance_ * last_price, settings['default_qty'])
        else:
            calc_default_qty_ = lambda balance_, last_price: settings[
                'default_qty']
        calc_max_pos_size = lambda balance_, price_: balance_ * price_ * leverage
    else:
        calc_cost = lambda qty_, price_: qty_ * price_
        calc_liq_price = lambda balance_, pos_size_, pos_price_: \
            binance_calc_cross_shrt_liq_price(balance_, pos_size_, pos_price_, leverage=leverage) \
                if pos_size_ < 0.0 else \
                binance_calc_cross_long_liq_price(balance_, pos_size_, pos_price_, leverage=leverage)
        if settings['default_qty'] <= 0.0:
            calc_default_qty_ = lambda balance_, last_price: \
                calc_default_qty(max(min_qty, round_up(min_notional / last_price, qty_step)),
                                 qty_step, balance_ / last_price, settings['default_qty'])
        else:
            calc_default_qty_ = lambda balance_, last_price: \
                max(settings['default_qty'], round_up(min_notional / last_price, qty_step))
        calc_max_pos_size = lambda balance_, price_: balance_ / price_ * leverage

    calc_long_reentry_price_ = lambda balance_, pos_margin_, pos_price_, highest_bid_: \
        min(highest_bid_, calc_long_reentry_price(price_step, grid_spacing, grid_coefficient,
                                           balance_, pos_margin_, pos_price_))
    calc_shrt_reentry_price_ = lambda balance_, pos_margin_, pos_price_, lowest_ask_: \
        max(lowest_ask_, calc_shrt_reentry_price(price_step, grid_spacing, grid_coefficient,
                                                 balance_, pos_margin_, pos_price_))
    balance = starting_balance
    trades = []
    ob = [
        min(trades_list[0]['price'], trades_list[1]['price']),
        max(trades_list[0]['price'], trades_list[1]['price'])
    ]

    pos_size = 0.0
    pos_price = 0.0

    bid_price = ob[0]
    ask_price = ob[1]

    liq_price = 0.0

    pnl_sum = 0.0
    loss_sum = 0.0

    ema_alpha = 2 / (settings['ema_span'] + 1)
    ema_alpha_ = 1 - ema_alpha
    ema = trades_list[0]['price']

    k = 0
    prev_len_trades = 0

    for t in trades_list:
        if t['buyer_maker']:
            # buy
            if pos_size == 0.0:
                # no pos
                bid_price = min(ob[0], round_dn(ema, price_step))
                bid_qty = calc_default_qty_(balance, ob[0])
            elif pos_size > 0.0:
                # long pos
                liq_price = calc_liq_price(balance, pos_size, pos_price)
                if calc_diff(liq_price, ob[0]) < liq_diff_threshold:
                    # long soft stop, no reentry
                    bid_price = 0.0
                else:
                    # long reentry
                    bid_qty = calc_entry_qty(qty_step, ddown_factor,
                                             calc_default_qty_(balance, ob[0]),
                                             calc_max_pos_size(balance, ob[0]),
                                             pos_size)
                    if bid_qty >= min_qty:
                        pos_margin = calc_cost(pos_size, pos_price) / leverage
                        bid_price = calc_long_reentry_price_(
                            balance, pos_margin, pos_price, ob[0])
                    else:
                        bid_price = 0.0
            else:
                # short pos
                liq_price = calc_liq_price(balance, pos_size, pos_price)
                if calc_diff(liq_price, ob[0]) < liq_diff_threshold:
                    # short soft stop
                    bid_price = ob[0]
                    bid_qty = round_up(-pos_size * stop_loss_pos_reduction,
                                       qty_step)
                else:
                    if t['price'] <= pos_price:
                        # short close
                        qtys, prices = calc_shrt_closes(
                            price_step, qty_step, min_qty, min_markup,
                            max_markup, pos_size, pos_price, ob[0],
                            n_close_orders)
                        bid_qty = qtys[0]
                        bid_price = prices[0]
                    else:
                        bid_price = 0.0
            ob[0] = t['price']
            if t['price'] < bid_price:
                # filled trade
                cost = calc_cost(bid_qty, bid_price)
                pnl = -cost * maker_fee
                if pos_size >= 0.0:
                    # create or increase long pos
                    trade_side = 'long'
                    trade_type = 'entry'
                    new_pos_size = pos_size + bid_qty
                    pos_price = pos_price * (pos_size / new_pos_size) + \
                        bid_price * (bid_qty / new_pos_size)
                    pos_size = new_pos_size
                    roi = 0.0
                else:
                    # close short pos
                    trade_side = 'shrt'
                    gain = pos_price / bid_price - 1
                    pnl += cost * gain
                    trade_type = 'close' if gain > 0.0 else 'stop_loss'
                    pos_size = pos_size + bid_qty
                    roi = gain * leverage
                balance += pnl
                pnl_sum += pnl
                trades.append({
                    'trade_id':
                    k,
                    'side':
                    trade_side,
                    'type':
                    trade_type,
                    'price':
                    bid_price,
                    'qty':
                    bid_qty,
                    'pnl':
                    pnl,
                    'roi':
                    roi,
                    'pos_size':
                    pos_size,
                    'pos_price':
                    pos_price,
                    'liq_price':
                    calc_liq_price(balance, pos_size, pos_price)
                })
        else:
            # sell
            if pos_size == 0.0:
                # no pos
                ask_price = max(ob[1], round_up(ema, price_step))
                ask_qty = -calc_default_qty_(balance, ob[1])
            elif pos_size > 0.0:
                # long pos
                liq_price = calc_liq_price(balance, pos_size, pos_price)
                if calc_diff(liq_price, ob[1]) < liq_diff_threshold:
                    # long soft stop
                    ask_price = ob[1]
                    ask_qty = -round_up(pos_size * stop_loss_pos_reduction,
                                        qty_step)
                else:
                    if t['price'] >= pos_price:
                        # long close
                        qtys, prices = calc_long_closes(
                            price_step, qty_step, min_qty, min_markup,
                            max_markup, pos_size, pos_price, ob[1],
                            n_close_orders)
                        ask_qty = qtys[0]
                        ask_price = prices[0]
                    else:
                        ask_price = 9e9
            else:
                # short pos
                liq_price = calc_liq_price(balance, pos_size, pos_price)
                if calc_diff(liq_price, ob[1]) < liq_diff_threshold:
                    # shrt soft stop, no reentry
                    ask_price = 9e9
                else:
                    # shrt reentry
                    ask_qty = -calc_entry_qty(
                        qty_step, ddown_factor,
                        calc_default_qty_(balance, ob[1]),
                        calc_max_pos_size(balance, ob[1]), pos_size)
                    if -ask_qty >= min_qty:
                        pos_margin = calc_cost(-pos_size, pos_price) / leverage
                        ask_price = calc_shrt_reentry_price_(
                            balance, pos_margin, pos_price, ob[0])
                    else:
                        ask_price = 9e9
            ob[1] = t['price']
            if t['price'] > ask_price:
                # filled trade
                cost = calc_cost(-ask_qty, ask_price)
                pnl = -cost * maker_fee
                if pos_size <= 0.0:
                    # create or increase shrt pos
                    trade_side = 'shrt'
                    trade_type = 'entry'
                    new_pos_size = pos_size + ask_qty
                    pos_price = pos_price * (pos_size / new_pos_size) + \
                        ask_price * (ask_qty / new_pos_size)
                    pos_size = new_pos_size
                    roi = 0.0
                else:
                    # close long pos
                    trade_side = 'long'
                    gain = ask_price / pos_price - 1
                    pnl += cost * gain
                    trade_type = 'close' if gain > 0.0 else 'stop_loss'
                    pos_size = pos_size + ask_qty
                    roi = gain * leverage
                balance += pnl
                pnl_sum += pnl
                trades.append({
                    'trade_id':
                    k,
                    'side':
                    trade_side,
                    'type':
                    trade_type,
                    'price':
                    ask_price,
                    'qty':
                    ask_qty,
                    'pnl':
                    pnl,
                    'roi':
                    roi,
                    'pos_size':
                    pos_size,
                    'pos_price':
                    pos_price,
                    'liq_price':
                    calc_liq_price(balance, pos_size, pos_price)
                })
        ema = ema * ema_alpha_ + t['price'] * ema_alpha
        k += 1
        if k % 10000 == 0 or len(trades) != prev_len_trades:
            if trades and trades[-1]['type'] == 'stop_loss':
                loss_sum += pnl
                if break_on_loss:
                    print('break on loss')
                    return trades
            balance = max(balance, settings['balance'])
            prev_len_trades = len(trades)
            progress = k / len(trades_list)
            if pnl_sum < 0.0 and progress >= settings['break_on_negative_pnl']:
                print('break on negative pnl')
                return trades
            line = f"\r{progress:.3f} pnl sum {pnl_sum:.8f} "
            line += f"loss sum {loss_sum:.6f} balance {balance:.6f} "
            line += f"qty {calc_default_qty_(balance, ob[0]):.4f} "
            line += f"liq diff {min(1.0, calc_diff(trades[-1]['liq_price'], ob[0])):.3f} "
            line += f"pos size {pos_size:.6f} "
            print(line, end=' ')
    return trades
コード例 #2
0
def backtest(trades_list: [dict], settings: dict):
    # trades format is [{price: float, buyer_maker: bool}]

    # no static mode
    grid_spacing = settings['grid_spacing']
    grid_coefficient = settings['grid_coefficient']
    price_step = settings['price_step']
    qty_step = settings['qty_step']
    inverse = settings['inverse']
    liq_diff_threshold = settings['liq_diff_threshold']
    stop_loss_pos_reduction = settings['stop_loss_pos_reduction']
    min_qty = settings['min_qty']
    ddown_factor = settings['ddown_factor']
    leverage = settings['leverage']
    max_leverage = settings['max_leverage']
    maker_fee = settings['maker_fee']
    taker_fee = settings['taker_fee']
    min_markup = settings['min_markup']
    max_markup = settings['max_markup']
    n_close_orders = settings['n_close_orders']

    do_long = settings['do_long']
    do_shrt = settings['do_shrt']

    min_notional = settings[
        'min_notional'] if 'min_notional' in settings else 0.0
    cross_mode = settings['cross_mode'] if 'cross_mode' in settings else True

    if inverse:
        calc_cost = lambda qty_, price_: qty_ / price_
        if settings['cross_mode']:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                bybit_calc_cross_shrt_liq_price(balance_,
                                                pos_size_,
                                                pos_price_,
                                                leverage=max_leverage) \
                    if pos_size_ < 0.0 else \
                    bybit_calc_cross_long_liq_price(balance_,
                                                    pos_size_,
                                                    pos_price_,
                                                    leverage=max_leverage)
        else:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                bybit_calc_isolated_shrt_liq_price(balance_,
                                                   pos_size_,
                                                   pos_price_,
                                                   leverage=leverage) \
                    if pos_size_ < 0.0 else \
                    bybit_calc_isolated_long_liq_price(balance_,
                                                       pos_size_,
                                                       pos_price_,
                                                       leverage=leverage)
        if settings['default_qty'] <= 0.0:
            calc_default_qty_ = lambda balance_, last_price: \
                calc_default_qty(min_qty, qty_step, balance_ * last_price, settings['default_qty'])
        else:
            calc_default_qty_ = lambda balance_, last_price: settings[
                'default_qty']
        calc_max_pos_size = lambda balance_, price_: balance_ * price_ * leverage
    else:
        calc_cost = lambda qty_, price_: qty_ * price_
        if settings['cross_mode']:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                binance_calc_cross_shrt_liq_price(balance_,
                                                  pos_size_,
                                                  pos_price_,
                                                  leverage=leverage) \
                    if pos_size_ < 0.0 else \
                    binance_calc_cross_long_liq_price(balance_,
                                                      pos_size_,
                                                      pos_price_,
                                                      leverage=leverage)
        else:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                binance_calc_isolated_shrt_liq_price(balance_,
                                                     pos_size_,
                                                     pos_price_,
                                                     leverage=leverage) \
                    if pos_size_ < 0.0 else \
                    binance_calc_isolated_long_liq_price(balance_,
                                                         pos_size_,
                                                         pos_price_,
                                                         leverage=leverage)
        if settings['default_qty'] <= 0.0:
            calc_default_qty_ = lambda balance_, last_price: \
                calc_default_qty(max(min_qty, round_up(min_notional / last_price, qty_step)),
                                 qty_step, balance_ / last_price, settings['default_qty'])
        else:
            calc_default_qty_ = lambda balance_, last_price: \
                max(settings['default_qty'], round_up(min_notional / last_price, qty_step))
        calc_max_pos_size = lambda balance_, price_: balance_ / price_ * leverage

    calc_long_reentry_price_ = lambda balance_, pos_margin_, pos_price_, highest_bid_: \
        min(highest_bid_, calc_long_reentry_price(price_step, grid_spacing, grid_coefficient,
                                           balance_, pos_margin_, pos_price_))
    calc_shrt_reentry_price_ = lambda balance_, pos_margin_, pos_price_, lowest_ask_: \
        max(lowest_ask_, calc_shrt_reentry_price(price_step, grid_spacing, grid_coefficient,
                                                 balance_, pos_margin_, pos_price_))
    balance = settings['starting_balance']
    trades = []
    ob = [
        min(trades_list[0]['price'], trades_list[1]['price']),
        max(trades_list[0]['price'], trades_list[1]['price'])
    ]

    pos_size = 0.0
    pos_price = 0.0

    bid_price = ob[0]
    ask_price = ob[1]

    liq_price = 0.0

    pnl_sum = 0.0
    loss_sum = 0.0
    profit_sum = 0.0

    ema_alpha = 2 / (settings['ema_span'] + 1)
    ema_alpha_ = 1 - ema_alpha
    ema = trades_list[0]['price']

    k = 0
    break_on = {
        e[0]: eval(e[1])
        for e in settings['break_on'] if e[0].startswith('ON:')
    }
    for t in trades_list:
        append_trade = False
        if t['buyer_maker']:
            # buy
            if pos_size == 0.0:
                # no pos
                if do_long:
                    bid_price = min(ob[0], round_dn(ema, price_step))
                    bid_qty = calc_default_qty_(balance, ob[0])
                else:
                    bid_price = 0.0
                    bid_qty = 0.0
            elif pos_size > 0.0:
                if calc_diff(
                        liq_price, ob[1]
                ) < liq_diff_threshold and t['price'] <= liq_price:
                    # long liq
                    print(f'break on long liquidation, liq price: {liq_price}')
                    return []
                # long reentry
                bid_qty = calc_entry_qty(qty_step, ddown_factor,
                                         calc_default_qty_(balance, ob[0]),
                                         calc_max_pos_size(balance, ob[0]),
                                         pos_size)
                if bid_qty >= max(min_qty, min_notional / t['price']):
                    pos_margin = calc_cost(pos_size, pos_price) / leverage
                    bid_price = calc_long_reentry_price_(
                        balance, pos_margin, pos_price, ob[0])
                else:
                    bid_price = 0.0
            else:
                # short pos
                if calc_diff(liq_price, ob[0]) < liq_diff_threshold:
                    # short soft stop
                    bid_price = ob[0]
                    bid_qty = round_up(-pos_size * stop_loss_pos_reduction,
                                       qty_step)
                else:
                    if t['price'] <= pos_price:
                        # short close
                        min_close_qty = \
                            max(min_qty,
                                round_dn(calc_default_qty_(balance, ob[0]) * 0.5, qty_step))
                        qtys, prices = calc_shrt_closes(
                            price_step, qty_step, min_qty, min_markup,
                            max_markup, min_close_qty, pos_size, pos_price,
                            ob[0], n_close_orders)
                        if len(qtys) > 0:
                            bid_qty = qtys[0]
                            bid_price = prices[0]
                        else:
                            bid_price = 0.0
                    else:
                        bid_price = 0.0
            ob[0] = t['price']
            if t['price'] < bid_price and bid_qty >= min_qty:
                # filled trade
                qty = bid_qty
                price = bid_price
                cost = calc_cost(bid_qty, bid_price)
                pnl = -cost * maker_fee
                if pos_size >= 0.0:
                    # create or increase long pos
                    trade_side = 'long'
                    trade_type = 'entry'
                    new_pos_size = pos_size + bid_qty
                    pos_price = pos_price * (pos_size / new_pos_size) + \
                        bid_price * (bid_qty / new_pos_size)
                    pos_size = new_pos_size
                    roi = 0.0
                else:
                    # close short pos
                    trade_side = 'shrt'
                    gain = pos_price / bid_price - 1
                    pnl += cost * gain
                    if gain > 0.0:
                        trade_type = 'close'
                        profit_sum += pnl
                    else:
                        trade_type = 'stop_loss'
                        loss_sum += pnl
                    pos_size = pos_size + bid_qty
                    roi = gain * leverage
                balance += pnl
                pnl_sum += pnl
                liq_price = calc_liq_price(balance, pos_size, pos_price)
                append_trade = True
        else:
            # sell
            if pos_size == 0.0:
                # no pos
                if do_shrt:
                    ask_price = max(ob[1], round_up(ema, price_step))
                    ask_qty = -calc_default_qty_(balance, ob[1])
                else:
                    ask_price = 9e9
                    ask_qty = 0.0
            elif pos_size > 0.0:
                # long pos
                if calc_diff(liq_price, ob[1]) < liq_diff_threshold:
                    # long soft stop
                    ask_price = ob[1]
                    ask_qty = -round_up(pos_size * stop_loss_pos_reduction,
                                        qty_step)
                else:
                    if t['price'] >= pos_price:
                        # long close
                        min_close_qty = \
                            max(min_qty,
                                round_dn(calc_default_qty_(balance, ob[1]) * 0.5, qty_step))
                        qtys, prices = calc_long_closes(
                            price_step, qty_step, min_qty, min_markup,
                            max_markup, min_close_qty, pos_size, pos_price,
                            ob[1], n_close_orders)
                        if len(qtys) > 0:
                            ask_qty = qtys[0]
                            ask_price = prices[0]
                        else:
                            ask_price = 9e9
                    else:
                        ask_price = 9e9
            else:
                if calc_diff(
                        liq_price, ob[1]
                ) < liq_diff_threshold and t['price'] >= liq_price:
                    # shrt liq
                    print(f'break on shrt liquidation, liq price: {liq_price}')
                    return []
                # shrt reentry
                ask_qty = -calc_entry_qty(
                    qty_step, ddown_factor, calc_default_qty_(balance, ob[1]),
                    calc_max_pos_size(balance, ob[1]), pos_size)
                if -ask_qty >= max(min_qty, min_notional / t['price']):
                    pos_margin = calc_cost(-pos_size, pos_price) / leverage
                    ask_price = calc_shrt_reentry_price_(
                        balance, pos_margin, pos_price, ob[0])
                else:
                    ask_price = 9e9
            ob[1] = t['price']
            if t['price'] > ask_price and abs(ask_qty) >= min_qty:
                # filled trade
                qty = ask_qty
                price = ask_price
                cost = calc_cost(-ask_qty, ask_price)
                pnl = -cost * maker_fee
                if pos_size <= 0.0:
                    # create or increase shrt pos
                    trade_side = 'shrt'
                    trade_type = 'entry'
                    new_pos_size = pos_size + ask_qty
                    pos_price = pos_price * (pos_size / new_pos_size) + \
                        ask_price * (ask_qty / new_pos_size)
                    pos_size = new_pos_size
                    roi = 0.0
                else:
                    # close long pos
                    trade_side = 'long'
                    gain = ask_price / pos_price - 1
                    pnl += cost * gain
                    if gain > 0.0:
                        trade_type = 'close'
                        profit_sum += pnl
                    else:
                        trade_type = 'stop_loss'
                        loss_sum += pnl
                    pos_size = pos_size + ask_qty
                    roi = gain * leverage
                balance += pnl
                pnl_sum += pnl
                liq_price = calc_liq_price(balance, pos_size, pos_price)
                append_trade = True
        if append_trade:
            progress = k / len(trades_list)
            total_gain = (pnl_sum + settings['starting_balance']
                          ) / settings['starting_balance']
            n_days_ = (t['timestamp'] -
                       trades_list[0]['timestamp']) / (1000 * 60 * 60 * 24)
            adg = total_gain**(1 / n_days_)
            trades.append({
                'trade_id':
                k,
                'side':
                trade_side,
                'type':
                trade_type,
                'price':
                price,
                'qty':
                qty,
                'pnl':
                pnl,
                'roi':
                roi,
                'pos_size':
                pos_size,
                'pos_price':
                pos_price,
                'balance':
                balance,
                'max_pos_size':
                calc_max_pos_size(balance, t['price']),
                'pnl_sum':
                pnl_sum,
                'loss_sum':
                loss_sum,
                'profit_sum':
                profit_sum,
                'progress':
                progress,
                'liq_price':
                liq_price,
                'gain':
                total_gain,
                'n_days':
                n_days_,
                'average_daily_gain':
                adg,
                'liq_diff':
                min(1.0, calc_diff(liq_price, t['price'])),
                'timestamp':
                t['timestamp']
            })
            balance = max(balance, settings['starting_balance'])
            line = f"\r{progress:.3f} net pnl {pnl_sum:.8f} "
            line += f"profit sum {profit_sum:.5f} "
            line += f"loss sum {loss_sum:.5f} "
            line += f"balance {balance:.5f} qty {calc_default_qty_(balance, ob[0]):.4f} "
            line += f"adg {trades[-1]['average_daily_gain']:.3f} "
            line += f"max pos pct {abs(pos_size) / calc_max_pos_size(balance, t['price']):.3f} "
            line += f"liq diff {min(1.0, calc_diff(trades[-1]['liq_price'], ob[0])):.3f} "
            line += f"pos size {pos_size:.4f} "
            print(line, end=' ')
            for key, condition in break_on.items():
                if condition(trades[-1], t):
                    print('break on', key)
                    return []
        ema = ema * ema_alpha_ + t['price'] * ema_alpha
        k += 1
    return trades
コード例 #3
0
ファイル: backtest.py プロジェクト: JohnKearney1/Passiv-CLI
def backtest(ticks: np.ndarray, settings: dict):

    # ticks formatting [price: float, buyer_maker: bool, timestamp: float]

    ss = settings

    pos_size, pos_price, reentry_price, reentry_qty, liq_price = 0.0, 0.0, 0.0, 0.0, 0.0
    closest_long_liq, closest_shrt_liq = 1.0, 1.0
    stop_loss_liq_diff_price, stop_loss_pos_price_diff_price, stop_loss_price = 0.0, 0.0, 0.0
    actual_balance = ss['starting_balance']
    apparent_balance = actual_balance * ss['balance_pct']

    pnl_plus_fees_cumsum, loss_cumsum, profit_cumsum, fee_paid_cumsum = 0.0, 0.0, 0.0, 0.0

    if ss['inverse']:
        min_qty_f = calc_min_qty_inverse
        long_pnl_f = calc_long_pnl_inverse
        shrt_pnl_f = calc_shrt_pnl_inverse
        cost_f = calc_cost_inverse
        pos_margin_f = calc_margin_cost_inverse
        max_pos_size_f = calc_max_pos_size_inverse
        min_entry_qty_f = calc_min_entry_qty_inverse
        long_liq_price_f = lambda bal, psize, pprice: \
            bybit_calc_cross_long_liq_price(bal, psize, pprice, ss['max_leverage'])
        shrt_liq_price_f = lambda bal, psize, pprice: \
            bybit_calc_cross_shrt_liq_price(bal, psize, pprice, ss['max_leverage'])
    else:
        min_qty_f = calc_min_qty_linear
        long_pnl_f = calc_long_pnl_linear
        shrt_pnl_f = calc_shrt_pnl_linear
        cost_f = calc_cost_linear
        pos_margin_f = calc_margin_cost_linear
        max_pos_size_f = calc_max_pos_size_linear
        min_entry_qty_f = calc_min_entry_qty_linear
        long_liq_price_f = lambda bal, psize, pprice: \
            binance_calc_cross_long_liq_price(bal, psize, pprice, ss['leverage'])
        shrt_liq_price_f = lambda bal, psize, pprice: \
            binance_calc_cross_shrt_liq_price(bal, psize, pprice, ss['leverage'])

    break_on = {e[0]: eval(e[1]) for e in settings['break_on'] if e[0].startswith('ON:')}

    ema = ticks[0][0]
    ema_alpha = 2 / (ss['ema_span'] + 1)
    ema_alpha_ = 1 - ema_alpha
    prev_trade_ts = 0
    min_trade_delay_millis = ss['latency_simulation_ms'] if 'latency_simulation_ms' in ss else 1000

    trades = []
    ob = [min(ticks[0][0], ticks[1][0]),
          max(ticks[0][0], ticks[1][0])]
    for k, t in enumerate(ticks):
        did_trade = False
        if t[1]:
            # maker buy, taker sel
            if pos_size == 0.0:
                # create long pos
                if ss['do_long']:
                    price = calc_no_pos_bid_price(ss['price_step'], ss['ema_spread'], ema, ob[0])
                    if t[0] < price and ss['do_long']:
                        did_trade = True
                        qty = min_entry_qty_f(ss['qty_step'], ss['min_qty'], ss['min_cost'],
                                              ss['entry_qty_pct'], ss['leverage'], apparent_balance,
                                              price)
                        trade_type, trade_side = 'entry', 'long'
                        pnl = 0.0
                        fee_paid = -cost_f(qty, price) * ss['maker_fee']
            elif pos_size > 0.0:
                closest_long_liq = min(calc_diff(liq_price, t[0]), closest_long_liq)
                if t[0] <= liq_price and closest_long_liq < 0.2:
                    # long liquidation
                    print('\nlong liquidation')
                    return []
                if t[0] < reentry_price:
                    # add to long pos
                    did_trade, qty, price = True, reentry_qty, reentry_price
                    trade_type, trade_side = 'reentry', 'long'
                    pnl = 0.0
                    fee_paid = -cost_f(qty, price) * ss['maker_fee']
                # check if long stop loss triggered
                if t[0] <= stop_loss_liq_diff_price:
                    stop_loss_price = ob[1]
                    stop_loss_type = 'stop_loss_liq_diff'
                elif t[0] <= stop_loss_pos_price_diff_price:
                    stop_loss_price = ob[1]
                    stop_loss_type = 'stop_loss_pos_price_diff'
                else:
                    stop_loss_price = 0.0
            else:
                if t[0] <= pos_price:
                    # close shrt pos
                    min_close_qty = calc_min_close_qty(
                        ss['qty_step'], ss['min_qty'], ss['min_close_qty_multiplier'],
                        min_entry_qty_f(ss['qty_step'], ss['min_qty'], ss['min_cost'],
                                        ss['entry_qty_pct'], ss['leverage'], apparent_balance,
                                        t[0])
                    )
                    qtys, prices = calc_shrt_closes(ss['price_step'],
                                                    ss['qty_step'],
                                                    min_close_qty,
                                                    ss['min_markup'],
                                                    ss['max_markup'],
                                                    pos_size,
                                                    pos_price,
                                                    ob[0],
                                                    ss['n_close_orders'])
                    if t[0] < prices[0]:
                        did_trade, qty, price = True, qtys[0], prices[0]
                        trade_type, trade_side = 'close', 'shrt'
                        pnl = shrt_pnl_f(pos_price, price, qty)
                        fee_paid = -cost_f(qty, price) * ss['maker_fee']
                elif t[0] < stop_loss_price:
                    # shrt stop loss
                    did_trade = True
                    qty = calc_pos_reduction_qty(ss['qty_step'], ss['stop_loss_pos_reduction'],
                                                 pos_size)
                    price = stop_loss_price
                    trade_type, trade_side = stop_loss_type, 'shrt'
                    pnl = shrt_pnl_f(pos_price, price, qty)
                    fee_paid = -cost_f(qty, price) * ss['maker_fee']

            ob[0] = t[0]
        else:
            # maker sel, taker buy
            if pos_size == 0.0:
                # create shrt pos
                if ss['do_shrt']:
                    price = calc_no_pos_ask_price(ss['price_step'], ss['ema_spread'], ema, ob[1])
                    if t[0] > price:
                        did_trade = True
                        qty = -min_entry_qty_f(ss['qty_step'], ss['min_qty'], ss['min_cost'],
                                               ss['entry_qty_pct'], ss['leverage'],
                                               apparent_balance, price)
                        trade_type, trade_side = 'entry', 'shrt'
                        pnl = 0.0
                        fee_paid = -cost_f(-qty, price) * ss['maker_fee']
            elif pos_size < 0.0:
                closest_shrt_liq = min(calc_diff(liq_price, t[0]), closest_shrt_liq)
                if t[0] >= liq_price and closest_shrt_liq < 0.2:
                    # shrt liquidation
                    print('\nshrt liquidation')
                    return []
                if t[0] > reentry_price:
                    # add to shrt pos
                    did_trade, qty, price = True, reentry_qty, reentry_price
                    trade_type, trade_side = 'reentry', 'shrt'
                    pnl = 0.0
                    fee_paid = -cost_f(-qty, price) * ss['maker_fee']
                # check if shrt stop loss triggered
                if t[0] >= stop_loss_liq_diff_price:
                    stop_loss_price = ob[0]
                    stop_loss_type = 'stop_loss_liq_diff'
                elif t[0] >= stop_loss_pos_price_diff_price:
                    stop_loss_price = ob[0]
                    stop_loss_type = 'stop_loss_pos_price_diff'
                else:
                    stop_loss_price = 0.0
            else:
                # close long pos
                if t[0] >= pos_price:
                    min_close_qty = calc_min_close_qty(
                        ss['qty_step'], ss['min_qty'], ss['min_close_qty_multiplier'],
                        min_entry_qty_f(ss['qty_step'], ss['min_qty'], ss['min_cost'],
                                        ss['entry_qty_pct'], ss['leverage'], apparent_balance,
                                        t[0])
                    )
                    qtys, prices = calc_long_closes(ss['price_step'],
                                                    ss['qty_step'],
                                                    min_close_qty,
                                                    ss['min_markup'],
                                                    ss['max_markup'],
                                                    pos_size,
                                                    pos_price,
                                                    ob[1],
                                                    ss['n_close_orders'])
                    if t[0] > prices[0]:
                        did_trade, qty, price = True, qtys[0], prices[0]
                        trade_type, trade_side = 'close', 'long'
                        pnl = long_pnl_f(pos_price, price, -qty)
                        fee_paid =  - cost_f(-qty, price) * ss['maker_fee']
                elif stop_loss_price > 0.0 and t[0] > stop_loss_price:
                    # long stop loss
                    did_trade = True
                    qty = -calc_pos_reduction_qty(ss['qty_step'], ss['stop_loss_pos_reduction'],
                                                  pos_size)
                    price = stop_loss_price
                    trade_type, trade_side = stop_loss_type, 'long'
                    pnl = long_pnl_f(pos_price, price, qty)
                    fee_paid = -cost_f(-qty, price) * ss['maker_fee']
            ob[1] = t[0]
        ema = ema * ema_alpha_ + t[0] * ema_alpha
        if did_trade:
            if t[2] - prev_trade_ts < min_trade_delay_millis:
                if trade_type == 'reentry':
                    # because of live bot's multiple open orders,
                    # allow consecutive reentries whose timestamp diff < min delay
                    if trades[-1]['type'] != 'reentry':
                        continue
                else:
                    continue
            prev_trade_ts = t[2]
            new_pos_size = round_(pos_size + qty, 0.0000000001)
            if 'entry' in trade_type:
                pos_price = pos_price * abs(pos_size / new_pos_size) + \
                    price * abs(qty / new_pos_size) if new_pos_size else np.nan
            pos_size = new_pos_size
            actual_balance = max(ss['starting_balance'], actual_balance + pnl + fee_paid)
            apparent_balance = actual_balance * ss['balance_pct']
            if pos_size == 0.0:
                liq_price = 0.0
            elif pos_size > 0.0:
                liq_price = long_liq_price_f(actual_balance, pos_size, pos_price)
            else:
                liq_price = shrt_liq_price_f(actual_balance, pos_size, pos_price)
            if liq_price < 0.0:
                liq_price = 0.0
            progress = k / len(ticks)
            pnl_plus_fee = pnl + fee_paid
            pnl_plus_fees_cumsum += pnl_plus_fee
            if trade_type.startswith('stop_loss'):
                loss_cumsum += pnl
            else:
                profit_cumsum += pnl
            fee_paid_cumsum += fee_paid
            total_gain = (pnl_plus_fees_cumsum + settings['starting_balance']) / settings['starting_balance']
            n_days_ = (t[2] - ticks[0][2]) / (1000 * 60 * 60 * 24)
            try:
                adg = total_gain ** (1 / n_days_) if (n_days_ > 0.0 and total_gain > 0.0) else 0.0
            except:
                adg = 0.0
            avg_gain_per_tick = \
                (actual_balance / settings['starting_balance']) ** (1 / (len(trades) + 1))
            millis_since_prev_trade = t[2] - trades[-1]['timestamp'] if trades else 0.0
            trades.append({'trade_id': k, 'side': trade_side, 'type': trade_type, 'price': price,
                           'qty': qty, 'pos_price': pos_price, 'pos_size': pos_size,
                           'liq_price': liq_price, 'pnl': pnl, 'fee_paid': fee_paid,
                           'pnl_plus_fee': pnl_plus_fee, 'fee_paid_cumsum': fee_paid_cumsum,
                           'apparent_balance': apparent_balance, 'actual_balance': actual_balance, 
                           'profit_cumsum': profit_cumsum, 'loss_cumsum': loss_cumsum,
                           'pnl_plus_fees_cumsum': pnl_plus_fees_cumsum,
                           'average_daily_gain': adg, 'timestamp': t[2],
                           'closest_long_liq': closest_long_liq,
                           'closest_shrt_liq': closest_shrt_liq,
                           'closest_liq': min(closest_long_liq, closest_shrt_liq),
                           'avg_gain_per_tick': avg_gain_per_tick,
                           'millis_since_prev_trade': millis_since_prev_trade,
                           'progress': progress})
            closest_long_liq, closest_shrt_liq = 1.0, 1.0
            for key, condition in break_on.items():
                if condition(trades, ticks, k):
                    print('break on', key)
                    return []
            if pos_size > 0.0:
                stop_loss_liq_diff_price = liq_price * (1 + ss['stop_loss_liq_diff'])
                stop_loss_pos_price_diff_price = pos_price * (1 - ss['stop_loss_pos_price_diff'])
                stop_loss_price = 0.0
                reentry_price = min(
                    ob[0],
                    calc_long_reentry_price(ss['price_step'], ss['grid_spacing'],
                                            ss['grid_coefficient'], apparent_balance,
                                            pos_margin_f(ss['leverage'], pos_size, pos_price),
                                            pos_price)
                )
                reentry_price = max(ss['price_step'], reentry_price)
                min_qty_ = min_qty_f(ss['qty_step'], ss['min_qty'], ss['min_cost'], reentry_price)
                reentry_qty = calc_reentry_qty(ss['qty_step'],
                                               ss['ddown_factor'],
                                               min_qty_,
                                               max_pos_size_f(ss['leverage'], apparent_balance,
                                                              reentry_price),
                                               pos_size)
                if reentry_qty < min_qty_:
                    reentry_price = ss['price_step']
                trades[-1]['reentry_price'] = reentry_price
            elif pos_size < 0.0:
                stop_loss_liq_diff_price = liq_price * (1 - ss['stop_loss_liq_diff']) \
                    if liq_price > 0.0 else pos_price * 10000
                stop_loss_pos_price_diff_price = pos_price * (1 + ss['stop_loss_pos_price_diff'])
                stop_loss_price = 0.0
                reentry_price = max([
                    ss['price_step'],
                    ob[1],
                    calc_shrt_reentry_price(ss['price_step'], ss['grid_spacing'],
                                            ss['grid_coefficient'], apparent_balance,
                                            pos_margin_f(ss['leverage'], pos_size, pos_price),
                                            pos_price)
                ])
                min_qty_ = min_qty_f(ss['qty_step'], ss['min_qty'], ss['min_cost'], reentry_price)
                reentry_qty = -calc_reentry_qty(ss['qty_step'],
                                                ss['ddown_factor'],
                                                min_qty_,
                                                max_pos_size_f(ss['leverage'], apparent_balance,
                                                                  reentry_price),
                                                pos_size)
                if -reentry_qty < min_qty_:
                    reentry_price = 9e12
                trades[-1]['reentry_price'] = reentry_price
            else:
                trades[-1]['reentry_price'] = np.nan


            line = f"\r{progress:.3f} pnl plus fees cumsum {pnl_plus_fees_cumsum:.8f} "
            line += f"profit cumsum {profit_cumsum:.5f} "
            line += f"loss cumsum {loss_cumsum:.5f} "
            line += f"actual_bal {actual_balance:.4f} "
            line += f"apparent_bal {apparent_balance:.4f} "
            #line += f"qty {calc_min_entry_qty_(apparent_balance, ob[0]):.4f} "
            #line += f"adg {trades[-1]['average_daily_gain']:.3f} "
            #line += f"max pos pct {abs(pos_size) / calc_max_pos_size(apparent_balance, t[0]):.3f} "
            line += f"pos size {pos_size:.4f} "
            print(line, end=' ')
    return trades