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 +2 -2
- bbstrader/core/__init__.py +2 -0
- bbstrader/core/data.py +424 -7
- bbstrader/core/utils.py +2 -0
- bbstrader/models/__init__.py +1 -0
- bbstrader/models/ml.py +7 -7
- bbstrader/models/nlp.py +784 -0
- bbstrader/trading/execution.py +4 -1
- bbstrader/trading/scripts.py +144 -58
- bbstrader/trading/utils.py +69 -0
- {bbstrader-0.2.94.dist-info → bbstrader-0.2.96.dist-info}/METADATA +10 -2
- {bbstrader-0.2.94.dist-info → bbstrader-0.2.96.dist-info}/RECORD +16 -15
- {bbstrader-0.2.94.dist-info → bbstrader-0.2.96.dist-info}/WHEEL +1 -1
- bbstrader/trading/script.py +0 -155
- {bbstrader-0.2.94.dist-info → bbstrader-0.2.96.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.94.dist-info → bbstrader-0.2.96.dist-info/licenses}/LICENSE +0 -0
- {bbstrader-0.2.94.dist-info → bbstrader-0.2.96.dist-info}/top_level.txt +0 -0
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.
|
|
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)
|
bbstrader/core/__init__.py
CHANGED
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
|
-
|
|
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
|
|
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 =
|
|
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
bbstrader/models/__init__.py
CHANGED
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
|
|
753
|
-
msg += f
|
|
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 (
|
|
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
|
"""
|