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

Add API for yfinance #16

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist/
dist/
.benchmarks/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 1.2.0 (2024-03-25)

- Add API to call yfinance to receive stock information and future options chain

## 1.1.0 (2024-03-24)

- Refactor the engine's `run` method for readability.
Expand Down
9 changes: 8 additions & 1 deletion optionlab/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typing


VERSION = "1.1.0"
VERSION = "1.2.0"


if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -46,6 +46,7 @@
get_profit_range,
get_pop,
)
from .api import get_options_chain, get_stock_history

__version__ = VERSION
__all__ = (
Expand Down Expand Up @@ -87,6 +88,9 @@
"get_theta",
# plot
"plot_pl",
# api
"get_options_chain",
"get_stock_history",
)

# A mapping of {<member name>: (package, <module name>)} defining dynamic imports
Expand Down Expand Up @@ -129,6 +133,9 @@
"get_theta": (__package__, ".black_scholes"),
# plot
"plot_pl": (__package__, ".plot"),
# api
"get_options_chain": (__package__, ".api"),
"get_stock_history": (__package__, ".api"),
}


Expand Down
21 changes: 21 additions & 0 deletions optionlab/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import datetime as dt

import pandas as pd
from yfinance import Ticker

from optionlab.models import OptionsChain


def get_options_chain(ticker: str, expiration_date: dt.date) -> OptionsChain:
stock = Ticker(ticker)
res = stock.option_chain(expiration_date.strftime("%Y-%m-%d"))
return OptionsChain(
calls=res.calls,
puts=res.puts,
underlying=res.underlying,
)


def get_stock_history(ticker: str, num_of_months: int) -> pd.DataFrame:
stock = Ticker(ticker)
return stock.history(period=f"{num_of_months}mo")
107 changes: 107 additions & 0 deletions optionlab/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from typing import Literal

import numpy as np
import pandas as pd
from humps import decamelize
from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict

OptionType = Literal["call", "put"]
Expand Down Expand Up @@ -357,3 +359,108 @@ def return_in_the_domain_ratio(self) -> float:
return abs(
self.maximum_return_in_the_domain / self.minimum_return_in_the_domain
)


class UnderlyingAsset(BaseModel):
symbol: str
region: str
quote_type: Literal["EQUITY"]
quote_source_name: Literal["Delayed Quote", "Nasdaq Real Time Price"]
triggerable: bool
currency: Literal["USD"]
market_state: Literal["CLOSED", "OPEN", "POST"]
regular_market_change_percent: float
regular_market_price: float
exchange: str
short_name: str
long_name: str
exchange_timezone_name: str
exchange_timezone_short_name: str
gmt_off_set_milliseconds: int
market: Literal["us_market"]
esg_populated: bool
first_trade_date_milliseconds: int
post_market_change_percent: float
post_market_time: int
post_market_price: float
post_market_change: float
regular_market_change: float
regular_market_time: int
regular_market_day_high: float
regular_market_day_range: tuple[float, float]
regular_market_day_low: float
regular_market_volume: float
regular_market_previous_close: float
bid: float
ask: float
bid_size: int
ask_size: int
full_exchange_name: str
financial_currency: Literal["USD"]
regular_market_open: float
average_daily_volume3_month: int
average_daily_volume10_day: int
fifty_two_week_low_change: float
fifty_two_week_low_change_percent: float
fifty_two_week_range: tuple[float, float]
fifty_two_week_high_change: float
fifty_two_week_high_change_percent: float
fifty_two_week_low: float
fifty_two_week_high: float
fifty_two_week_change_percent: float
dividend_date: int
earnings_timestamp: int
earnings_timestamp_start: int
earnings_timestamp_end: int
trailing_annual_dividend_rate: float
trailing_pe: float
dividend_rate: float
trailing_annual_dividend_yield: float
dividend_yield: float
eps_trailing_twelve_months: float
eps_forward: float
eps_current_year: float
price_eps_current_year: float
shares_outstanding: int
book_value: float
fifty_day_average: float
fifty_day_average_change: float
fifty_day_average_change_percent: float
two_hundred_day_average: float
two_hundred_day_average_change: float
two_hundred_day_average_change_percent: float
market_cap: int
forward_pe: float
price_to_book: float
source_interval: int
exchange_data_delayed_by: int
average_analyst_rating: str
tradeable: bool
crypto_tradeable: bool
display_name: str


class OptionsChain(BaseModel):
calls: pd.DataFrame
puts: pd.DataFrame
underlying: UnderlyingAsset
model_config = ConfigDict(arbitrary_types_allowed=True)

@field_validator("underlying", mode="before")
@classmethod
def validate_underlying(cls, v: dict) -> UnderlyingAsset:
day_range_split = v["regularMarketDayRange"].split(" - ")
fifty_two_week_range_split = v["fiftyTwoWeekRange"].split(" - ")
return UnderlyingAsset.model_validate(
decamelize(v)
| {
"regular_market_day_range": (
float(day_range_split[0]),
float(day_range_split[1]),
),
"fifty_two_week_range": (
float(fifty_two_week_range_split[0]),
float(fifty_two_week_range_split[1]),
),
}
)
10 changes: 10 additions & 0 deletions optionlab/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,13 @@ def get_nonbusiness_days(
nonbusiness_days += 1

return nonbusiness_days


def get_fridays_date(weeks_until: int = 0) -> dt.date:

current_date = dt.datetime.now()

days_until_friday = 4 - current_date.weekday() + 7
datetime = current_date + dt.timedelta(days=days_until_friday + weeks_until * 7)

return datetime.date()
Loading
Loading