Пример #1
0
 def add_pbrs_to_pos(self, position_: dict):
     position = position_.copy()
     position['long']['pbr'] = (qty_to_cost(position['long']['size'], position['long']['price'],
                                            self.xk['inverse'], self.xk['c_mult']) /
                                position['wallet_balance']) if position['wallet_balance'] else 0.0
     position['shrt']['pbr'] = (qty_to_cost(position['shrt']['size'], position['shrt']['price'],
                                            self.xk['inverse'], self.xk['c_mult']) /
                                position['wallet_balance']) if position['wallet_balance'] else 0.0
     return position
Пример #2
0
 def add_wallet_exposures_to_pos(self, position_: dict):
     position = position_.copy()
     position["long"]["wallet_exposure"] = ((qty_to_cost(
         position["long"]["size"],
         position["long"]["price"],
         self.xk["inverse"],
         self.xk["c_mult"],
     ) / position["wallet_balance"]) if position["wallet_balance"] else 0.0)
     position["short"]["wallet_exposure"] = ((qty_to_cost(
         position["short"]["size"],
         position["short"]["price"],
         self.xk["inverse"],
         self.xk["c_mult"],
     ) / position["wallet_balance"]) if position["wallet_balance"] else 0.0)
     return position
Пример #3
0
 def calc_orders(self):
     default_orders = super().calc_orders()
     orders = []
     remaining_cost = self.balance[self.quot]["onhand"]
     for order in sorted(default_orders,
                         key=lambda x: calc_diff(x["price"], self.price)):
         if order["price"] > min(
                 self.max_price,
                 round_dn(self.price * self.price_multiplier_up,
                          self.price_step),
         ):
             print(f'price {order["price"]} too high')
             continue
         if order["price"] < max(
                 self.min_price,
                 round_up(self.price * self.price_multiplier_dn,
                          self.price_step),
         ):
             print(f'price {order["price"]} too low')
             continue
         if order["side"] == "buy":
             cost = qty_to_cost(order["qty"], order["price"], self.inverse,
                                self.c_mult)
             if cost > remaining_cost:
                 adjusted_qty = round_dn(remaining_cost / order["price"],
                                         self.qty_step)
                 min_entry_qty = calc_min_entry_qty(
                     order["price"],
                     self.inverse,
                     self.qty_step,
                     self.min_qty,
                     self.min_cost,
                 )
                 if adjusted_qty >= min_entry_qty:
                     orders.append({**order, **{"qty": adjusted_qty}})
                     remaining_cost = 0.0
             else:
                 orders.append(order)
                 remaining_cost -= cost
         else:
             orders.append(order)
     return orders
Пример #4
0
def analyze_fills(fills_long: list, fills_short: list, stats: list,
                  config: dict) -> (pd.DataFrame, pd.DataFrame, dict):
    sdf = pd.DataFrame(
        stats,
        columns=[
            "timestamp",
            "bkr_price_long",
            "bkr_price_short",
            "psize_long",
            "pprice_long",
            "psize_short",
            "pprice_short",
            "price",
            "closest_bkr_long",
            "closest_bkr_short",
            "balance_long",
            "balance_short",
            "equity_long",
            "equity_short",
        ],
    )
    longs = pd.DataFrame(
        fills_long,
        columns=[
            "trade_id",
            "timestamp",
            "pnl",
            "fee_paid",
            "balance",
            "equity",
            "qty",
            "price",
            "psize",
            "pprice",
            "type",
        ],
    )
    longs.index = longs.timestamp
    shorts = pd.DataFrame(
        fills_short,
        columns=[
            "trade_id",
            "timestamp",
            "pnl",
            "fee_paid",
            "balance",
            "equity",
            "qty",
            "price",
            "psize",
            "pprice",
            "type",
        ],
    )
    shorts.index = shorts.timestamp
    longs.loc[:, "wallet_exposure"] = [
        qty_to_cost(x.psize, x.pprice, config["inverse"], config["c_mult"]) /
        x.balance if x.balance > 0.0 else 0.0 for x in longs.itertuples()
    ]
    shorts.loc[:, "wallet_exposure"] = [
        qty_to_cost(x.psize, x.pprice, config["inverse"], config["c_mult"]) /
        x.balance if x.balance > 0.0 else 0.0 for x in shorts.itertuples()
    ]
    sdf.loc[:, "wallet_exposure_long"] = [
        qty_to_cost(x.psize_long, x.pprice_long, config["inverse"],
                    config["c_mult"]) /
        x.balance_long if x.balance_long > 0.0 else 0.0
        for x in sdf.itertuples()
    ]
    sdf.loc[:, "wallet_exposure_short"] = [
        qty_to_cost(x.psize_short, x.pprice_short, config["inverse"],
                    config["c_mult"]) /
        x.balance_short if x.balance_short > 0.0 else 0.0
        for x in sdf.itertuples()
    ]
    n_days = (sdf.timestamp.iloc[-1] - sdf.timestamp.iloc[0]) / (1000 * 60 *
                                                                 60 * 24)
    pos_changes_long = sdf[sdf.psize_long != sdf.psize_long.shift()]
    pos_changes_long_ms_diff = np.diff(
        list(pos_changes_long.timestamp) + [sdf.timestamp.iloc[-1]])
    hrs_stuck_max_long = pos_changes_long_ms_diff.max() / (1000 * 60 * 60)
    hrs_stuck_avg_long = pos_changes_long_ms_diff.mean() / (1000 * 60 * 60)
    pos_changes_short = sdf[sdf.psize_short != sdf.psize_short.shift()]
    pos_changes_short_ms_diff = np.diff(
        list(pos_changes_short.timestamp) + [sdf.timestamp.iloc[-1]])
    hrs_stuck_max_short = pos_changes_short_ms_diff.max() / (1000 * 60 * 60)
    hrs_stuck_avg_short = pos_changes_short_ms_diff.mean() / (1000 * 60 * 60)
    lpprices = sdf[sdf.psize_long != 0.0]
    spprices = sdf[sdf.psize_short != 0.0]
    pa_distance_long = (((lpprices.pprice_long - lpprices.price).abs() /
                         lpprices.price)
                        if len(lpprices) > 0 else pd.Series([100.0]))
    pa_distance_short = (((spprices.pprice_short - spprices.price).abs() /
                          spprices.price)
                         if len(spprices) > 0 else pd.Series([100.0]))
    gain_long = longs.pnl.sum() / sdf.balance_long.iloc[0]
    gain_short = shorts.pnl.sum() / sdf.balance_short.iloc[0]

    ms2d = 1000 * 60 * 60 * 24
    if len(longs) > 0:
        daily_equity_long = sdf.groupby(sdf.timestamp //
                                        ms2d).equity_long.last()
        daily_gains_long = daily_equity_long / daily_equity_long.shift(1) - 1
        adg_long = daily_gains_long.mean()
        DGstd_long = daily_gains_long.std()
        adg_DGstd_ratio_long = adg_long / DGstd_long if len(
            daily_gains_long) > 0 else 0.0
        if any("bankruptcy" in e for e in longs.type.unique()):
            adg_long = 0.01**(
                1 / n_days) - 1  # reward bankrupt runs lasting longer
    else:
        adg_long = adg_DGstd_ratio_long = 0.0
        DGstd_long = 100.0

    if len(shorts) > 0:
        daily_equity_short = sdf.groupby(sdf.timestamp //
                                         ms2d).equity_short.last()
        daily_gains_short = daily_equity_short / daily_equity_short.shift(
            1) - 1
        adg_short = daily_gains_short.mean()
        DGstd_short = daily_gains_short.std()
        adg_DGstd_ratio_short = adg_short / DGstd_short if len(
            daily_gains_short) > 0 else 0.0
        if any("bankruptcy" in e for e in shorts.type.unique()):
            adg_short = 0.01**(
                1 / n_days) - 1  # reward bankrupt runs lasting longer
    else:
        adg_short = adg_DGstd_ratio_short = 0.0
        DGstd_short = 100.0

    pos_costs_long = longs.apply(
        lambda x: qty_to_cost(x["psize"], x["pprice"], config["inverse"],
                              config["c_mult"]),
        axis=1,
    )
    pos_costs_short = shorts.apply(
        lambda x: qty_to_cost(x["psize"], x["pprice"], config["inverse"],
                              config["c_mult"]),
        axis=1,
    )
    biggest_pos_cost_long = pos_costs_long.max() if len(longs) > 0 else 0.0
    biggest_pos_cost_short = pos_costs_short.max() if len(shorts) > 0 else 0.0
    volume_quote_long = (longs.apply(
        lambda x: qty_to_cost(x["qty"], x["price"], config["inverse"], config[
            "c_mult"]),
        axis=1,
    ).sum() if len(longs) > 0 else 0.0)
    volume_quote_short = (shorts.apply(
        lambda x: qty_to_cost(x["qty"], x["price"], config["inverse"], config[
            "c_mult"]),
        axis=1,
    ).sum() if len(shorts) > 0 else 0.0)

    analysis = {
        "exchange":
        config["exchange"] if "exchange" in config else "unknown",
        "symbol":
        config["symbol"] if "symbol" in config else "unknown",
        "starting_balance":
        sdf.balance_long.iloc[0],
        "pa_distance_mean_long":
        pa_distance_long.mean(),
        "pa_distance_max_long":
        pa_distance_long.max(),
        "pa_distance_std_long":
        pa_distance_long.std(),
        "pa_distance_mean_short":
        pa_distance_short.mean(),
        "pa_distance_max_short":
        pa_distance_short.max(),
        "pa_distance_std_short":
        pa_distance_short.std(),
        "gain_long":
        gain_long,
        "adg_long":
        adg_long if adg_long == adg_long else -1.0,
        "adg_per_exposure_long":
        adg_long /
        config["long"]["wallet_exposure_limit"] if config["long"]["enabled"]
        and config["long"]["wallet_exposure_limit"] > 0.0 else 0.0,
        "gain_short":
        gain_short,
        "adg_short":
        adg_short if adg_short == adg_short else -1.0,
        "adg_per_exposure_short":
        adg_short /
        config["short"]["wallet_exposure_limit"] if config["short"]["enabled"]
        and config["short"]["wallet_exposure_limit"] > 0.0 else 0.0,
        "adg_DGstd_ratio_long":
        adg_DGstd_ratio_long,
        "adg_DGstd_ratio_short":
        adg_DGstd_ratio_short,
        "DGstd_long":
        DGstd_long,
        "DGstd_short":
        DGstd_short,
        "n_days":
        n_days,
        "n_fills_long":
        len(fills_long),
        "n_fills_short":
        len(fills_short),
        "n_closes_long":
        len(longs[longs.type.str.contains("close")]),
        "n_closes_short":
        len(shorts[shorts.type.str.contains("close")]),
        "n_normal_closes_long":
        len(longs[longs.type.str.contains("nclose")]),
        "n_normal_closes_short":
        len(shorts[shorts.type.str.contains("nclose")]),
        "n_entries_long":
        len(longs[longs.type.str.contains("entry")]),
        "n_entries_short":
        len(shorts[shorts.type.str.contains("entry")]),
        "n_ientries_long":
        len(longs[longs.type.str.contains("ientry")]),
        "n_ientries_short":
        len(shorts[shorts.type.str.contains("ientry")]),
        "n_rentries_long":
        len(longs[longs.type.str.contains("rentry")]),
        "n_rentries_short":
        len(shorts[shorts.type.str.contains("rentry")]),
        "n_unstuck_closes_long":
        len(longs[longs.type.str.contains("unstuck_close")]),
        "n_unstuck_closes_short":
        len(shorts[shorts.type.str.contains("unstuck_close")]),
        "n_unstuck_entries_long":
        len(longs[longs.type.str.contains("unstuck_entry")]),
        "n_unstuck_entries_short":
        len(shorts[shorts.type.str.contains("unstuck_entry")]),
        "avg_fills_per_day_long":
        len(longs) / n_days,
        "avg_fills_per_day_short":
        len(shorts) / n_days,
        "hrs_stuck_max_long":
        hrs_stuck_max_long,
        "hrs_stuck_avg_long":
        hrs_stuck_avg_long,
        "hrs_stuck_max_short":
        hrs_stuck_max_short,
        "hrs_stuck_avg_short":
        hrs_stuck_avg_short,
        "hrs_stuck_max":
        max(hrs_stuck_max_long, hrs_stuck_max_short),
        "hrs_stuck_avg":
        max(hrs_stuck_avg_long, hrs_stuck_avg_short),
        "loss_sum_long":
        longs[longs.pnl < 0.0].pnl.sum(),
        "loss_sum_short":
        shorts[shorts.pnl < 0.0].pnl.sum(),
        "profit_sum_long":
        longs[longs.pnl > 0.0].pnl.sum(),
        "profit_sum_short":
        shorts[shorts.pnl > 0.0].pnl.sum(),
        "pnl_sum_long": (pnl_sum_long := longs.pnl.sum()),
        "pnl_sum_short": (pnl_sum_short := shorts.pnl.sum()),
        "fee_sum_long": (fee_sum_long := longs.fee_paid.sum()),
        "fee_sum_short": (fee_sum_short := shorts.fee_paid.sum()),
        "net_pnl_plus_fees_long":
        pnl_sum_long + fee_sum_long,
        "net_pnl_plus_fees_short":
        pnl_sum_short + fee_sum_short,
        "final_equity_long":
        sdf.equity_long.iloc[-1],
        "final_balance_long":
        sdf.balance_long.iloc[-1],
        "final_equity_short":
        sdf.equity_short.iloc[-1],
        "final_balance_short":
        sdf.balance_short.iloc[-1],
        "closest_bkr_long":
        sdf.closest_bkr_long.min(),
        "closest_bkr_short":
        sdf.closest_bkr_short.min(),
        "eqbal_ratio_min_long": (eqbal_ratios_long :=
                                 sdf.equity_long / sdf.balance_long).min(),
        "eqbal_ratio_mean_long":
        eqbal_ratios_long.mean(),
        "eqbal_ratio_min_short": (eqbal_ratios_short :=
                                  sdf.equity_short / sdf.balance_short).min(),
        "eqbal_ratio_mean_short":
        eqbal_ratios_short.mean(),
        "biggest_psize_long":
        longs.psize.abs().max(),
        "biggest_psize_short":
        shorts.psize.abs().max(),
        "biggest_psize_quote_long":
        biggest_pos_cost_long,
        "biggest_psize_quote_short":
        biggest_pos_cost_short,
        "volume_quote_long":
        volume_quote_long,
        "volume_quote_short":
        volume_quote_short,
    }
Пример #5
0
def analyze_fills(fills: list, stats: list, config: dict) -> (pd.DataFrame, pd.DataFrame, dict):
    sdf = pd.DataFrame(stats, columns=['timestamp', 'balance', 'equity', 'bkr_price', 'long_psize',
                                       'long_pprice', 'shrt_psize', 'shrt_pprice', 'price', 'closest_bkr'])
    fdf = pd.DataFrame(fills, columns=['trade_id', 'timestamp', 'pnl', 'fee_paid', 'balance',
                                       'equity', 'qty', 'price', 'psize', 'pprice', 'type'])
    fdf.loc[:, 'pbr'] = [qty_to_cost(x.psize, x.pprice, config['inverse'], config['c_mult']) / x.balance
                         if x.balance > 0.0 else 0.0 for x in fdf.itertuples()]
    sdf.loc[:, 'long_pbr'] = [qty_to_cost(x.long_psize, x.long_pprice, config['inverse'], config['c_mult']) / x.balance
                              if x.balance > 0.0 else 0.0 for x in sdf.itertuples()]
    sdf.loc[:, 'shrt_pbr'] = [qty_to_cost(x.shrt_psize, x.shrt_pprice, config['inverse'], config['c_mult']) / x.balance
                              if x.balance > 0.0 else 0.0 for x in sdf.itertuples()]
    gain = sdf.balance.iloc[-1] / sdf.balance.iloc[0]
    n_days = (sdf.timestamp.iloc[-1] - sdf.timestamp.iloc[0]) / (1000 * 60 * 60 * 24)
    adg = gain ** (1 / n_days) - 1
    gain -= 1
    fills_per_day = len(fills) / n_days
    long_pos_changes = sdf[sdf.long_psize != sdf.long_psize.shift()]
    long_pos_changes_ms_diff = np.diff([sdf.timestamp.iloc[0]] + list(long_pos_changes.timestamp) + [sdf.timestamp.iloc[-1]])
    hrs_stuck_max_long = long_pos_changes_ms_diff.max() / (1000 * 60 * 60)
    hrs_stuck_avg_long = long_pos_changes_ms_diff.mean() / (1000 * 60 * 60)
    shrt_pos_changes = sdf[sdf.shrt_psize != sdf.shrt_psize.shift()]
    shrt_pos_changes_ms_diff = np.diff([sdf.timestamp.iloc[0]] + list(shrt_pos_changes.timestamp) + [sdf.timestamp.iloc[-1]])
    hrs_stuck_max_shrt = shrt_pos_changes_ms_diff.max() / (1000 * 60 * 60)
    hrs_stuck_avg_shrt = shrt_pos_changes_ms_diff.mean() / (1000 * 60 * 60)
    lpprices = sdf[sdf.long_pprice != 0.0]
    spprices = sdf[sdf.shrt_pprice != 0.0]
    pa_closeness_long = ((lpprices.long_pprice - lpprices.price).abs() / lpprices.price) if len(lpprices) > 0 else pd.Series([100.0])
    pa_closeness_shrt = ((spprices.shrt_pprice - spprices.price).abs() / spprices.price) if len(spprices) > 0 else pd.Series([100.0])

    analysis = {
        'exchange': config['exchange'] if 'exchange' in config else 'unknown',
        'symbol': config['symbol'] if 'symbol' in config else 'unknown',
        'starting_balance': sdf.balance.iloc[0],
        'pa_closeness_mean_long': pa_closeness_long.mean(),
        'pa_closeness_median_long': pa_closeness_long.median(),
        'pa_closeness_max_long': pa_closeness_long.max(),
        'pa_closeness_mean_shrt': pa_closeness_shrt.mean(),
        'pa_closeness_median_shrt': pa_closeness_shrt.median(),
        'pa_closeness_max_shrt': pa_closeness_shrt.max(),
        'average_daily_gain': adg,
        'adjusted_daily_gain': np.tanh(20 * adg) / 20,
        'gain': gain,
        'n_days': n_days,
        'n_fills': len(fills),
        'n_entries': len(fdf[fdf.type.str.contains('entry')]),
        'n_closes': len(fdf[fdf.type.str.contains('close')]),
        'n_ientries': len(fdf[fdf.type.str.contains('ientry')]),
        'n_rentries': len(fdf[fdf.type.str.contains('rentry')]),
        'avg_fills_per_day': fills_per_day,
        'hrs_stuck_max_long': hrs_stuck_max_long,
        'hrs_stuck_avg_long': hrs_stuck_avg_long,
        'hrs_stuck_max': hrs_stuck_max_long,
        'hrs_stuck_avg': hrs_stuck_avg_long,
        'hrs_stuck_max_shrt': hrs_stuck_max_shrt,
        'hrs_stuck_avg_shrt': hrs_stuck_avg_shrt,
        'hrs_stuck_max': hrs_stuck_max_shrt,
        'hrs_stuck_avg': hrs_stuck_avg_shrt,
        'loss_sum': fdf[fdf.pnl < 0.0].pnl.sum(),
        'profit_sum': fdf[fdf.pnl > 0.0].pnl.sum(),
        'pnl_sum': (pnl_sum := fdf.pnl.sum()),
        'fee_sum': (fee_sum := fdf.fee_paid.sum()),
        'net_pnl_plus_fees': pnl_sum + fee_sum,
        'final_equity': sdf.equity.iloc[-1],
        'final_balance': sdf.balance.iloc[-1],
        'closest_bkr': sdf.closest_bkr.min(),
        'eqbal_ratio_min': (eqbal_ratios := sdf.equity / sdf.balance).min(),
        'eqbal_ratio_mean': eqbal_ratios.mean(),
        'eqbal_ratio_median': eqbal_ratios.median(),
        'biggest_psize': fdf.psize.abs().max(),
    }
Пример #6
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"
Пример #7
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
Пример #8
0
def calc_recursive_entries_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,
    whole_grid=False,
):
    entries = []
    psize_ = psize
    pprice_ = pprice
    lowest_ask_ = lowest_ask
    i = 0
    infinite_loop_break = 30
    while True:
        i += 1
        if i > infinite_loop_break:
            break
        entry_qty, entry_price, entry_type = 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,
        )
        if entry_qty == 0.0:
            break
        if entries and entry_price == entries[-1][1]:
            break
        psize_, pprice_ = calc_new_psize_pprice(psize_, pprice_, entry_qty,
                                                entry_price, qty_step)
        lowest_ask_ = max(lowest_ask, entry_price)
        wallet_exposure = qty_to_cost(psize_, pprice_, inverse,
                                      c_mult) / balance
        if "unstuck" in entry_type:
            if len(entries) == 0:
                # return unstucking entry only if it's the only one
                return [(entry_qty, entry_price, entry_type, psize_, pprice_,
                         wallet_exposure)]
        else:
            entries.append((entry_qty, entry_price, entry_type, psize_,
                            pprice_, wallet_exposure))
        if not whole_grid and psize == 0.0:
            break
    return entries
Пример #9
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"