Beispiel #1
0
 async def create_orders(self, orders_to_create: [dict]) -> [dict]:
     if not orders_to_create:
         return []
     if self.ts_locked['create_orders'] > self.ts_released['create_orders']:
         return []
     self.ts_locked['create_orders'] = time()
     try:
         creations = []
         for oc in sorted(orders_to_create, key=lambda x: calc_diff(x['price'], self.price)):
             try:
                 creations.append((oc, asyncio.create_task(self.execute_order(oc))))
             except Exception as e:
                 print_(['error creating order a', oc, e], n=True)
         created_orders = []
         for oc, c in creations:
             try:
                 o = await c
                 created_orders.append(o)
                 if 'side' in o:
                     print_(['  created order', o['symbol'], o['side'], o['position_side'], o['qty'],
                             o['price']], n=True)
                     if o['order_id'] not in {x['order_id'] for x in self.open_orders}:
                         self.open_orders.append(o)
                 else:
                     print_(['error creating order b', o, oc], n=True)
                 self.dump_log({'log_type': 'create_order', 'data': o})
             except Exception as e:
                 print_(['error creating order c', oc, c.exception(), e], n=True)
                 self.dump_log({'log_type': 'create_order', 'data': {'result': str(c.exception()),
                                                                     'error': repr(e), 'data': oc}})
         return created_orders
     finally:
         self.ts_released['create_orders'] = time()
Beispiel #2
0
 async def create_orders(self, orders_to_create: [dict]) -> [dict]:
     if not orders_to_create:
         return []
     if self.ts_locked["create_orders"] > self.ts_released["create_orders"]:
         return []
     self.ts_locked["create_orders"] = time()
     try:
         creations = []
         for oc in sorted(orders_to_create,
                          key=lambda x: calc_diff(x["price"], self.price)):
             try:
                 creations.append(
                     (oc, asyncio.create_task(self.execute_order(oc))))
             except Exception as e:
                 print_(["error creating order a", oc, e], n=True)
         created_orders = []
         for oc, c in creations:
             try:
                 o = await c
                 created_orders.append(o)
                 if "side" in o:
                     print_(
                         [
                             "  created order",
                             o["symbol"],
                             o["side"],
                             o["position_side"],
                             o["qty"],
                             o["price"],
                         ],
                         n=True,
                     )
                     if o["order_id"] not in {
                             x["order_id"]
                             for x in self.open_orders
                     }:
                         self.open_orders.append(o)
                 else:
                     print_(["error creating order b", o, oc], n=True)
                 self.dump_log({"log_type": "create_order", "data": o})
             except Exception as e:
                 print_(["error creating order c", oc,
                         c.exception(), e],
                        n=True)
                 self.dump_log({
                     "log_type": "create_order",
                     "data": {
                         "result": str(c.exception()),
                         "error": repr(e),
                         "data": oc,
                     },
                 })
         return created_orders
     finally:
         self.ts_released["create_orders"] = time()
Beispiel #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
Beispiel #4
0
    async def cancel_and_create(self):
        if self.ts_locked["cancel_and_create"] > self.ts_released[
                "cancel_and_create"]:
            return
        self.ts_locked["cancel_and_create"] = time()
        try:
            to_cancel_, to_create_ = filter_orders(
                self.open_orders,
                self.calc_orders(),
                keys=["side", "position_side", "qty", "price"],
            )

            to_cancel, to_create = [], []
            for elm in to_cancel_:
                if elm["position_side"] == "long":
                    if self.long_mode == "tp_only":
                        if elm["side"] == "sell":
                            to_cancel.append(elm)
                    elif self.long_mode != "manual":
                        to_cancel.append(elm)
                if elm["position_side"] == "shrt":
                    if self.shrt_mode == "tp_only":
                        if elm["side"] == "buy":
                            to_cancel.append(elm)
                    elif self.shrt_mode != "manual":
                        to_cancel.append(elm)
            for elm in to_create_:
                if elm["position_side"] == "long":
                    if self.long_mode == "tp_only":
                        if elm["side"] == "sell":
                            to_create.append(elm)
                    elif self.long_mode != "manual":
                        to_create.append(elm)
                if elm["position_side"] == "shrt":
                    if self.shrt_mode == "tp_only":
                        if elm["side"] == "buy":
                            to_create.append(elm)
                    elif self.shrt_mode != "manual":
                        to_create.append(elm)

            to_cancel = sorted(to_cancel,
                               key=lambda x: calc_diff(x["price"], self.price))
            to_create = sorted(to_create,
                               key=lambda x: calc_diff(x["price"], self.price))

            results = []
            if to_cancel:
                # to avoid building backlog, cancel n+1 orders, create n orders
                results.append(
                    asyncio.create_task(
                        self.cancel_orders(
                            to_cancel[:self.n_orders_per_execution + 1])))
                await asyncio.sleep(
                    0.01
                )  # sleep 10 ms between sending cancellations and sending creations
            if to_create:
                results.append(await self.create_orders(
                    to_create[:self.n_orders_per_execution]))
            if any(results):
                print()
            await asyncio.sleep(self.delay_between_executions
                                )  # sleep before releasing lock
            return results
        finally:
            self.ts_released["cancel_and_create"] = time()
Beispiel #5
0
    def calc_orders(self):
        balance = self.position['wallet_balance']
        long_psize = self.position['long']['size']
        long_pprice = self.position['long']['price']
        shrt_psize = self.position['shrt']['size']
        shrt_pprice = self.position['shrt']['price']

        if self.hedge_mode:
            do_long = self.do_long or long_psize != 0.0
            do_shrt = self.do_shrt or shrt_psize != 0.0
        else:
            no_pos = long_psize == 0.0 and shrt_psize == 0.0
            do_long = (no_pos and self.do_long) or long_psize != 0.0
            do_shrt = (no_pos and self.do_shrt) or shrt_psize != 0.0
        self.xk['do_long'] = do_long
        self.xk['do_shrt'] = do_shrt

        orders = []

        if self.long_mode == 'panic':
            if long_psize != 0.0:
                orders.append({
                    'side': 'sell',
                    'position_side': 'long',
                    'qty': abs(long_psize),
                    'price': float(self.ob[1]),
                    'type': 'limit',
                    'reduce_only': True,
                    'custom_id': 'long_panic_close'
                })
        else:
            long_entries = calc_long_entry_grid(
                balance, long_psize, long_pprice, self.ob[0],
                self.xk['inverse'], self.xk['do_long'], self.xk['qty_step'],
                self.xk['price_step'], self.xk['min_qty'], self.xk['min_cost'],
                self.xk['c_mult'], self.xk['grid_span'][0],
                self.xk['pbr_limit'][0], self.xk['max_n_entry_orders'][0],
                self.xk['initial_qty_pct'][0],
                self.xk['eprice_pprice_diff'][0],
                self.xk['secondary_pbr_allocation'][0],
                self.xk['secondary_pprice_diff'][0],
                self.xk['eprice_exp_base'][0])
            long_closes = calc_long_close_grid(
                balance, long_psize, long_pprice, self.ob[1], self.xk['spot'],
                self.xk['inverse'], self.xk['qty_step'], self.xk['price_step'],
                self.xk['min_qty'], self.xk['min_cost'], self.xk['c_mult'],
                self.xk['pbr_limit'][0], self.xk['initial_qty_pct'][0],
                self.xk['min_markup'][0], self.xk['markup_range'][0],
                self.xk['n_close_orders'][0])
            orders += [{
                'side': 'buy',
                'position_side': 'long',
                'qty': abs(float(o[0])),
                'price': float(o[1]),
                'type': 'limit',
                'reduce_only': False,
                'custom_id': o[2]
            } for o in long_entries if o[0] > 0.0]
            orders += [{
                'side': 'sell',
                'position_side': 'long',
                'qty': abs(float(o[0])),
                'price': float(o[1]),
                'type': 'limit',
                'reduce_only': True,
                'custom_id': o[2]
            } for o in long_closes if o[0] < 0.0]
        if self.shrt_mode == 'panic':
            if shrt_psize != 0.0:
                orders.append({
                    'side': 'buy',
                    'position_side': 'shrt',
                    'qty': abs(shrt_psize),
                    'price': float(self.ob[0]),
                    'type': 'limit',
                    'reduce_only': True,
                    'custom_id': 'shrt_panic_close'
                })
        else:
            shrt_entries = calc_shrt_entry_grid(
                balance, shrt_psize, shrt_pprice, self.ob[1],
                self.xk['inverse'], self.xk['do_shrt'], self.xk['qty_step'],
                self.xk['price_step'], self.xk['min_qty'], self.xk['min_cost'],
                self.xk['c_mult'], self.xk['grid_span'][1],
                self.xk['pbr_limit'][1], self.xk['max_n_entry_orders'][1],
                self.xk['initial_qty_pct'][1],
                self.xk['eprice_pprice_diff'][1],
                self.xk['secondary_pbr_allocation'][1],
                self.xk['secondary_pprice_diff'][1],
                self.xk['eprice_exp_base'][1])
            shrt_closes = calc_shrt_close_grid(
                balance, shrt_psize, shrt_pprice, self.ob[0], self.xk['spot'],
                self.xk['inverse'], self.xk['qty_step'], self.xk['price_step'],
                self.xk['min_qty'], self.xk['min_cost'], self.xk['c_mult'],
                self.xk['pbr_limit'][1], self.xk['initial_qty_pct'][1],
                self.xk['min_markup'][1], self.xk['markup_range'][1],
                self.xk['n_close_orders'][1])
            orders += [{
                'side': 'sell',
                'position_side': 'shrt',
                'qty': abs(float(o[0])),
                'price': float(o[1]),
                'type': 'limit',
                'reduce_only': False,
                'custom_id': o[2]
            } for o in shrt_entries if o[0] < 0.0]
            orders += [{
                'side': 'buy',
                'position_side': 'shrt',
                'qty': abs(float(o[0])),
                'price': float(o[1]),
                'type': 'limit',
                'reduce_only': True,
                'custom_id': o[2]
            } for o in shrt_closes if o[0] > 0.0]
        return sorted(orders, key=lambda x: calc_diff(x['price'], self.price))
Beispiel #6
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
Beispiel #7
0
 def update_output_information(self):
     self.ts_released["print"] = time()
     line = f"{self.symbol} "
     closes_long = sorted(
         [
             o for o in self.open_orders
             if o["side"] == "sell" and o["position_side"] == "long"
         ],
         key=lambda x: x["price"],
     )
     entries_long = sorted(
         [
             o for o in self.open_orders
             if o["side"] == "buy" and o["position_side"] == "long"
         ],
         key=lambda x: x["price"],
     )
     if self.position["long"]["size"] != 0.0 or closes_long or entries_long:
         leqty, leprice = ((entries_long[-1]["qty"],
                            entries_long[-1]["price"]) if entries_long else
                           (0.0, 0.0))
         lcqty, lcprice = ((closes_long[0]["qty"],
                            closes_long[0]["price"]) if closes_long else
                           (0.0, 0.0))
         line += f"l {self.position['long']['size']} @ "
         line += f"{round_(self.position['long']['price'], self.price_step)}, "
         line += f"e {leqty} @ {leprice}, c {lcqty} @ {lcprice} "
         line += f"l.EMAs {[round_dynamic(e, 5) for e in self.emas_long]} "
     closes_short = sorted(
         [
             o for o in self.open_orders
             if o["side"] == "buy" and o["position_side"] == "short"
         ],
         key=lambda x: x["price"],
     )
     entries_short = sorted(
         [
             o for o in self.open_orders
             if o["side"] == "sell" and o["position_side"] == "short"
         ],
         key=lambda x: x["price"],
     )
     if self.position["short"][
             "size"] != 0.0 or closes_short or entries_short:
         seqty, seprice = ((entries_short[0]["qty"],
                            entries_short[0]["price"]) if entries_short else
                           (0.0, 0.0))
         scqty, scprice = ((closes_short[-1]["qty"],
                            closes_short[-1]["price"]) if closes_short else
                           (0.0, 0.0))
         line += f"s {self.position['short']['size']} @ "
         line += f"{round_(self.position['short']['price'], self.price_step)}, "
         line += f"e {seqty} @ {seprice}, c {scqty} @ {scprice} "
         line += f"s.EMAs {[round_dynamic(e, 5) for e in self.emas_short]} "
     if calc_diff(self.position["long"]["liquidation_price"],
                  self.price) < calc_diff(
                      self.position["short"]["liquidation_price"],
                      self.price):
         liq_price = self.position["long"]["liquidation_price"]
     else:
         liq_price = self.position["short"]["liquidation_price"]
     line += f"|| last {self.price} "
     line += f"lpprc diff {calc_diff(self.position['long']['price'], self.price):.3f} "
     line += f"spprc diff {calc_diff(self.position['short']['price'], self.price):.3f} "
     line += f"liq {round_dynamic(liq_price, 5)} "
     line += f"lw {self.position['long']['wallet_exposure']:.3f} "
     line += f"sw {self.position['short']['wallet_exposure']:.3f} "
     line += f"bal {round_dynamic(self.position['wallet_balance'], 5)} "
     line += f"eq {round_dynamic(self.position['equity'], 5)} "
     print_([line], r=True)
Beispiel #8
0
    async def cancel_and_create(self):
        if self.ts_locked["cancel_and_create"] > self.ts_released[
                "cancel_and_create"]:
            return
        if any(self.error_halt.values()):
            print_([
                f"warning:  error in rest api fetch {self.error_halt}, " +
                "halting order creations/cancellations"
            ])
            return
        self.ts_locked["cancel_and_create"] = time()
        try:
            ideal_orders = [
                o for o in self.calc_orders()
                if not any(k in o["custom_id"] for k in ["ientry", "unstuck"])
                or abs(o["price"] - self.price) / self.price < 0.01
            ]
            to_cancel_, to_create_ = filter_orders(
                self.open_orders,
                ideal_orders,
                keys=["side", "position_side", "qty", "price"],
            )

            to_cancel, to_create = [], []
            for elm in to_cancel_:
                if elm["position_side"] == "long":
                    if self.long_mode == "tp_only":
                        if elm["side"] == "sell":
                            to_cancel.append(elm)
                    elif self.long_mode != "manual":
                        to_cancel.append(elm)
                if elm["position_side"] == "short":
                    if self.short_mode == "tp_only":
                        if elm["side"] == "buy":
                            to_cancel.append(elm)
                    elif self.short_mode != "manual":
                        to_cancel.append(elm)
            for elm in to_create_:
                if elm["position_side"] == "long":
                    if self.long_mode == "tp_only":
                        if elm["side"] == "sell":
                            to_create.append(elm)
                    elif self.long_mode != "manual":
                        to_create.append(elm)
                if elm["position_side"] == "short":
                    if self.short_mode == "tp_only":
                        if elm["side"] == "buy":
                            to_create.append(elm)
                    elif self.short_mode != "manual":
                        to_create.append(elm)

            to_cancel = sorted(to_cancel,
                               key=lambda x: calc_diff(x["price"], self.price))
            to_create = sorted(to_create,
                               key=lambda x: calc_diff(x["price"], self.price))
            """
            print('to_cancel', to_cancel.values())
            print('to create', to_create.values())
            return
            """

            results = []
            if to_cancel:
                # to avoid building backlog, cancel n+1 orders, create n orders
                results.append(
                    asyncio.create_task(
                        self.cancel_orders(
                            to_cancel[:self.n_orders_per_execution + 1])))
                await asyncio.sleep(
                    0.01
                )  # sleep 10 ms between sending cancellations and sending creations
            if to_create:
                results.append(await self.create_orders(
                    to_create[:self.n_orders_per_execution]))
            if any(results):
                print()
            await asyncio.sleep(self.delay_between_executions
                                )  # sleep before releasing lock
            return results
        finally:
            self.ts_released["cancel_and_create"] = time()
Beispiel #9
0
    def calc_orders(self):
        balance = self.position["wallet_balance"]
        psize_long = self.position["long"]["size"]
        pprice_long = self.position["long"]["price"]
        psize_short = self.position["short"]["size"]
        pprice_short = self.position["short"]["price"]

        if self.hedge_mode:
            do_long = self.do_long or psize_long != 0.0
            do_short = self.do_short or psize_short != 0.0
        else:
            no_pos = psize_long == 0.0 and psize_short == 0.0
            do_long = (no_pos and self.do_long) or psize_long != 0.0
            do_short = (no_pos and self.do_short) or psize_short != 0.0
        self.xk["do_long"] = do_long
        self.xk["do_short"] = do_short

        orders = []

        if self.long_mode == "panic":
            if psize_long != 0.0:
                orders.append({
                    "side": "sell",
                    "position_side": "long",
                    "qty": abs(psize_long),
                    "price": float(self.ob[1]),
                    "type": "limit",
                    "reduce_only": True,
                    "custom_id": "long_panic_close",
                })
        else:
            if do_long:
                if self.passivbot_mode == "recursive_grid":
                    entries_long = calc_recursive_entries_long(
                        balance,
                        psize_long,
                        pprice_long,
                        self.ob[0],
                        min(self.emas_long),
                        self.xk["inverse"],
                        self.xk["qty_step"],
                        self.xk["price_step"],
                        self.xk["min_qty"],
                        self.xk["min_cost"],
                        self.xk["c_mult"],
                        self.xk["initial_qty_pct"][0],
                        self.xk["initial_eprice_ema_dist"][0],
                        self.xk["ddown_factor"][0],
                        self.xk["rentry_pprice_dist"][0],
                        self.xk["rentry_pprice_dist_wallet_exposure_weighting"]
                        [0],
                        self.xk["wallet_exposure_limit"][0],
                        self.xk["auto_unstuck_ema_dist"][0],
                        self.xk["auto_unstuck_wallet_exposure_threshold"][0],
                    )
                elif self.passivbot_mode == "static_grid":
                    entries_long = calc_entry_grid_long(
                        balance,
                        psize_long,
                        pprice_long,
                        self.ob[0],
                        min(self.emas_long),
                        self.xk["inverse"],
                        self.xk["do_long"],
                        self.xk["qty_step"],
                        self.xk["price_step"],
                        self.xk["min_qty"],
                        self.xk["min_cost"],
                        self.xk["c_mult"],
                        self.xk["grid_span"][0],
                        self.xk["wallet_exposure_limit"][0],
                        self.xk["max_n_entry_orders"][0],
                        self.xk["initial_qty_pct"][0],
                        self.xk["initial_eprice_ema_dist"][0],
                        self.xk["eprice_pprice_diff"][0],
                        self.xk["secondary_allocation"][0],
                        self.xk["secondary_pprice_diff"][0],
                        self.xk["eprice_exp_base"][0],
                        self.xk["auto_unstuck_wallet_exposure_threshold"][0],
                        self.xk["auto_unstuck_ema_dist"][0],
                    )
                else:
                    raise Exception(
                        f"unknown passivbot mode {self.passivbot_mode}")
                orders += [{
                    "side": "buy",
                    "position_side": "long",
                    "qty": abs(float(o[0])),
                    "price": float(o[1]),
                    "type": "limit",
                    "reduce_only": False,
                    "custom_id": o[2],
                } for o in entries_long if o[0] > 0.0]
            if do_long or self.long_mode == "tp_only":
                closes_long = calc_close_grid_long(
                    balance,
                    psize_long,
                    pprice_long,
                    self.ob[1],
                    max(self.emas_long),
                    self.xk["inverse"],
                    self.xk["qty_step"],
                    self.xk["price_step"],
                    self.xk["min_qty"],
                    self.xk["min_cost"],
                    self.xk["c_mult"],
                    self.xk["wallet_exposure_limit"][0],
                    self.xk["min_markup"][0],
                    self.xk["markup_range"][0],
                    self.xk["n_close_orders"][0],
                    self.xk["auto_unstuck_wallet_exposure_threshold"][0],
                    self.xk["auto_unstuck_ema_dist"][0],
                )
                orders += [{
                    "side": "sell",
                    "position_side": "long",
                    "qty": abs(float(o[0])),
                    "price": float(o[1]),
                    "type": "limit",
                    "reduce_only": True,
                    "custom_id": o[2],
                } for o in closes_long if o[0] < 0.0]
        if self.short_mode == "panic":
            if psize_short != 0.0:
                orders.append({
                    "side": "buy",
                    "position_side": "short",
                    "qty": abs(psize_short),
                    "price": float(self.ob[0]),
                    "type": "limit",
                    "reduce_only": True,
                    "custom_id": "short_panic_close",
                })
        else:
            if do_short:
                if self.passivbot_mode == "recursive_grid":
                    entries_short = calc_recursive_entries_short(
                        balance,
                        psize_short,
                        pprice_short,
                        self.ob[1],
                        max(self.emas_short),
                        self.xk["inverse"],
                        self.xk["qty_step"],
                        self.xk["price_step"],
                        self.xk["min_qty"],
                        self.xk["min_cost"],
                        self.xk["c_mult"],
                        self.xk["initial_qty_pct"][1],
                        self.xk["initial_eprice_ema_dist"][1],
                        self.xk["ddown_factor"][1],
                        self.xk["rentry_pprice_dist"][1],
                        self.xk["rentry_pprice_dist_wallet_exposure_weighting"]
                        [1],
                        self.xk["wallet_exposure_limit"][1],
                        self.xk["auto_unstuck_ema_dist"][1],
                        self.xk["auto_unstuck_wallet_exposure_threshold"][1],
                    )
                elif self.passivbot_mode == "static_grid":
                    entries_short = calc_entry_grid_short(
                        balance,
                        psize_short,
                        pprice_short,
                        self.ob[1],
                        max(self.emas_short),
                        self.xk["inverse"],
                        self.xk["do_short"],
                        self.xk["qty_step"],
                        self.xk["price_step"],
                        self.xk["min_qty"],
                        self.xk["min_cost"],
                        self.xk["c_mult"],
                        self.xk["grid_span"][1],
                        self.xk["wallet_exposure_limit"][1],
                        self.xk["max_n_entry_orders"][1],
                        self.xk["initial_qty_pct"][1],
                        self.xk["initial_eprice_ema_dist"][1],
                        self.xk["eprice_pprice_diff"][1],
                        self.xk["secondary_allocation"][1],
                        self.xk["secondary_pprice_diff"][1],
                        self.xk["eprice_exp_base"][1],
                        self.xk["auto_unstuck_wallet_exposure_threshold"][1],
                        self.xk["auto_unstuck_ema_dist"][1],
                    )
                else:
                    raise Exception(
                        f"unknown passivbot mode {self.passivbot_mode}")
                orders += [{
                    "side": "sell",
                    "position_side": "short",
                    "qty": abs(float(o[0])),
                    "price": float(o[1]),
                    "type": "limit",
                    "reduce_only": False,
                    "custom_id": o[2],
                } for o in entries_short if o[0] < 0.0]
            if do_short or self.short_mode == "tp_only":
                closes_short = calc_close_grid_short(
                    balance,
                    psize_short,
                    pprice_short,
                    self.ob[0],
                    min(self.emas_short),
                    self.xk["inverse"],
                    self.xk["qty_step"],
                    self.xk["price_step"],
                    self.xk["min_qty"],
                    self.xk["min_cost"],
                    self.xk["c_mult"],
                    self.xk["wallet_exposure_limit"][1],
                    self.xk["min_markup"][1],
                    self.xk["markup_range"][1],
                    self.xk["n_close_orders"][1],
                    self.xk["auto_unstuck_wallet_exposure_threshold"][1],
                    self.xk["auto_unstuck_ema_dist"][1],
                )
                orders += [{
                    "side": "buy",
                    "position_side": "short",
                    "qty": abs(float(o[0])),
                    "price": float(o[1]),
                    "type": "limit",
                    "reduce_only": True,
                    "custom_id": o[2],
                } for o in closes_short if o[0] > 0.0]
        return sorted(orders, key=lambda x: calc_diff(x["price"], self.price))