mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
Starts crypto tools menu (#1668)
* started crypto tools menu * allow zero float * added obb script Co-authored-by: Chavithra <chavithra@gmail.com> Co-authored-by: didierlopes.eth <dro.lopes@campus.fct.unl.pt> Co-authored-by: minhhoang1023 <40023817+minhhoang1023@users.noreply.github.com>
This commit is contained in:
@@ -63,7 +63,18 @@ class CryptoController(CryptoBaseController):
|
||||
"prt",
|
||||
"resources",
|
||||
]
|
||||
CHOICES_MENUS = ["ta", "dd", "ov", "disc", "onchain", "defi", "nft", "pred", "qa"]
|
||||
CHOICES_MENUS = [
|
||||
"ta",
|
||||
"dd",
|
||||
"ov",
|
||||
"disc",
|
||||
"onchain",
|
||||
"defi",
|
||||
"tools",
|
||||
"nft",
|
||||
"pred",
|
||||
"qa",
|
||||
]
|
||||
|
||||
DD_VIEWS_MAPPING = {
|
||||
"cg": pycoingecko_view,
|
||||
@@ -106,6 +117,7 @@ class CryptoController(CryptoBaseController):
|
||||
> ov overview of the cryptocurrencies, e.g.: market cap, DeFi, latest news, top exchanges, stables
|
||||
> onchain information on different blockchains, e.g.: eth gas fees, whale alerts, DEXes info
|
||||
> defi decentralized finance information, e.g.: dpi, llama, tvl, lending, borrow, funding
|
||||
> tools explore different tools e.g.: apytoapr, il
|
||||
> nft non-fungible tokens, e.g.: today drops{has_ticker_start}
|
||||
> dd due-diligence for loaded coin, e.g.: coin information, social media, market stats
|
||||
> ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv
|
||||
@@ -430,6 +442,15 @@ class CryptoController(CryptoBaseController):
|
||||
else:
|
||||
console.print("No coin selected. Use 'load' to load a coin.\n")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_tools(self, _):
|
||||
"""Process tools command"""
|
||||
from openbb_terminal.cryptocurrency.tools.tools_controller import (
|
||||
ToolsController,
|
||||
)
|
||||
|
||||
self.queue = self.load_class(ToolsController, self.queue)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_disc(self, _):
|
||||
"""Process disc command"""
|
||||
|
||||
0
openbb_terminal/cryptocurrency/tools/__init__.py
Normal file
0
openbb_terminal/cryptocurrency/tools/__init__.py
Normal file
13
openbb_terminal/cryptocurrency/tools/tools_api.py
Normal file
13
openbb_terminal/cryptocurrency/tools/tools_api.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Tools context API."""
|
||||
import os
|
||||
from openbb_terminal.helper_classes import ModelsNamespace as _models
|
||||
|
||||
# flake8: noqa
|
||||
# pylint: disable=unused-import
|
||||
|
||||
# Menu commands
|
||||
from .tools_view import display_apy as aprtoapy
|
||||
from .tools_view import display_il as il
|
||||
|
||||
# Models
|
||||
models = _models(os.path.abspath(os.path.dirname(__file__)))
|
||||
180
openbb_terminal/cryptocurrency/tools/tools_controller.py
Normal file
180
openbb_terminal/cryptocurrency/tools/tools_controller.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""Tools Controller Module"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
# pylint: disable=C0302
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from openbb_terminal import feature_flags as obbff
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.helper_funcs import (
|
||||
EXPORT_ONLY_RAW_DATA_ALLOWED,
|
||||
check_non_negative_float,
|
||||
check_percentage_range,
|
||||
check_positive,
|
||||
check_positive_float,
|
||||
parse_known_args_and_warn,
|
||||
)
|
||||
from openbb_terminal.cryptocurrency.tools import tools_view
|
||||
from openbb_terminal.menu import session
|
||||
from openbb_terminal.parent_classes import BaseController
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ToolsController(BaseController):
|
||||
"""Tools Controller class"""
|
||||
|
||||
CHOICES_COMMANDS = ["aprtoapy", "il"]
|
||||
|
||||
PATH = "/crypto/tools/"
|
||||
|
||||
def __init__(self, queue: List[str] = None):
|
||||
"""Constructor"""
|
||||
super().__init__(queue)
|
||||
|
||||
if session and obbff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.controller_choices}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
|
||||
help_text = """[cmds]
|
||||
aprtoapy convert apr to apy
|
||||
il calculate impermanent loss[/cmds]
|
||||
"""
|
||||
console.print(text=help_text, menu="Cryptocurrency - Tools")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_il(self, other_args: List[str]):
|
||||
"""Process il command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="il",
|
||||
description="""Tool to calculate Impermanent Loss in a custom liquidity pool.
|
||||
Users can provide percentages increases for two tokens (and their weight in the liquidity pool)
|
||||
and verify the impermanent loss that can occur.""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-pcA",
|
||||
"--priceChangeA",
|
||||
dest="priceChangeA",
|
||||
type=check_non_negative_float,
|
||||
help="Token A price change in percentage",
|
||||
default=0,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-pcB",
|
||||
"--priceChangeB",
|
||||
dest="priceChangeB",
|
||||
type=check_non_negative_float,
|
||||
help="Token B price change in percentage",
|
||||
default=100,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--proportion",
|
||||
dest="proportion",
|
||||
type=check_percentage_range,
|
||||
help="""Pool proportion. E.g., 50 means that pool contains 50%% of token A and 50%% of token B,
|
||||
30 means that pool contains 30%% of token A and 70%% of token B""",
|
||||
default=50,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--value",
|
||||
dest="value",
|
||||
type=check_positive_float,
|
||||
help="Initial amount of dollars that user provides to liquidity pool",
|
||||
default=1000,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--narrative",
|
||||
dest="narrative",
|
||||
action="store_true",
|
||||
help="Flag to show narrative instead of dataframe",
|
||||
default=False,
|
||||
)
|
||||
|
||||
if other_args and not other_args[0][0] == "-":
|
||||
other_args.insert(0, "-pcA")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
|
||||
if ns_parser:
|
||||
tools_view.display_il(
|
||||
price_changeA=ns_parser.priceChangeA,
|
||||
price_changeB=ns_parser.priceChangeB,
|
||||
proportion=ns_parser.proportion,
|
||||
initial_pool_value=ns_parser.value,
|
||||
narrative=ns_parser.narrative,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_aprtoapy(self, other_args: List[str]):
|
||||
"""Process aprtoapy command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="aprtoapy",
|
||||
description="""
|
||||
Tool to calculate APY from APR value.
|
||||
Compouding periods, i.e., the number of times compounded per year
|
||||
can be defined with -c argument.
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--apr",
|
||||
dest="apr",
|
||||
type=check_positive_float,
|
||||
help="APR value in percentage to convert",
|
||||
default=100,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--compounding",
|
||||
dest="compounding",
|
||||
type=check_positive,
|
||||
help="Number of compounded periods in a year. 12 means compounding monthly",
|
||||
default=12,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--narrative",
|
||||
dest="narrative",
|
||||
action="store_true",
|
||||
help="Flag to show narrative instead of dataframe",
|
||||
default=False,
|
||||
)
|
||||
|
||||
if other_args and not other_args[0][0] == "-":
|
||||
other_args.insert(0, "--apr")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
|
||||
if ns_parser:
|
||||
tools_view.display_apy(
|
||||
apr=ns_parser.apr,
|
||||
compounding_times=ns_parser.compounding,
|
||||
narrative=ns_parser.narrative,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
58
openbb_terminal/cryptocurrency/tools/tools_helpers.py
Normal file
58
openbb_terminal/cryptocurrency/tools/tools_helpers.py
Normal file
@@ -0,0 +1,58 @@
|
||||
def change_variation(change: float) -> float:
|
||||
"""Helper to convert change variation
|
||||
|
||||
Parameters
|
||||
----------
|
||||
change: float
|
||||
percentage change
|
||||
|
||||
Returns
|
||||
-------
|
||||
float:
|
||||
converted value
|
||||
"""
|
||||
return (100 + change) / 100
|
||||
|
||||
|
||||
def calculate_hold_value(changeA: float, changeB: float, proportion: float) -> float:
|
||||
"""Calculates hold value of two different coins
|
||||
|
||||
Parameters
|
||||
----------
|
||||
changeA: float
|
||||
price change of crypto A in percentage
|
||||
changeB: float
|
||||
price change of crypto B in percentage
|
||||
proportion: float
|
||||
percentage of first token in pool
|
||||
|
||||
Returns
|
||||
-------
|
||||
float:
|
||||
hold value
|
||||
"""
|
||||
return (change_variation(changeA) * proportion) / 100 + (
|
||||
(change_variation(changeB) * (100 - proportion)) / 100
|
||||
)
|
||||
|
||||
|
||||
def calculate_pool_value(changeA, changeB, proportion):
|
||||
"""Calculates pool value of two different coins
|
||||
|
||||
Parameters
|
||||
----------
|
||||
changeA: float
|
||||
price change of crypto A in percentage
|
||||
changeB: float
|
||||
price change of crypto B in percentage
|
||||
proportion: float
|
||||
percentage of first token in pool
|
||||
|
||||
Returns
|
||||
-------
|
||||
float:
|
||||
pool value
|
||||
"""
|
||||
return pow(change_variation(changeA), proportion / 100) * pow(
|
||||
change_variation(changeB), (100 - proportion) / 100
|
||||
)
|
||||
118
openbb_terminal/cryptocurrency/tools/tools_model.py
Normal file
118
openbb_terminal/cryptocurrency/tools/tools_model.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Tools Model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
from typing import Tuple
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from openbb_terminal.cryptocurrency.tools.tools_helpers import (
|
||||
calculate_hold_value,
|
||||
calculate_pool_value,
|
||||
)
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def calculate_apy(apr: float, compounding_times: int) -> Tuple[pd.DataFrame, str]:
|
||||
"""Converts apr into apy
|
||||
|
||||
Parameters
|
||||
----------
|
||||
apr: float
|
||||
value in percentage
|
||||
compounding_times: int
|
||||
number of compounded periods in a year
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple:
|
||||
- pd.DataFrame: dataframe with results
|
||||
- str: narrative version of results
|
||||
"""
|
||||
apy = (pow((1 + (apr / 100) / compounding_times), compounding_times) - 1) * 100
|
||||
apy_str = f"""
|
||||
An APR of {apr}% compounded {compounding_times} times per year equals to an APY of {round(apy,3)}%
|
||||
"""
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"Metric": [
|
||||
"APR",
|
||||
"Compounding Times",
|
||||
"APY",
|
||||
],
|
||||
"Value": [
|
||||
f"{apr}%",
|
||||
compounding_times,
|
||||
f"{round(apy, 3)}%",
|
||||
],
|
||||
}
|
||||
)
|
||||
return df, apy_str
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def calculate_il(
|
||||
price_changeA: float,
|
||||
price_changeB: float,
|
||||
proportion: float,
|
||||
initial_pool_value: float,
|
||||
) -> Tuple[pd.DataFrame, str]:
|
||||
"""Calculates Impermanent Loss in a custom liquidity pool
|
||||
|
||||
Parameters
|
||||
----------
|
||||
price_changeA: float
|
||||
price change of crypto A in percentage
|
||||
price_changeB: float
|
||||
price change of crypto B in percentage
|
||||
proportion: float
|
||||
percentage of first token in pool
|
||||
initial_pool_value: float
|
||||
initial value that pool contains
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple:
|
||||
- pd.DataFrame: dataframe with results
|
||||
- str: narrative version of results
|
||||
"""
|
||||
pool_value = calculate_pool_value(price_changeA, price_changeB, proportion)
|
||||
hold_value = calculate_hold_value(price_changeA, price_changeB, proportion)
|
||||
il = abs(((pool_value / hold_value) - 1) * 100)
|
||||
hold_value = hold_value * initial_pool_value
|
||||
pool_value = pool_value * initial_pool_value
|
||||
il_str = f"""
|
||||
Ignoring fees/rewards and only accounting for impermanent loss:
|
||||
|
||||
A change of {price_changeA}% in token A and {price_changeB}% in token B in
|
||||
a pool with a proportion of {proportion}/{100-proportion} and with an initial
|
||||
value of ${initial_pool_value} would result in an impermant loss of {round(il,2)}%
|
||||
If you just hold the tokens you would have ${round(hold_value,2)} whereas
|
||||
in the pool you would have ${round(pool_value,2)}
|
||||
"""
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"Metric": [
|
||||
"Price Change Token A",
|
||||
"Price Change Token B",
|
||||
"Initial Pool Value",
|
||||
"Proportion",
|
||||
"Impermanent Loss",
|
||||
"Hold Value",
|
||||
"Pool Value",
|
||||
],
|
||||
"Value": [
|
||||
f"{price_changeA}%",
|
||||
f"{price_changeB}%",
|
||||
f"${initial_pool_value}",
|
||||
f"{proportion}/{100-proportion}",
|
||||
f"{round(il,2)}%",
|
||||
f"${round(hold_value,2)}",
|
||||
f"${round(pool_value,2)}",
|
||||
],
|
||||
}
|
||||
)
|
||||
return df, il_str
|
||||
104
openbb_terminal/cryptocurrency/tools/tools_view.py
Normal file
104
openbb_terminal/cryptocurrency/tools/tools_view.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Tools View"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from openbb_terminal.cryptocurrency.tools.tools_model import calculate_apy, calculate_il
|
||||
from openbb_terminal.helper_funcs import export_data, print_rich_table
|
||||
from openbb_terminal.rich_config import console
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_apy(apr: float, compounding_times: int, narrative: bool, export: str):
|
||||
"""Displays APY value converted from APR
|
||||
|
||||
Parameters
|
||||
----------
|
||||
apr: float
|
||||
value in percentage
|
||||
compounding_times: int
|
||||
number of compounded periods in a year
|
||||
narrative: str
|
||||
display narrative version instead of dataframe
|
||||
export : str
|
||||
Export dataframe data to csv,json,xlsx file
|
||||
|
||||
Returns
|
||||
-------
|
||||
"""
|
||||
df, apy_str = calculate_apy(apr, compounding_times)
|
||||
|
||||
if narrative:
|
||||
console.print(apy_str)
|
||||
else:
|
||||
print_rich_table(
|
||||
df,
|
||||
headers=list(df.columns),
|
||||
show_index=False,
|
||||
title="APR/APY Calculator",
|
||||
)
|
||||
console.print("")
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"aprtoapy",
|
||||
apy_str,
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_il(
|
||||
price_changeA: int,
|
||||
price_changeB: int,
|
||||
proportion: int,
|
||||
initial_pool_value: int,
|
||||
narrative: bool,
|
||||
export: str,
|
||||
):
|
||||
"""Displays Impermanent Loss in a custom liquidity pool
|
||||
|
||||
Parameters
|
||||
----------
|
||||
price_changeA: float
|
||||
price change of crypto A in percentage
|
||||
price_changeB: float
|
||||
price change of crypto B in percentage
|
||||
proportion: float
|
||||
percentage of first token in pool
|
||||
initial_pool_value: float
|
||||
initial value that pool contains
|
||||
narrative: str
|
||||
display narrative version instead of dataframe
|
||||
export : str
|
||||
Export dataframe data to csv,json,xlsx file
|
||||
|
||||
Returns
|
||||
-------
|
||||
"""
|
||||
df, il_str = calculate_il(
|
||||
price_changeA=price_changeA,
|
||||
price_changeB=price_changeB,
|
||||
proportion=proportion,
|
||||
initial_pool_value=initial_pool_value,
|
||||
)
|
||||
if narrative:
|
||||
console.print(il_str)
|
||||
else:
|
||||
print_rich_table(
|
||||
df,
|
||||
headers=list(df.columns),
|
||||
show_index=False,
|
||||
title="Impermanent Loss Calculator",
|
||||
)
|
||||
console.print("")
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"il",
|
||||
df,
|
||||
)
|
||||
@@ -317,6 +317,31 @@ def check_positive_float(value) -> float:
|
||||
return new_value
|
||||
|
||||
|
||||
def check_percentage_range(num) -> float:
|
||||
"""
|
||||
Checks if float is between 0 and 100. 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 = 100.0
|
||||
mini = 0.0
|
||||
if num <= mini or num >= maxi:
|
||||
log_and_raise(argparse.ArgumentTypeError("Value must be between 0 and 100"))
|
||||
return num
|
||||
|
||||
|
||||
def check_proportion_range(num) -> float:
|
||||
"""
|
||||
Checks if float is between 0 and 1. If so, return it.
|
||||
|
||||
5
scripts/test_crypto_tools.openbb
vendored
Normal file
5
scripts/test_crypto_tools.openbb
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
crypto
|
||||
tools
|
||||
aprtoapy
|
||||
il
|
||||
exit
|
||||
@@ -292,6 +292,13 @@ def test_call_func_expect_queue(expected_queue, func, queue):
|
||||
[],
|
||||
dict(),
|
||||
),
|
||||
(
|
||||
"call_tools",
|
||||
[],
|
||||
"CryptoController.load_class",
|
||||
[],
|
||||
dict(),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_call_func(
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
,Metric,Value
|
||||
0,APR,100%
|
||||
1,Compounding Times,12
|
||||
2,APY,161.304%
|
||||
|
@@ -0,0 +1,8 @@
|
||||
,Metric,Value
|
||||
0,Price Change Token A,100%
|
||||
1,Price Change Token B,200%
|
||||
2,Initial Pool Value,$1000
|
||||
3,Proportion,50/50
|
||||
4,Impermanent Loss,2.02%
|
||||
5,Hold Value,$2500.0
|
||||
6,Pool Value,$2449.49
|
||||
|
@@ -0,0 +1,224 @@
|
||||
# IMPORTATION STANDARD
|
||||
import os
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
import pytest
|
||||
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.cryptocurrency.tools import tools_controller
|
||||
|
||||
# pylint: disable=E1101
|
||||
# pylint: disable=W0603
|
||||
# pylint: disable=E1111
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.parametrize(
|
||||
"queue, expected",
|
||||
[
|
||||
(["load", "help"], []),
|
||||
(["quit", "help"], ["help"]),
|
||||
],
|
||||
)
|
||||
def test_menu_with_queue(expected, mocker, queue):
|
||||
path_controller = "openbb_terminal.cryptocurrency.tools.tools_controller"
|
||||
|
||||
# MOCK SWITCH
|
||||
mocker.patch(
|
||||
target=f"{path_controller}.ToolsController.switch",
|
||||
return_value=["quit"],
|
||||
)
|
||||
result_menu = tools_controller.ToolsController(queue=queue).menu()
|
||||
|
||||
assert result_menu == expected
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
def test_menu_without_queue_completion(mocker):
|
||||
path_controller = "openbb_terminal.cryptocurrency.tools.tools_controller"
|
||||
|
||||
# ENABLE AUTO-COMPLETION : HELPER_FUNCS.MENU
|
||||
mocker.patch(
|
||||
target="openbb_terminal.feature_flags.USE_PROMPT_TOOLKIT",
|
||||
new=True,
|
||||
)
|
||||
mocker.patch(
|
||||
target="openbb_terminal.parent_classes.session",
|
||||
)
|
||||
mocker.patch(
|
||||
target="openbb_terminal.parent_classes.session.prompt",
|
||||
return_value="quit",
|
||||
)
|
||||
|
||||
# DISABLE AUTO-COMPLETION : CONTROLLER.COMPLETER
|
||||
mocker.patch.object(
|
||||
target=tools_controller.obbff,
|
||||
attribute="USE_PROMPT_TOOLKIT",
|
||||
new=True,
|
||||
)
|
||||
mocker.patch(
|
||||
target=f"{path_controller}.session",
|
||||
)
|
||||
mocker.patch(
|
||||
target=f"{path_controller}.session.prompt",
|
||||
return_value="quit",
|
||||
)
|
||||
|
||||
result_menu = tools_controller.ToolsController(queue=None).menu()
|
||||
|
||||
assert result_menu == []
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.parametrize(
|
||||
"mock_input",
|
||||
["help", "homee help", "home help", "mock"],
|
||||
)
|
||||
def test_menu_without_queue_sys_exit(mock_input, mocker):
|
||||
path_controller = "openbb_terminal.cryptocurrency.tools.tools_controller"
|
||||
|
||||
# DISABLE AUTO-COMPLETION
|
||||
mocker.patch.object(
|
||||
target=tools_controller.obbff,
|
||||
attribute="USE_PROMPT_TOOLKIT",
|
||||
new=False,
|
||||
)
|
||||
mocker.patch(
|
||||
target=f"{path_controller}.session",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
# MOCK USER INPUT
|
||||
mocker.patch("builtins.input", return_value=mock_input)
|
||||
|
||||
# MOCK SWITCH
|
||||
class SystemExitSideEffect:
|
||||
def __init__(self):
|
||||
self.first_call = True
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if self.first_call:
|
||||
self.first_call = False
|
||||
raise SystemExit()
|
||||
return ["quit"]
|
||||
|
||||
mock_switch = mocker.Mock(side_effect=SystemExitSideEffect())
|
||||
mocker.patch(
|
||||
target=f"{path_controller}.ToolsController.switch",
|
||||
new=mock_switch,
|
||||
)
|
||||
|
||||
result_menu = tools_controller.ToolsController(queue=None).menu()
|
||||
|
||||
assert result_menu == []
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.record_stdout
|
||||
def test_print_help():
|
||||
controller = tools_controller.ToolsController(queue=None)
|
||||
controller.print_help()
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.parametrize(
|
||||
"an_input, expected_queue",
|
||||
[
|
||||
("", []),
|
||||
("/help", ["home", "help"]),
|
||||
("help/help", ["help", "help"]),
|
||||
("q", ["quit"]),
|
||||
("h", []),
|
||||
(
|
||||
"r",
|
||||
["quit", "quit", "reset", "crypto", "tools"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_switch(an_input, expected_queue):
|
||||
controller = tools_controller.ToolsController(queue=None)
|
||||
queue = controller.switch(an_input=an_input)
|
||||
|
||||
assert queue == expected_queue
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
def test_call_cls(mocker):
|
||||
mocker.patch("os.system")
|
||||
|
||||
controller = tools_controller.ToolsController(queue=None)
|
||||
controller.call_cls([])
|
||||
|
||||
assert controller.queue == []
|
||||
os.system.assert_called_once_with("cls||clear")
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.parametrize(
|
||||
"func, queue, expected_queue",
|
||||
[
|
||||
(
|
||||
"call_exit",
|
||||
[],
|
||||
["quit", "quit", "quit"],
|
||||
),
|
||||
("call_exit", ["help"], ["quit", "quit", "quit", "help"]),
|
||||
("call_home", [], ["quit", "quit"]),
|
||||
("call_help", [], []),
|
||||
("call_quit", [], ["quit"]),
|
||||
("call_quit", ["help"], ["quit", "help"]),
|
||||
(
|
||||
"call_reset",
|
||||
[],
|
||||
["quit", "quit", "reset", "crypto", "tools"],
|
||||
),
|
||||
(
|
||||
"call_reset",
|
||||
["help"],
|
||||
["quit", "quit", "reset", "crypto", "tools", "help"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_call_func_expect_queue(expected_queue, func, queue):
|
||||
controller = tools_controller.ToolsController(queue=queue)
|
||||
result = getattr(controller, func)([])
|
||||
|
||||
assert result is None
|
||||
assert controller.queue == expected_queue
|
||||
|
||||
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.parametrize(
|
||||
"tested_func, other_args, mocked_func, called_args, called_kwargs",
|
||||
[
|
||||
(
|
||||
"call_aprtoapy",
|
||||
[],
|
||||
"tools_view.display_apy",
|
||||
[],
|
||||
dict(),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_call_func(
|
||||
tested_func, mocked_func, other_args, called_args, called_kwargs, mocker
|
||||
):
|
||||
path_controller = "openbb_terminal.cryptocurrency.tools.tools_controller"
|
||||
|
||||
if mocked_func:
|
||||
mock = mocker.Mock()
|
||||
mocker.patch(
|
||||
target=f"{path_controller}.{mocked_func}",
|
||||
new=mock,
|
||||
)
|
||||
|
||||
controller = tools_controller.ToolsController(queue=None)
|
||||
getattr(controller, tested_func)(other_args)
|
||||
|
||||
if called_args or called_kwargs:
|
||||
mock.assert_called_once_with(*called_args, **called_kwargs)
|
||||
else:
|
||||
mock.assert_called_once()
|
||||
else:
|
||||
controller = tools_controller.ToolsController(queue=None)
|
||||
getattr(controller, tested_func)(other_args)
|
||||
@@ -0,0 +1,33 @@
|
||||
# IMPORTATION STANDARD
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
import pytest
|
||||
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.cryptocurrency.tools import tools_model
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
@pytest.mark.parametrize(
|
||||
"apr,compound_times",
|
||||
[(100, 12)],
|
||||
)
|
||||
def test_calculate_apy(apr, compound_times, recorder):
|
||||
df, _ = tools_model.calculate_apy(apr=apr, compounding_times=compound_times)
|
||||
recorder.capture(df)
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
@pytest.mark.parametrize(
|
||||
"price_changeA,price_changeB,proportion,initial_pool_value", [(100, 200, 50, 1000)]
|
||||
)
|
||||
def test_calculate_il(
|
||||
price_changeA, price_changeB, proportion, initial_pool_value, recorder
|
||||
):
|
||||
df, _ = tools_model.calculate_il(
|
||||
price_changeA=price_changeA,
|
||||
price_changeB=price_changeB,
|
||||
proportion=proportion,
|
||||
initial_pool_value=initial_pool_value,
|
||||
)
|
||||
recorder.capture(df)
|
||||
@@ -0,0 +1,37 @@
|
||||
# IMPORTATION STANDARD
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
import pytest
|
||||
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.cryptocurrency.tools import tools_view
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
@pytest.mark.record_stdout
|
||||
def test_display_il(mocker):
|
||||
# MOCK EXPORT_DATA
|
||||
mocker.patch(target="openbb_terminal.cryptocurrency.tools.tools_view.export_data")
|
||||
|
||||
tools_view.display_il(
|
||||
price_changeA=100,
|
||||
price_changeB=200,
|
||||
proportion=50,
|
||||
initial_pool_value=1000,
|
||||
narrative=False,
|
||||
export="",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
@pytest.mark.record_stdout
|
||||
def test_display_apy(mocker):
|
||||
# MOCK EXPORT_DATA
|
||||
mocker.patch(target="openbb_terminal.cryptocurrency.tools.tools_view.export_data")
|
||||
|
||||
tools_view.display_apy(
|
||||
apr=100,
|
||||
compounding_times=12,
|
||||
narrative=False,
|
||||
export="",
|
||||
)
|
||||
4
tests/openbb_terminal/cryptocurrency/tools/txt/test_tools_controller/test_print_help.txt
vendored
Normal file
4
tests/openbb_terminal/cryptocurrency/tools/txt/test_tools_controller/test_print_help.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
aprtoapy convert apr to apy
|
||||
il calculate impermanent loss
|
||||
|
||||
5
tests/openbb_terminal/cryptocurrency/tools/txt/test_tools_view/test_display_apy.txt
vendored
Normal file
5
tests/openbb_terminal/cryptocurrency/tools/txt/test_tools_view/test_display_apy.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Metric Value
|
||||
0 APR 100%
|
||||
1 Compounding Times 12
|
||||
2 APY 161.304%
|
||||
|
||||
9
tests/openbb_terminal/cryptocurrency/tools/txt/test_tools_view/test_display_il.txt
vendored
Normal file
9
tests/openbb_terminal/cryptocurrency/tools/txt/test_tools_view/test_display_il.txt
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Metric Value
|
||||
0 Price Change Token A 100%
|
||||
1 Price Change Token B 200%
|
||||
2 Initial Pool Value $1000
|
||||
3 Proportion 50/50
|
||||
4 Impermanent Loss 2.02%
|
||||
5 Hold Value $2500.0
|
||||
6 Pool Value $2449.49
|
||||
|
||||
@@ -13,6 +13,7 @@ Source:
|
||||
> ov overview of the cryptocurrencies, e.g.: market cap, DeFi, latest news, top exchanges, stables
|
||||
> onchain information on different blockchains, e.g.: eth gas fees, whale alerts, DEXes info
|
||||
> defi decentralized finance information, e.g.: dpi, llama, tvl, lending, borrow, funding
|
||||
> tools explore different tools e.g.: apytoapr, il
|
||||
> nft non-fungible tokens, e.g.: today drops[unvl]
|
||||
> dd due-diligence for loaded coin, e.g.: coin information, social media, market stats
|
||||
> ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv
|
||||
|
||||
5
website/content/terminal/cryptocurrency/tools/_index.md
vendored
Normal file
5
website/content/terminal/cryptocurrency/tools/_index.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
geekdocCollapseSection: true
|
||||
---
|
||||
|
||||
{{< toc-tree >}}
|
||||
13
website/content/terminal/cryptocurrency/tools/aprtoapy/_index.md
vendored
Normal file
13
website/content/terminal/cryptocurrency/tools/aprtoapy/_index.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
`usage: aprtoapy [--apr APR] [-c COMPOUNDING] [-n] [-h] [--export EXPORT]`
|
||||
|
||||
Tool to calculate APY from APR value. Compouding periods, i.e., the number of times compounded per year can be defined with -c argument.
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
--apr APR APR value in percentage to convert (default: 100)
|
||||
-c COMPOUNDING, --compounding COMPOUNDING
|
||||
Number of compounded periods in a year. 12 means compounding monthly (default: 12)
|
||||
-n, --narrative Flag to show narra`ive instead of dataframe (default: False)
|
||||
-h, --help show this help message (default: False)
|
||||
--export EXPORT Export raw data into csv, json, xlsx (default: )
|
||||
```
|
||||
20
website/content/terminal/cryptocurrency/tools/il/_index.md
vendored
Normal file
20
website/content/terminal/cryptocurrency/tools/il/_index.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
`usage: il [-pcA PRICECHANGEA] [-pcB PRICECHANGEB] [-p PROPORTION] [-v VALUE] [-n] [-h] [--export EXPORT`
|
||||
|
||||
Tool to calculate Impermanent Loss in a custom liquidity pool. Users can provide percentages increases for two tokens (and their weight in
|
||||
the liquidity pool) and verify the impermanent loss that can occur.
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-pcA PRICECHANGEA, --priceChangeA PRICECHANGEA
|
||||
Token A price change in percentage (default: 0)
|
||||
-pcB PRICECHANGEB, --priceChangeB PRICECHANGEB
|
||||
Token B price change in percentage (default: 100)
|
||||
-p PROPORTION, --proportion PROPORTION
|
||||
Pool proportion. E.g., 50 means that pool contains 50% of token A and 50% of token B, 30 means that pool contains
|
||||
30% of token A and 70% of token B (default: 50)
|
||||
-v VALUE, --value VALUE
|
||||
Initial amount of dollars that user provides to liquidity pool (default: 1000)
|
||||
-n, --narrative Flag to show narrative instead of dataframe (default: False)
|
||||
-h, --help show this help message (default: False)
|
||||
--export EXPORT Export raw data into csv, json, xlsx (default: )
|
||||
```
|
||||
7
website/data/menu/main.yml
vendored
7
website/data/menu/main.yml
vendored
@@ -666,6 +666,13 @@ main:
|
||||
ref: "/terminal/cryptocurrency/discovery/cpsearch"
|
||||
- name: cmctop
|
||||
ref: "/terminal/cryptocurrency/discovery/cmctop"
|
||||
- name: tools
|
||||
ref: "/terminal/cryptocurrency/tools"
|
||||
sub:
|
||||
- name: aprtoapy
|
||||
ref: "/terminal/cryptocurrency/tools/aprtoapy"
|
||||
- name: il
|
||||
ref: "/terminal/cryptocurrency/tools/il"
|
||||
- name: overview
|
||||
ref: "/terminal/cryptocurrency/overview"
|
||||
sub:
|
||||
|
||||
Reference in New Issue
Block a user