def __init__(self, verbose: bool = False): """Constructor. Args: verbose (bool, optional): [description]. Defaults to False.""" Logger.verbose_console_log( verbose=arguments.verbose, message="Stocker is initializing in verbose mode", message_type=Message.MESSAGE_TYPE.STATUS) self.verbose = verbose self.ciphers: dict = initialize_lock_and_key_ciphers() self.keys: dict = load_json_resource( file_name_cipher=self.ciphers["file_name"], data_cipher=self.ciphers['data'], resource_file_name=Stocker.KEY_FILE_NAME) self.passes: dict = load_json_resource( file_name_cipher=self.ciphers["file_name"], data_cipher=self.ciphers['data'], resource_file_name=Stocker.PASS_FILE_NAME)[Stocker.USER_NAME] deposit_history: DataFrame = self.load_csv_resource( resource_file_name=Stocker.DEPOSIT_HISTORY_FILE_NAME) self.binance_account = BinanceAccount( deposit_history=deposit_history[deposit_history.Service == "binance"], transaction_history=self.load_csv_resource( resource_file_name=Stocker. BINANCE_TRANSACTION_HISTORY_FILE_NAME), api_key=self.keys['binance.us']['API Key'], api_secret=self.keys['binance.us']['Secret Key']) self.coinbase_account = CoinbaseAccount( deposit_history=deposit_history[deposit_history.Service == "coinbase"], transaction_history=self.load_csv_resource( resource_file_name=Stocker. COINBASE_TRANSACTION_HISTORY_FILE_NAME), api_key=self.keys['coinbase']['API Key'], api_secret=self.keys['coinbase']['API Secret']) self.mint = Mint(email=self.passes['mint']['email'], password=self.passes['mint']['password']) self.holdings: Holdings = Holdings.load( holding_file_name=Stocker.HOLDINGS_FILE_NAME, file_name_cipher=self.ciphers['file_name'], data_cipher=self.ciphers['data'], mint=self.mint) # Start price update thread self.price_checker_thread: PriceChecker = PriceChecker( stocker=self, verbose=self.verbose) self.price_checker_thread.start() self.holdings.initial_update.wait(timeout_ms=60000) self.generated_windows = {}
def connection_test(self) -> bool: try: test_connection = self.create_connection() Logger.console_log(message="Connection test with " + self.database + " located at " + self.host + " was a success", status=Message.MESSAGE_TYPE.SUCCESS) return True except mariadb.Error as err: Logger.console_log(message="Unable to establish connection with database " + self.database + ". Error [" + str(err) + "] was returned", status=Message.MESSAGE_TYPE.FAIL) return False
def run(self) -> None: self.running = True Logger.verbose_console_log(verbose=self.verbose, message=str(type(self)) + " is running...", message_type=Message.MESSAGE_TYPE.STATUS) while self.running: epoch_start_time = time() self.stocker.holdings.update(binance_account = self.stocker.binance_account, coinbase_account = self.stocker.coinbase_account, verbose = self.stocker.verbose) pseudo_realtime_timestep(epoch_start_time=epoch_start_time, timestep=1/self.rate)
def calculate_equity(self, holding_type: Union[str, Holdings.HOLDING_TYPE] = "all", verbose: bool = False) -> float: """[summary] Args: holding_type (Union[str, Holdings.HOLDING_TYPE], optional): [description]. Defaults to "all". Returns: float: [description]""" assert type(holding_type) == str or type( holding_type) == Holdings.HOLDING_TYPE Logger.verbose_console_log( verbose=verbose, message=str(type(self)) + " is calculating the equity of holding_type: " + str(holding_type), message_type=Message.MESSAGE_TYPE.STATUS) equity: float = 0. if type(holding_type) == str: holding_type: Holdings.HOLDING_TYPE = Holdings.HOLDING_TYPE( holding_type) if holding_type == Holdings.HOLDING_TYPE.STOCK or holding_type == Holdings.HOLDING_TYPE.ALL: equity += Holdings.calculate_holding_equity(holdings=self.stocks, verbose=verbose) if holding_type == Holdings.HOLDING_TYPE.CRYPTOCURRENCY or holding_type == Holdings.HOLDING_TYPE.ALL: equity += Holdings.calculate_holding_equity( holdings=self.cryptocoins, verbose=verbose) if holding_type == Holdings.HOLDING_TYPE.CHECKING or holding_type == Holdings.HOLDING_TYPE.ALL: equity += Holdings.calculate_holding_equity( holdings=self.checking_accounts, verbose=verbose) if (holding_type == Holdings.HOLDING_TYPE.FLOATING_USD or holding_type == Holdings.HOLDING_TYPE.ALL ) and self.floating_usd is not None: equity += Holdings.calculate_holding_equity( holdings=self.floating_usd, verbose=verbose) Logger.verbose_console_log( verbose=verbose, message=str(type(self)) + " equity of holding_type: " + str(holding_type) + " = " + "$%.2f" % equity, message_type=Message.MESSAGE_TYPE.SUCCESS) return equity
def open_window(self, window_class: Callable[[], QMainWindow]) -> None: """Opens a window class and displays it. Args: window_class (Callable[[], QMainWindow]): [description]""" Logger.verbose_console_log(verbose=self.verbose, message=str(type(self)) + " is opening window: " + str(window_class), message_type=Message.MESSAGE_TYPE.STATUS) window = window_class(stocker=self) if window_class in self.generated_windows.keys(): self.generated_windows[window_class].append(window) else: self.generated_windows[window_class] = [window] window.show()
def get_stock_price(symbol: str) -> float: """[summary] Args: symbol (str): [description] Returns: DataFrame: [description]""" try: price: float = get_live_price(symbol) return price except Exception as error: Logger.console_log( message= "Exception {} encountered when attempting to get price for symbol {}. Sleeping and trying again." .format(error, symbol), message_type=Message.MESSAGE_TYPE.MINOR_FAIL) sleep(120) return get_stock_price(symbol=symbol)
def calculate_holding_equity(holdings: Union[ Dict[str, Holdings.Stock], Dict[str, Holdings.Cryptocoin], Dict[str, Holdings.CheckingAccount], Dict[str, float]], verbose: bool = False) -> float: """[summary] Args: holdings (Union[Dict[str, Holdings.Stock], Dict[str, Holdings.Cryptocoin], Dict[str, Holdings.CheckingAccount], Dict[str, Holdings.FloatingUSD]]): [description] verbose (bool, optional): [description]. Defaults to False. Returns: float: [description]""" total_equity: float = 0. if len(holdings.keys()) > 0: equity_type: Callable = type(holdings[list(holdings.keys())[0]]) else: return 0. for holding_name, holding in holdings.items(): if equity_type == float or equity_type == int: holding_equity = holding else: holding_equity = holding.calculate_equity() total_equity += holding_equity Logger.verbose_console_log( verbose=verbose, message=str(holding_name) + " has a " + str(equity_type) + " value of " + "$%.2f" % holding_equity, message_type=Message.MESSAGE_TYPE.STATUS) Logger.verbose_console_log( verbose=verbose, message=str(Holdings) + " has calculated a total " + str(equity_type) + " equity of " + "$%.2f" % total_equity, message_type=Message.MESSAGE_TYPE.SUCCESS) return total_equity
def __init__(self, verbose: bool = True, log: bool = False, overwrite_log: bool = False) -> None: Thread.__init__(self) self.available_cpus = cpu_count() - 1 self.logger = Logger(name=DataCollector.SERVER_NAME, file_log=log, verbose=verbose, overwrite=overwrite_log) self.ciphers: Dict[str, VigenereCipher] = initialize_lock_and_key_ciphers() self.passes: dict = load_json_resource( file_name_cipher=self.ciphers["file_name"], data_cipher=self.ciphers['data'], resource_file_name=DataCollector.PASS_FILE_NAME)[ DataCollector.USER_NAME] self.mint = Mint(email=self.passes['mint']['email'], password=self.passes['mint']['password']) self.mode: DataCollector.SERVER_MODE = DataCollector.SERVER_MODE.INVALID self.threads = {} # Initialize Threads # Build worker threads for available number of cpus self.threads[DataCollector.WorkerThread] = [] for available_cpu_index in range(self.available_cpus): self.threads[DataCollector.WorkerThread].append( DataCollector.WorkerThread(thread_id=available_cpu_index, scraper_server=self)) # Build Executive Thread self.threads[ DataCollector.ExecutiveThread] = DataCollector.ExecutiveThread( thread_id=0, scraper_server=self, worker_threads=self.threads[DataCollector.WorkerThread])
def get_crypto_price( coin_name: str, binance_account: Union[None, BinanceAccount] = None, coinbase_account: Union[None, CoinbaseAccount] = None) -> float: """[summary] Args: coin_name (str): [description] binance_account (Union[None, BinanceAccount], optional): [description]. Defaults to None. coinbase_account (Union[None, CoinbaseAccount], optional): [description]. Defaults to None. Returns: float: [description]""" try: return get_price(coin_name, "USD")[coin_name]["USD"] except Exception as error: Logger.console_log( message= f"Exception {error} found when attempting to get price from cryptocompare.", message_type=Message.MESSAGE_TYPE.MINOR_FAIL) if binance_account is not None: try: return float( binance_account.interface.get_ticker(symbol=coin_name + "USD")['lastPrice']) except Exception as error: Logger.console_log( message= f"Exception {error} found when attempting to get price from binance.", message_type=Message.MESSAGE_TYPE.MINOR_FAIL) if coinbase_account is not None: try: return float( coinbase_account.interface.get_buy_price( currency_pair=coin_name + "-USD")['amount']) except Exception as error: Logger.console_log( message= f"Exception {error} found when attempting to get price from coinbase.", message_type=Message.MESSAGE_TYPE.MINOR_FAIL) sleep(5 * 60) return get_crypto_price(coin_name=coin_name, binance_account=binance_account, coinbase_account=coinbase_account)
class DataCollector(Thread): """Scraper Server object""" SERVER_NAME: str = "DataCollector" HOLDINGS_FILE_NAME: str = "holdings.json" PASS_FILE_NAME: str = "pass.json" USER_NAME: str = "jakeadelic" def __init__(self, verbose: bool = True, log: bool = False, overwrite_log: bool = False) -> None: Thread.__init__(self) self.available_cpus = cpu_count() - 1 self.logger = Logger(name=DataCollector.SERVER_NAME, file_log=log, verbose=verbose, overwrite=overwrite_log) self.ciphers: Dict[str, VigenereCipher] = initialize_lock_and_key_ciphers() self.passes: dict = load_json_resource( file_name_cipher=self.ciphers["file_name"], data_cipher=self.ciphers['data'], resource_file_name=DataCollector.PASS_FILE_NAME)[ DataCollector.USER_NAME] self.mint = Mint(email=self.passes['mint']['email'], password=self.passes['mint']['password']) self.mode: DataCollector.SERVER_MODE = DataCollector.SERVER_MODE.INVALID self.threads = {} # Initialize Threads # Build worker threads for available number of cpus self.threads[DataCollector.WorkerThread] = [] for available_cpu_index in range(self.available_cpus): self.threads[DataCollector.WorkerThread].append( DataCollector.WorkerThread(thread_id=available_cpu_index, scraper_server=self)) # Build Executive Thread self.threads[ DataCollector.ExecutiveThread] = DataCollector.ExecutiveThread( thread_id=0, scraper_server=self, worker_threads=self.threads[DataCollector.WorkerThread]) def run(self) -> None: """ """ self.logger.start() self.logger.log(message="Starting " + DataCollector.SERVER_NAME, message_type=Message.MESSAGE_TYPE.STATUS) self.threads[DataCollector.ExecutiveThread].start() # Start Worker Threads for worker_thread in self.threads[DataCollector.WorkerThread]: worker_thread.start() # Wait for Worker Threads to Complete for worker_thread in self.threads[DataCollector.WorkerThread]: worker_thread.join() # Wait for Executive Thread to Complete self.threads[DataCollector.ExecutiveThread].join() """ try: while mode != DataCollector.SERVER_MODE.SHUTTING_DOWN: mode: DataCollector.SERVER_MODE = DataCollector.SERVER_MODE.set_mode() if mode == DataCollector.SERVER_MODE.STOCK_MARKET_OPEN: pass elif mode == DataCollector.SERVER_MODE.STOCK_MARKET_CLOSED: pass self.logger.log(message=f"{DataCollector.SERVER_NAME} is running in mode {mode}.", message_type=Message.MESSAGE_TYPE.STATUS) sleep(1) except (KeyboardInterrupt, SystemExit) as error: self.logger.log(message=f"{DataCollector.SERVER_NAME} is shutting down due to exception: {error}", message_type=Message.MESSAGE_TYPE.STATUS) """ self.logger.stop() def load_holdings(self, holding_file_name: str) -> Holdings: """[summary] Args: holding_file_name (str): [description] Returns: Holdings: [description]""" stocks: Dict[str, Holdings.Stock] = {} crypto: Dict[str, Holdings.Cryptocoin] = {} checking_accounts: Dict[str, Holdings.CheckingAccount] = {} floating_usd: Dict[str, float] = {} stock_and_crypto_holdings: Dict[str, Dict[str, Dict[ str, Any]]] = self.load_json_resource( resource_file_name=holding_file_name) # Load stocks for stock_symbol, stock_data in stock_and_crypto_holdings[ 'stock'].items(): stocks[stock_symbol] = Holdings.Stock( symbol=stock_symbol, name=stock_data['name'], quantity=stock_data['quantity'], cost_basis_per_share=stock_data['cost basis per share']) # Load crypto for coin, coin_data in stock_and_crypto_holdings['crypto'].items(): crypto[coin] = Holdings.Cryptocoin( name=coin_data["name"], coin=coin, quantity=coin_data['quantity'], investment=coin_data['investment']) # Load float for float_location, usd_in_float in stock_and_crypto_holdings[ 'float'].items(): floating_usd[float_location] = usd_in_float # Load checking accounts from mint for account_data in self.mint.get_accounts(False): checking_accounts[ account_data['accountName']] = Holdings.CheckingAccount( name=account_data['accountName'], equity=account_data['value']) self.mint.close() return Holdings(stocker=self, stocks=stocks, cryptocoins=crypto, checking_accounts=checking_accounts, floating_usd=floating_usd) class ExecutiveThread(Thread): """[summary]""" def __init__(self, thread_id: int, data_collector: DataCollector, worker_threads: list): """[summary] Args: thread_id (int): [description] scraper_server (object): [description] worker_threads (list): [description]""" Thread.__init__(self) self.thread_id = thread_id self.data_collector = data_collector self.worker_threads = worker_threads def __str__(self) -> str: """ """ return str(type(self)) + "_" + str(self.thread_id) def run(self) -> None: """TODO""" pass class WorkerThread(Thread): """[summary]""" def __init__(self, thread_id: int, data_collector: DataCollector): """ Constructor. :param thread_id: :param scraper_server: """ Thread.__init__(self) self.thread_id = thread_id self.data_collector: DataCollector = data_collector self.holdings_to_update: List[Holdings.Holding] = [] def __str__(self) -> str: """ """ return str(type(self)) + "_" + str(self.thread_id) def run(self) -> None: """TODO""" pass class SERVER_MODE(Enum): INVALID = "invalid" STOCK_MARKET_OPEN = "stock market open" STOCK_MARKET_CLOSED = "stock market closed" SHUTTING_DOWN = "shutting down" @staticmethod def set_mode() -> DataCollector.SERVER_MODE: """[summary] Returns: DataCollector.SERVER_MODE: [description]""" right_now = datetime.now() right_now_hour = int(right_now.hour) right_now_minute = int(right_now.minute) minutes = right_now_hour * 60 + right_now_minute # If we are between 9:30 am and 4 pm the stock market is open if 960 > minutes > 570 and is_datetime_weekday( current_time=datetime.now()): return DataCollector.SERVER_MODE.STOCK_MARKET_OPEN else: return DataCollector.SERVER_MODE.STOCK_MARKET_CLOSED
def update(self, binance_account: Union[BinanceAccount, None] = None, coinbase_account: Union[CoinbaseAccount, None] = None, verbose: bool = False) -> None: """[summary]""" update_time = time() self.lock.acquire() for coin_name, coin in self.cryptocoins.items(): coin.price = get_crypto_price(coin_name=coin_name, binance_account=binance_account, coinbase_account=coinbase_account) Logger.verbose_console_log( verbose=verbose, message= f"[CRYPTO] {coin_name} is currently valued at ${coin.price} per coin.", message_type=Message.MESSAGE_TYPE.STATUS) self.crypto_df = self.crypto_df.append( coin.to_series(update_time=update_time)) self.crypto_df = self.crypto_df.sort_index() for stock_symbol, stock in self.stocks.items(): stock.price = get_stock_price(symbol=stock_symbol) Logger.verbose_console_log( verbose=verbose, message= f"[STOCK] {stock.name} is currently valued at ${stock.price} per share.", message_type=Message.MESSAGE_TYPE.STATUS) self.stocks_df = self.stocks_df.append( stock.to_series(update_time=update_time)) self.stocks_df = self.stocks_df.sort_index() for account_name, checking_account in self.checking_accounts.items(): #checking_account.update() Logger.verbose_console_log( verbose=verbose, message="[CHECKING] " + account_name + " is currently valued at $" + str(checking_account.equity), message_type=Message.MESSAGE_TYPE.STATUS) self.checking_account_df = self.checking_account_df.append( checking_account.to_series(update_time=update_time)) self.checking_account_df = self.checking_account_df.sort_index() for location, usd_in_float in self.floating_usd.items(): self.floating_usd_df = self.floating_usd_df.append( Series( { "datetime": update_time, "location": location, "equity": usd_in_float }, name=time())) self.floating_usd_df = self.floating_usd_df.sort_index() self.lock.release() Logger.verbose_console_log(verbose=verbose, message=str(type(self)) + " is finished updating.", message_type=Message.MESSAGE_TYPE.SUCCESS) if not self.initial_update.is_set(): self.initial_update.set()
def stop(self) -> None: self.running = False Logger.verbose_console_log(verbose=self.verbose, message=str(type(self)) + " is stopping...", message_type=Message.MESSAGE_TYPE.STATUS)