diff --git a/.gitignore b/.gitignore index e70fb1f1b..c509411fd 100644 --- a/.gitignore +++ b/.gitignore @@ -347,9 +347,7 @@ backtest_results/ historical_data/ # Config files -!api_key_secrets/binance/example_user.json -!api_key_secrets/bybit/example_user.json -api_key_secrets/* +api-keys.json !live_settings/binance/default.json !live_settings/bybit/default.json live_settings/* diff --git a/backtest.py b/backtest.py index 4f0a48173..fcbf5164e 100644 --- a/backtest.py +++ b/backtest.py @@ -5,11 +5,11 @@ import matplotlib.pyplot as plt import nevergrad as ng +import pandas as pd import ray from ray import tune from ray.tune.schedulers import AsyncHyperBandScheduler from ray.tune.suggest import ConcurrencyLimiter -# from ray.tune.suggest.hyperopt import HyperOptSearch from ray.tune.suggest.nevergrad import NevergradSearch from downloader import Downloader, prep_backtest_config @@ -152,7 +152,7 @@ def backtest(config: dict, ticks: np.ndarray, return_fills=False, do_print=False long_pnl_f = calc_long_pnl_inverse shrt_pnl_f = calc_shrt_pnl_inverse cost_f = calc_cost_inverse - iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, \ + iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, highest_bid, lowest_ask, last_price, do_long, do_shrt: \ iter_entries_inverse(config['price_step'], config['qty_step'], config['min_qty'], config['min_cost'], config['ddown_factor'], config['qty_pct'], @@ -185,7 +185,7 @@ def backtest(config: dict, ticks: np.ndarray, return_fills=False, do_print=False long_pnl_f = calc_long_pnl_linear shrt_pnl_f = calc_shrt_pnl_linear cost_f = calc_cost_linear - iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, \ + iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, highest_bid, lowest_ask, last_price, do_long, do_shrt: \ iter_entries_linear(config['price_step'], config['qty_step'], config['min_qty'], config['min_cost'], config['ddown_factor'], config['qty_pct'], diff --git a/binance.py b/binance.py index ad8910ac0..ea783d3cb 100644 --- a/binance.py +++ b/binance.py @@ -1,27 +1,22 @@ import asyncio +import hashlib +import hmac import json -import websockets -import os import sys -import numpy as np -import pandas as pd -import pprint -import datetime -import aiohttp -import hmac -import hashlib +from time import time from urllib.parse import urlencode -from math import ceil -from math import floor -from time import time, sleep -from typing import Callable, Iterator -from passivbot import load_key_secret, load_live_settings, make_get_filepath, print_, \ - ts_to_date, flatten, filter_orders, Bot, start_bot, round_up, round_dn, \ - calc_min_order_qty, sort_dict_keys, \ - iter_long_closes_linear, iter_shrt_closes_linear, calc_ema, iter_entries_linear, \ + +import aiohttp +import numpy as np +import websockets + +from passivbot import load_key_secret, load_live_settings, print_, \ + ts_to_date, flatten, Bot, start_bot, round_up, calc_min_order_qty, sort_dict_keys, \ + iter_long_closes_linear, iter_shrt_closes_linear, iter_entries_linear, \ iter_long_closes_inverse, iter_shrt_closes_inverse, calc_ema, iter_entries_inverse, \ calc_cost_linear, calc_cost_inverse + async def create_bot(user: str, settings: str): bot = BinanceBot(user, settings) await bot._init() @@ -38,7 +33,6 @@ def __init__(self, user: str, settings: dict): self.base_endpoint = '' self.key, self.secret = load_key_secret('binance', user) - async def public_get(self, url: str, params: dict = {}) -> dict: async with self.session.get(self.base_endpoint + url, params=params) as response: result = await response.text() @@ -54,8 +48,8 @@ async def private_(self, type_: str, url: str, params: dict = {}) -> dict: params[k] = str(params[k]) params = sort_dict_keys(params) params['signature'] = hmac.new(self.secret.encode('utf-8'), - urlencode(params).encode('utf-8'), - hashlib.sha256).hexdigest() + urlencode(params).encode('utf-8'), + hashlib.sha256).hexdigest() headers = {'X-MBX-APIKEY': self.key} async with getattr(self.session, type_)(self.base_endpoint + url, params=params, headers=headers) as response: @@ -102,9 +96,8 @@ def init_market_type(self): self.markup_range, self.n_close_orders, balance, pos_size, pos_price, highest_bid) - - self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, \ - liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ + self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, + liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ iter_entries_linear(self.price_step, self.qty_step, self.min_qty, self.min_cost, self.ddown_factor, self.qty_pct, self.leverage, self.grid_spacing, self.grid_coefficient, self.ema_spread, @@ -112,7 +105,6 @@ def init_market_type(self): long_psize, long_pprice, shrt_psize, shrt_pprice, liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt) - self.cost_f = calc_cost_linear else: print('inverse perpetual') @@ -144,8 +136,8 @@ def init_market_type(self): self.markup_range, self.n_close_orders, balance, pos_size, pos_price, highest_bid) - self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, \ - liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ + self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, + liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ iter_entries_inverse(self.price_step, self.qty_step, self.min_qty, self.min_cost, self.ddown_factor, self.qty_pct, self.leverage, self.grid_spacing, self.grid_coefficient, self.ema_spread, @@ -195,7 +187,7 @@ async def _init(self): max_lev = 0 for e in leverage_bracket: if ('pair' in e and e['pair'] == self.pair) or \ - ('symbol' in e and e['symbol'] ==self.symbol): + ('symbol' in e and e['symbol'] == self.symbol): for br in e['brackets']: max_lev = max(max_lev, int(br['initialLeverage'])) break @@ -213,7 +205,7 @@ async def init_ema(self): ticks = sorted(ticks + additional_ticks, key=lambda x: x['trade_id']) ema = ticks[0]['price'] for i in range(1, len(ticks)): - if ticks[i]['price'] != ticks[i-1]['price']: + if ticks[i]['price'] != ticks[i - 1]['price']: ema = ema * self.ema_alpha_ + ticks[i]['price'] * self.ema_alpha self.ema = ema @@ -233,11 +225,11 @@ async def check_if_other_positions(self, abort=True): do_abort = True for e in open_orders: if e['symbol'] != self.symbol: - print('\n\nWARNING\n\n') - print('account has open orders in other symbol:', e) - print('\naborting') - print('\n\n') - do_abort = True + print('\n\nWARNING\n\n') + print('account has open orders in other symbol:', e) + print('\naborting') + print('\n\n') + do_abort = True if do_abort: if abort: raise Exception('please close other positions and cancel other open orders') @@ -321,7 +313,7 @@ async def fetch_position(self) -> dict: if e['asset'] == (self.quot if self.market_type == 'linear_perpetual' else self.coin): position['wallet_balance'] = float(e['balance']) / self.contract_size position['equity'] = position['wallet_balance'] + float(e['crossUnPnl']) / self.contract_size - #position['available_margin'] = float(e['availableBalance']) / self.contract_size + # position['available_margin'] = float(e['availableBalance']) / self.contract_size # position['available_balance'] = float(e['availableBalance']) break return position @@ -434,4 +426,3 @@ async def main() -> None: if __name__ == '__main__': asyncio.run(main()) - diff --git a/bybit.py b/bybit.py index cd78c58e1..024a2b203 100644 --- a/bybit.py +++ b/bybit.py @@ -1,27 +1,22 @@ import asyncio +import hashlib +import hmac import json -import websockets -import os import sys +from time import time +from urllib.parse import urlencode + +import aiohttp import numpy as np -import pandas as pd -import pprint -import hashlib -import hmac +import websockets from dateutil import parser -from math import ceil -from math import floor -from time import time, sleep -from typing import Callable, Iterator -from passivbot import load_key_secret, load_live_settings, make_get_filepath, print_, \ - ts_to_date, flatten, Bot, start_bot, round_up, round_dn, \ - calc_min_order_qty_inverse, sort_dict_keys, calc_ema, calc_diff, \ - iter_long_closes_inverse, iter_shrt_closes_inverse, iter_entries_inverse, \ + +from passivbot import load_key_secret, load_live_settings, print_, \ + ts_to_date, flatten, Bot, start_bot, calc_min_order_qty_inverse, sort_dict_keys, calc_ema, iter_long_closes_inverse, \ + iter_shrt_closes_inverse, iter_entries_inverse, \ iter_long_closes_linear, iter_shrt_closes_linear, iter_entries_linear, calc_long_pnl_linear, \ calc_long_pnl_inverse, calc_shrt_pnl_linear, \ calc_shrt_pnl_inverse, calc_cost_linear, calc_cost_inverse -import aiohttp -from urllib.parse import urlencode def first_capitalized(s: str): @@ -53,7 +48,6 @@ def format_tick(tick: dict) -> dict: async def fetch_ticks(cc, symbol: str, from_id: int = None, do_print=True) -> [dict]: - params = {'symbol': symbol, 'limit': 1000} if from_id: params['from'] = max(0, from_id) @@ -68,9 +62,11 @@ async def fetch_ticks(cc, symbol: str, from_id: int = None, do_print=True) -> [d ts_to_date(trades[0]['timestamp'] / 1000)]) return trades + def date_to_ts(date: str): return parser.parse(date).timestamp() * 1000 + async def create_bot(user: str, settings: str): bot = Bybit(user, settings) await bot._init() @@ -112,8 +108,8 @@ def init_market_type(self): self.markup_range, self.n_close_orders, balance, pos_size, pos_price, highest_bid) - self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, \ - liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ + self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, + liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ iter_entries_linear(self.price_step, self.qty_step, self.min_qty, self.min_cost, self.ddown_factor, self.qty_pct, self.leverage, self.grid_spacing, self.grid_coefficient, self.ema_spread, @@ -164,8 +160,8 @@ def init_market_type(self): self.markup_range, self.n_close_orders, balance, pos_size, pos_price, highest_bid) - self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, \ - liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ + self.iter_entries = lambda balance, long_psize, long_pprice, shrt_psize, shrt_pprice, + liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt: \ iter_entries_inverse(self.price_step, self.qty_step, self.min_qty, self.min_cost, self.ddown_factor, self.qty_pct, self.leverage, self.grid_spacing, self.grid_coefficient, self.ema_spread, @@ -173,19 +169,24 @@ def init_market_type(self): long_psize, long_pprice, shrt_psize, shrt_pprice, liq_price, highest_bid, lowest_ask, ema, last_price, do_long, do_shrt) - self.endpoints['balance'] = '/v2/private/wallet/balance' def determine_pos_side(self, o: dict) -> str: side = o['side'].lower() if side == 'buy': - if 'entry' in o['order_link_id']: position_side = 'long' - elif 'close' in o['order_link_id']: position_side = 'shrt' - else: position_side = 'unknown' + if 'entry' in o['order_link_id']: + position_side = 'long' + elif 'close' in o['order_link_id']: + position_side = 'shrt' + else: + position_side = 'unknown' else: - if 'entry' in o['order_link_id']: position_side = 'shrt' - elif 'close' in o['order_link_id']: position_side = 'long' - else: position_side = 'both' + if 'entry' in o['order_link_id']: + position_side = 'shrt' + elif 'close' in o['order_link_id']: + position_side = 'long' + else: + position_side = 'both' return position_side async def _init(self): @@ -220,7 +221,7 @@ async def init_ema(self): ticks = sorted(ticks + additional_ticks, key=lambda x: x['trade_id']) ema = ticks[0]['price'] for i in range(1, len(ticks)): - if ticks[i]['price'] != ticks[i-1]['price']: + if ticks[i]['price'] != ticks[i - 1]['price']: ema = ema * self.ema_alpha_ + ticks[i]['price'] * self.ema_alpha self.ema = ema @@ -229,7 +230,6 @@ async def init_order_book(self): self.ob = [float(ticker['result'][0]['bid_price']), float(ticker['result'][0]['ask_price'])] self.price = float(ticker['result'][0]['last_price']) - async def fetch_open_orders(self) -> [dict]: fetched = await self.private_get(self.endpoints['open_orders'], {'symbol': self.symbol}) @@ -298,7 +298,6 @@ async def fetch_position(self) -> dict: long_pos = [e['data'] for e in fetched['result'] if e['data']['position_idx'] == 1][0] shrt_pos = [e['data'] for e in fetched['result'] if e['data']['position_idx'] == 2][0] - position['long'] = {'size': float(long_pos['size']), 'price': float(long_pos['entry_price']), 'leverage': float(long_pos['leverage']), @@ -319,7 +318,7 @@ async def fetch_position(self) -> dict: async def execute_order(self, order: dict) -> dict: params = {'symbol': self.symbol, - 'side': first_capitalized(order['side']), + 'side': first_capitalized(order['side']), 'order_type': first_capitalized(order['type']), 'qty': float(order['qty']) if self.market_type == 'linear_perpetual' else int(order['qty']), 'close_on_trigger': False} @@ -405,7 +404,7 @@ async def init_exchange_settings(self): elif self.market_type == 'inverse_perpetual': res = await self.private_post('/v2/private/position/leverage/save', {'symbol': self.symbol, 'leverage': 0}) - + print(res) except Exception as e: print(e) @@ -455,5 +454,3 @@ async def main() -> None: if __name__ == '__main__': asyncio.run(main()) - - diff --git a/changelog.md b/changelog.md index 8ebdb84ff..f778020a3 100644 --- a/changelog.md +++ b/changelog.md @@ -160,39 +160,4 @@ All notable changes to this project will be documented in this file. - renamed settings["margin_limit"] to settings["balance"] - bug fixes and changes in trade data downloading - if there already is historical trade data downloaded, run the script `rename_trade_data_csvs.py` to rename all files - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +- \ No newline at end of file diff --git a/downloader.py b/downloader.py index 4d1575813..c84749578 100644 --- a/downloader.py +++ b/downloader.py @@ -1,6 +1,7 @@ import gc import hjson +import pandas as pd from dateutil import parser, tz from binance import create_bot as create_bot_binance diff --git a/passivbot.py b/passivbot.py index 170b66bfe..08686ac02 100644 --- a/passivbot.py +++ b/passivbot.py @@ -1,21 +1,20 @@ +import asyncio +import datetime import json import os -import datetime -import numpy as np -import pandas as pd -import pprint -import asyncio -import traceback import sys -from time import time, sleep -from typing import Iterator, Tuple +from time import time +import numpy as np if '--nojit' in sys.argv: print('not using numba') + + def njit(pyfunc=None, **kwargs): def wrap(func): return func + if pyfunc is not None: return wrap(pyfunc) else: @@ -29,10 +28,12 @@ def wrap(func): def round_up(n: float, step: float, safety_rounding=10) -> float: return np.round(np.ceil(n / step) * step, safety_rounding) + @njit def round_dn(n: float, step: float, safety_rounding=10) -> float: return np.round(np.floor(n / step) * step, safety_rounding) + @njit def round_(n: float, step: float, safety_rounding=10) -> float: return np.round(np.round(n / step) * step, safety_rounding) @@ -176,7 +177,7 @@ def iter_entries_inverse(price_step: float, ema: float, last_price: float, do_long: bool = True, - do_shrt: bool = True,): + do_shrt: bool = True, ): # yields both long and short entries # (qty, price, new_psize, new_pprice, comment) @@ -208,7 +209,7 @@ def iter_entries_inverse(price_step: float, else: stop_loss_qty = min(abs_shrt_psize, max(calc_min_order_qty_inverse(qty_step, min_qty, min_cost, qty_pct, - leverage, balance, highest_bid), + leverage, balance, highest_bid), round_dn(abs_shrt_psize * stop_loss_pos_pct, qty_step))) # if sufficient margin available, increase long pos, otherwise, reduce shrt pos margin_cost = calc_margin_cost_inverse(leverage, stop_loss_qty, highest_bid) @@ -226,9 +227,6 @@ def iter_entries_inverse(price_step: float, yield stop_loss_qty, highest_bid, shrt_psize, shrt_pprice, 'stop_loss_shrt_close' available_margin += margin_cost - - - while True: long_entry = calc_next_long_entry_inverse( @@ -308,12 +306,13 @@ def calc_next_long_entry_inverse(price_step: float, if long_qty >= min_long_order_qty: new_long_psize = round_(long_psize + long_qty, qty_step) long_pprice = long_pprice * (long_psize / new_long_psize) + \ - price * (long_qty / new_long_psize) + price * (long_qty / new_long_psize) long_psize = new_long_psize return long_qty, price, long_psize, long_pprice, 'reentry' else: return 0.0, np.nan, long_psize, long_pprice, 'reentry' + @njit def calc_next_shrt_entry_inverse(price_step: float, qty_step: float, @@ -378,7 +377,6 @@ def iter_long_closes_inverse(price_step: float, pos_size: float, pos_price: float, lowest_ask: float): - # yields tuple (qty, price, new_pos_size) if pos_size == 0.0: @@ -426,7 +424,6 @@ def iter_shrt_closes_inverse(price_step: float, pos_size: float, pos_price: float, highest_bid: float): - # yields tuple (qty, price, new_pos_size) if pos_size == 0.0: @@ -475,8 +472,8 @@ def calc_cross_long_liq_price_bybit_inverse(balance, bankruptcy_price = calc_cross_long_bankruptcy_price_bybit_inverse(pos_size, order_cost, balance, order_margin) if bankruptcy_price == 0.0: return 0.0 - rhs = -(balance - order_margin - (pos_size / pos_price) * mm - \ - (pos_size * 0.00075) / bankruptcy_price) + rhs = -(balance - order_margin - (pos_size / pos_price) * mm - + (pos_size * 0.00075) / bankruptcy_price) return (pos_price * pos_size) / (pos_size - pos_price * rhs) @@ -487,18 +484,18 @@ def calc_cross_long_bankruptcy_price_bybit_inverse(pos_size, order_cost, balance @njit def calc_cross_shrt_liq_price_bybit_inverse(balance, - pos_size, - pos_price, - leverage, - mm=0.005) -> float: + pos_size, + pos_price, + leverage, + mm=0.005) -> float: _pos_size = abs(pos_size) order_cost = _pos_size / pos_price order_margin = order_cost / leverage bankruptcy_price = calc_cross_shrt_bankruptcy_price_bybit_inverse(_pos_size, order_cost, balance, order_margin) if bankruptcy_price == 0.0: return 0.0 - rhs = -(balance - order_margin - (_pos_size / pos_price) * mm - \ - (_pos_size * 0.00075) / bankruptcy_price) + rhs = -(balance - order_margin - (_pos_size / pos_price) * mm - + (_pos_size * 0.00075) / bankruptcy_price) shrt_liq_price = (pos_price * _pos_size) / (pos_price * rhs + _pos_size) if shrt_liq_price <= 0.0: return 0.0 @@ -538,7 +535,7 @@ def calc_cross_hedge_liq_price_binance_inverse(balance: float, mml = 0.02 mms = 0.02 numerator = abs_long_pos_size * mml + abs_shrt_pos_size * mms + abs_long_pos_size - abs_shrt_pos_size - cm = contract_size #contract_multiplier? + cm = contract_size # contract_multiplier? long_pos_cost = abs_long_pos_size / long_pos_price if long_pos_price > 0.0 else 0.0 shrt_pos_cost = abs_shrt_pos_size / shrt_pos_price if shrt_pos_price > 0.0 else 0.0 denom = balance / cm + long_pos_cost - shrt_pos_cost @@ -616,7 +613,7 @@ def calc_cross_hedge_liq_price_binance_linear(balance: float, shrt_pos_margin = abs_shrt_pos_size * shrt_pos_price / leverage mml = 0.006 mms = 0.006 - #tmm = max(long_pos_margin, shrt_pos_margin) + # tmm = max(long_pos_margin, shrt_pos_margin) tmm = long_pos_margin + shrt_pos_margin numerator = (balance - tmm + long_pos_margin + shrt_pos_margin - abs_long_pos_size * long_pos_price + abs_shrt_pos_size * shrt_pos_price) @@ -679,7 +676,7 @@ def iter_entries_linear(price_step: float, ema: float, last_price: float, do_long: bool = True, - do_shrt: bool = True,): + do_shrt: bool = True, ): # yields both long and short entries # also yields stop loss orders if triggered # (qty, price, new_psize, new_pprice, comment) @@ -810,12 +807,13 @@ def calc_next_long_entry_linear(price_step: float, if long_qty >= min_long_order_qty: new_long_psize = round_(long_psize + long_qty, qty_step) long_pprice = long_pprice * (long_psize / new_long_psize) + \ - price * (long_qty / new_long_psize) + price * (long_qty / new_long_psize) long_psize = new_long_psize return long_qty, price, long_psize, long_pprice, 'long_reentry' else: return 0.0, np.nan, long_psize, long_pprice, 'long_reentry' + @njit def calc_next_shrt_entry_linear(price_step: float, qty_step: float, @@ -874,7 +872,6 @@ def iter_long_closes_linear(price_step: float, pos_size: float, pos_price: float, lowest_ask: float): - # yields tuple (qty, price, new_pos_size) if pos_size == 0.0: @@ -922,7 +919,6 @@ def iter_shrt_closes_linear(price_step: float, pos_size: float, pos_price: float, highest_bid: float): - # yields tuple (qty, price, new_pos_size) if pos_size == 0.0: @@ -994,7 +990,6 @@ def calc_min_order_qty(min_qty: float, return max(min_qty, round_dn(leveraged_balance_ito_contracts * qty_pct, qty_step)) - @njit def calc_reentry_qty(qty_step: float, ddown_factor: float, @@ -1063,7 +1058,6 @@ def ts_to_date(timestamp: float) -> str: def filter_orders(actual_orders: [dict], ideal_orders: [dict], keys: [str] = ['symbol', 'side', 'qty', 'price']) -> ([dict], [dict]): - # returns (orders_to_delete, orders_to_create) if not actual_orders: @@ -1195,7 +1189,7 @@ async def create_orders(self, orders_to_create: [dict]) -> dict: except Exception as e: print_(['error creating order b', oc, c.exception(), e], n=True) self.dump_log({'log_type': 'create_order', 'data': {'result': str(c.exception()), - 'error': repr(e), 'data': oc}}) + 'error': repr(e), 'data': oc}}) self.ts_released['create_orders'] = time() return created_orders @@ -1224,7 +1218,7 @@ async def cancel_orders(self, orders_to_cancel: [dict]) -> [dict]: except Exception as e: print_(['error cancelling order b', oc, c.exception(), e], n=True) self.dump_log({'log_type': 'cancel_order', 'data': {'result': str(c.exception()), - 'error': repr(e), 'data': oc}}) + 'error': repr(e), 'data': oc}}) self.ts_released['cancel_orders'] = time() return canceled_orders @@ -1295,7 +1289,6 @@ def calc_orders(self): 'reduce_only': True, 'custom_id': 'close'}) return long_entry_orders + shrt_entry_orders + long_close_orders + shrt_close_orders - async def cancel_and_create(self): await asyncio.sleep(0.01) await self.update_position() diff --git a/start_bot.py b/start_bot.py index ff23bd7c0..35cca6451 100644 --- a/start_bot.py +++ b/start_bot.py @@ -1,18 +1,15 @@ +import sys from subprocess import Popen from time import sleep -import sys user = sys.argv[1] symbol = sys.argv[2] path_to_config = sys.argv[3] - - max_n_restarts = 30 restart_k = 0 - while True: try: print(f"\nStarting {user} {symbol} {path_to_config}")