Files
OpenBB/gamestonk_terminal/etf/prediction_techniques/pred_controller.py
2022-01-02 22:03:36 +00:00

988 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

""" Prediction Controller """
__docformat__ = "numpy"
# pylint:disable=too-many-lines
import argparse
import difflib
from typing import List, Union
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
get_flair,
parse_known_args_and_warn,
check_positive,
valid_date,
get_next_stock_market_days,
EXPORT_ONLY_FIGURES_ALLOWED,
try_except,
system_clear,
)
from gamestonk_terminal.menu import session
from gamestonk_terminal.common.prediction_techniques import (
arima_view,
arima_model,
ets_model,
mc_model,
ets_view,
knn_view,
neural_networks_view,
regression_view,
pred_helper,
mc_view,
)
from gamestonk_terminal.stocks import stocks_helper
class PredictionTechniquesController:
"""Prediction Techniques Controller class"""
# Command choices
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"load",
"pick",
"ets",
"knn",
"regression",
"arima",
"mlp",
"rnn",
"lstm",
"conv1d",
"mc",
]
CHOICES += CHOICES_COMMANDS
def __init__(
self,
ticker: str,
start: datetime,
interval: str,
stock: pd.DataFrame,
queue: List[str] = None,
):
"""Constructor"""
stock["Returns"] = stock["Adj Close"].pct_change()
stock["LogRet"] = np.log(stock["Adj Close"]) - np.log(
stock["Adj Close"].shift(1)
)
stock = stock.rename(columns={"Adj Close": "AdjClose"})
stock = stock.dropna()
self.stock = stock
self.ticker = ticker
self.start = start
self.interval = interval
self.target = "AdjClose"
self.pred_parser = argparse.ArgumentParser(add_help=False, prog="pred")
self.pred_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
self.completer: Union[None, NestedCompleter] = None
if session and gtff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.CHOICES}
choices["load"]["-r"] = {c: {} for c in stocks_helper.INTERVALS}
choices["pick"] = {c: {} for c in self.stock.columns}
choices["ets"]["-t"] = {c: {} for c in ets_model.TRENDS}
choices["ets"]["-s"] = {c: {} for c in ets_model.SEASONS}
choices["arima"]["-i"] = {c: {} for c in arima_model.ICS}
choices["mc"]["--dist"] = {c: {} for c in mc_model.DISTRIBUTIONS}
self.completer = NestedCompleter.from_nested_dict(choices)
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
s_intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"]
if self.start:
stock_info = f"{s_intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})"
else:
stock_info = "{s_intraday} Stock: {self.ticker}"
help_string = f"""
Prediction Techniques Menu:
load load new ticker
pick pick new target variable
Ticker Loaded: {stock_info}
Target Column: {self.target}
Models:
ets exponential smoothing (e.g. Holt-Winters)
knn k-Nearest Neighbors
regression polynomial regression
arima autoregressive integrated moving average
mlp MultiLayer Perceptron
rnn Recurrent Neural Network
lstm Long-Short Term Memory
conv1d 1D Convolutional Neural Network
mc Monte-Carlo simulations
"""
print(help_string)
def switch(self, an_input: str):
"""Process and dispatch input
Parameters
-------
an_input : str
string with input arguments
Returns
-------
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return self.queue
# Navigation slash is being used
if "/" in an_input:
actions = an_input.split("/")
# Absolute path is specified
if not actions[0]:
an_input = "home"
# Relative path so execute first instruction
else:
an_input = actions[0]
# Add all instructions to the queue
for cmd in actions[1:][::-1]:
if cmd:
self.queue.insert(0, cmd)
(known_args, other_args) = self.pred_parser.parse_known_args(an_input.split())
# Redirect commands to their correct functions
if known_args.cmd:
if known_args.cmd in ("..", "q"):
known_args.cmd = "quit"
elif known_args.cmd in ("?", "h"):
known_args.cmd = "help"
elif known_args.cmd == "r":
known_args.cmd = "reset"
getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
return self.queue
def call_cls(self, _):
"""Process cls command"""
system_clear()
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
def call_help(self, _):
"""Process help command"""
self.print_help()
def call_quit(self, _):
"""Process quit menu command"""
print("")
self.queue.insert(0, "quit")
def call_exit(self, _):
"""Process exit terminal command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
def call_reset(self, _):
"""Process reset command"""
self.queue.insert(0, "pred")
if self.ticker:
self.queue.insert(0, f"load {self.ticker}")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
def call_load(self, other_args: List[str]):
"""Process load command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="load",
description="Load stock ticker to perform analysis on. When the data source is 'yf', an Indian ticker can be"
" loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in"
" https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.",
)
parser.add_argument(
"-t",
"--ticker",
action="store",
dest="ticker",
required="-h" not in other_args,
help="Stock ticker",
)
parser.add_argument(
"-s",
"--start",
type=valid_date,
default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"),
dest="start",
help="The starting date (format YYYY-MM-DD) of the stock",
)
parser.add_argument(
"-e",
"--end",
type=valid_date,
default=datetime.now().strftime("%Y-%m-%d"),
dest="end",
help="The ending date (format YYYY-MM-DD) of the stock",
)
parser.add_argument(
"-i",
"--interval",
action="store",
dest="interval",
type=int,
default=1440,
choices=stocks_helper.INTERVALS,
help="Intraday stock minutes",
)
parser.add_argument(
"--source",
action="store",
dest="source",
choices=stocks_helper.SOURCES,
default="yf",
help="Source of historical data.",
)
parser.add_argument(
"-p",
"--prepost",
action="store_true",
default=False,
dest="prepost",
help="Pre/After market hours. Only works for 'yf' source, and intraday data",
)
# For the case where a user uses: 'load BB'
if other_args and "-t" not in other_args and "-h" not in other_args:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
ns_parser.start,
ns_parser.interval,
ns_parser.end,
ns_parser.prepost,
ns_parser.source,
)
if not df_stock_candidate.empty:
self.stock = df_stock_candidate
if "." in ns_parser.ticker:
self.ticker = ns_parser.ticker.upper().split(".")[0]
else:
self.ticker = ns_parser.ticker.upper()
self.start = ns_parser.start
self.interval = str(ns_parser.interval) + "min"
self.stock["Returns"] = self.stock["Adj Close"].pct_change()
self.stock["LogRet"] = np.log(self.stock["Adj Close"]) - np.log(
self.stock["Adj Close"].shift(1)
)
self.stock = self.stock.rename(columns={"Adj Close": "AdjClose"})
self.stock = self.stock.dropna()
@try_except
def call_pick(self, other_args: List[str]):
"""Process pick command"""
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False,
prog="pick",
description="""
Change target variable
""",
)
parser.add_argument(
"-t",
"--target",
dest="target",
choices=list(self.stock.columns),
help="Select variable to analyze",
)
if other_args and "-t" not in other_args and "-h" not in other_args:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
self.target = ns_parser.target
print("")
@try_except
def call_ets(self, other_args: List[str]):
"""Process ets command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="ets",
description="""
Exponential Smoothing, see https://otexts.com/fpp2/taxonomy.html
Trend='N', Seasonal='N': Simple Exponential Smoothing
Trend='N', Seasonal='A': Exponential Smoothing
Trend='N', Seasonal='M': Exponential Smoothing
Trend='A', Seasonal='N': Holt’s linear method
Trend='A', Seasonal='A': Additive Holt-Winters’ method
Trend='A', Seasonal='M': Multiplicative Holt-Winters’ method
Trend='Ad', Seasonal='N': Additive damped trend method
Trend='Ad', Seasonal='A': Exponential Smoothing
Trend='Ad', Seasonal='M': Holt-Winters’ damped method
Trend component: N: None, A: Additive, Ad: Additive Damped
Seasonality component: N: None, A: Additive, M: Multiplicative
""",
)
parser.add_argument(
"-d",
"--days",
action="store",
dest="n_days",
type=check_positive,
default=5,
help="prediction days.",
)
parser.add_argument(
"-t",
"--trend",
action="store",
dest="trend",
choices=ets_model.TRENDS,
default="N",
help="Trend component: N: None, A: Additive, Ad: Additive Damped.",
)
parser.add_argument(
"-s",
"--seasonal",
action="store",
dest="seasonal",
choices=ets_model.SEASONS,
default="N",
help="Seasonality component: N: None, A: Additive, M: Multiplicative.",
)
parser.add_argument(
"-p",
"--periods",
action="store",
dest="seasonal_periods",
type=check_positive,
default=5,
help="Seasonal periods.",
)
parser.add_argument(
"-e",
"--end",
action="store",
type=valid_date,
dest="s_end_date",
default=None,
help="The end date (format YYYY-MM-DD) to select - Backtesting",
)
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.stock.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.stock.index[0],
n_next_days=5 + ns_parser.n_days,
)[-1]:
print(
"Backtesting not allowed, since End Date is too close to Start Date to train model\n"
)
ets_view.display_exponential_smoothing(
ticker=self.ticker,
values=self.stock[self.target],
n_predict=ns_parser.n_days,
trend=ns_parser.trend,
seasonal=ns_parser.seasonal,
seasonal_periods=ns_parser.seasonal_periods,
s_end_date=ns_parser.s_end_date,
export=ns_parser.export,
)
@try_except
def call_knn(self, other_args: List[str]):
"""Process knn command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="knn",
description="""
K nearest neighbors is a simple algorithm that stores all
available cases and predict the numerical target based on a similarity measure
(e.g. distance functions).
""",
)
parser.add_argument(
"-i",
"--input",
action="store",
dest="n_inputs",
type=check_positive,
default=40,
help="number of days to use as input for prediction.",
)
parser.add_argument(
"-d",
"--days",
action="store",
dest="n_days",
type=check_positive,
default=5,
help="prediction days.",
)
parser.add_argument(
"-j",
"--jumps",
action="store",
dest="n_jumps",
type=check_positive,
default=1,
help="number of jumps in training data.",
)
parser.add_argument(
"-n",
"--neighbors",
action="store",
dest="n_neighbors",
type=check_positive,
default=20,
help="number of neighbors to use on the algorithm.",
)
parser.add_argument(
"-e",
"--end",
action="store",
type=valid_date,
dest="s_end_date",
default=None,
help="The end date (format YYYY-MM-DD) to select for testing",
)
parser.add_argument(
"-t",
"--test_size",
default=0.2,
dest="valid_split",
type=float,
help="Percentage of data to validate in sample",
)
parser.add_argument(
"--no_shuffle",
action="store_false",
dest="no_shuffle",
default=True,
help="Specify if shuffling validation inputs.",
)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
knn_view.display_k_nearest_neighbors(
ticker=self.ticker,
data=self.stock[self.target],
n_neighbors=ns_parser.n_neighbors,
n_input_days=ns_parser.n_inputs,
n_predict_days=ns_parser.n_days,
test_size=ns_parser.valid_split,
end_date=ns_parser.s_end_date,
no_shuffle=ns_parser.no_shuffle,
)
@try_except
def call_regression(self, other_args: List[str]):
"""Process linear command"""
parser = argparse.ArgumentParser(
add_help=False,
prog="regression",
description="""
Regression attempts to model the relationship between
two variables by fitting a linear/quadratic/cubic/other equation to
observed data. One variable is considered to be an explanatory variable,
and the other is considered to be a dependent variable.
""",
)
parser.add_argument(
"-i",
"--input",
action="store",
dest="n_inputs",
type=check_positive,
default=40,
help="number of days to use for prediction.",
)
parser.add_argument(
"-d",
"--days",
action="store",
dest="n_days",
type=check_positive,
default=5,
help="prediction days.",
)
parser.add_argument(
"-j",
"--jumps",
action="store",
dest="n_jumps",
type=check_positive,
default=1,
help="number of jumps in training data.",
)
parser.add_argument(
"-e",
"--end",
action="store",
type=valid_date,
dest="s_end_date",
default=None,
help="The end date (format YYYY-MM-DD) to select - Backtesting",
)
parser.add_argument(
"-p",
"--polynomial",
action="store",
dest="n_polynomial",
type=check_positive,
default=1,
help="polynomial associated with regression.",
)
if (
other_args
and "-h" not in other_args
and ("-p" not in other_args or "--polynomial" not in other_args)
):
other_args.insert(0, "-p")
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
# BACKTESTING CHECK
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.stock.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.stock.index[0],
n_next_days=5 + ns_parser.n_days,
)[-1]:
print(
"Backtesting not allowed, since End Date is too close to Start Date to train model\n"
)
regression_view.display_regression(
dataset=self.ticker,
values=self.stock[self.target],
poly_order=ns_parser.n_polynomial,
n_input=ns_parser.n_inputs,
n_predict=ns_parser.n_days,
n_jumps=ns_parser.n_jumps,
s_end_date=ns_parser.s_end_date,
export=ns_parser.export,
)
@try_except
def call_arima(self, other_args: List[str]):
"""Process arima command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="arima",
description="""
In statistics and econometrics, and in particular in time series analysis, an
autoregressive integrated moving average (ARIMA) model is a generalization of an
autoregressive moving average (ARMA) model. Both of these models are fitted to time
series data either to better understand the data or to predict future points in the
series (forecasting). ARIMA(p,d,q) where parameters p, d, and q are non-negative
integers, p is the order (number of time lags) of the autoregressive model, d is the
degree of differencing (the number of times the data have had past values subtracted),
and q is the order of the moving-average model.
""",
)
parser.add_argument(
"-d",
"--days",
action="store",
dest="n_days",
type=check_positive,
default=5,
help="prediction days.",
)
parser.add_argument(
"-i",
"--ic",
action="store",
dest="s_ic",
type=str,
default="aic",
choices=arima_model.ICS,
help="information criteria.",
)
parser.add_argument(
"-s",
"--seasonal",
action="store_true",
default=False,
dest="b_seasonal",
help="Use weekly seasonal data.",
)
parser.add_argument(
"-o",
"--order",
action="store",
dest="s_order",
default="",
type=str,
help="arima model order (p,d,q) in format: p,d,q.",
)
parser.add_argument(
"-r",
"--results",
action="store_true",
dest="b_results",
default=False,
help="results about ARIMA summary flag.",
)
parser.add_argument(
"-e",
"--end",
action="store",
type=valid_date,
dest="s_end_date",
default=None,
help="The end date (format YYYY-MM-DD) to select - Backtesting",
)
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
# BACKTESTING CHECK
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.stock.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.stock.index[0],
n_next_days=5 + ns_parser.n_days,
)[-1]:
print(
"Backtesting not allowed, since End Date is too close to Start Date to train model\n"
)
arima_view.display_arima(
dataset=self.ticker,
values=self.stock[self.target],
arima_order=ns_parser.s_order,
n_predict=ns_parser.n_days,
seasonal=ns_parser.b_seasonal,
ic=ns_parser.s_ic,
results=ns_parser.b_results,
s_end_date=ns_parser.s_end_date,
export=ns_parser.export,
)
@try_except
def call_mlp(self, other_args: List[str]):
"""Process mlp command"""
try:
ns_parser = pred_helper.parse_args(
prog="mlp",
description="""Multi-Layered-Perceptron. """,
other_args=other_args,
)
if ns_parser:
neural_networks_view.display_mlp(
dataset=self.ticker,
data=self.stock[self.target],
n_input_days=ns_parser.n_inputs,
n_predict_days=ns_parser.n_days,
learning_rate=ns_parser.lr,
epochs=ns_parser.n_epochs,
batch_size=ns_parser.n_batch_size,
test_size=ns_parser.valid_split,
n_loops=ns_parser.n_loops,
no_shuffle=ns_parser.no_shuffle,
)
except Exception as e:
print(e, "\n")
finally:
pred_helper.restore_env()
def call_rnn(self, other_args: List[str]):
"""Process rnn command"""
try:
ns_parser = pred_helper.parse_args(
prog="rnn",
description="""Recurrent Neural Network. """,
other_args=other_args,
)
if ns_parser:
neural_networks_view.display_rnn(
dataset=self.ticker,
data=self.stock[self.target],
n_input_days=ns_parser.n_inputs,
n_predict_days=ns_parser.n_days,
learning_rate=ns_parser.lr,
epochs=ns_parser.n_epochs,
batch_size=ns_parser.n_batch_size,
test_size=ns_parser.valid_split,
n_loops=ns_parser.n_loops,
no_shuffle=ns_parser.no_shuffle,
)
except Exception as e:
print(e, "\n")
finally:
pred_helper.restore_env()
def call_lstm(self, other_args: List[str]):
"""Process lstm command"""
try:
ns_parser = pred_helper.parse_args(
prog="lstm",
description="""Long-Short Term Memory. """,
other_args=other_args,
)
if ns_parser:
neural_networks_view.display_lstm(
dataset=self.ticker,
data=self.stock[self.target],
n_input_days=ns_parser.n_inputs,
n_predict_days=ns_parser.n_days,
learning_rate=ns_parser.lr,
epochs=ns_parser.n_epochs,
batch_size=ns_parser.n_batch_size,
test_size=ns_parser.valid_split,
n_loops=ns_parser.n_loops,
no_shuffle=ns_parser.no_shuffle,
)
except Exception as e:
print(e, "\n")
finally:
pred_helper.restore_env()
def call_conv1d(self, other_args: List[str]):
"""Process conv1d command"""
try:
ns_parser = pred_helper.parse_args(
prog="conv1d",
description="""1D CNN.""",
other_args=other_args,
)
if ns_parser:
neural_networks_view.display_conv1d(
dataset=self.ticker,
data=self.stock[self.target],
n_input_days=ns_parser.n_inputs,
n_predict_days=ns_parser.n_days,
learning_rate=ns_parser.lr,
epochs=ns_parser.n_epochs,
batch_size=ns_parser.n_batch_size,
test_size=ns_parser.valid_split,
n_loops=ns_parser.n_loops,
no_shuffle=ns_parser.no_shuffle,
)
except Exception as e:
print(e, "\n")
finally:
pred_helper.restore_env()
@try_except
def call_mc(self, other_args: List[str]):
"""Process mc command"""
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False,
prog="mc",
description="""
Perform Monte Carlo forecasting
""",
)
parser.add_argument(
"-d",
"--days",
help="Days to predict",
dest="n_days",
type=check_positive,
default=30,
)
parser.add_argument(
"-n",
"--num",
help="Number of simulations to perform",
dest="n_sims",
default=100,
)
parser.add_argument(
"--dist",
choices=mc_model.DISTRIBUTIONS,
default="lognormal",
dest="dist",
help="Whether to model returns or log returns",
)
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if ns_parser:
if self.target != "AdjClose":
print("MC Prediction designed for AdjClose prices\n")
mc_view.display_mc_forecast(
data=self.stock[self.target],
n_future=ns_parser.n_days,
n_sims=ns_parser.n_sims,
use_log=ns_parser.dist == "lognormal",
export=ns_parser.export,
)
def menu(
ticker: str,
start: datetime,
interval: str,
stock: pd.DataFrame,
queue: List[str] = None,
):
"""Prediction Techniques Menu"""
pred_controller = PredictionTechniquesController(
ticker, start, interval, stock, queue
)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if pred_controller.queue and len(pred_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if pred_controller.queue[0] in ("q", "..", "quit"):
if len(pred_controller.queue) > 1:
return pred_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = pred_controller.queue[0]
pred_controller.queue = pred_controller.queue[1:]
# Print the current location because this was an instruction and we want user to know what was the action
if an_input and an_input.split(" ")[0] in pred_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /stocks/pred/ $ {an_input}")
# Get input command from user
else:
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
pred_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and pred_controller.completer:
try:
an_input = session.prompt(
f"{get_flair()} /stocks/pred/ $ ",
completer=pred_controller.completer,
search_ignore_case=True,
)
except KeyboardInterrupt:
# Exit in case of keyboard interrupt
an_input = "exit"
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/pred/ $ ")
try:
# Process the input command
pred_controller.queue = pred_controller.switch(an_input)
except SystemExit:
print(
f"\nThe command '{an_input}' doesn't exist on the /stocks/options menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
pred_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
else:
candidate_input = similar_cmd[0]
if candidate_input == an_input:
an_input = ""
pred_controller.queue = []
print("\n")
continue
print(f" Replacing by '{an_input}'.")
pred_controller.queue.insert(0, an_input)
else:
print("\n")
an_input = ""
pred_controller.queue = []