Example #1
0
    async def fetch_position(self) -> dict:
        position = {}
        if 'linear_perpetual' in self.market_type:
            fetched, bal = await asyncio.gather(
                self.private_get(self.endpoints['position'],
                                 {'symbol': self.symbol}),
                self.private_get(self.endpoints['balance'],
                                 {'coin': self.quot}))
            long_pos = [e for e in fetched['result'] if e['side'] == 'Buy'][0]
            shrt_pos = [e for e in fetched['result'] if e['side'] == 'Sell'][0]
            position['wallet_balance'] = float(
                bal['result'][self.quot]['wallet_balance'])
        else:
            fetched, bal = await asyncio.gather(
                self.private_get(self.endpoints['position'],
                                 {'symbol': self.symbol}),
                self.private_get(self.endpoints['balance'],
                                 {'coin': self.coin}))
            position['wallet_balance'] = float(
                bal['result'][self.coin]['wallet_balance'])
            if 'inverse_perpetual' in self.market_type:
                if fetched['result']['side'] == 'Buy':
                    long_pos = fetched['result']
                    shrt_pos = {
                        'size': 0.0,
                        'entry_price': 0.0,
                        'liq_price': 0.0
                    }
                else:
                    long_pos = {
                        'size': 0.0,
                        'entry_price': 0.0,
                        'liq_price': 0.0
                    }
                    shrt_pos = fetched['result']
            elif 'inverse_futures' in self.market_type:
                long_pos = [
                    e['data'] for e in fetched['result']
                    if e['data']['position_idx'] == 1
                ][0]
                shrt_pos = [
                    e['data'] for e in fetched['result']
                    if e['data']['position_idx'] == 2
                ][0]
            else:
                raise Exception('unknown market type')

        position['long'] = {
            'size': round_(float(long_pos['size']), self.qty_step),
            'price': float(long_pos['entry_price']),
            'liquidation_price': float(long_pos['liq_price'])
        }
        position['shrt'] = {
            'size': -round_(float(shrt_pos['size']), self.qty_step),
            'price': float(shrt_pos['entry_price']),
            'liquidation_price': float(shrt_pos['liq_price'])
        }
        return position
Example #2
0
def plot_wrap(config, data):
    print("n_days", round_(config["n_days"], 0.1))
    print("starting_balance", config["starting_balance"])
    print("backtesting...")
    sts = time()
    fills_long, fills_short, stats = backtest(config, data, do_print=True)
    print(f"{time() - sts:.2f} seconds elapsed")
    if not fills_long and not fills_short:
        print("no fills")
        return
    longs, shorts, sdf, result = analyze_fills(fills_long, fills_short, stats,
                                               config)
    config["result"] = result
    config["plots_dirpath"] = make_get_filepath(
        os.path.join(config["plots_dirpath"],
                     f"{ts_to_date(time())[:19].replace(':', '')}", ""))
    longs.to_csv(config["plots_dirpath"] + "fills_long.csv")
    shorts.to_csv(config["plots_dirpath"] + "fills_short.csv")
    sdf.to_csv(config["plots_dirpath"] + "stats.csv")
    df = pd.DataFrame({
        **{
            "timestamp": data[:, 0],
            "qty": data[:, 1],
            "price": data[:, 2]
        },
        **{}
    })
    print("dumping plots...")
    dump_plots(config, longs, shorts, sdf, df, n_parts=config["n_parts"])
Example #3
0
def plot_wrap(config, data):
    print('n_days', round_(config['n_days'], 0.1))
    print('starting_balance', config['starting_balance'])
    print('backtesting...')
    sts = time()
    fills, stats = backtest(config, data, do_print=True)
    print(f'{time() - sts:.2f} seconds elapsed')
    if not fills:
        print('no fills')
        return
    fdf, sdf, result = analyze_fills(fills, stats, config)
    config['result'] = result
    config['plots_dirpath'] = make_get_filepath(os.path.join(
        config['plots_dirpath'], f"{ts_to_date(time())[:19].replace(':', '')}", '')
    )
    fdf.to_csv(config['plots_dirpath'] + "fills.csv")
    sdf.to_csv(config['plots_dirpath'] + "stats.csv")
    df = pd.DataFrame({**{'timestamp': data[:, 0], 'qty': data[:, 1], 'price': data[:, 2]},
                       **{}})
    print('dumping plots...')
    dump_plots(config, fdf, sdf, df)
Example #4
0
    if args.long_wallet_exposure_limit is not None:
        print(
            f"overriding long wallet exposure limit ({config['long']['pbr_limit']}) "
            f"with new value: {args.long_wallet_exposure_limit}"
        )
        config["long"]["pbr_limit"] = args.long_wallet_exposure_limit
    if args.short_wallet_exposure_limit is not None:
        print(
            f"overriding short wallet exposure limit ({config['shrt']['pbr_limit']}) "
            f"with new value: {args.short_wallet_exposure_limit}"
        )
        config["shrt"]["pbr_limit"] = args.short_wallet_exposure_limit

    if 'spot' in config['market_type']:
        live_config = spotify_config(live_config)
    downloader = Downloader(config)
    print()
    for k in (keys := ['exchange', 'spot', 'symbol', 'market_type', 'config_type', 'starting_balance', 'start_date', 'end_date',
                       'latency_simulation_ms']):
        if k in config:
            print(f"{k: <{max(map(len, keys)) + 2}} {config[k]}")
    print()
    data = await downloader.get_sampled_ticks()
    config['n_days'] = round_((data[-1][0] - data[0][0]) / (1000 * 60 * 60 * 24), 0.1)
    pprint.pprint(denumpyize(live_config))
    plot_wrap(config, data)


if __name__ == '__main__':
    asyncio.run(main())
Example #5
0
    def standardize_user_stream_event(
            self, event: Union[List[Dict], Dict]) -> Union[List[Dict], Dict]:
        events = []
        if 'topic' in event:
            if event['topic'] == 'order':
                for elm in event['data']:
                    if elm['symbol'] == self.symbol:
                        if elm['order_status'] == 'Created':
                            pass
                        elif elm['order_status'] == 'Rejected':
                            pass
                        elif elm['order_status'] == 'New':
                            new_open_order = {
                                'order_id':
                                elm['order_id'],
                                'symbol':
                                elm['symbol'],
                                'price':
                                float(elm['price']),
                                'qty':
                                float(elm['qty']),
                                'type':
                                elm['order_type'].lower(),
                                'side': (side := elm['side'].lower()),
                                'timestamp':
                                date_to_ts(elm['timestamp' if self.
                                               inverse else 'update_time'])
                            }
                            if 'inverse_perpetual' in self.market_type:
                                if self.position['long']['size'] == 0.0:
                                    if self.position['shrt']['size'] == 0.0:
                                        new_open_order[
                                            'position_side'] = 'long' if new_open_order[
                                                'side'] == 'buy' else 'shrt'
                                    else:
                                        new_open_order[
                                            'position_side'] = 'shrt'
                                else:
                                    new_open_order['position_side'] = 'long'
                            elif 'inverse_futures' in self.market_type:
                                new_open_order[
                                    'position_side'] = determine_pos_side(elm)
                            else:
                                new_open_order['position_side'] = ('long' if (
                                    (new_open_order['side'] == 'buy'
                                     and elm['create_type'] == 'CreateByUser')
                                    or
                                    (new_open_order['side'] == 'sell' and
                                     elm['create_type'] == 'CreateByClosing'))
                                                                   else 'shrt')
                            events.append({'new_open_order': new_open_order})
                        elif elm['order_status'] == 'PartiallyFilled':
                            events.append({
                                'deleted_order_id': elm['order_id'],
                                'partially_filled': True
                            })
                        elif elm['order_status'] == 'Filled':
                            events.append({
                                'deleted_order_id': elm['order_id'],
                                'filled': True
                            })
                        elif elm['order_status'] == 'Cancelled':
                            events.append(
                                {'deleted_order_id': elm['order_id']})
                        elif elm['order_status'] == 'PendingCancel':
                            pass
                    else:
                        events.append({
                            'other_symbol': elm['symbol'],
                            'other_type': event['topic']
                        })
            elif event['topic'] == 'execution':
                for elm in event['data']:
                    if elm['symbol'] == self.symbol:
                        if elm['exec_type'] == 'Trade':
                            # already handled by "order"
                            pass
                    else:
                        events.append({
                            'other_symbol': elm['symbol'],
                            'other_type': event['topic']
                        })
            elif event['topic'] == 'position':
                for elm in event['data']:
                    if elm['symbol'] == self.symbol:
                        standardized = {}
                        if elm['side'] == 'Buy':
                            standardized['long_psize'] = round_(
                                float(elm['size']), self.qty_step)
                            standardized['long_pprice'] = float(
                                elm['entry_price'])
                        elif elm['side'] == 'Sell':
                            standardized['shrt_psize'] = -round_(
                                abs(float(elm['size'])), self.qty_step)
                            standardized['shrt_pprice'] = float(
                                elm['entry_price'])

                        events.append(standardized)
                        if self.inverse:
                            events.append({
                                'wallet_balance':
                                float(elm['wallet_balance'])
                            })
                    else:
                        events.append({
                            'other_symbol': elm['symbol'],
                            'other_type': event['topic']
                        })
            elif not self.inverse and event['topic'] == 'wallet':
                for elm in event['data']:
                    events.append(
                        {'wallet_balance': float(elm['wallet_balance'])})
        return events
Example #6
0
async def main():
    parser = argparse.ArgumentParser(
        prog="Backtest", description="Backtest given passivbot config.")
    parser.add_argument("live_config_path",
                        type=str,
                        help="path to live config to test")
    parser = add_argparse_args(parser)
    parser.add_argument(
        "-lw",
        "--long_wallet_exposure_limit",
        "--long-wallet-exposure-limit",
        type=float,
        required=False,
        dest="long_wallet_exposure_limit",
        default=None,
        help=
        "specify long wallet exposure limit, overriding value from live config",
    )
    parser.add_argument(
        "-sw",
        "--short_wallet_exposure_limit",
        "--short-wallet-exposure-limit",
        type=float,
        required=False,
        dest="short_wallet_exposure_limit",
        default=None,
        help=
        "specify short wallet exposure limit, overriding value from live config",
    )
    parser.add_argument(
        "-le",
        "--long_enabled",
        "--long-enabled",
        type=str,
        required=False,
        dest="long_enabled",
        default=None,
        help="specify long enabled [y/n], overriding value from live config",
    )
    parser.add_argument(
        "-se",
        "--short_enabled",
        "--short-enabled",
        type=str,
        required=False,
        dest="short_enabled",
        default=None,
        help="specify short enabled [y/n], overriding value from live config",
    )
    parser.add_argument(
        "-np",
        "--n_parts",
        "--n-parts",
        type=int,
        required=False,
        dest="n_parts",
        default=None,
        help="set n backtest slices to plot",
    )
    parser.add_argument(
        "-oh",
        "--ohlcv",
        help="use 1m ohlcv instead of 1s ticks",
        action="store_true",
    )
    args = parser.parse_args()
    if args.symbol is None:
        tmp_cfg = load_hjson_config(args.backtest_config_path)
        symbols = (tmp_cfg["symbol"] if type(tmp_cfg["symbol"]) == list else
                   tmp_cfg["symbol"].split(","))
    else:
        symbols = args.symbol.split(",")
    for symbol in symbols:
        args = parser.parse_args()
        args.symbol = symbol
        config = await prepare_backtest_config(args)
        config["n_parts"] = args.n_parts
        live_config = load_live_config(args.live_config_path)
        config.update(live_config)

        if args.long_wallet_exposure_limit is not None:
            print(
                f"overriding long wallet exposure limit ({config['long']['wallet_exposure_limit']}) "
                f"with new value: {args.long_wallet_exposure_limit}")
            config["long"][
                "wallet_exposure_limit"] = args.long_wallet_exposure_limit
        if args.short_wallet_exposure_limit is not None:
            print(
                f"overriding short wallet exposure limit ({config['short']['wallet_exposure_limit']}) "
                f"with new value: {args.short_wallet_exposure_limit}")
            config["short"][
                "wallet_exposure_limit"] = args.short_wallet_exposure_limit
        if args.long_enabled is not None:
            config["long"]["enabled"] = "y" in args.long_enabled.lower()
        if args.short_enabled is not None:
            config["short"]["enabled"] = "y" in args.short_enabled.lower()
        if "spot" in config["market_type"]:
            live_config = spotify_config(live_config)
        config["ohlcv"] = args.ohlcv

        print()
        for k in (keys := [
                "exchange",
                "spot",
                "symbol",
                "market_type",
                "passivbot_mode",
                "config_type",
                "starting_balance",
                "start_date",
                "end_date",
                "latency_simulation_ms",
                "base_dir",
        ]):
            if k in config:
                print(f"{k: <{max(map(len, keys)) + 2}} {config[k]}")
        print()
        if config["ohlcv"]:
            data = load_hlc_cache(
                symbol,
                config["start_date"],
                config["end_date"],
                base_dir=config["base_dir"],
                spot=config["spot"],
            )
        else:
            downloader = Downloader(config)
            data = await downloader.get_sampled_ticks()
        config["n_days"] = round_(
            (data[-1][0] - data[0][0]) / (1000 * 60 * 60 * 24), 0.1)
        pprint.pprint(denumpyize(live_config))
        plot_wrap(config, data)
Example #7
0
    async def fetch_position(self) -> dict:
        position = {}
        if "linear_perpetual" in self.market_type:
            fetched, bal = await asyncio.gather(
                self.private_get(self.endpoints["position"],
                                 {"symbol": self.symbol}),
                self.private_get(self.endpoints["balance"],
                                 {"coin": self.quot}),
            )
            long_pos = [e for e in fetched["result"] if e["side"] == "Buy"][0]
            short_pos = [e for e in fetched["result"]
                         if e["side"] == "Sell"][0]
            position["wallet_balance"] = float(
                bal["result"][self.quot]["wallet_balance"])
        else:
            fetched, bal = await asyncio.gather(
                self.private_get(self.endpoints["position"],
                                 {"symbol": self.symbol}),
                self.private_get(self.endpoints["balance"],
                                 {"coin": self.coin}),
            )
            position["wallet_balance"] = float(
                bal["result"][self.coin]["wallet_balance"])
            if "inverse_perpetual" in self.market_type:
                if fetched["result"]["side"] == "Buy":
                    long_pos = fetched["result"]
                    short_pos = {
                        "size": 0.0,
                        "entry_price": 0.0,
                        "liq_price": 0.0
                    }
                else:
                    long_pos = {
                        "size": 0.0,
                        "entry_price": 0.0,
                        "liq_price": 0.0
                    }
                    short_pos = fetched["result"]
            elif "inverse_futures" in self.market_type:
                long_pos = [
                    e["data"] for e in fetched["result"]
                    if e["data"]["position_idx"] == 1
                ][0]
                short_pos = [
                    e["data"] for e in fetched["result"]
                    if e["data"]["position_idx"] == 2
                ][0]
            else:
                raise Exception("unknown market type")

        position["long"] = {
            "size": round_(float(long_pos["size"]), self.qty_step),
            "price": float(long_pos["entry_price"]),
            "liquidation_price": float(long_pos["liq_price"]),
        }
        position["short"] = {
            "size": -round_(float(short_pos["size"]), self.qty_step),
            "price": float(short_pos["entry_price"]),
            "liquidation_price": float(short_pos["liq_price"]),
        }
        return position
Example #8
0
                for elm in event["data"]:
                    if elm["symbol"] == self.symbol:
                        if elm["exec_type"] == "Trade":
                            # already handled by "order"
                            pass
                    else:
                        events.append({
                            "other_symbol": elm["symbol"],
                            "other_type": event["topic"],
                        })
            elif event["topic"] == "position":
                for elm in event["data"]:
                    if elm["symbol"] == self.symbol:
                        standardized = {}
                        if elm["side"] == "Buy":
                            standardized["psize_long"] = round_(
                                float(elm["size"]), self.qty_step)
                            standardized["pprice_long"] = float(
                                elm["entry_price"])
                        elif elm["side"] == "Sell":
                            standardized["psize_short"] = -round_(
                                abs(float(elm["size"])), self.qty_step)
                            standardized["pprice_short"] = float(
                                elm["entry_price"])

                        events.append(standardized)
                        if self.inverse:
                            events.append({
                                "wallet_balance":
                                float(elm["wallet_balance"])
                            })
                    else:
Example #9
0
def calc_recursive_entry_long(
    balance,
    psize,
    pprice,
    highest_bid,
    ema_band_lower,
    inverse,
    qty_step,
    price_step,
    min_qty,
    min_cost,
    c_mult,
    initial_qty_pct,
    initial_eprice_ema_dist,
    ddown_factor,
    rentry_pprice_dist,
    rentry_pprice_dist_wallet_exposure_weighting,
    wallet_exposure_limit,
    auto_unstuck_ema_dist,
    auto_unstuck_wallet_exposure_threshold,
):
    ientry_price = max(
        price_step,
        min(
            highest_bid,
            round_dn(ema_band_lower * (1 - initial_eprice_ema_dist),
                     price_step)),
    )
    min_entry_qty = calc_min_entry_qty(ientry_price, inverse, qty_step,
                                       min_qty, min_cost)
    ientry_qty = max(
        min_entry_qty,
        round_(
            cost_to_qty(balance, ientry_price, inverse, c_mult) *
            wallet_exposure_limit * initial_qty_pct,
            qty_step,
        ),
    )
    if psize == 0.0:
        # normal ientry
        return ientry_qty, ientry_price, "long_ientry_normal"
    elif psize < ientry_qty * 0.8:
        # partial ientry
        entry_qty = max(min_entry_qty, round_(ientry_qty - psize, qty_step))
        return entry_qty, ientry_price, "long_ientry_partial"
    else:
        wallet_exposure = qty_to_cost(psize, pprice, inverse, c_mult) / balance
        if wallet_exposure >= wallet_exposure_limit * 1.001:
            # no entry if wallet_exposure within 0.1% of limit
            return 0.0, 0.0, ""
        threshold = wallet_exposure_limit * (
            1 - auto_unstuck_wallet_exposure_threshold)
        if auto_unstuck_wallet_exposure_threshold != 0.0 and wallet_exposure > threshold * 0.99:
            # auto unstuck mode
            entry_price = round_dn(
                min([
                    highest_bid, pprice,
                    ema_band_lower * (1 - auto_unstuck_ema_dist)
                ]), price_step)
            entry_qty = find_entry_qty_bringing_wallet_exposure_to_target(
                balance, psize, pprice, wallet_exposure_limit, entry_price,
                inverse, qty_step, c_mult)
            min_entry_qty = calc_min_entry_qty(entry_price, inverse, qty_step,
                                               min_qty, min_cost)
            return (max(entry_qty,
                        min_entry_qty), entry_price, "long_unstuck_entry")
        else:
            # normal reentry
            ratio = wallet_exposure / wallet_exposure_limit
            entry_price = round_dn(
                pprice *
                (1 - rentry_pprice_dist *
                 (1 + ratio * rentry_pprice_dist_wallet_exposure_weighting)),
                price_step,
            )
            entry_price = min(highest_bid, entry_price)
            min_entry_qty = calc_min_entry_qty(entry_price, inverse, qty_step,
                                               min_qty, min_cost)
            entry_qty = max(min_entry_qty,
                            round_(psize * ddown_factor, qty_step))
            wallet_exposure_if_filled = calc_wallet_exposure_if_filled(
                balance, psize, pprice, entry_qty, entry_price, inverse,
                c_mult, qty_step)
            if wallet_exposure_if_filled > wallet_exposure_limit * 1.01:
                entry_qty = find_entry_qty_bringing_wallet_exposure_to_target(
                    balance,
                    psize,
                    pprice,
                    wallet_exposure_limit,
                    entry_price,
                    inverse,
                    qty_step,
                    c_mult,
                )
                entry_qty = max(entry_qty, min_entry_qty)
            return entry_qty, entry_price, "long_rentry"
Example #10
0
def backtest_recursive_grid(
    ticks,
    starting_balance,
    latency_simulation_ms,
    maker_fee,
    inverse,
    do_long,
    do_short,
    qty_step,
    price_step,
    min_qty,
    min_cost,
    c_mult,
    ema_span_0,
    ema_span_1,
    initial_qty_pct,
    initial_eprice_ema_dist,
    wallet_exposure_limit,
    ddown_factor,
    rentry_pprice_dist,
    rentry_pprice_dist_wallet_exposure_weighting,
    min_markup,
    markup_range,
    n_close_orders,
    auto_unstuck_wallet_exposure_threshold,
    auto_unstuck_ema_dist,
):
    if len(ticks[0]) == 3:
        timestamps = ticks[:, 0]
        closes = ticks[:, 2]
        lows = closes
        highs = closes
    else:
        timestamps = ticks[:, 0]
        highs = ticks[:, 1]
        lows = ticks[:, 2]
        closes = ticks[:, 3]

    balance_long = balance_short = equity_long = equity_short = starting_balance
    psize_long, pprice_long, psize_short, pprice_short = 0.0, 0.0, 0.0, 0.0

    fills_long, fills_short, stats = [], [], []

    entry_long, entry_short = (0.0, 0.0, ""), (0.0, 0.0, "")
    closes_long, closes_short = [(0.0, 0.0, "")], [(0.0, 0.0, "")]
    bkr_price_long = bkr_price_short = 0.0

    next_entry_update_ts_long = 0
    next_entry_update_ts_short = 0
    next_close_grid_update_ts_long = 0
    next_close_grid_update_ts_short = 0
    next_stats_update = 0

    closest_bkr_long = closest_bkr_short = 1.0

    spans_multiplier = 60 / ((timestamps[1] - timestamps[0]) / 1000)

    spans_long = [
        ema_span_0[0], (ema_span_0[0] * ema_span_1[0])**0.5, ema_span_1[0]
    ]
    spans_long = np.array(
        sorted(spans_long)) * spans_multiplier if do_long else np.ones(3)
    spans_short = [
        ema_span_0[1], (ema_span_0[1] * ema_span_1[1])**0.5, ema_span_1[1]
    ]
    spans_short = np.array(
        sorted(spans_short)) * spans_multiplier if do_short else np.ones(3)
    assert max(spans_long) < len(
        ticks), "ema_span_1 long larger than len(prices)"
    assert max(spans_short) < len(
        ticks), "ema_span_1 short larger than len(prices)"
    spans_long = np.where(spans_long < 1.0, 1.0, spans_long)
    spans_short = np.where(spans_short < 1.0, 1.0, spans_short)
    max_span_long = int(round(max(spans_long)))
    max_span_short = int(round(max(spans_short)))
    emas_long, emas_short = np.repeat(closes[0], 3), np.repeat(closes[0], 3)
    alphas_long = 2.0 / (spans_long + 1.0)
    alphas__long = 1.0 - alphas_long
    alphas_short = 2.0 / (spans_short + 1.0)
    alphas__short = 1.0 - alphas_short

    long_wallet_exposure = 0.0
    short_wallet_exposure = 0.0
    long_wallet_exposure_auto_unstuck_threshold = (
        (wallet_exposure_limit[0] *
         (1 - auto_unstuck_wallet_exposure_threshold[0]))
        if auto_unstuck_wallet_exposure_threshold[0] != 0.0 else
        wallet_exposure_limit[0] * 10)
    short_wallet_exposure_auto_unstuck_threshold = (
        (wallet_exposure_limit[1] *
         (1 - auto_unstuck_wallet_exposure_threshold[1]))
        if auto_unstuck_wallet_exposure_threshold[1] != 0.0 else
        wallet_exposure_limit[1] * 10)
    for k in range(0, len(ticks)):
        if do_long:
            emas_long = calc_ema(alphas_long, alphas__long, emas_long,
                                 closes[k])
            if k >= max_span_long:
                # check bankruptcy
                bkr_diff_long = calc_diff(bkr_price_long, closes[k])
                closest_bkr_long = min(closest_bkr_long, bkr_diff_long)
                if closest_bkr_long < 0.06:
                    # consider bankruptcy within 6% as liquidation
                    if psize_long != 0.0:
                        fee_paid = -qty_to_cost(psize_long, pprice_long,
                                                inverse, c_mult) * maker_fee
                        pnl = calc_pnl_long(pprice_long, closes[k],
                                            -psize_long, inverse, c_mult)
                        balance_long = 0.0
                        equity_long = 0.0
                        psize_long, pprice_long = 0.0, 0.0
                        fills_long.append((
                            k,
                            timestamps[k],
                            pnl,
                            fee_paid,
                            balance_long,
                            equity_long,
                            -psize_long,
                            closes[k],
                            0.0,
                            0.0,
                            "long_bankruptcy",
                        ))
                    do_long = False
                    if not do_short:
                        return fills_long, fills_short, stats

                # check if long entry order should be updated
                if timestamps[k] >= next_entry_update_ts_long:
                    entry_long = calc_recursive_entry_long(
                        balance_long,
                        psize_long,
                        pprice_long,
                        closes[k - 1],
                        min(emas_long),
                        inverse,
                        qty_step,
                        price_step,
                        min_qty,
                        min_cost,
                        c_mult,
                        initial_qty_pct[0],
                        initial_eprice_ema_dist[0],
                        ddown_factor[0],
                        rentry_pprice_dist[0],
                        rentry_pprice_dist_wallet_exposure_weighting[0],
                        wallet_exposure_limit[0],
                        auto_unstuck_ema_dist[0],
                        auto_unstuck_wallet_exposure_threshold[0],
                    )
                    next_entry_update_ts_long = timestamps[
                        k] + 1000 * 60 * 5  # five mins delay

                # check if close grid should be updated
                if timestamps[k] >= next_close_grid_update_ts_long:
                    closes_long = calc_close_grid_long(
                        balance_long,
                        psize_long,
                        pprice_long,
                        closes[k - 1],
                        max(emas_long),
                        inverse,
                        qty_step,
                        price_step,
                        min_qty,
                        min_cost,
                        c_mult,
                        wallet_exposure_limit[0],
                        min_markup[0],
                        markup_range[0],
                        n_close_orders[0],
                        auto_unstuck_wallet_exposure_threshold[0],
                        auto_unstuck_ema_dist[0],
                    )
                    next_close_grid_update_ts_long = timestamps[
                        k] + 1000 * 60 * 5  # five mins delay

                # check if long entry filled
                while entry_long[0] != 0.0 and lows[k] < entry_long[1]:
                    next_entry_update_ts_long = min(
                        next_entry_update_ts_long,
                        timestamps[k] + latency_simulation_ms)
                    next_close_grid_update_ts_long = min(
                        next_close_grid_update_ts_long,
                        timestamps[k] + latency_simulation_ms)
                    psize_long, pprice_long = calc_new_psize_pprice(
                        psize_long,
                        pprice_long,
                        entry_long[0],
                        entry_long[1],
                        qty_step,
                    )
                    fee_paid = -qty_to_cost(entry_long[0], entry_long[1],
                                            inverse, c_mult) * maker_fee
                    balance_long += fee_paid
                    equity_long = balance_long + calc_pnl_long(
                        pprice_long, closes[k], psize_long, inverse, c_mult)
                    fills_long.append((
                        k,
                        timestamps[k],
                        0.0,
                        fee_paid,
                        balance_long,
                        equity_long,
                        entry_long[0],
                        entry_long[1],
                        psize_long,
                        pprice_long,
                        entry_long[2],
                    ))
                    bkr_price_long = calc_bankruptcy_price(
                        balance_long,
                        psize_long,
                        pprice_long,
                        0.0,
                        0.0,
                        inverse,
                        c_mult,
                    )
                    long_wallet_exposure = (
                        qty_to_cost(psize_long, pprice_long, inverse, c_mult) /
                        balance_long)
                    entry_long = calc_recursive_entry_long(
                        balance_long,
                        psize_long,
                        pprice_long,
                        closes[k - 1],
                        min(emas_long),
                        inverse,
                        qty_step,
                        price_step,
                        min_qty,
                        min_cost,
                        c_mult,
                        initial_qty_pct[0],
                        initial_eprice_ema_dist[0],
                        ddown_factor[0],
                        rentry_pprice_dist[0],
                        rentry_pprice_dist_wallet_exposure_weighting[0],
                        wallet_exposure_limit[0],
                        auto_unstuck_ema_dist[0],
                        auto_unstuck_wallet_exposure_threshold[0],
                    )

                # check if long closes filled
                while (psize_long > 0.0 and closes_long
                       and closes_long[0][0] < 0.0
                       and highs[k] > closes_long[0][1]):
                    next_entry_update_ts_long = min(
                        next_entry_update_ts_long,
                        timestamps[k] + latency_simulation_ms)
                    next_close_grid_update_ts_long = min(
                        next_close_grid_update_ts_long,
                        timestamps[k] + latency_simulation_ms)
                    close_qty_long = closes_long[0][0]
                    new_psize_long = round_(psize_long + close_qty_long,
                                            qty_step)
                    if new_psize_long < 0.0:
                        print(
                            "warning: long close qty greater than long psize")
                        print("psize_long", psize_long)
                        print("pprice_long", pprice_long)
                        print("closes_long[0]", closes_long[0])
                        close_qty_long = -psize_long
                        new_psize_long, pprice_long = 0.0, 0.0
                    psize_long = new_psize_long
                    fee_paid = (-qty_to_cost(close_qty_long, closes_long[0][1],
                                             inverse, c_mult) * maker_fee)
                    pnl = calc_pnl_long(pprice_long, closes_long[0][1],
                                        close_qty_long, inverse, c_mult)
                    balance_long += fee_paid + pnl
                    equity_long = balance_long + calc_pnl_long(
                        pprice_long, closes[k], psize_long, inverse, c_mult)
                    fills_long.append((
                        k,
                        timestamps[k],
                        pnl,
                        fee_paid,
                        balance_long,
                        equity_long,
                        close_qty_long,
                        closes_long[0][1],
                        psize_long,
                        pprice_long,
                        closes_long[0][2],
                    ))
                    closes_long = closes_long[1:]
                    bkr_price_long = calc_bankruptcy_price(
                        balance_long,
                        psize_long,
                        pprice_long,
                        0.0,
                        0.0,
                        inverse,
                        c_mult,
                    )
                    long_wallet_exposure = (
                        qty_to_cost(psize_long, pprice_long, inverse, c_mult) /
                        balance_long)

                if psize_long == 0.0:
                    # update entry order
                    next_entry_update_ts_long = min(
                        next_entry_update_ts_long,
                        timestamps[k] + latency_simulation_ms,
                    )
                else:
                    if closes[k] > pprice_long:
                        # update closes after 2.5 secs
                        next_close_grid_update_ts_long = min(
                            next_close_grid_update_ts_long,
                            timestamps[k] + latency_simulation_ms + 2500,
                        )
                    elif long_wallet_exposure >= long_wallet_exposure_auto_unstuck_threshold:
                        # update both entry and closes after 15 secs
                        next_close_grid_update_ts_long = min(
                            next_close_grid_update_ts_long,
                            timestamps[k] + latency_simulation_ms + 15000,
                        )
                        next_entry_update_ts_long = min(
                            next_entry_update_ts_long,
                            timestamps[k] + latency_simulation_ms + 15000,
                        )

        if do_short:
            emas_short = calc_ema(alphas_short, alphas__short, emas_short,
                                  closes[k])
            if k >= max_span_short:
                # check bankruptcy
                bkr_diff_short = calc_diff(bkr_price_short, closes[k])
                closest_bkr_short = min(closest_bkr_short, bkr_diff_short)

                if closest_bkr_short < 0.06:
                    # consider bankruptcy within 6% as liquidation
                    if psize_short != 0.0:
                        fee_paid = (-qty_to_cost(psize_short, pprice_short,
                                                 inverse, c_mult) * maker_fee)
                        pnl = calc_pnl_short(pprice_short, closes[k],
                                             -psize_short, inverse, c_mult)
                        balance_short = 0.0
                        equity_short = 0.0
                        psize_short, pprice_short = 0.0, 0.0
                        fills_short.append((
                            k,
                            timestamps[k],
                            pnl,
                            fee_paid,
                            balance_short,
                            equity_short,
                            -psize_short,
                            closes[k],
                            0.0,
                            0.0,
                            "short_bankruptcy",
                        ))
                    do_short = False
                    if not do_long:
                        return fills_long, fills_short, stats

                # check if entry order should be updated
                if timestamps[k] >= next_entry_update_ts_short:
                    entry_short = calc_recursive_entry_short(
                        balance_short,
                        psize_short,
                        pprice_short,
                        closes[k - 1],
                        max(emas_short),
                        inverse,
                        qty_step,
                        price_step,
                        min_qty,
                        min_cost,
                        c_mult,
                        initial_qty_pct[1],
                        initial_eprice_ema_dist[1],
                        ddown_factor[1],
                        rentry_pprice_dist[1],
                        rentry_pprice_dist_wallet_exposure_weighting[1],
                        wallet_exposure_limit[1],
                        auto_unstuck_ema_dist[1],
                        auto_unstuck_wallet_exposure_threshold[1],
                    )
                    next_entry_update_ts_short = timestamps[
                        k] + 1000 * 60 * 5  # five mins delay
                # check if close grid should be updated
                if timestamps[k] >= next_close_grid_update_ts_short:
                    closes_short = calc_close_grid_short(
                        balance_short,
                        psize_short,
                        pprice_short,
                        closes[k - 1],
                        min(emas_short),
                        inverse,
                        qty_step,
                        price_step,
                        min_qty,
                        min_cost,
                        c_mult,
                        wallet_exposure_limit[1],
                        min_markup[1],
                        markup_range[1],
                        n_close_orders[1],
                        auto_unstuck_wallet_exposure_threshold[1],
                        auto_unstuck_ema_dist[1],
                    )
                    next_close_grid_update_ts_short = timestamps[
                        k] + 1000 * 60 * 5  # five mins delay

                # check if short entry filled
                while entry_short[0] != 0.0 and highs[k] > entry_short[1]:
                    next_entry_update_ts_short = min(
                        next_entry_update_ts_short,
                        timestamps[k] + latency_simulation_ms)
                    next_close_grid_update_ts_short = min(
                        next_close_grid_update_ts_short,
                        timestamps[k] + latency_simulation_ms)
                    psize_short, pprice_short = calc_new_psize_pprice(
                        psize_short,
                        pprice_short,
                        entry_short[0],
                        entry_short[1],
                        qty_step,
                    )
                    fee_paid = (-qty_to_cost(entry_short[0], entry_short[1],
                                             inverse, c_mult) * maker_fee)
                    balance_short += fee_paid
                    equity_short = balance_short + calc_pnl_short(
                        pprice_short, closes[k], psize_short, inverse, c_mult)
                    fills_short.append((
                        k,
                        timestamps[k],
                        0.0,
                        fee_paid,
                        balance_short,
                        equity_short,
                        entry_short[0],
                        entry_short[1],
                        psize_short,
                        pprice_short,
                        entry_short[2],
                    ))
                    bkr_price_short = calc_bankruptcy_price(
                        balance_short,
                        0.0,
                        0.0,
                        psize_short,
                        pprice_short,
                        inverse,
                        c_mult,
                    )
                    short_wallet_exposure = (qty_to_cost(
                        psize_short, pprice_short, inverse, c_mult) /
                                             balance_short)
                    entry_short = calc_recursive_entry_short(
                        balance_short,
                        psize_short,
                        pprice_short,
                        closes[k - 1],
                        max(emas_short),
                        inverse,
                        qty_step,
                        price_step,
                        min_qty,
                        min_cost,
                        c_mult,
                        initial_qty_pct[1],
                        initial_eprice_ema_dist[1],
                        ddown_factor[1],
                        rentry_pprice_dist[1],
                        rentry_pprice_dist_wallet_exposure_weighting[1],
                        wallet_exposure_limit[1],
                        auto_unstuck_ema_dist[1],
                        auto_unstuck_wallet_exposure_threshold[1],
                    )
                # check if short closes filled
                while (psize_short < 0.0 and closes_short
                       and closes_short[0][0] > 0.0
                       and lows[k] < closes_short[0][1]):
                    next_entry_update_ts_short = min(
                        next_entry_update_ts_short,
                        timestamps[k] + latency_simulation_ms)
                    next_close_grid_update_ts_short = min(
                        next_close_grid_update_ts_short,
                        timestamps[k] + latency_simulation_ms)
                    close_qty_short = closes_short[0][0]
                    new_psize_short = round_(psize_short + close_qty_short,
                                             qty_step)
                    if new_psize_short > 0.0:
                        print(
                            "warning: short close qty greater than short psize"
                        )
                        print("psize_short", psize_short)
                        print("pprice_short", pprice_short)
                        print("closes_short[0]", closes_short[0])
                        close_qty_short = abs(psize_short)
                        new_psize_short, pprice_short = 0.0, 0.0
                    psize_short = new_psize_short
                    fee_paid = (-qty_to_cost(
                        close_qty_short, closes_short[0][1], inverse, c_mult) *
                                maker_fee)
                    pnl = calc_pnl_short(pprice_short, closes_short[0][1],
                                         close_qty_short, inverse, c_mult)
                    balance_short += fee_paid + pnl
                    equity_short = balance_short + calc_pnl_short(
                        pprice_short, closes[k], psize_short, inverse, c_mult)
                    fills_short.append((
                        k,
                        timestamps[k],
                        pnl,
                        fee_paid,
                        balance_short,
                        equity_short,
                        close_qty_short,
                        closes_short[0][1],
                        psize_short,
                        pprice_short,
                        closes_short[0][2],
                    ))
                    closes_short = closes_short[1:]
                    bkr_price_short = calc_bankruptcy_price(
                        balance_short,
                        0.0,
                        0.0,
                        psize_short,
                        pprice_short,
                        inverse,
                        c_mult,
                    )
                    short_wallet_exposure = (qty_to_cost(
                        psize_short, pprice_short, inverse, c_mult) /
                                             balance_short)

                if psize_short == 0.0:
                    # update entry order now
                    next_entry_update_ts_short = min(
                        next_entry_update_ts_short,
                        timestamps[k] + latency_simulation_ms,
                    )
                else:
                    if closes[k] > pprice_short:
                        # update closes after 2.5 secs
                        next_close_grid_update_ts_short = min(
                            next_close_grid_update_ts_short,
                            timestamps[k] + latency_simulation_ms + 2500,
                        )
                    elif short_wallet_exposure >= short_wallet_exposure_auto_unstuck_threshold:
                        # update both entry and closes after 15 secs
                        next_close_grid_update_ts_short = min(
                            next_close_grid_update_ts_short,
                            timestamps[k] + latency_simulation_ms + 15000,
                        )
                        next_entry_update_ts_short = min(
                            next_entry_update_ts_short,
                            timestamps[k] + latency_simulation_ms + 15000,
                        )

        # process stats
        if timestamps[k] >= next_stats_update:
            equity_long = balance_long + calc_pnl_long(
                pprice_long, closes[k], psize_long, inverse, c_mult)
            equity_short = balance_short + calc_pnl_short(
                pprice_short, closes[k], psize_short, inverse, c_mult)
            stats.append((
                timestamps[k],
                bkr_price_long,
                bkr_price_short,
                psize_long,
                pprice_long,
                psize_short,
                pprice_short,
                closes[k],
                closest_bkr_long,
                closest_bkr_short,
                balance_long,
                balance_short,
                equity_long,
                equity_short,
            ))
            next_stats_update = timestamps[k] + 60 * 1000

    return fills_long, fills_short, stats
Example #11
0
def calc_recursive_entry_short(
    balance,
    psize,
    pprice,
    lowest_ask,
    ema_band_upper,
    inverse,
    qty_step,
    price_step,
    min_qty,
    min_cost,
    c_mult,
    initial_qty_pct,
    initial_eprice_ema_dist,
    ddown_factor,
    rentry_pprice_dist,
    rentry_pprice_dist_wallet_exposure_weighting,
    wallet_exposure_limit,
    auto_unstuck_ema_dist,
    auto_unstuck_wallet_exposure_threshold,
):
    abs_psize = abs(psize)
    ientry_price = max(
        lowest_ask,
        round_up(ema_band_upper * (1 + initial_eprice_ema_dist), price_step))
    min_entry_qty = calc_min_entry_qty(ientry_price, inverse, qty_step,
                                       min_qty, min_cost)
    ientry_qty = max(
        min_entry_qty,
        round_(
            cost_to_qty(balance, ientry_price, inverse, c_mult) *
            wallet_exposure_limit * initial_qty_pct,
            qty_step,
        ),
    )
    if abs_psize == 0.0:
        # normal ientry
        return -ientry_qty, ientry_price, "short_ientry_normal"
    elif abs_psize < ientry_qty * 0.8:
        # partial ientry
        entry_qty = max(min_entry_qty, round_(ientry_qty - abs_psize,
                                              qty_step))
        return -entry_qty, ientry_price, "short_ientry_partial"
    else:
        wallet_exposure = qty_to_cost(abs_psize, pprice, inverse,
                                      c_mult) / balance
        if wallet_exposure >= wallet_exposure_limit * 1.001:
            # no entry if wallet_exposure within 0.1% of limit
            return 0.0, 0.0, ""
        threshold = wallet_exposure_limit * (
            1 - auto_unstuck_wallet_exposure_threshold)
        if auto_unstuck_wallet_exposure_threshold != 0.0 and wallet_exposure > threshold * 0.99:
            # auto unstuck mode
            entry_price = round_up(
                max([
                    lowest_ask, pprice,
                    ema_band_upper * (1 + auto_unstuck_ema_dist)
                ]), price_step)
            entry_qty = find_entry_qty_bringing_wallet_exposure_to_target(
                balance,
                abs_psize,
                pprice,
                wallet_exposure_limit,
                entry_price,
                inverse,
                qty_step,
                c_mult,
            )
            min_entry_qty = calc_min_entry_qty(entry_price, inverse, qty_step,
                                               min_qty, min_cost)
            return (-max(entry_qty, min_entry_qty), entry_price,
                    "short_unstuck_entry")
        else:
            # normal reentry
            ratio = wallet_exposure / wallet_exposure_limit
            entry_price = round_up(
                pprice *
                (1 + rentry_pprice_dist *
                 (1 + ratio * rentry_pprice_dist_wallet_exposure_weighting)),
                price_step,
            )
            entry_price = max(entry_price, lowest_ask)
            min_entry_qty = calc_min_entry_qty(entry_price, inverse, qty_step,
                                               min_qty, min_cost)
            entry_qty = max(min_entry_qty,
                            round_(abs_psize * ddown_factor, qty_step))
            wallet_exposure_if_filled = calc_wallet_exposure_if_filled(
                balance, abs_psize, pprice, entry_qty, entry_price, inverse,
                c_mult, qty_step)
            if wallet_exposure_if_filled > wallet_exposure_limit * 1.01:
                # crop qty
                entry_qty = find_entry_qty_bringing_wallet_exposure_to_target(
                    balance,
                    abs_psize,
                    pprice,
                    wallet_exposure_limit,
                    entry_price,
                    inverse,
                    qty_step,
                    c_mult,
                )
                entry_qty = max(entry_qty, min_entry_qty)
            return -entry_qty, entry_price, "short_rentry"