text: """Helper functions"""
  __docformat__ = "numpy"
  import argparse
  from typing import List
  from datetime import datetime, timedelta, time as Time
  import os
  import random
  import re
  import sys
  import pandas as pd
  from pytz import timezone
  import iso8601
  import matplotlib
  import matplotlib.pyplot as plt
  from holidays import US as holidaysUS
  from colorama import Fore, Style
  from pandas._config.config import get_option
  from pandas.plotting import register_matplotlib_converters
  import pandas.io.formats.format
  import requests
  from screeninfo import get_monitors
  from gamestonk_terminal import feature_flags as gtff
  from gamestonk_terminal import config_plot as cfgPlot
  
  register_matplotlib_converters()
  if cfgPlot.BACKEND is not None:
      matplotlib.use(cfgPlot.BACKEND)
  
  NO_EXPORT = 0
  EXPORT_ONLY_RAW_DATA_ALLOWED = 1
  EXPORT_ONLY_FIGURES_ALLOWED = 2
  EXPORT_BOTH_RAW_DATA_AND_FIGURES = 3
  
  MENU_GO_BACK = 0
  MENU_QUIT = 1
  MENU_RESET = 2
  
  
  def check_int_range(mini: int, maxi: int):
      """Checks if argparse argument is an int between 2 values.
  
      Parameters
      ----------
      mini: int
          Min value to compare
      maxi: int
          Max value to compare
  
      Returns
      -------
      int_range_checker:
          Function that compares the three integers
      """
  
      # Define the function with default arguments
      def int_range_checker(num: int) -> int:
          """Checks if int is between a high and low value
  
          Parameters
          ----------
          num: int
              Input integer
  
          Returns
          -------
          num: int
              Input number if conditions are met
  
          Raises
          -------
          argparse.ArgumentTypeError
              Input number not between min and max values
          """
          num = int(num)
          if num < mini or num > maxi:
              raise argparse.ArgumentTypeError(f"must be in range [{mini},{maxi}]")
          return num
  
      # Return function handle to checking function
      return int_range_checker
  
  
  def check_non_negative(value) -> int:
      """Argparse type to check non negative int"""
      ivalue = int(value)
      if ivalue < 0:
          raise argparse.ArgumentTypeError(f"{value} is negative")
      return ivalue
  
  
  def check_positive_list(value) -> List[int]:
      """Argparse type to return list of positive ints"""
      list_of_nums = value.split(",")
      list_of_pos = []
      for ivalue in list_of_nums:
          ival = int(ivalue)
          if ival <= 0:
              raise argparse.ArgumentTypeError(
                  f"{value} is an invalid positive int value"
              )
          list_of_pos.append(ival)
      return list_of_pos
  
  
  def check_positive(value) -> int:
      """Argparse type to check positive int"""
      ivalue = int(value)
      if ivalue <= 0:
          raise argparse.ArgumentTypeError(f"{value} is an invalid positive int value")
      return ivalue
  
  
  def check_positive_float(value) -> float:
      """Argparse type to check positive int"""
      fvalue = float(value)
      if fvalue <= 0:
          raise argparse.ArgumentTypeError(f"{value} is not a positive float value")
      return fvalue
  
  
  def check_proportion_range(num) -> float:
      """Checks if float is between 0 and 1. If so, return it.
  
      Parameters
      ----------
      num: float
          Input float
      Returns
      -------
      num: float
          Input number if conditions are met
      Raises
      -------
      argparse.ArgumentTypeError
          Input number not between min and max values
      """
      num = float(num)
      maxi = 1.0
      mini = 0.0
      if num < mini or num > maxi:
          raise argparse.ArgumentTypeError("Value must be between 0 and 1")
      return num
  
  
  def valid_date(s: str) -> datetime:
      """Argparse type to check date is in valid format"""
      try:
          return datetime.strptime(s, "%Y-%m-%d")
      except ValueError as value_error:
          raise argparse.ArgumentTypeError(f"Not a valid date: {s}") from value_error
  
  
  def plot_view_stock(df: pd.DataFrame, symbol: str, interval: str):
      """
      Plot the loaded stock dataframe
      Parameters
      ----------
      df: Dataframe
          Dataframe of prices and volumnes
      symbol: str
          Symbol of ticker
      interval: str
          Stock data resolution for plotting purposes
  
      """
      df.sort_index(ascending=True, inplace=True)
      bar_colors = ["r" if x[1].Open < x[1].Close else "g" for x in df.iterrows()]
  
      try:
          fig, ax = plt.subplots(
              2,
              1,
              gridspec_kw={"height_ratios": [3, 1]},
              figsize=plot_autoscale(),
              dpi=cfgPlot.PLOT_DPI,
          )
      except Exception as e:
          print(e)
          print(
              "Encountered an error trying to open a chart window. Check your X server configuration."
          )
          return
  
      # In order to make nice Volume plot, make the bar width = interval
      if interval == "1440min":
          bar_width = timedelta(days=1)
          title_string = "Daily"
      else:
          bar_width = timedelta(minutes=int(interval.split("m")[0]))
          title_string = f"{int(interval.split('m')[0])} min"
  
      ax[0].yaxis.tick_right()
      if "Adj Close" in df.columns:
          ax[0].plot(df.index, df["Adj Close"], c=cfgPlot.VIEW_COLOR)
      else:
          ax[0].plot(df.index, df["Close"], c=cfgPlot.VIEW_COLOR)
      ax[0].set_xlim(df.index[0], df.index[-1])
      ax[0].set_xticks([])
      ax[0].yaxis.set_label_position("right")
      ax[0].set_ylabel("Share Price ($)")
      ax[0].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5)
  
      ax[0].spines["top"].set_visible(False)
      ax[0].spines["left"].set_visible(False)
      ax[1].bar(
          df.index, df.Volume / 1_000_000, color=bar_colors, alpha=0.8, width=bar_width
      )
      ax[1].set_xlim(df.index[0], df.index[-1])
      ax[1].yaxis.tick_right()
      ax[1].yaxis.set_label_position("right")
      ax[1].set_ylabel("Volume (1M)")
      ax[1].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5)
      ax[1].spines["top"].set_visible(False)
      ax[1].spines["left"].set_visible(False)
      ax[1].set_xlabel("Time")
      fig.suptitle(
          symbol + " " + title_string,
          size=20,
          x=0.15,
          y=0.95,
          fontfamily="serif",
          fontstyle="italic",
      )
      if gtff.USE_ION:
          plt.ion()
      fig.tight_layout(pad=2)
      plt.setp(ax[1].get_xticklabels(), rotation=20, horizontalalignment="right")
  
      plt.show()
      print("")
  
  
  def us_market_holidays(years) -> list:
      """get US market holidays"""
      if isinstance(years, int):
          years = [
              years,
          ]
      # https://www.nyse.com/markets/hours-calendars
      marketHolidays = [
          "Martin Luther King Jr. Day",
          "Washington's Birthday",
          "Memorial Day",
          "Independence Day",
          "Labor Day",
          "Thanksgiving",
          "Christmas Day",
      ]
      #   http://www.maa.clell.de/StarDate/publ_holidays.html
      goodFridays = {
          2010: "2010-04-02",
          2011: "2011-04-22",
          2012: "2012-04-06",
          2013: "2013-03-29",
          2014: "2014-04-18",
          2015: "2015-04-03",
          2016: "2016-03-25",
          2017: "2017-04-14",
          2018: "2018-03-30",
          2019: "2019-04-19",
          2020: "2020-04-10",
          2021: "2021-04-02",
          2022: "2022-04-15",
          2023: "2023-04-07",
          2024: "2024-03-29",
          2025: "2025-04-18",
          2026: "2026-04-03",
          2027: "2027-03-26",
          2028: "2028-04-14",
          2029: "2029-03-30",
          2030: "2030-04-19",
      }
      marketHolidays_and_obsrvd = marketHolidays + [
          holiday + " (Observed)" for holiday in marketHolidays
      ]
      allHolidays = holidaysUS(years=years)
      validHolidays = []
      for date in list(allHolidays):
          if allHolidays[date] in marketHolidays_and_obsrvd:
              validHolidays.append(date)
      for year in years:
          new_Year = datetime.strptime(f"{year}-01-01", "%Y-%m-%d")
          if new_Year.weekday() != 5:  # ignore saturday
              validHolidays.append(new_Year.date())
          if new_Year.weekday() == 6:  # add monday for Sunday
              validHolidays.append(new_Year.date() + timedelta(1))
      for year in years:
          validHolidays.append(datetime.strptime(goodFridays[year], "%Y-%m-%d").date())
      return validHolidays
  
  
  def b_is_stock_market_open() -> bool:
      """checks if the stock market is open"""
      # Get current US time
      now = datetime.now(timezone("US/Eastern"))
      # Check if it is a weekend
      if now.date().weekday() > 4:
          return False
      # Check if it is a holiday
      if now.strftime("%Y-%m-%d") in us_market_holidays(now.year):
          return False
      # Check if it hasn't open already
      if now.time() < Time(hour=9, minute=30, second=0):
          return False
      # Check if it has already closed
      if now.time() > Time(hour=16, minute=0, second=0):
          return False
      # Otherwise, Stock Market is open!
      return True
  
  
  def long_number_format(num) -> str:
      if isinstance(num, float):
          magnitude = 0
          while abs(num) >= 1000:
              magnitude += 1
              num /= 1000.0
          num_str = int(num) if num.is_integer() else f"{num:.3f}"
          return f"{num_str} {' KMBTP'[magnitude]}".strip()
      if isinstance(num, int):
          num = str(num)
      if num.lstrip("-").isdigit():
          num = int(num)
          num /= 1.0
          magnitude = 0
          while abs(num) >= 1000:
              magnitude += 1
              num /= 1000.0
          num_str = int(num) if num.is_integer() else f"{num:.3f}"
          return f"{num_str} {' KMBTP'[magnitude]}".strip()
      return num
  
  
  def clean_data_values_to_float(val: str) -> float:
      """Cleans data to float based on string ending"""
      # Remove any leading or trailing parentheses and spaces
      val = val.strip("( )")
      if val == "-":
          val = "0"
  
      # Convert percentage to decimal
      if val.endswith("%"):
          return float(val[:-1]) / 100.0
      if val.endswith("B"):
          return float(val[:-1]) * 1_000_000_000
      if val.endswith("M"):
          return float(val[:-1]) * 1_000_000
      if val.endswith("K"):
          return float(val[:-1]) * 1000
      return float(val)
  
  
  def int_or_round_float(x) -> str:
      if (x - int(x) < -sys.float_info.epsilon) or (x - int(x) > sys.float_info.epsilon):
          return " " + str(round(x, 2))
  
      return " " + str(int(x))
  
  
  def divide_chunks(data, n):
      # looping till length of data
      for i in range(0, len(data), n):
          yield data[i : i + n]
  
  
  def get_next_stock_market_days(last_stock_day, n_next_days) -> list:
      """gets the next stock market day. Checks against weekends and holidays"""
      n_days = 0
      l_pred_days = []
      years: list = []
      holidays: list = []
      while n_days < n_next_days:
          last_stock_day += timedelta(hours=24)
          year = last_stock_day.date().year
          if year not in years:
              years.append(year)
              holidays += us_market_holidays(year)
          # Check if it is a weekend
          if last_stock_day.date().weekday() > 4:
              continue
          # Check if it is a holiday
          if last_stock_day.strftime("%Y-%m-%d") in holidays:
              continue
          # Otherwise stock market is open
          n_days += 1
          l_pred_days.append(last_stock_day)
  
      return l_pred_days
  
  
  def get_data(tweet):
      """Gets twitter data from API request"""
      if "+" in tweet["created_at"]:
          s_datetime = tweet["created_at"].split(" +")[0]
      else:
          s_datetime = iso8601.parse_date(tweet["created_at"]).strftime(
              "%Y-%m-%d %H:%M:%S"
          )
  
      s_text = tweet["full_text"] if "full_text" in tweet.keys() else tweet["text"]
      return {"created_at": s_datetime, "text": s_text}
  
  
  def clean_tweet(tweet: str, s_ticker: str) -> str:
      """Cleans tweets to be fed to sentiment model"""
      whitespace = re.compile(r"\s+")
      web_address = re.compile(r"(?i)http(s):\/\/[a-z0-9.~_\-\/]+")
      ticker = re.compile(fr"(?i)@{s_ticker}(?=\b)")
      user = re.compile(r"(?i)@[a-z0-9_]+")
  
      tweet = whitespace.sub(" ", tweet)
      tweet = web_address.sub("", tweet)
      tweet = ticker.sub(s_ticker, tweet)
      tweet = user.sub("", tweet)
  
      return tweet
  
  
  def get_user_agent() -> str:
      user_agent_strings = [
          "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:86.1) Gecko/20100101 Firefox/86.1",
          "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:86.1) Gecko/20100101 Firefox/86.1",
          "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:82.1) Gecko/20100101 Firefox/82.1",
          "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:86.0) Gecko/20100101 Firefox/86.0",
          "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:86.0) Gecko/20100101 Firefox/86.0",
          "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:83.0) Gecko/20100101 Firefox/83.0",
          "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:84.0) Gecko/20100101 Firefox/84.0",
      ]
  
      return random.choice(user_agent_strings)
  
  
  # monkey patch Pandas
  def text_adjustment_init(self):
      self.ansi_regx = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
      self.encoding = get_option("display.encoding")
  
  
  def text_adjustment_len(self, text):
      # return compat.strlen(self.ansi_regx.sub("", text), encoding=self.encoding)
      return len(self.ansi_regx.sub("", text))
  
  
  def text_adjustment_justify(self, texts, max_len, mode="right"):
      jfunc = (
          str.ljust
          if (mode == "left")
          else str.rjust
          if (mode == "right")
          else str.center
      )
      out = []
      for s in texts:
          escapes = self.ansi_regx.findall(s)
          if len(escapes) == 2:
              out.append(
                  escapes[0].strip()
                  + jfunc(self.ansi_regx.sub("", s), max_len)
                  + escapes[1].strip()
              )
          else:
              out.append(jfunc(s, max_len))
      return out
  
  
  # pylint: disable=unused-argument
  def text_adjustment_join_unicode(self, lines, sep=""):
      try:
          return sep.join(lines)
      except UnicodeDecodeError:
          # sep = compat.text_type(sep)
          return sep.join([x.decode("utf-8") if isinstance(x, str) else x for x in lines])
  
  
  # pylint: disable=unused-argument
  def text_adjustment_adjoin(self, space, *lists, **kwargs):
      # Add space for all but the last column:
      pads = ([space] * (len(lists) - 1)) + [0]
      max_col_len = max(len(col) for col in lists)
      new_cols = []
      for col, pad in zip(lists, pads):
          width = max(self.len(s) for s in col) + pad
          c = self.justify(col, width, mode="left")
          # Add blank cells to end of col if needed for different col lens:
          if len(col) < max_col_len:
              c.extend([" " * width] * (max_col_len - len(col)))
          new_cols.append(c)
  
      rows = [self.join_unicode(row_tup) for row_tup in zip(*new_cols)]
      return self.join_unicode(rows, sep="\n")
  
  
  # https://github.com/pandas-dev/pandas/issues/18066#issuecomment-522192922
  def patch_pandas_text_adjustment():
      pandas.io.formats.format.TextAdjustment.__init__ = text_adjustment_init
      pandas.io.formats.format.TextAdjustment.len = text_adjustment_len
      pandas.io.formats.format.TextAdjustment.justify = text_adjustment_justify
      pandas.io.formats.format.TextAdjustment.join_unicode = text_adjustment_join_unicode
      pandas.io.formats.format.TextAdjustment.adjoin = text_adjustment_adjoin
  
  
  def parse_known_args_and_warn(
      parser: argparse.ArgumentParser,
      other_args: List[str],
      export_allowed: int = NO_EXPORT,
  ):
      """Parses list of arguments into the supplied parser
  
      Parameters
      ----------
      parser: argparse.ArgumentParser
          Parser with predefined arguments
      other_args: List[str]
          List of arguments to parse
      export_allowed: int
          Choose from NO_EXPORT, EXPORT_ONLY_RAW_DATA_ALLOWED,
          EXPORT_ONLY_FIGURES_ALLOWED and EXPORT_BOTH_RAW_DATA_AND_FIGURES
  
      Returns
      -------
      ns_parser:
          Namespace with parsed arguments
      """
      parser.add_argument(
          "-h", "--help", action="store_true", help="show this help message"
      )
      if export_allowed > NO_EXPORT:
          choices_export = []
          help_export = "Export "
  
          if export_allowed == EXPORT_ONLY_RAW_DATA_ALLOWED:
              choices_export += ["csv", "json", "xlsx"]
              help_export += "raw data into csv, json, xlsx "
          if export_allowed > EXPORT_ONLY_RAW_DATA_ALLOWED:
              choices_export += ["png", "jpg", "pdf", "svg"]
              if export_allowed == EXPORT_BOTH_RAW_DATA_AND_FIGURES:
                  help_export += "or "
              help_export += "figure into png, jpg, pdf, svg "
  
          parser.add_argument(
              "--export",
              choices=choices_export,
              default="",
              type=str,
              dest="export",
              help=help_export,
          )
  
      if gtff.USE_CLEAR_AFTER_CMD:
          os.system("cls||clear")
  
      (ns_parser, l_unknown_args) = parser.parse_known_args(other_args)
  
      if ns_parser.help:
          parser.print_help()
          print("")
          return None
  
      if l_unknown_args:
          print(f"The following args couldn't be interpreted: {l_unknown_args}")
  
      return ns_parser
  
  
  def financials_colored_values(val: str) -> str:
      if val == "N/A" or str(val) == "nan":
          val = f"{Fore.YELLOW}N/A{Style.RESET_ALL}"
      elif sum(c.isalpha() for c in val) < 2:
          if "%" in val and "-" in val or "%" not in val and "(" in val:
              val = f"{Fore.RED}{val}{Style.RESET_ALL}"
          elif "%" in val:
              val = f"{Fore.GREEN}{val}{Style.RESET_ALL}"
      return val
  
  
  def check_ohlc(type_ohlc: str) -> str:
      """Check that data is in ohlc"""
      if bool(re.match("^[ohlca]+$", type_ohlc)):
          return type_ohlc
      raise argparse.ArgumentTypeError("The type specified is not recognized")
  
  
  def lett_to_num(word: str) -> str:
      """Matches ohlca to integers"""
      replacements = [("o", "1"), ("h", "2"), ("l", "3"), ("c", "4"), ("a", "5")]
      for (a, b) in replacements:
          word = word.replace(a, b)
      return word
  
  
  def get_flair() -> str:
      flair = {
          "rocket": "(🚀🚀)",
          "diamond": "(💎💎)",
          "stars": "(✨)",
          "baseball": "(⚾)",
          "boat": "(⛵)",
          "phone": "(☎)",
          "mercury": "(☿)",
          "sun": "(☼)",
          "moon": "(☾)",
          "nuke": "(☢)",
          "hazard": "(☣)",
          "tunder": "(☈)",
          "king": "(♔)",
          "queen": "(♕)",
          "knight": "(♘)",
          "recycle": "(♻)",
          "scales": "(⚖)",
          "ball": "(⚽)",
          "golf": "(⛳)",
          "piece": "(☮)",
          "yy": "(☯)",
      }
  
      if flair.get(gtff.USE_FLAIR):
          return flair[gtff.USE_FLAIR]
  
      return ""
  
  
  def str_to_bool(value) -> bool:
      """Match a string to a boolean value"""
      if isinstance(value, bool):
          return value
      if value.lower() in {"false", "f", "0", "no", "n"}:
          return False
      if value.lower() in {"true", "t", "1", "yes", "y"}:
          return True
      raise ValueError(f"{value} is not a valid boolean value")
  
  
  def get_screeninfo():
      screens = get_monitors()  # Get all available monitors
      if len(screens) - 1 < cfgPlot.MONITOR:  # Check to see if chosen monitor is detected
          monitor = 0
          print(f"Could not locate monitor {cfgPlot.MONITOR}, using primary monitor.")
      else:
          monitor = cfgPlot.MONITOR
      main_screen = screens[monitor]  # Choose what monitor to get
  
      return (main_screen.width, main_screen.height)
  
  
  def plot_autoscale():
  
      if gtff.USE_PLOT_AUTOSCALING:
          x, y = get_screeninfo()  # Get screen size
          x = ((x) * cfgPlot.PLOT_WIDTH_PERCENTAGE * 10 ** -2) / (
              cfgPlot.PLOT_DPI
          )  # Calculate width
          if cfgPlot.PLOT_HEIGHT_PERCENTAGE == 100:  # If full height
              y = y - 60  # Remove the height of window toolbar
          y = ((y) * cfgPlot.PLOT_HEIGHT_PERCENTAGE * 10 ** -2) / (cfgPlot.PLOT_DPI)
      else:  # If not autoscale, use size defined in config_plot.py
          x = cfgPlot.PLOT_WIDTH / (cfgPlot.PLOT_DPI)
          y = cfgPlot.PLOT_HEIGHT / (cfgPlot.PLOT_DPI)
      return x, y
  
  
  def get_last_time_market_was_open(dt):
      # Check if it is a weekend
      if dt.date().weekday() > 4:
          dt = get_last_time_market_was_open(dt - timedelta(hours=24))
  
      # Check if it is a holiday
      if dt.strftime("%Y-%m-%d") in holidaysUS():
          dt = get_last_time_market_was_open(dt - timedelta(hours=24))
  
      dt = dt.replace(hour=21, minute=0, second=0)
  
      return dt
  
  
  def export_data(
      export_type: str, dir_path: str, func_name: str, df: pd.DataFrame = pd.DataFrame()
  ):
      """Export data to a file.
  
      Parameters
      ----------
      export_type : str
          Type of export between: csv,json,xlsx,xls
      dir_path : str
          Path of directory from where this function is called
      func_name : str
          Name of the command that invokes this function
      df : pd.Dataframe
          Datframe containing the data
      """
      if export_type:
          export_dir = dir_path.replace("gamestonk_terminal", "exports")
  
          now = datetime.now()
          full_path = os.path.abspath(
              os.path.join(
                  export_dir,
                  f"{func_name}_{now.strftime('%Y%m%d_%H%M%S')}",
              )
          )
  
          if "," not in export_type:
              export_type += ","
  
          for exp_type in export_type.split(","):
              if exp_type:
                  saved_path = f"{full_path}.{exp_type}"
  
                  if exp_type == "csv":
                      df.to_csv(saved_path)
                  elif exp_type == "json":
                      df.to_json(saved_path)
                  elif exp_type in "xlsx":
                      df.to_excel(saved_path, index=True, header=True)
                  elif exp_type == "png":
                      plt.savefig(saved_path)
                  elif exp_type == "jpg":
                      plt.savefig(saved_path)
                  elif exp_type == "pdf":
                      plt.savefig(saved_path)
                  elif exp_type == "svg":
                      plt.savefig(saved_path)
                  else:
                      print("Wrong export file specified.\n")
  
                  print(f"Saved file: {saved_path}\n")
  
  
  def get_rf() -> float:
      """
      Uses the fiscaldata.gov API to get most recent T-Bill rate
  
      Returns
      -------
      float
          The current US T-Bill rate
      """
      try:
          base = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service"
          end = "/v2/accounting/od/avg_interest_rates"
          filters = "?filter=security_desc:eq:Treasury Bills&sort=-record_date"
          response = requests.get(base + end + filters)
          latest = response.json()["data"][0]
          return round(float(latest["avg_interest_rate_amt"]) / 100, 8)
      except Exception:
          return 0.02
  