Skip to content

Commit 0ce0cd2

Browse files
authored
V2.1.7 (#58)
* update pypi * adding tests * added cache for yfinance, tweaked news and recommendations * added cache for yfinance, tweaked news and recommendations
1 parent b2402ed commit 0ce0cd2

File tree

10 files changed

+346
-69
lines changed

10 files changed

+346
-69
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ MANIFEST
3737
pip-log.txt
3838
pip-delete-this-directory.txt
3939

40+
yfinance.cache
41+
4042
# Unit test / coverage reports
4143
htmlcov/
4244
.tox/

poetry.lock

Lines changed: 101 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "py-alpaca-api"
3-
version = "2.1.6"
3+
version = "2.1.7"
44
description = "Python package, for communicating with Alpaca Markets REST API."
55
authors = ["TexasCoding <[email protected]>"]
66
readme = "README.md"
@@ -16,7 +16,10 @@ pendulum = "^3.0.0"
1616
pandas = "^2.2.2"
1717
prophet = "^1.1.5"
1818
beautifulsoup4 = "^4.12.3"
19-
yfinance = "^0.2.40"
19+
numpy = "^1.26.0"
20+
yfinance = {extras = ["optional"], version = "^0.2.40"}
21+
requests-cache = "^1.2.0"
22+
requests-ratelimiter = "^0.6.0"
2023

2124

2225
[tool.poetry.group.dev.dependencies]

src/py_alpaca_api/stock/assets.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,15 @@ def get_all(
4747
excluded_exchanges: List[str] = ["OTC"],
4848
) -> pd.DataFrame:
4949
"""
50-
Retrieves a DataFrame of all active, fractionable, and tradable US equity assets, excluding those from the OTC exchange.
50+
Retrieves a DataFrame of all active, fractionable, and tradable US equity assets, excluding those from the
51+
OTC exchange.
5152
5253
Args:
5354
status (str, optional): The status of the assets to retrieve. Defaults to "active".
54-
exchange (str, optional): The exchange to filter the assets by. Defaults to an empty string, which retrieves assets from all exchanges.
55-
excluded_exchanges (List[str], optional): A list of exchanges to exclude from the results. Defaults to ["OTC"].
55+
exchange (str, optional): The exchange to filter the assets by. Defaults to an empty string,
56+
which retrieves assets from all exchanges.
57+
excluded_exchanges (List[str], optional): A list of exchanges to exclude from the results.
58+
Defaults to ["OTC"].
5659
5760
Returns:
5861
pd.DataFrame: A DataFrame containing the retrieved assets.
@@ -68,9 +71,26 @@ def get_all(
6871

6972
assets_df = assets_df[
7073
(assets_df["status"] == "active")
71-
& (assets_df["fractionable"])
72-
& (assets_df["tradable"])
73-
& (~assets_df["exchange"].isin(excluded_exchanges))
74-
]
75-
assets_df.reset_index(drop=True, inplace=True)
74+
& assets_df["fractionable"]
75+
& assets_df["tradable"]
76+
& ~assets_df["exchange"].isin(excluded_exchanges)
77+
].reset_index(drop=True)
78+
79+
assets_df = assets_df.astype(
80+
{
81+
"id": "string",
82+
"class": "string",
83+
"exchange": "string",
84+
"symbol": "string",
85+
"name": "string",
86+
"status": "string",
87+
"tradable": "bool",
88+
"marginable": "bool",
89+
"shortable": "bool",
90+
"easy_to_borrow": "bool",
91+
"fractionable": "bool",
92+
"maintenance_margin_requirement": "float",
93+
}
94+
)
95+
7696
return assets_df

src/py_alpaca_api/stock/history.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
class History:
1313
def __init__(self, data_url: str, headers: Dict[str, str], asset: Assets) -> None:
1414
"""
15+
Initializes an instance of the History class.
16+
1517
Args:
1618
data_url: A string representing the URL of the data.
1719
headers: A dictionary containing the headers to be included in the request.
1820
asset: An instance of the Asset class representing the asset.
19-
2021
"""
2122
self.data_url = data_url
2223
self.headers = headers
@@ -26,23 +27,25 @@ def __init__(self, data_url: str, headers: Dict[str, str], asset: Assets) -> Non
2627
# /////// Check if Asset is Stock \\\\\\\ #
2728
###########################################
2829
def check_if_stock(self, symbol: str) -> AssetModel:
29-
"""Check if asset is stock
30+
"""Check if the asset corresponding to the symbol is a stock.
31+
3032
Args:
31-
symbol: The symbol of the asset to be checked.
33+
symbol (str): The symbol of the asset to be checked.
3234
3335
Returns:
3436
AssetModel: The asset information for the given symbol.
3537
3638
Raises:
3739
ValueError: If there is an error getting the asset information or if the asset is not a stock.
3840
"""
39-
4041
try:
4142
asset = self.asset.get(symbol)
4243
except Exception as e:
43-
raise ValueError(e)
44+
raise ValueError(str(e))
45+
4446
if asset.asset_class != "us_equity":
4547
raise ValueError(f"{symbol} is not a stock.")
48+
4649
return asset
4750

4851
###########################################
@@ -61,12 +64,13 @@ def get_stock_data(
6164
adjustment: str = "raw",
6265
) -> pd.DataFrame:
6366
"""
67+
Retrieves historical stock data for a given symbol within a specified date range and timeframe.
68+
6469
Args:
6570
symbol: The stock symbol to fetch data for.
6671
start: The start date for historical data in the format "YYYY-MM-DD".
6772
end: The end date for historical data in the format "YYYY-MM-DD".
6873
timeframe: The timeframe for the historical data. Default is "1d".
69-
Possible values are "1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", or "1M".
7074
feed: The data feed source. Default is "sip".
7175
currency: The currency for historical data. Default is "USD".
7276
limit: The number of data points to fetch. Default is 1000.
@@ -100,17 +104,15 @@ def get_stock_data(
100104
'Invalid timeframe. Must be "1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", or "1M"'
101105
)
102106

103-
params = {
104-
"timeframe": timeframe_mapping[
105-
timeframe
106-
], # Timeframe for historical data, default: 1d
107-
"start": start, # Start date for historical data
108-
"end": end, # End date for historical data
109-
"currency": currency, # Currency for historical data, default: USD
110-
"limit": limit, # Limit number of data points, default: 1000
111-
"adjustment": adjustment, # Adjustment for historical data, default: raw
112-
"feed": feed, # Data feed source, default: iex
113-
"sort": sort, # Sort order, default: asc
107+
params: dict = {
108+
"timeframe": timeframe_mapping[timeframe],
109+
"start": start,
110+
"end": end,
111+
"currency": currency,
112+
"limit": limit,
113+
"adjustment": adjustment,
114+
"feed": feed,
115+
"sort": sort,
114116
}
115117
symbol_data = self.get_historical_data(symbol, url, params)
116118
bar_data_df = self.preprocess_data(symbol_data, symbol)

src/py_alpaca_api/stock/predictor.py

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ def generate_forecast(model, future_periods=14):
9494
.iloc[0]
9595
.yhat
9696
)
97-
9897
return round(two_week_forecast, 2)
9998

10099
def get_losers_to_gainers(
@@ -103,42 +102,36 @@ def get_losers_to_gainers(
103102
losers_to_scan: int = 200,
104103
future_periods: int = 5,
105104
) -> list:
106-
"""
107-
Predicts future gainers based on the previous day's losers using Prophet forecasting.
108-
109-
Args:
110-
gain_ratio: The minimum gain ratio required for a stock to be considered a future gainer.
111-
losers_to_scan: The number of previous day's losers to scan.
112-
future_periods: The number of future periods to forecast.
113-
114-
Returns:
115-
A list of future gainers.
116-
117-
Raises:
118-
Exception: If there is an error while predicting future gainers for a stock.
119-
"""
120105
previous_day_losers = self.screener.losers(total_losers_returned=losers_to_scan)
121106
losers_list = previous_day_losers["symbol"].tolist()
122-
123107
future_gainers = []
124-
125-
for i, ticker in enumerate(losers_list):
126-
try:
127-
symbol_data = self.get_stock_data(ticker)
128-
symbol_model = self.train_prophet_model(symbol_data)
129-
symbol_forecast = self.generate_forecast(
130-
symbol_model, future_periods=future_periods
131-
)
132-
previous_price = previous_day_losers[
133-
previous_day_losers["symbol"] == ticker
134-
].iloc[0]["price"]
135-
gain_prediction = round(
136-
((symbol_forecast - previous_price) / previous_price) * 100, 2
137-
)
138-
if gain_prediction >= gain_ratio:
139-
future_gainers.append(ticker)
140-
except Exception as e:
141-
logger.error(f"Error predicting {ticker}: {e}")
142-
continue
108+
for _, ticker in enumerate(losers_list):
109+
is_gainer, gainer = self._handle_ticker(
110+
ticker, gain_ratio, future_periods, previous_day_losers
111+
)
112+
if is_gainer:
113+
future_gainers.append(gainer)
143114
print(f"Predicted [bold]{len(future_gainers)}[/bold] future gainers.")
144115
return future_gainers
116+
117+
@staticmethod
118+
def _get_gain_prediction(symbol_forecast, previous_price):
119+
return round(((symbol_forecast - previous_price) / previous_price) * 100, 2)
120+
121+
def _handle_ticker(self, ticker, gain_ratio, future_periods, previous_day_losers):
122+
try:
123+
symbol_data = self.get_stock_data(ticker)
124+
symbol_model = self.train_prophet_model(symbol_data)
125+
symbol_forecast = self.generate_forecast(
126+
symbol_model, future_periods=future_periods
127+
)
128+
previous_price = previous_day_losers[
129+
previous_day_losers["symbol"] == ticker
130+
].iloc[0]["price"]
131+
gain_prediction = self._get_gain_prediction(symbol_forecast, previous_price)
132+
if gain_prediction >= gain_ratio:
133+
return True, ticker
134+
except Exception as e:
135+
logger.error(f"Error predicting {ticker}: {e}")
136+
137+
return False, None

0 commit comments

Comments
 (0)