Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge with develop #5551

Merged
merged 1 commit into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
restore-keys: ${{ runner.os }}-linting-${{ hashFiles('**/poetry.lock') }}

- run: |
pip install bandit black codespell mypy==1.5.1 pylint==2.17.0 ruff==0.0.285
pip install bandit black codespell==2.2.5 mypy==1.5.1 pylint==2.17.0 ruff==0.0.285
pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil types-setuptools types-six
- run: bandit -x ./tests -r . || true
- run: black --diff --check .
Expand Down
2 changes: 1 addition & 1 deletion build/docker/compose.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
OPENBBTERMINAL_DOCKER_REGISTRY="ghcr.io"
OPENBBTERMINAL_DOCKER_RELEASE_VERSION="3.2.2"
OPENBBTERMINAL_DOCKER_RELEASE_VERSION="3.2.3"
8 changes: 4 additions & 4 deletions build/nsis/setup.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
!define NAME "OpenBB Terminal"
!define COMPANY "OpenBB"
!define APPFILE "OpenBBTerminal.exe"
!define VERSION "3.2.2"
!define VERSION "3.2.3"
!define SLUG "${NAME} v${VERSION}"

;--------------------------------
; Info for Installer.exe
VIProductVersion 3.2.0.0
VIProductVersion 3.2.3.0
VIAddVersionKey ProductName "OpenBB Terminal"
VIAddVersionKey Comments "An installer for OpenBB Terminal. For additional details, visit OpenBB.co"
VIAddVersionKey CompanyName OpenBB.co
VIAddVersionKey FileDescription "OpenBB Terminal Program"
VIAddVersionKey FileVersion 3.2.2.0
VIAddVersionKey ProductVersion 3.2.2.0
VIAddVersionKey FileVersion 3.2.3.0
VIAddVersionKey ProductVersion 3.2.3.0
VIAddVersionKey InternalName "OpenBB Terminal"

;--------------------------------
Expand Down
1 change: 1 addition & 0 deletions build/pyinstaller/macOS/build-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ signFiles() {
echo "Code Sign OpenBB Executable File"
codesign --deep --force --verify --verbose --options runtime --entitlements "$ENTITLEMENTS" -s "$APPLE_SIGNING_IDENTITY" build/pyinstaller/macOS/target/darwinpkg/Applications/OpenBB\ Terminal/.OpenBB/OpenBBTerminal
codesign --deep --force --verify --verbose --options runtime --entitlements "$ENTITLEMENTS" -s "$APPLE_SIGNING_IDENTITY" build/pyinstaller/macOS/target/darwinpkg/Applications/OpenBB\ Terminal/.OpenBB/OpenBBPlotsBackend
codesign --deep --force --verify --verbose --options runtime --entitlements "$ENTITLEMENTS" -s "$APPLE_SIGNING_IDENTITY" build/pyinstaller/macOS/target/darwinpkg/Applications/OpenBB\ Terminal/.OpenBB/pywry
}

createInstallationDirectory() {
Expand Down
8 changes: 4 additions & 4 deletions build/pyinstaller/version.rc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(3, 2, 2, 0),
prodvers=(3, 2, 2, 0),
filevers=(3, 2, 3, 0),
prodvers=(3, 2, 3, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x0,
# Contains a bitmask that specifies the Boolean attributes of the file.
Expand All @@ -32,10 +32,10 @@ VSVersionInfo(
[StringStruct('Comments', 'The OpenBB Terminal. For additional details, visit OpenBB.co'),
StringStruct('CompanyName', 'OpenBB'),
StringStruct('FileDescription', 'OpenBB Terminal Program'),
StringStruct('FileVersion', '3.2.2.0'),
StringStruct('FileVersion', '3.2.3.0'),
StringStruct('InternalName', 'OpenBB Terminal'),
StringStruct('ProductName', 'OpenBB Terminal'),
StringStruct('ProductVersion', '3.2.2.0')])
StringStruct('ProductVersion', '3.2.3.0')])
]),
VarFileInfo([VarStruct('Translation', [1033, 1200])])
]
Expand Down
2 changes: 1 addition & 1 deletion openbb_terminal/core/models/system_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class SystemModel(BaseModel):
PLATFORM: str = str(platform.platform())

# OpenBB section
VERSION: str = "3.2.2"
VERSION: str = "3.2.3"

# Logging section
LOGGING_APP_ID: str = "REPLACE_ME"
Expand Down
59 changes: 59 additions & 0 deletions openbb_terminal/etf/etf_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class ETFController(BaseController):
"load",
"overview",
"holdings",
"holding_perf",
"news",
"candle",
"weights",
Expand Down Expand Up @@ -106,6 +107,7 @@ def print_help(self):
mt.add_raw("\n")
mt.add_cmd("overview", self.etf_name)
mt.add_cmd("holdings", self.etf_name)
mt.add_cmd("holding_perf", self.etf_name)
mt.add_cmd("weights", self.etf_name)
mt.add_cmd("news", self.etf_name)
mt.add_cmd("candle", self.etf_name)
Expand Down Expand Up @@ -638,3 +640,60 @@ def call_compare(self, other_args):
if ns_parser.sheet_name
else None,
)

@log_start_end(log=logger)
def call_holding_perf(self, other_args: List[str]):
"""Process holdings performance command"""

parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="holding_perf",
description="Look at ETF company holdings' performance",
)
parser.add_argument(
"-s",
"--start-date",
type=valid_date,
default=(datetime.now().date() - timedelta(days=366)),
dest="start",
help="The starting date (format YYYY-MM-DD) to get each holding's price",
)
parser.add_argument(
"-e",
"--end-date",
type=valid_date,
default=datetime.now().date(),
dest="end",
help="The ending date (format YYYY-MM-DD) to get each holding's price",
)
parser.add_argument(
"-l",
"--limit",
type=check_positive,
dest="limit",
help="Number of holdings to get",
default=20,
)
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-l")

ns_parser = self.parse_known_args_and_warn(
parser,
other_args,
export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
raw=True,
)
if ns_parser:
if self.etf_name:
fmp_view.view_etf_holdings_performance(
ticker=self.etf_name,
start_date=ns_parser.start,
end_date=ns_parser.end,
limit=ns_parser.limit,
export=ns_parser.export,
sheet_name=ns_parser.sheet_name,
raw=ns_parser.raw,
)
else:
console.print("Please load a ticker using <load name>. \n")
164 changes: 161 additions & 3 deletions openbb_terminal/etf/fmp_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

import json
import logging
from typing import Dict
from typing import Any, Dict, List
from urllib.error import HTTPError
from urllib.request import urlopen

import pandas as pd

from openbb_terminal.core.session.current_user import get_current_user
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console
from openbb_terminal.decorators import check_api_key, log_start_end
from openbb_terminal.helper_funcs import request
from openbb_terminal.rich_config import console, optional_rich_track

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -47,3 +50,158 @@ def get_etf_sector_weightings(name: str) -> Dict:
raise ValueError(data["Error Message"])

return data


@log_start_end(log=logger)
@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
def get_stock_price_change(
tickers: List[str], start_date: str, end_date: str
) -> Dict[str, float]:
"""Get stock's price percent change over specified time period.

Parameters
----------
tickers : List[str]
Ticker(s) to get information for.
start: str
Date from which data is fetched in format YYYY-MM-DD
end: str
Date from which data is fetched in format YYYY-MM-DD

Returns
-------
Dict[str, float]
Percent change of closing price over time period, or dictionary of ticker, change pairs.
"""
tickers_tracker = optional_rich_track(
tickers, False, "Gathering stock prices", len(tickers)
)
current_user = get_current_user()
data_aggregate = dict()

for tick in tickers_tracker:
tickers_req = str(tick) + ","
for _ in range(4):
try:
_tick = next(tickers_tracker)
tickers_req += _tick + ","
except StopIteration:
break

url = f"""https://financialmodelingprep.com/api/v3/historical-price-full/{tickers_req}?\
from={start_date}&to={end_date}&serietype=line\
&apikey={current_user.credentials.API_KEY_FINANCIALMODELINGPREP}"""

response = request(url)
if response.status_code != 200 or "Error Message" in response.json():
message = f"Error, Status Code: {response.status_code}."
message = (
message
if "Error Message" not in response.json()
else message + "\n" + response.json()["Error Message"] + ".\n"
)
console.print(message)
return dict()

data = response.json()
stock_list = data
if "historicalStockList" in data:
stock_list = data["historicalStockList"]

for stock in stock_list:
close_end = stock["historical"][0]["close"]
close_start = stock["historical"][-1]["close"]
pct_change = 100 * (close_end - close_start) / close_start
data_aggregate[stock["symbol"]] = pct_change

return data_aggregate


@log_start_end(log=logger)
@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
def get_etf_holdings(ticker: str, limit: int = 10) -> List[Dict[str, Any]]:
"""This endpoint returns all stocks held by a specific ETF.

Parameters
----------
ticker : str
ETF ticker.
limit: int
Limit amount of stocks to return. FMP returns data
by descending weighting.

Returns
-------
List[Dict[str,any]]
Info for stock holdings in the ETF.
"""

current_user = get_current_user()
url = f"""https://financialmodelingprep.com/api/v3/etf-holder/{ticker}\
?apikey={current_user.credentials.API_KEY_FINANCIALMODELINGPREP}"""
response = request(url)
if response.status_code != 200 or "Error Message" in response.json():
message = f"Error, Status Code: {response.status_code}."
message = (
message
if "Error Message" not in response.json()
else message + "\n" + response.json()["Error Message"] + ".\n"
)
console.print(message)
return []

return response.json()[0:limit]


@log_start_end(log=logger)
@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
def get_holdings_pct_change(
ticker: str,
start_date: str,
end_date: str,
limit: int = 10,
) -> pd.DataFrame:
"""Calculate percent change for each holding in ETF.

Parameters
----------
ticker : str
ETF ticker.
limit: int
Limit amount of stocks to return. FMP returns data
by descending weighting.

Returns
-------
pd.DataFrame
Calculated percentage change for each stock in the ETF, in descending order.
"""

df = pd.DataFrame(columns=["Ticker", "Name", "Percent Change"], data=[])
holdings = get_etf_holdings(ticker, limit)
tickers = []
for stock in holdings:
tickers.append(stock.get("asset", " "))

pct_changes = get_stock_price_change(tickers, start_date, end_date)

for stock in holdings:
pct_change = pct_changes.get(stock["asset"], 0)
if pct_change == 0:
console.print(
f"""Percent change not found for: {stock["asset"]}: {stock["name"]}"""
)
new_df = pd.DataFrame(
{
"Ticker": stock["asset"],
"Name": stock["name"],
"Percent Change": pct_changes.get(stock["asset"], 0),
},
index=[0],
)

df = pd.concat([df, new_df], ignore_index=True)

sorted_df = df.sort_values(by="Percent Change", ascending=False, inplace=False)

return sorted_df
Loading
Loading