async def decide(self): if self.price <= self.highest_bid: self.ts_locked['decide'] = time() print_(['bid maybe taken'], n=True) await self.cancel_and_create() self.ts_released['decide'] = time() return if self.price >= self.lowest_ask: self.ts_locked['decide'] = time() print_(['ask maybe taken'], n=True) await self.cancel_and_create() self.ts_released['decide'] = time() return if time() - self.ts_locked['decide'] > 5: self.ts_locked['decide'] = time() await self.cancel_and_create() self.ts_released['decide'] = time() return if time() - self.ts_released['print'] >= 0.5: self.ts_released['print'] = time() line = f"{self.symbol} " line += f"long {self.position['long']['size']} @ " line += f"{round_(self.position['long']['price'], self.price_step)} " long_closes = sorted([o for o in self.open_orders if o['side'] == 'sell' and o['position_side'] == 'long'], key=lambda x: x['price']) long_entries = sorted([o for o in self.open_orders if o['side'] == 'buy' and o['position_side'] == 'long'], key=lambda x: x['price']) line += f"close @ {long_closes[0]['price'] if long_closes else 0.0} " line += f"enter @ {long_entries[-1]['price'] if long_entries else 0.0} " line += f"|| shrt {self.position['shrt']['size']} @ " line += f"{round_(self.position['shrt']['price'], self.price_step)} " shrt_closes = sorted([o for o in self.open_orders if o['side'] == 'buy' and (o['position_side'] == 'shrt' or (o['position_side'] == 'both' and self.position['shrt']['size'] != 0.0))], key=lambda x: x['price']) shrt_entries = sorted([o for o in self.open_orders if o['side'] == 'sell' and (o['position_side'] == 'shrt' or (o['position_side'] == 'both' and self.position['shrt']['size'] != 0.0))], key=lambda x: x['price']) line += f"close @ {shrt_closes[-1]['price'] if shrt_closes else 0.0} " line += f"enter @ {shrt_entries[0]['price'] if shrt_entries else 0.0} " if self.position['long']['size'] > abs(self.position['shrt']['size']): liq_price = self.position['long']['liquidation_price'] sl_trigger_price = round_(liq_price / (1 - self.stop_loss_liq_diff), self.price_step) else: liq_price = self.position['shrt']['liquidation_price'] sl_trigger_price = round_(liq_price / (1 + self.stop_loss_liq_diff), self.price_step) line += f"|| last {self.price} liq {liq_price} " line += f"sl trig {round_(sl_trigger_price, self.price_step)} " line += f"ema {round_(self.ema, self.price_step)} " line += f"bal {round_dynamic(self.position['wallet_balance'], 4)} " line += f"equity {round_dynamic(self.position['equity'], 4)} " line += f"v. {round_dynamic(self.volatility, 8)} " print_([line], r=True)
async def send_daily_async(): nr_of_days = 7 today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) daily = {} position = await self._bot.fetch_position() wallet_balance = position['wallet_balance'] for idx, item in enumerate(range(0, nr_of_days)): start_of_day = today - timedelta(days=idx) end_of_day = start_of_day + timedelta(days=1) start_time = int(start_of_day.timestamp()) * 1000 end_time = int(end_of_day.timestamp()) * 1000 daily_trades = await self._bot.fetch_fills( start_time=start_time, end_time=end_time) pln_summary = 0 for trade in daily_trades: pln_summary += float(trade['realized_pnl']) daily[idx] = {} daily[idx]['date'] = start_of_day.strftime('%d-%m') daily[idx]['pnl'] = pln_summary table = PrettyTable(['Date', 'PNL', 'Daily %']) pnl_sum = 0.0 for item in daily.keys(): day_profit = daily[item]['pnl'] pnl_sum += day_profit previous_day_close_wallet_balance = wallet_balance - day_profit profit_pct = ((wallet_balance / previous_day_close_wallet_balance) - 1) * 100 \ if previous_day_close_wallet_balance > 0.0 else 0.0 wallet_balance = previous_day_close_wallet_balance table.add_row([ daily[item]['date'], compress_float(day_profit, 3), round_(profit_pct, 0.01) ]) pct_sum = ((position['wallet_balance'] + pnl_sum) / position['wallet_balance'] - 1) * 100 \ if position['wallet_balance'] > 0.0 else 0.0 table.add_row(['-------', '------', '---------']) table.add_row([ 'Total', compress_float(pnl_sum, 3), round_(pct_sum, 0.01) ]) msg = f'<pre>{table.get_string(border=True, padding_width=1, junction_char=" ", vertical_char=" ", hrules=HEADER)}</pre>' self.send_msg(msg)
def _open_orders(self, update=None, context=None): open_orders = self._bot.open_orders order_table = PrettyTable(["pside", "side", "price", "qty"]) for order in open_orders: price = round_(order['price'], self._bot.price_step) qty = round_(order['qty'], self._bot.qty_step) side = order['side'] position_side = order['position_side'] order_table.add_row([position_side, side, price, qty]) table_msg = order_table.get_string(sortby="price", border=True, padding_width=1, junction_char=' ', vertical_char=' ', hrules=HEADER) msg = f'<pre>{table_msg}</pre>' self.send_msg(msg)
async def send_closed_trades_async(): trades = await self._bot.fetch_fills(limit=100) closed_trades = [t for t in trades if t['realized_pnl'] != 0.0] closed_trades.sort(key=lambda x: x['timestamp'], reverse=True) table = PrettyTable(['Date', 'Pos.', 'Price', f'Pnl {self._bot.margin_coin}']) for trade in closed_trades[:self.n_trades]: trade_date = datetime.fromtimestamp(trade['timestamp'] / 1000) table.add_row( [trade_date.strftime('%m-%d %H:%M'), trade['position_side'], round_(trade['price'], self._bot.price_step), round_(trade['realized_pnl'], 0.01)]) msg = f'Closed trades:\n' \ f'<pre>{table.get_string(border=True, padding_width=1, junction_char=" ", vertical_char=" ", hrules=HEADER)}</pre>' self.send_msg(msg)
async def main(args: list): config_name = args[1] backtest_config = await prep_backtest_config(config_name) if backtest_config[ 'exchange'] == 'bybit' and not backtest_config['inverse']: print('bybit usdt linear backtesting not supported') return downloader = Downloader(backtest_config) ticks = await downloader.get_ticks(True) backtest_config['n_days'] = round_( (ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) start_candidate = None if (s := '--start') in args: try: if os.path.isdir(args[args.index(s) + 1]): start_candidate = [ json.load(open(f)) for f in glob.glob( os.path.join(args[args.index(s) + 1], '*.json')) ] print('Starting with all configurations in directory.') else: start_candidate = json.load(open(args[args.index(s) + 1])) print('Starting with specified configuration.') except: print('Could not find specified configuration.')
async def main(args: list): config_name = args[1] candidate = args[2] backtest_config = await prep_backtest_config(config_name) if backtest_config[ 'exchange'] == 'bybit' and not backtest_config['inverse']: print('bybit usdt linear backtesting not supported') return downloader = Downloader(backtest_config) ticks = await downloader.get_ticks(True) backtest_config['n_days'] = round_( (ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) try: candidate = json.load(open(candidate)) print('plotting given candidate') except Exception as e: print(os.listdir(backtest_config['session_dirpath'])) try: candidate = json.load( open(backtest_config['session_dirpath'] + 'live_config.json')) print('plotting best candidate') except: return print(json.dumps(candidate, indent=4)) plot_wrap(backtest_config, ticks, candidate) return
def plot_wrap(bc, ticks, candidate): n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) print('backtesting...') result, fdf = backtest_wrap(ticks, { **bc, **{ 'break_on': {} }, **candidate }, do_print=True) if fdf is None or len(fdf) == 0: print('no trades') return backtest_config = {**bc, **candidate, **result} backtest_config['session_dirpath'] = make_get_filepath( os.path.join( 'plots', bc['exchange'], bc['symbol'], f"{n_days}_days_{ts_to_date(time())[:19].replace(':', '')}", '')) fdf.to_csv(backtest_config['session_dirpath'] + f"backtest_trades_{result['key']}.csv") df = pd.DataFrame({ 'price': ticks[:, 0], 'buyer_maker': ticks[:, 1], 'timestamp': ticks[:, 2] }) dump_plots(backtest_config, fdf, df)
def plot_wrap(bc, ticks, live_config): n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) print('n_days', round_(n_days, 0.1)) config = {**bc, **live_config} print('backtesting...') fills, stats, did_finish = backtest(config, ticks, do_print=True) if not fills: print('no fills') return fdf, result = analyze_fills(fills, config, ticks[-1][2]) config['result'] = result config['plots_dirpath'] = make_get_filepath(os.path.join( config['plots_dirpath'], f"{ts_to_date(time())[:19].replace(':', '')}", '') ) fdf.to_csv(config['plots_dirpath'] + "fills.csv") df = pd.DataFrame({'price': ticks[:, 0], 'buyer_maker': ticks[:, 1], 'timestamp': ticks[:, 2]}) dump_plots(config, fdf, df)
def backtest_tune(ticks: np.ndarray, backtest_config: dict, current_best: Union[dict, list] = None): config = create_config(backtest_config) n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) session_dirpath = make_get_filepath(os.path.join('reports', backtest_config['exchange'], backtest_config['symbol'], f"{n_days}_days_{ts_to_date(time())[:19].replace(':', '')}", '')) iters = 10 if 'iters' in backtest_config: iters = backtest_config['iters'] else: print('Parameter iters should be defined in the configuration. Defaulting to 10.') num_cpus = 2 if 'num_cpus' in backtest_config: num_cpus = backtest_config['num_cpus'] else: print('Parameter num_cpus should be defined in the configuration. Defaulting to 2.') n_particles = 10 if 'n_particles' in backtest_config: n_particles = backtest_config['n_particles'] phi1 = 1.4962 phi2 = 1.4962 omega = 0.7298 if 'options' in backtest_config: phi1 = backtest_config['options']['c1'] phi2 = backtest_config['options']['c2'] omega = backtest_config['options']['w'] current_best_params = [] if current_best: if type(current_best) == list: for c in current_best: c = clean_start_config(c, config, backtest_config['ranges']) current_best_params.append(c) else: current_best = clean_start_config(current_best, config, backtest_config['ranges']) current_best_params.append(current_best) ray.init(num_cpus=num_cpus, logging_level=logging.FATAL, log_to_driver=False) pso = ng.optimizers.ConfiguredPSO(transform='identity', popsize=n_particles, omega=omega, phip=phi1, phig=phi2) algo = NevergradSearch(optimizer=pso, points_to_evaluate=current_best_params) algo = ConcurrencyLimiter(algo, max_concurrent=num_cpus) scheduler = AsyncHyperBandScheduler() analysis = tune.run(tune.with_parameters(wrap_backtest, ticks=ticks), metric='objective', mode='max', name='search', search_alg=algo, scheduler=scheduler, num_samples=iters, config=config, verbose=1, reuse_actors=True, local_dir=session_dirpath, progress_reporter=LogReporter(metric_columns=['daily_gain', 'closest_liquidation', 'max_hours_between_fills', 'objective'], parameter_columns=[k for k in backtest_config['ranges'] if type( config[k]) == ray.tune.sample.Float or type( config[k]) == ray.tune.sample.Integer])) ray.shutdown() return analysis
async def main(args: list): backtest_config = await prep_backtest_config(args[1]) if backtest_config['exchange'] == 'bybit' and not backtest_config['inverse']: print('bybit usdt linear backtesting not supported') return downloader = Downloader(backtest_config) ticks = await downloader.get_ticks(True) backtest_config['n_days'] = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) try: live_config = json.load(open(args[2])) print('backtesting and plotting given candidate') except Exception as e: print('failed to load live config') return print(json.dumps(live_config, indent=4)) plot_wrap(backtest_config, ticks, live_config)
def backtest_tune(ticks: np.ndarray, backtest_config: dict, current_best: Union[dict, list] = None): config = create_config(backtest_config) n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) backtest_config['optimize_dirpath'] = os.path.join( backtest_config['optimize_dirpath'], ts_to_date(time())[:19].replace(':', ''), '') if 'iters' in backtest_config: iters = backtest_config['iters'] else: print( 'Parameter iters should be defined in the configuration. Defaulting to 10.' ) iters = 10 if 'num_cpus' in backtest_config: num_cpus = backtest_config['num_cpus'] else: print( 'Parameter num_cpus should be defined in the configuration. Defaulting to 2.' ) num_cpus = 2 n_particles = backtest_config[ 'n_particles'] if 'n_particles' in backtest_config else 10 phi1 = 1.4962 phi2 = 1.4962 omega = 0.7298 if 'options' in backtest_config: phi1 = backtest_config['options']['c1'] phi2 = backtest_config['options']['c2'] omega = backtest_config['options']['w'] current_best_params = [] if current_best: if type(current_best) == list: for c in current_best: c = clean_start_config(c, config, backtest_config['ranges']) if c not in current_best_params: current_best_params.append(c) else: current_best = clean_start_config(current_best, config, backtest_config['ranges']) current_best_params.append(current_best) ray.init(num_cpus=num_cpus, logging_level=logging.FATAL, log_to_driver=False) pso = ng.optimizers.ConfiguredPSO(transform='identity', popsize=n_particles, omega=omega, phip=phi1, phig=phi2) algo = NevergradSearch(optimizer=pso, points_to_evaluate=current_best_params) algo = ConcurrencyLimiter(algo, max_concurrent=num_cpus) scheduler = AsyncHyperBandScheduler() if 'wfo' in config and config['wfo']: print('\n\nwalk forward optimization\n\n') wfo = WFO(ticks, backtest_config, P_train=0.5).set_train_N(4) backtest_wrap = lambda config: tune_report(wfo.backtest(config)) else: print('\n\nsimple sliding window optimization\n\n') backtest_wrap = tune.with_parameters(simple_sliding_window_wrap, ticks=ticks) analysis = tune.run(backtest_wrap, metric='objective', mode='max', name='search', search_alg=algo, scheduler=scheduler, num_samples=iters, config=config, verbose=1, reuse_actors=True, local_dir=backtest_config['optimize_dirpath'], progress_reporter=LogReporter( metric_columns=[ 'daily_gain', 'closest_liquidation', 'max_hrs_no_fills', 'max_hrs_no_fills_same_side', 'objective' ], parameter_columns=[ k for k in backtest_config['ranges'] if type(config[k]) == ray.tune.sample.Float or type(config[k]) == ray.tune.sample.Integer ]), raise_on_failed_trial=False) ray.shutdown() return analysis
def backtest(config: dict, ticks: np.ndarray, do_print=False) -> (list, list, bool): if len(ticks) <= config['ema_span']: return [], [], False long_psize, long_pprice = 0.0, 0.0 shrt_psize, shrt_pprice = 0.0, 0.0 liq_price, liq_diff = 0.0, 1.0 balance = config['starting_balance'] if all(x in config for x in ['long_pprice', 'long_psize', 'shrt_pprice', 'shrt_psize']): long_pprice, long_psize, shrt_pprice, shrt_psize = ( config["long_pprice"], config["long_psize"], config["shrt_pprice"], config["shrt_psize"], ) else: long_pprice, long_psize, shrt_pprice, shrt_psize = 0.0, 0.0, 0.0, 0.0 pnl_plus_fees_cumsum, loss_cumsum, profit_cumsum, fee_paid_cumsum = 0.0, 0.0, 0.0, 0.0 xk = {k: float(config[k]) for k in get_keys()} if config['exchange'] == 'binance': calc_liq_price = calc_liq_price_binance elif config['exchange'] == 'bybit': calc_liq_price = calc_liq_price_bybit prev_long_close_ts, prev_long_entry_ts, prev_long_close_price = 0, 0, 0.0 prev_shrt_close_ts, prev_shrt_entry_ts, prev_shrt_close_price = 0, 0, 0.0 latency_simulation_ms = config['latency_simulation_ms'] \ if 'latency_simulation_ms' in config else 1000 next_stats_update = 0 stats = [] def stats_update(): upnl_l = x if (x := calc_long_pnl( long_pprice, tick[0], long_psize, xk['inverse'], xk['contract_multiplier'])) == x else 0.0 upnl_s = y if (y := calc_shrt_pnl( shrt_pprice, tick[0], shrt_psize, xk['inverse'], xk['contract_multiplier'])) == y else 0.0 stats.append({ 'timestamp': tick[2], 'balance': balance, # Redundant with fills, but makes plotting easier 'equity': balance + upnl_l + upnl_s }) all_fills = [] fills = [] bids, asks = [], [] ob = [min(ticks[0][0], ticks[1][0]), max(ticks[0][0], ticks[1][0])] ema_span = int(round(config['ema_span'])) # emas = calc_emas(ticks[:, 0], ema_span) # price_stds = calc_stds(ticks[:, 0], ema_span) # volatilities = price_stds / emas ema_std_iterator = iter_indicator_chunks(ticks[:, 0], ema_span) ema_chunk, std_chunk, z = next(ema_std_iterator) volatility_chunk = std_chunk / ema_chunk zc = 0 closest_liq = 1.0 prev_update_plus_delay = ticks[ema_span][2] + latency_simulation_ms update_triggered = False prev_update_plus_5sec = 0 tick = ticks[0] stats_update() # tick tuple: (price, buyer_maker, timestamp) for k, tick in enumerate(ticks[ema_span:], start=ema_span): chunk_i = k - zc if chunk_i >= len(ema_chunk): ema_chunk, std_chunk, z = next(ema_std_iterator) volatility_chunk = std_chunk / ema_chunk zc = z * len(ema_chunk) chunk_i = k - zc # Update the stats every 1/2 hour if tick[2] > next_stats_update: closest_liq = min(closest_liq, calc_diff(liq_price, tick[0])) stats_update() next_stats_update = tick[2] + 1000 * 60 * 30 fills = [] if tick[1]: if liq_diff < 0.05 and long_psize > -shrt_psize and tick[ 0] <= liq_price: fills.append({ 'qty': -long_psize, 'price': tick[0], 'pside': 'long', 'type': 'long_liquidation', 'side': 'sel', 'pnl': calc_long_pnl(long_pprice, tick[0], long_psize, xk['inverse'], xk['contract_multiplier']), 'fee_paid': -calc_cost(long_psize, tick[0], xk['inverse'], xk['contract_multiplier']) * config['taker_fee'], 'long_psize': 0.0, 'long_pprice': 0.0, 'shrt_psize': 0.0, 'shrt_pprice': 0.0, 'liq_price': 0.0, 'liq_diff': 1.0 }) long_psize, long_pprice, shrt_psize, shrt_pprice = 0.0, 0.0, 0.0, 0.0 else: if bids: if tick[0] <= bids[0][1]: update_triggered = True while bids: if tick[0] < bids[0][1]: bid = bids.pop(0) fill = { 'qty': bid[0], 'price': bid[1], 'side': 'buy', 'type': bid[4], 'fee_paid': -calc_cost(bid[0], bid[1], xk['inverse'], xk['contract_multiplier']) * config['maker_fee'] } if 'close' in bid[4]: fill['pnl'] = calc_shrt_pnl( shrt_pprice, bid[1], bid[0], xk['inverse'], xk['contract_multiplier']) shrt_psize = min( 0.0, round_(shrt_psize + bid[0], config['qty_step'])) fill.update({ 'pside': 'shrt', 'long_psize': long_psize, 'long_pprice': long_pprice, 'shrt_psize': shrt_psize, 'shrt_pprice': shrt_pprice }) prev_shrt_close_ts = tick[2] else: fill['pnl'] = 0.0 long_psize, long_pprice = calc_new_psize_pprice( long_psize, long_pprice, bid[0], bid[1], xk['qty_step']) if long_psize < 0.0: long_psize, long_pprice = 0.0, 0.0 fill.update({ 'pside': 'long', 'long_psize': bid[2], 'long_pprice': bid[3], 'shrt_psize': shrt_psize, 'shrt_pprice': shrt_pprice }) prev_long_entry_ts = tick[2] fills.append(fill) else: break ob[0] = tick[0] else: if liq_diff < 0.05 and -shrt_psize > long_psize and tick[ 0] >= liq_price: fills.append({ 'qty': -shrt_psize, 'price': tick[0], 'pside': 'shrt', 'type': 'shrt_liquidation', 'side': 'buy', 'pnl': calc_shrt_pnl(shrt_pprice, tick[0], shrt_psize, xk['inverse'], xk['contract_multiplier']), 'fee_paid': -calc_cost(shrt_psize, tick[0], xk['inverse'], xk['contract_multiplier']) * config['taker_fee'], 'long_psize': 0.0, 'long_pprice': 0.0, 'shrt_psize': 0.0, 'shrt_pprice': 0.0, 'liq_price': 0.0, 'liq_diff': 1.0 }) long_psize, long_pprice, shrt_psize, shrt_pprice = 0.0, 0.0, 0.0, 0.0 else: if asks: if tick[0] >= asks[0][1]: update_triggered = True while asks: if tick[0] > asks[0][1]: ask = asks.pop(0) fill = { 'qty': ask[0], 'price': ask[1], 'side': 'sel', 'type': ask[4], 'fee_paid': -calc_cost(ask[0], ask[1], xk['inverse'], xk['contract_multiplier']) * config['maker_fee'] } if 'close' in ask[4]: fill['pnl'] = calc_long_pnl( long_pprice, ask[1], ask[0], xk['inverse'], xk['contract_multiplier']) long_psize = max( 0.0, round_(long_psize + ask[0], config['qty_step'])) fill.update({ 'pside': 'long', 'long_psize': long_psize, 'long_pprice': long_pprice, 'shrt_psize': shrt_psize, 'shrt_pprice': shrt_pprice }) prev_long_close_ts = tick[2] else: fill['pnl'] = 0.0 shrt_psize, shrt_pprice = calc_new_psize_pprice( shrt_psize, shrt_pprice, ask[0], ask[1], xk['qty_step']) if shrt_psize > 0.0: shrt_psize, shrt_pprice = 0.0, 0.0 fill.update({ 'pside': 'shrt', 'long_psize': long_psize, 'long_pprice': long_pprice, 'shrt_psize': shrt_psize, 'shrt_pprice': shrt_pprice }) prev_shrt_entry_ts = tick[2] liq_diff = calc_diff(liq_price, tick[0]) fill.update({ 'liq_price': liq_price, 'liq_diff': liq_diff }) fills.append(fill) else: break ob[1] = tick[0] if tick[2] > prev_update_plus_delay and ( update_triggered or tick[2] > prev_update_plus_5sec): prev_update_plus_delay = tick[2] + latency_simulation_ms prev_update_plus_5sec = tick[2] + 5000 update_triggered = False bids, asks = [], [] liq_diff = calc_diff(liq_price, tick[0]) closest_liq = min(closest_liq, liq_diff) for tpl in iter_entries(balance, long_psize, long_pprice, shrt_psize, shrt_pprice, liq_price, ob[0], ob[1], ema_chunk[k - zc], tick[0], volatility_chunk[k - zc], **xk): if len(bids) > 2 and len(asks) > 2: break if tpl[0] > 0.0: bids.append(tpl) elif tpl[0] < 0.0: asks.append(tpl) else: break if tick[0] <= shrt_pprice and shrt_pprice > 0.0: for tpl in iter_shrt_closes(balance, shrt_psize, shrt_pprice, ob[0], **xk): bids.append(list(tpl) + [shrt_pprice, 'shrt_close']) if tick[0] >= long_pprice and long_pprice > 0.0: for tpl in iter_long_closes(balance, long_psize, long_pprice, ob[1], **xk): asks.append(list(tpl) + [long_pprice, 'long_close']) bids = sorted(bids, key=lambda x: x[1], reverse=True) asks = sorted(asks, key=lambda x: x[1]) if len(fills) > 0: for fill in fills: balance += fill['pnl'] + fill['fee_paid'] upnl_l = calc_long_pnl(long_pprice, tick[0], long_psize, xk['inverse'], xk['contract_multiplier']) upnl_s = calc_shrt_pnl(shrt_pprice, tick[0], shrt_psize, xk['inverse'], xk['contract_multiplier']) liq_price = calc_liq_price(balance, long_psize, long_pprice, shrt_psize, shrt_pprice, xk['inverse'], xk['contract_multiplier'], config['max_leverage']) liq_diff = calc_diff(liq_price, tick[0]) fill.update({'liq_price': liq_price, 'liq_diff': liq_diff}) fill['equity'] = balance + upnl_l + upnl_s fill['available_margin'] = calc_available_margin( balance, long_psize, long_pprice, shrt_psize, shrt_pprice, tick[0], xk['inverse'], xk['contract_multiplier'], xk['leverage']) for side_ in ['long', 'shrt']: if fill[f'{side_}_pprice'] == 0.0: fill[f'{side_}_pprice'] = np.nan fill['balance'] = balance fill['timestamp'] = tick[2] fill['trade_id'] = k fill['gain'] = fill['equity'] / config['starting_balance'] fill['n_days'] = (tick[2] - ticks[ema_span][2]) / (1000 * 60 * 60 * 24) fill['closest_liq'] = closest_liq try: fill['average_daily_gain'] = fill['gain'] ** (1 / fill['n_days']) \ if (fill['n_days'] > 0.5 and fill['gain'] > 0.0) else 0.0 except: fill['average_daily_gain'] = 0.0 all_fills.append(fill) if balance <= 0.0 or 'liquidation' in fill['type']: return all_fills, stats, False if do_print: line = f"\r{k / len(ticks):.3f} " line += f"adg {all_fills[-1]['average_daily_gain']:.4f} " line += f"closest_liq {closest_liq:.4f} " print(line, end=' ') tick = ticks[-1] stats_update() return all_fills, stats, True
args = parser.parse_args() config = await prep_config(args) print() for k in (keys := [ 'exchange', 'symbol', 'starting_balance', 'start_date', 'end_date', 'latency_simulation_ms', 'do_long', 'do_shrt' ]): if k in config: print(f"{k: <{max(map(len, keys)) + 2}} {config[k]}") print() if config['exchange'] == 'bybit' and not config['inverse']: print('bybit usdt linear backtesting not supported') return downloader = Downloader(config) ticks = await downloader.get_ticks(True) config['n_days'] = round_( (ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1) try: live_config = json.load(open(args.live_config_path)) print('backtesting and plotting given candidate') except Exception as e: print('failed to load live config') return print(json.dumps(live_config, indent=4)) plot_wrap(config, ticks, live_config) if __name__ == '__main__': asyncio.run(main())