bbstrader 0.2.94__py3-none-any.whl → 0.2.96__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bbstrader might be problematic. Click here for more details.

bbstrader/__main__.py CHANGED
@@ -6,7 +6,7 @@ from colorama import Fore
6
6
 
7
7
  from bbstrader.btengine.scripts import backtest
8
8
  from bbstrader.metatrader.scripts import copy_trades
9
- from bbstrader.trading.script import execute_strategy
9
+ from bbstrader.trading.scripts import execute_strategy
10
10
 
11
11
  DESCRIPTION = "BBSTRADER"
12
12
  USAGE_TEXT = """
@@ -36,7 +36,7 @@ def main():
36
36
  parser.add_argument("--run", type=str, nargs="?", default=None, help="Run a module")
37
37
  args, unknown = parser.parse_known_args()
38
38
  if ("-h" in sys.argv or "--help" in sys.argv) and args.run is None:
39
- print(USAGE_TEXT)
39
+ print(Fore.WHITE + USAGE_TEXT)
40
40
  sys.exit(0)
41
41
  if args.run == "copier":
42
42
  copy_trades(unknown)
@@ -0,0 +1,2 @@
1
+ from bbstrader.core.data import * # noqa: F403
2
+ from bbstrader.core.utils import * # noqa: F403
bbstrader/core/data.py CHANGED
@@ -1,11 +1,429 @@
1
+ import json
2
+ import re
3
+ import ssl
4
+ from datetime import datetime
5
+ from typing import List
6
+ from urllib.request import urlopen
7
+
8
+ import certifi
9
+ import pandas as pd
10
+ import praw
11
+ import requests
12
+ import tweepy
13
+ import yfinance as yf
14
+ from bs4 import BeautifulSoup
1
15
  from financetoolkit import Toolkit
2
16
 
3
- __all__ = [
4
- 'FMP',
5
- ]
17
+ __all__ = ["FmpData", "FmpNews", "FinancialNews"]
18
+
19
+
20
+ def _get_search_query(query: str) -> str:
21
+ if " " in query:
22
+ return query
23
+ try:
24
+ name = yf.Ticker(query).info["shortName"]
25
+ return query + " " + name
26
+ except Exception:
27
+ return query
28
+
29
+
30
+ def _find_news(query: str | List[str], text):
31
+ if isinstance(query, str):
32
+ query = query.split(" ")
33
+ pattern = r"\b(?:" + "|".join(map(re.escape, query)) + r")\b"
34
+ if re.search(pattern, text, re.IGNORECASE):
35
+ return True
36
+ return False
37
+
38
+
39
+ def _filter_news(news: List[str], query: str | List[str]) -> List[str]:
40
+ return [text for text in news if _find_news(query, text)]
41
+
42
+
43
+ class FmpNews(object):
44
+ """
45
+ ``FmpNews`` is responsible for retrieving financial news, press releases, and articles from Financial Modeling Prep (FMP).
46
+
47
+ ``FmpNews`` provides methods to fetch the latest stock, crypto, forex, and general financial news,
48
+ as well as financial articles and press releases.
49
+ """
50
+
51
+ def __init__(self, api: str):
52
+ """
53
+ Args:
54
+ api (str): The API key for accessing FMP's news data.
55
+
56
+ Example:
57
+ fmp_news = FmpNews(api="your_api_key_here")
58
+ """
59
+ if api is None:
60
+ raise ValueError("API key is required For FmpNews")
61
+ self.__api = api
62
+
63
+ def _jsonparsed_data(self, url):
64
+ context = ssl.create_default_context(cafile=certifi.where())
65
+ with urlopen(url, context=context) as response:
66
+ data = response.read().decode("utf-8")
67
+ return json.loads(data)
68
+
69
+ def _load_news(self, news_type, symbol=None, **kwargs) -> List[dict]:
70
+ params = {"start": "from", "end": "to", "page": "page", "limit": "limit"}
71
+ base_url = f"https://financialmodelingprep.com/stable/news/{news_type}-latest?apikey={self.__api}"
72
+ if news_type == "articles":
73
+ assert symbol is None, ValueError("symbol not supported for articles")
74
+ base_url = f"https://financialmodelingprep.com/stable/fmp-articles?apikey={self.__api}"
75
+ elif symbol is not None:
76
+ base_url = f"https://financialmodelingprep.com/stable/news/{news_type}?symbols={symbol}&apikey={self.__api}"
77
+
78
+ for param, value in params.items():
79
+ if kwargs.get(param) is not None:
80
+ base_url += f"&{value.strip()}={kwargs.get(param)}"
81
+
82
+ return self._jsonparsed_data(base_url)
83
+
84
+ def get_articles(self, **kwargs) -> List[dict]:
85
+ def html_parser(content):
86
+ soup = BeautifulSoup(content, "html.parser")
87
+ text = soup.get_text(separator="\n")
88
+ return text.replace("\n", "")
89
+
90
+ articles = self._load_news("articles", **kwargs)
91
+ df = pd.DataFrame(articles)
92
+ df = df[["title", "date", "content", "tickers"]]
93
+ df["content"] = df["content"].apply(html_parser)
94
+ return df.to_dict(orient="records")
95
+
96
+ def get_releases(self, symbol=None, **kwargs):
97
+ return self._load_news("press-releases", symbol, **kwargs)
98
+
99
+ def get_stock_news(self, symbol=None, **kwargs):
100
+ return self._load_news("stock", symbol, **kwargs)
101
+
102
+ def get_crypto_news(self, symbol=None, **kwargs):
103
+ return self._load_news("crypto", symbol, **kwargs)
104
+
105
+ def get_forex_news(self, symbol=None, **kwargs):
106
+ return self._load_news("forex", symbol, **kwargs)
107
+
108
+ def _last_date(self, date):
109
+ return datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
110
+
111
+ def parse_news(self, news: List[dict], symbol=None, **kwargs) -> List[str]:
112
+ start = kwargs.get("start")
113
+ end = kwargs.get("end")
114
+ end_date = self._last_date(end) if end is not None else datetime.now().date()
115
+
116
+ def parse_record(record):
117
+ return " ".join(
118
+ [
119
+ record.pop("symbol", ""),
120
+ record.pop("title", ""),
121
+ record.pop("text", ""),
122
+ record.pop("content", ""),
123
+ record.pop("tickers", ""),
124
+ ]
125
+ )
126
+
127
+ parsed_news = []
128
+ for record in news:
129
+ date = record.get("publishedDate")
130
+ published_date = self._last_date(record.get("date", date)).date()
131
+ start_date = (
132
+ self._last_date(start).date() if start is not None else published_date
133
+ )
134
+ if published_date >= start_date and published_date <= end_date:
135
+ if symbol is not None:
136
+ if record.get("symbol", "") == symbol or symbol in record.get(
137
+ "tickers", ""
138
+ ):
139
+ parsed_news.append(parse_record(record))
140
+ else:
141
+ parsed_news.append(parse_record(record))
142
+ return parsed_news
143
+
144
+ def get_latest_articles(self, articles=None, save=False, **kwargs) -> List[dict]:
145
+ end = kwargs.get("end")
146
+ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
147
+ end_date = self._last_date(end) if end is not None else self._last_date(now)
148
+ if articles is None:
149
+ try:
150
+ articles = pd.read_csv("latest_fmp_articles.csv")
151
+ articles = articles.to_dict(orient="records")
152
+ if self._last_date(articles[0]["date"]) < end_date:
153
+ articles = self.get_articles(**kwargs)
154
+ else:
155
+ return articles
156
+ except FileNotFoundError:
157
+ articles = self.get_articles(**kwargs)
158
+
159
+ if save and len(articles) > 0:
160
+ df = pd.DataFrame(articles)
161
+ df.to_csv("latest_fmp_articles.csv", index=False)
162
+ return articles
163
+
164
+ def get_news(self, query, source="articles", articles=None, symbol=None, **kwargs):
165
+ """
166
+ Retrieves relevant financial news based on the specified source.
167
+
168
+ Args:
169
+ query (str): The search query or keyword for filtering news, may also be a ticker.
170
+ source (str, optional): The news source to retrieve from. Defaults to "articles".
171
+ Available options: "articles", "releases", "stock", "crypto", "forex".
172
+ articles (list, optional): List of pre-fetched articles to use when source="articles". Defaults to None.
173
+ symbol (str, optional): The financial asset symbol (e.g., "AAPL" for stocks, "BTC" for crypto). Defaults to None.
174
+ **kwargs (dict):
175
+ Additional arguments required for fetching news data. May include:
176
+ - start (str): The start period for news retrieval (YYY-MM-DD)
177
+ - end (str): The end period for news retrieval (YYY-MM-DD)
178
+ - page (int): The number of page to load for each news
179
+ - limit (int): Maximum Responses per API Call
180
+
181
+ Returns:
182
+ list[dict]: A list of filtered news articles relevant to the query.
183
+ Returns an empty list if no relevant news is found.
184
+ """
185
+ query = _get_search_query(query)
186
+ source_methods = {
187
+ "articles": lambda: self.get_latest_articles(articles=articles, save=True),
188
+ "releases": lambda: self.get_releases(symbol=symbol, **kwargs),
189
+ "stock": lambda: self.get_stock_news(symbol=symbol, **kwargs),
190
+ "crypto": lambda: self.get_crypto_news(symbol=symbol, **kwargs),
191
+ "forex": lambda: self.get_forex_news(symbol=symbol, **kwargs),
192
+ }
193
+ news_source = source_methods.get(source, lambda: [])()
194
+ news = self.parse_news(news_source, symbol=symbol)
195
+ return _filter_news(news, query)
196
+
197
+
198
+ class FinancialNews(object):
199
+ """
200
+ The FinancialNews class provides methods to fetch financial news, articles, and discussions
201
+ from various sources such as Yahoo Finance, Google Finance, Reddit, and Twitter.
202
+ It also supports retrieving news using Financial Modeling Prep (FMP).
203
+
204
+ """
205
+
206
+ def _fetch_news(self, url, query, asset_type, n_news, headline_tag) -> List[str]:
207
+ headers = {"User-Agent": "Mozilla/5.0"}
208
+ try:
209
+ response = requests.get(url, headers=headers)
210
+ response.raise_for_status()
211
+ except requests.exceptions.RequestException:
212
+ response = None
213
+
214
+ if response is None or response.status_code != 200:
215
+ return []
216
+
217
+ query = _get_search_query(query)
218
+ soup = BeautifulSoup(response.text, "html.parser")
219
+
220
+ headlines = [
221
+ h.text.strip()
222
+ for h in soup.find_all(headline_tag)
223
+ if h.text and _find_news(query, h.text)
224
+ ]
225
+ return headlines[:n_news]
226
+
227
+ def get_yahoo_finance_news(self, query, asset_type="stock", n_news=10):
228
+ """
229
+ Fetches recent Yahoo Finance news headlines for a given financial asset.
230
+
231
+ Args:
232
+ query (str): The asset symbol or name (e.g., "AAPL").
233
+ asset_type (str, optional): The type of asset (e.g., "stock", "etf"). Defaults to "stock".
234
+ n_news (int, optional): The number of news headlines to return. Defaults to 10.
235
+
236
+ Returns:
237
+ list[str]: A list of Yahoo Finance news headlines relevant to the query.
238
+ """
239
+ url = (
240
+ f"https://finance.yahoo.com/quote/{query}/news"
241
+ if asset_type in ["stock", "etf"]
242
+ else "https://finance.yahoo.com/news"
243
+ )
244
+ return self._fetch_news(url, query, asset_type, n_news, "h3")
245
+
246
+ def get_google_finance_news(self, query, asset_type="stock", n_news=10):
247
+ """
248
+ Fetches recent Google Finance news headlines for a given financial asset.
249
+
250
+ Args:
251
+ query (str): The asset symbol or name (e.g., "AAPL").
252
+ asset_type (str, optional): The type of asset (e.g., "stock", "crypto"). Defaults to "stock".
253
+ n_news (int, optional): The number of news headlines to return. Defaults to 10.
254
+
255
+ Returns:
256
+ list[str]: A list of Google Finance news headlines relevant to the query.
257
+ """
258
+ search_terms = {
259
+ "stock": f"{query} stock OR {query} shares OR {query} market",
260
+ "etf": f"{query} ETF OR {query} fund OR {query} exchange-traded fund",
261
+ "futures": f"{query} futures OR {query} price OR {query} market",
262
+ "commodity": f"{query} price OR {query} futures OR {query} market",
263
+ "forex": f"{query} forex OR {query} exchange rate OR {query} market",
264
+ "crypto": f"{query} cryptocurrency OR {query} price OR {query} market",
265
+ "bond": f"{query} bond OR {query} yield OR {query} interest rate",
266
+ "index": f"{query} index OR {query} stock market OR {query} performance",
267
+ }
268
+ search_query = search_terms.get(asset_type, query)
269
+ url = f"https://news.google.com/search?q={search_query.replace(' ', '+')}"
270
+ return self._fetch_news(url, query, asset_type, n_news, "a")
271
+
272
+ def get_reddit_posts(
273
+ self,
274
+ symbol,
275
+ client_id=None,
276
+ client_secret=None,
277
+ user_agent=None,
278
+ asset_class="stock",
279
+ n_posts=10,
280
+ ) -> List[str]:
281
+ """
282
+ Fetches recent Reddit posts related to a financial asset.
283
+
284
+ This method queries relevant subreddits for posts mentioning the specified symbol
285
+ and returns posts based on the selected asset class (e.g., stock, forex, crypto).
286
+ The function uses the PRAW library to interact with Reddit's API.
287
+
288
+ Args:
289
+ symbol (str): The financial asset's symbol or name to search for.
290
+ client_id (str, optional): Reddit API client ID for authentication.
291
+ client_secret (str, optional): Reddit API client secret.
292
+ user_agent (str, optional): Reddit API user agent.
293
+ asset_class (str, optional): The type of financial asset. Defaults to "stock".
294
+ - "stock": Searches in stock-related subreddits (e.g., wallstreetbets, stocks).
295
+ - "forex": Searches in forex-related subreddits.
296
+ - "commodities": Searches in commodity-related subreddits (e.g., gold, oil).
297
+ - "etfs": Searches in ETF-related subreddits.
298
+ - "futures": Searches in futures and options trading subreddits.
299
+ - "crypto": Searches in cryptocurrency-related subreddits.
300
+ - If an unrecognized asset class is provided, defaults to stock-related subreddits.
301
+ n_posts (int, optional): The number of posts to return per subreddit. Defaults to 10.
302
+
303
+ Returns:
304
+ list[str]: A list of Reddit post contents matching the query.
305
+ Each entry contains the post title and body.
306
+ If no posts are found or an error occurs, returns an empty list.
307
+
308
+ Raises:
309
+ praw.exceptions.PRAWException: If an error occurs while interacting with Reddit's API.
310
+
311
+ Example:
312
+ >>> get_reddit_posts(symbol="AAPL", client_id="your_id", client_secret="your_secret", user_agent="your_agent", asset_class="stock", n_posts=5)
313
+ ["Apple stock is rallying today due to strong earnings.", "Should I buy $AAPL now?", ...]
314
+
315
+ Notes:
316
+ - Requires valid Reddit API credentials.
317
+ """
318
+
319
+ reddit = praw.Reddit(
320
+ client_id=client_id, client_secret=client_secret, user_agent=user_agent
321
+ )
322
+
323
+ subreddit_mapping = {
324
+ "stock": ["wallstreetbets", "stocks", "investing", "StockMarket"],
325
+ "forex": ["Forex", "ForexTrading", "DayTrading"],
326
+ "commodities": ["Commodities", "Gold", "Silverbugs", "oil"],
327
+ "etfs": ["ETFs", "investing"],
328
+ "futures": ["FuturesTrading", "OptionsTrading", "DayTrading"],
329
+ "crypto": ["CryptoCurrency", "Bitcoin", "ethereum", "altcoin"],
330
+ }
331
+ try:
332
+ subreddits = subreddit_mapping.get(asset_class.lower(), ["stocks"])
333
+ except Exception:
334
+ return []
335
+
336
+ posts = []
337
+ for sub in subreddits:
338
+ subreddit = reddit.subreddit(sub)
339
+ query = _get_search_query(symbol)
340
+ all_posts = subreddit.search(query, limit=n_posts)
341
+ for post in all_posts:
342
+ text = post.title + " " + post.selftext
343
+ if _find_news(query, text):
344
+ posts.append(text)
345
+ return posts
346
+
347
+ def get_twitter_posts(
348
+ self,
349
+ query,
350
+ asset_type="stock",
351
+ bearer=None,
352
+ api_key=None,
353
+ api_secret=None,
354
+ access_token=None,
355
+ access_secret=None,
356
+ n_posts=10,
357
+ ) -> List[str]:
358
+ """
359
+ Fetches recent tweets related to a financial asset.
360
+
361
+ This method queries Twitter for recent posts mentioning the specified asset
362
+ and filters the results based on the asset type (e.g., stock, forex, crypto).
363
+ The function uses the Tweepy API to fetch tweets and returns a list of tweet texts.
364
+
365
+ Args:
366
+ query (str): The main keyword to search for (e.g., a stock ticker or asset name).
367
+ asset_type (str, optional): The type of financial asset. Defaults to "stock".
368
+ - "stock": Searches for tweets mentioning the stock or shares.
369
+ - "forex": Searches for tweets mentioning foreign exchange (forex) or currency.
370
+ - "crypto": Searches for tweets mentioning cryptocurrency or related terms.
371
+ - "commodity": Searches for tweets mentioning commodities or futures trading.
372
+ - "index": Searches for tweets mentioning stock market indices.
373
+ - "bond": Searches for tweets mentioning bonds or fixed income securities.
374
+ - If an unrecognized asset type is provided, defaults to general finance-related tweets.
375
+ bearer (str, optional): Twitter API bearer token for authentication.
376
+ api_key (str, optional): Twitter API consumer key.
377
+ api_secret (str, optional): Twitter API consumer secret.
378
+ access_token (str, optional): Twitter API access token.
379
+ access_secret (str, optional): Twitter API access token secret.
380
+ n_posts (int, optional): The number of tweets to return. Defaults to 10.
381
+
382
+ Returns:
383
+ list[str]: A list of up to `n_posts` tweet texts matching the query.
384
+ If no tweets are found or an API error occurs, returns an empty list.
385
+
386
+ Raises:
387
+ tweepy.TweepyException: If an error occurs while making the Twitter API request.
388
+
389
+ Example:
390
+ >>> get_twitter_posts(query="AAPL", asset_type="stock", bearer="YOUR_BEARER_TOKEN", n_posts=5)
391
+ ["Apple stock surges after strong earnings!", "Is $AAPL a buy at this price?", ...]
392
+ """
393
+ client = tweepy.Client(
394
+ bearer_token=bearer,
395
+ consumer_key=api_key,
396
+ consumer_secret=api_secret,
397
+ access_token=access_token,
398
+ access_token_secret=access_secret,
399
+ )
400
+ asset_queries = {
401
+ "stock": f"{query} stock OR {query} shares -is:retweet lang:en",
402
+ "forex": f"{query} forex OR {query} currency -is:retweet lang:en",
403
+ "crypto": f"{query} cryptocurrency OR {query} crypto OR #{query} -is:retweet lang:en",
404
+ "commodity": f"{query} commodity OR {query} futures OR {query} trading -is:retweet lang:en",
405
+ "index": f"{query} index OR {query} market -is:retweet lang:en",
406
+ "bond": f"{query} bonds OR {query} fixed income -is:retweet lang:en",
407
+ }
408
+ # Get the correct query based on the asset type
409
+ search = asset_queries.get(
410
+ asset_type.lower(), f"{query} finance -is:retweet lang:en"
411
+ )
412
+ try:
413
+ tweets = client.search_recent_tweets(
414
+ query=search, max_results=100, tweet_fields=["text"]
415
+ )
416
+ query = _get_search_query(query)
417
+ news = [tweet.text for tweet in tweets.data] if tweets.data else []
418
+ return _filter_news(news, query)[:n_posts]
419
+ except tweepy.TweepyException:
420
+ return []
421
+
422
+ def get_fmp_news(self, api=None) -> FmpNews:
423
+ return FmpNews(api=api)
6
424
 
7
425
 
8
- class FMP(Toolkit):
426
+ class FmpData(Toolkit):
9
427
  """
10
428
  FMPData class for fetching data from Financial Modeling Prep API
11
429
  using the Toolkit class from financetoolkit package.
@@ -14,9 +432,8 @@ class FMP(Toolkit):
14
432
 
15
433
  """
16
434
 
17
- def __init__(self, api_key: str = '', symbols: str | list = 'AAPL'):
435
+ def __init__(self, api_key: str = "", symbols: str | list = "AAPL"):
18
436
  super().__init__(tickers=symbols, api_key=api_key)
19
437
 
20
438
 
21
- class DataBendo:
22
- ...
439
+ class DataBendo: ...
bbstrader/core/utils.py CHANGED
@@ -6,6 +6,8 @@ from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from typing import Any, Dict, List
8
8
 
9
+ __all__ = ["load_module", "load_class"]
10
+
9
11
 
10
12
  def load_module(file_path):
11
13
  """Load a module from a file path.
@@ -8,3 +8,4 @@ from bbstrader.models.optimization import * # noqa: F403
8
8
  from bbstrader.models.portfolio import * # noqa: F403
9
9
  from bbstrader.models.factors import * # noqa: F403
10
10
  from bbstrader.models.ml import * # noqa: F403
11
+ from bbstrader.models.nlp import * # noqa: F403
bbstrader/models/ml.py CHANGED
@@ -8,8 +8,8 @@ import lightgbm as lgb
8
8
  import matplotlib.pyplot as plt
9
9
  import numpy as np
10
10
  import pandas as pd
11
+ import pandas_ta as ta
11
12
  import seaborn as sns
12
-
13
13
  import yfinance as yf
14
14
  from alphalens import performance as perf
15
15
  from alphalens import plotting
@@ -21,11 +21,9 @@ from alphalens.utils import (
21
21
  )
22
22
  from scipy.stats import spearmanr
23
23
  from sklearn.preprocessing import LabelEncoder, StandardScaler
24
- import pandas_ta as ta
25
24
 
26
25
  warnings.filterwarnings("ignore")
27
26
 
28
-
29
27
  __all__ = ["OneStepTimeSeriesSplit", "MultipleTimeSeriesCV", "LightGBModel"]
30
28
 
31
29
 
@@ -749,8 +747,8 @@ class LightGBModel(object):
749
747
  index=metric_cols,
750
748
  )
751
749
  if verbose:
752
- msg = f'\t{p:3.0f} | {self.format_time(T)} ({t:3.0f}) | {params["learning_rate"]:5.2f} | '
753
- msg += f'{params["num_leaves"]:3.0f} | {params["feature_fraction"]:3.0%} | {params["min_data_in_leaf"]:4.0f} | '
750
+ msg = f"\t{p:3.0f} | {self.format_time(T)} ({t:3.0f}) | {params['learning_rate']:5.2f} | "
751
+ msg += f"{params['num_leaves']:3.0f} | {params['feature_fraction']:3.0%} | {params['min_data_in_leaf']:4.0f} | "
754
752
  msg += f" {max(ic):6.2%} | {ic_by_day.mean().max(): 6.2%} | {daily_ic_mean_n: 4.0f} | {ic_by_day.median().max(): 6.2%} | {daily_ic_median_n: 4.0f}"
755
753
  print(msg)
756
754
 
@@ -871,7 +869,7 @@ class LightGBModel(object):
871
869
  med = data.ic.median()
872
870
  rolling.plot(
873
871
  ax=axes[i],
874
- title=f"Horizon: {t} Day(s) | IC: Mean={avg*100:.2f} Median={med*100:.2f}",
872
+ title=f"Horizon: {t} Day(s) | IC: Mean={avg * 100:.2f} Median={med * 100:.2f}",
875
873
  )
876
874
  axes[i].axhline(avg, c="darkred", lw=1)
877
875
  axes[i].axhline(0, ls="--", c="k", lw=1)
@@ -1237,7 +1235,9 @@ class LightGBModel(object):
1237
1235
  try:
1238
1236
  return (predictions.unstack("symbol").prediction.tz_convert("UTC")), tickers
1239
1237
  except TypeError:
1240
- return (predictions.unstack("symbol").prediction.tz_localize("UTC")), tickers
1238
+ return (
1239
+ predictions.unstack("symbol").prediction.tz_localize("UTC")
1240
+ ), tickers
1241
1241
 
1242
1242
  def assert_last_date(self, predictions: pd.DataFrame):
1243
1243
  """