bbstrader 0.3.6__py3-none-any.whl → 0.3.7__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/compat.py CHANGED
@@ -1,16 +1,17 @@
1
1
  import platform
2
2
  import sys
3
+ from typing import Any
3
4
 
4
5
 
5
- def setup_mock_modules():
6
+ def setup_mock_modules() -> None:
6
7
  """Mock some modules not available on some OS to prevent import errors."""
7
8
  from unittest.mock import MagicMock
8
9
 
9
10
  class Mock(MagicMock):
10
11
  @classmethod
11
- def __getattr__(cls, name):
12
+ def __getattr__(cls, name: str) -> Any:
12
13
  return MagicMock()
13
-
14
+
14
15
  MOCK_MODULES = []
15
16
 
16
17
  # Mock Metatrader5 on Linux and MacOS
bbstrader/config.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
+ from datetime import datetime
2
3
  from pathlib import Path
3
- from typing import List
4
+ from typing import Any, List, Optional
4
5
 
5
6
 
6
7
  def get_config_dir(name: str = ".bbstrader") -> Path:
@@ -23,7 +24,7 @@ BBSTRADER_DIR = get_config_dir()
23
24
 
24
25
 
25
26
  class LogLevelFilter(logging.Filter):
26
- def __init__(self, levels: List[int]):
27
+ def __init__(self, levels: List[int]) -> None:
27
28
  """
28
29
  Initializes the filter with specific logging levels.
29
30
 
@@ -47,52 +48,35 @@ class LogLevelFilter(logging.Filter):
47
48
 
48
49
 
49
50
  class CustomFormatter(logging.Formatter):
50
- def formatTime(self, record, datefmt=None):
51
+ def formatTime(
52
+ self, record: logging.LogRecord, datefmt: Optional[str] = None
53
+ ) -> str:
51
54
  if hasattr(record, "custom_time"):
52
55
  # Use the custom time if provided
53
- record.created = record.custom_time.timestamp()
56
+ record.created = record.custom_time.timestamp() # type: ignore
54
57
  return super().formatTime(record, datefmt)
55
58
 
56
59
 
57
60
  class CustomLogger(logging.Logger):
58
- def __init__(self, name, level=logging.NOTSET):
61
+ def __init__(self, name: str, level: int = logging.NOTSET) -> None:
59
62
  super().__init__(name, level)
60
63
 
61
- def _log(
64
+ def log(
62
65
  self,
63
- level,
64
- msg,
65
- args,
66
- exc_info=None,
67
- extra=None,
68
- stack_info=False,
69
- stacklevel=1,
70
- custom_time=None,
71
- ):
72
- if extra is None:
73
- extra = {}
74
- # Add custom_time to the extra dictionary if provided
66
+ level: int,
67
+ msg: object,
68
+ *args: object,
69
+ custom_time: Optional[datetime] = None,
70
+ **kwargs: Any,
71
+ ) -> None:
75
72
  if custom_time:
76
- extra["custom_time"] = custom_time
77
- super()._log(level, msg, args, exc_info, extra, stack_info, stacklevel)
73
+ if "extra" not in kwargs or kwargs["extra"] is None:
74
+ kwargs["extra"] = {}
75
+ kwargs["extra"]["custom_time"] = custom_time
76
+ super().log(level, msg, *args, **kwargs)
78
77
 
79
- def info(self, msg, *args, custom_time=None, **kwargs):
80
- self._log(logging.INFO, msg, args, custom_time=custom_time, **kwargs)
81
78
 
82
- def debug(self, msg, *args, custom_time=None, **kwargs):
83
- self._log(logging.DEBUG, msg, args, custom_time=custom_time, **kwargs)
84
-
85
- def warning(self, msg, *args, custom_time=None, **kwargs):
86
- self._log(logging.WARNING, msg, args, custom_time=custom_time, **kwargs)
87
-
88
- def error(self, msg, *args, custom_time=None, **kwargs):
89
- self._log(logging.ERROR, msg, args, custom_time=custom_time, **kwargs)
90
-
91
- def critical(self, msg, *args, custom_time=None, **kwargs):
92
- self._log(logging.CRITICAL, msg, args, custom_time=custom_time, **kwargs)
93
-
94
-
95
- def config_logger(log_file: str, console_log=True):
79
+ def config_logger(log_file: str, console_log: bool = True) -> logging.Logger:
96
80
  # Use the CustomLogger
97
81
  logging.setLoggerClass(CustomLogger)
98
82
  logger = logging.getLogger(__name__)
bbstrader/core/data.py CHANGED
@@ -2,7 +2,7 @@ import json
2
2
  import re
3
3
  import ssl
4
4
  from datetime import datetime
5
- from typing import List, Literal
5
+ from typing import Any, Dict, List, Literal, Optional, Union
6
6
  from urllib.request import urlopen
7
7
 
8
8
  import certifi
@@ -27,7 +27,7 @@ def _get_search_query(query: str) -> str:
27
27
  return query
28
28
 
29
29
 
30
- def _find_news(query: str | List[str], text):
30
+ def _find_news(query: Union[str, List[str]], text: str) -> bool:
31
31
  if isinstance(query, str):
32
32
  query = query.split(" ")
33
33
  pattern = r"\b(?:" + "|".join(map(re.escape, query)) + r")\b"
@@ -36,7 +36,7 @@ def _find_news(query: str | List[str], text):
36
36
  return False
37
37
 
38
38
 
39
- def _filter_news(news: List[str], query: str | List[str]) -> List[str]:
39
+ def _filter_news(news: List[str], query: Union[str, List[str]]) -> List[str]:
40
40
  return [text for text in news if _find_news(query, text)]
41
41
 
42
42
 
@@ -48,7 +48,7 @@ class FmpNews(object):
48
48
  as well as financial articles and press releases.
49
49
  """
50
50
 
51
- def __init__(self, api: str):
51
+ def __init__(self, api: str) -> None:
52
52
  """
53
53
  Args:
54
54
  api (str): The API key for accessing FMP's news data.
@@ -60,13 +60,15 @@ class FmpNews(object):
60
60
  raise ValueError("API key is required For FmpNews")
61
61
  self.__api = api
62
62
 
63
- def _jsonparsed_data(self, url):
63
+ def _jsonparsed_data(self, url: str) -> Any:
64
64
  context = ssl.create_default_context(cafile=certifi.where())
65
65
  with urlopen(url, context=context) as response:
66
66
  data = response.read().decode("utf-8")
67
67
  return json.loads(data)
68
68
 
69
- def _load_news(self, news_type, symbol=None, **kwargs) -> List[dict]:
69
+ def _load_news(
70
+ self, news_type: str, symbol: Optional[str] = None, **kwargs: Any
71
+ ) -> List[Dict[str, Any]]:
70
72
  params = {"start": "from", "end": "to", "page": "page", "limit": "limit"}
71
73
  base_url = f"https://financialmodelingprep.com/stable/news/{news_type}-latest?apikey={self.__api}"
72
74
  if news_type == "articles":
@@ -81,8 +83,8 @@ class FmpNews(object):
81
83
 
82
84
  return self._jsonparsed_data(base_url)
83
85
 
84
- def get_articles(self, **kwargs) -> List[dict]:
85
- def html_parser(content):
86
+ def get_articles(self, **kwargs: Any) -> List[Dict[str, Any]]:
87
+ def html_parser(content: str) -> str:
86
88
  soup = BeautifulSoup(content, "html.parser")
87
89
  text = soup.get_text(separator="\n")
88
90
  return text.replace("\n", "")
@@ -91,29 +93,39 @@ class FmpNews(object):
91
93
  df = pd.DataFrame(articles)
92
94
  df = df[["title", "date", "content", "tickers"]]
93
95
  df["content"] = df["content"].apply(html_parser)
94
- return df.to_dict(orient="records")
96
+ return df.to_dict(orient="records") # type: ignore
95
97
 
96
- def get_releases(self, symbol=None, **kwargs):
98
+ def get_releases(
99
+ self, symbol: Optional[str] = None, **kwargs: Any
100
+ ) -> List[Dict[str, Any]]:
97
101
  return self._load_news("press-releases", symbol, **kwargs)
98
102
 
99
- def get_stock_news(self, symbol=None, **kwargs):
103
+ def get_stock_news(
104
+ self, symbol: Optional[str] = None, **kwargs: Any
105
+ ) -> List[Dict[str, Any]]:
100
106
  return self._load_news("stock", symbol, **kwargs)
101
107
 
102
- def get_crypto_news(self, symbol=None, **kwargs):
108
+ def get_crypto_news(
109
+ self, symbol: Optional[str] = None, **kwargs: Any
110
+ ) -> List[Dict[str, Any]]:
103
111
  return self._load_news("crypto", symbol, **kwargs)
104
112
 
105
- def get_forex_news(self, symbol=None, **kwargs):
113
+ def get_forex_news(
114
+ self, symbol: Optional[str] = None, **kwargs: Any
115
+ ) -> List[Dict[str, Any]]:
106
116
  return self._load_news("forex", symbol, **kwargs)
107
117
 
108
- def _last_date(self, date):
118
+ def _last_date(self, date: str) -> datetime:
109
119
  return datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
110
120
 
111
- def parse_news(self, news: List[dict], symbol=None, **kwargs) -> List[str]:
121
+ def parse_news(
122
+ self, news: List[Dict[str, Any]], symbol: Optional[str] = None, **kwargs: Any
123
+ ) -> List[str]:
112
124
  start = kwargs.get("start")
113
125
  end = kwargs.get("end")
114
126
  end_date = self._last_date(end) if end is not None else datetime.now().date()
115
127
 
116
- def parse_record(record):
128
+ def parse_record(record: Dict[str, Any]) -> str:
117
129
  return " ".join(
118
130
  [
119
131
  record.pop("symbol", ""),
@@ -127,7 +139,7 @@ class FmpNews(object):
127
139
  parsed_news = []
128
140
  for record in news:
129
141
  date = record.get("publishedDate")
130
- published_date = self._last_date(record.get("date", date)).date()
142
+ published_date = self._last_date(record.get("date", date)).date() # type: ignore
131
143
  start_date = (
132
144
  self._last_date(start).date() if start is not None else published_date
133
145
  )
@@ -141,18 +153,23 @@ class FmpNews(object):
141
153
  parsed_news.append(parse_record(record))
142
154
  return parsed_news
143
155
 
144
- def get_latest_articles(self, articles=None, save=False, **kwargs) -> List[dict]:
156
+ def get_latest_articles(
157
+ self,
158
+ articles: Optional[List[Dict[str, Any]]] = None,
159
+ save: bool = False,
160
+ **kwargs: Any,
161
+ ) -> List[Dict[str, Any]]:
145
162
  end = kwargs.get("end")
146
163
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
147
164
  end_date = self._last_date(end) if end is not None else self._last_date(now)
148
165
  if articles is None:
149
166
  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"]).hour < end_date.hour:
167
+ articles = pd.read_csv("latest_fmp_articles.csv") # type: ignore
168
+ articles = articles.to_dict(orient="records") # type: ignore
169
+ if self._last_date(articles[0]["date"]).hour < end_date.hour: # type: ignore
153
170
  articles = self.get_articles(**kwargs)
154
171
  else:
155
- return articles
172
+ return articles # type: ignore
156
173
  except FileNotFoundError:
157
174
  articles = self.get_articles(**kwargs)
158
175
 
@@ -162,8 +179,13 @@ class FmpNews(object):
162
179
  return articles
163
180
 
164
181
  def get_news(
165
- self, query, source="articles", articles=None, symbol: str = None, **kwargs
166
- ):
182
+ self,
183
+ query: str,
184
+ source: str = "articles",
185
+ articles: Optional[List[Dict[str, Any]]] = None,
186
+ symbol: Optional[str] = None,
187
+ **kwargs: Any,
188
+ ) -> List[str]:
167
189
  """
168
190
  Retrieves relevant financial news based on the specified source.
169
191
 
@@ -201,7 +223,7 @@ class FmpNews(object):
201
223
  news_source = source_methods.get(source, lambda: [])()
202
224
  if source == "articles":
203
225
  symbol = None # Articles do not require a symbol filter
204
- news = self.parse_news(news_source, symbol=symbol)
226
+ news = self.parse_news(news_source, symbol=symbol, **kwargs)
205
227
  return _filter_news(news, query)
206
228
 
207
229
 
@@ -213,7 +235,9 @@ class FinancialNews(object):
213
235
 
214
236
  """
215
237
 
216
- def _fetch_news(self, url, query, n_news, headline_tag) -> List[str]:
238
+ def _fetch_news(
239
+ self, url: str, query: str, n_news: int, headline_tag: str
240
+ ) -> List[str]:
217
241
  headers = {"User-Agent": "Mozilla/5.0"}
218
242
  try:
219
243
  response = requests.get(url, headers=headers)
@@ -234,7 +258,9 @@ class FinancialNews(object):
234
258
  ]
235
259
  return headlines[:n_news]
236
260
 
237
- def get_yahoo_finance_news(self, query: str, asset_type="stock", n_news=10):
261
+ def get_yahoo_finance_news(
262
+ self, query: str, asset_type: str = "stock", n_news: int = 10
263
+ ) -> List[str]:
238
264
  """
239
265
  Fetches recent Yahoo Finance news headlines for a given financial asset.
240
266
 
@@ -257,17 +283,17 @@ class FinancialNews(object):
257
283
  list[str]: A list of Yahoo Finance news headlines relevant to the query.
258
284
  """
259
285
  if asset_type == "forex" or asset_type == "future":
260
- assert (
261
- "=" in query
262
- ), "Forex query must contain '=' for currency pairs (e.g., EURUSD=X, CL=F)"
286
+ assert "=" in query, (
287
+ "Forex query must contain '=' for currency pairs (e.g., EURUSD=X, CL=F)"
288
+ )
263
289
  if asset_type == "crypto":
264
- assert (
265
- "-" in query
266
- ), "Crypto query must contain '-' for crypto pairs (e.g., BTC-USD, ETH-USD)"
290
+ assert "-" in query, (
291
+ "Crypto query must contain '-' for crypto pairs (e.g., BTC-USD, ETH-USD)"
292
+ )
267
293
  if asset_type == "index":
268
- assert query.startswith(
269
- "^"
270
- ), "Index query must start with '^' (e.g., ^GSPC for S&P 500)"
294
+ assert query.startswith("^"), (
295
+ "Index query must start with '^' (e.g., ^GSPC for S&P 500)"
296
+ )
271
297
  url = (
272
298
  f"https://finance.yahoo.com/quote/{query}/news"
273
299
  if asset_type in ["stock", "etf", "index", "future", "forex"]
@@ -275,7 +301,9 @@ class FinancialNews(object):
275
301
  )
276
302
  return self._fetch_news(url, query, n_news, "h3")
277
303
 
278
- def get_google_finance_news(self, query: str, asset_type="stock", n_news=10):
304
+ def get_google_finance_news(
305
+ self, query: str, asset_type: str = "stock", n_news: int = 10
306
+ ) -> List[str]:
279
307
  """
280
308
  Fetches recent Google Finance news headlines for a given financial asset.
281
309
 
@@ -403,14 +431,14 @@ class FinancialNews(object):
403
431
 
404
432
  def get_twitter_posts(
405
433
  self,
406
- query,
407
- asset_type="stock",
408
- bearer=None,
409
- api_key=None,
410
- api_secret=None,
411
- access_token=None,
412
- access_secret=None,
413
- n_posts=10,
434
+ query: str,
435
+ asset_type: str = "stock",
436
+ bearer: Optional[str] = None,
437
+ api_key: Optional[str] = None,
438
+ api_secret: Optional[str] = None,
439
+ access_token: Optional[str] = None,
440
+ access_secret: Optional[str] = None,
441
+ n_posts: int = 10,
414
442
  ) -> List[str]:
415
443
  """
416
444
  Fetches recent tweets related to a financial asset.
@@ -471,13 +499,13 @@ class FinancialNews(object):
471
499
  query=search, max_results=100, tweet_fields=["text"]
472
500
  )
473
501
  query = _get_search_query(query)
474
- news = [tweet.text for tweet in tweets.data] if tweets.data else []
502
+ news = [tweet.text for tweet in tweets.data] if tweets.data else [] # type: ignore
475
503
  return _filter_news(news, query)[:n_posts]
476
504
  except tweepy.TweepyException:
477
505
  return []
478
506
 
479
- def get_fmp_news(self, api=None) -> FmpNews:
480
- return FmpNews(api=api)
507
+ def get_fmp_news(self, api:str |None =None) -> FmpNews:
508
+ return FmpNews(api=api) # type: ignore
481
509
 
482
510
  def get_coindesk_news(
483
511
  self,
bbstrader/core/scripts.py CHANGED
@@ -4,7 +4,7 @@ import sys
4
4
  import textwrap
5
5
  import time
6
6
  from datetime import datetime, timedelta
7
- from typing import List, Literal
7
+ from typing import Any, Coroutine, Dict, List, Literal
8
8
 
9
9
  import nltk
10
10
  from loguru import logger
@@ -16,7 +16,7 @@ from bbstrader.core.data import FinancialNews
16
16
  from bbstrader.trading.utils import send_telegram_message
17
17
 
18
18
 
19
- def summarize_text(text, sentences_count=5):
19
+ def summarize_text(text: str, sentences_count: int = 5) -> str:
20
20
  """
21
21
  Generate a summary using TextRank algorithm.
22
22
  """
@@ -26,7 +26,7 @@ def summarize_text(text, sentences_count=5):
26
26
  return " ".join(str(sentence) for sentence in summary)
27
27
 
28
28
 
29
- def format_coindesk_article(article: dict) -> str:
29
+ def format_coindesk_article(article: Dict[str, Any]) -> str:
30
30
  if not all(
31
31
  k in article
32
32
  for k in (
@@ -35,7 +35,7 @@ def format_coindesk_article(article: dict) -> str:
35
35
  "published_on",
36
36
  "sentiment",
37
37
  "keywords",
38
- "keywords",
38
+ "status",
39
39
  "url",
40
40
  )
41
41
  ):
@@ -54,7 +54,7 @@ def format_coindesk_article(article: dict) -> str:
54
54
  return text
55
55
 
56
56
 
57
- def format_fmp_article(article: dict) -> str:
57
+ def format_fmp_article(article: Dict[str, Any]) -> str:
58
58
  if not all(k in article for k in ("title", "date", "content", "tickers")):
59
59
  return ""
60
60
  summary = summarize_text(article["content"])
@@ -69,28 +69,29 @@ def format_fmp_article(article: dict) -> str:
69
69
 
70
70
 
71
71
  async def send_articles(
72
- articles: List[dict],
72
+ articles: List[Dict[str, Any]],
73
73
  token: str,
74
74
  id: str,
75
75
  source: Literal["coindesk", "fmp"],
76
- interval=15,
77
- ):
76
+ interval: int = 15,
77
+ ) -> None:
78
78
  for article in articles:
79
79
  message = ""
80
80
  if source == "coindesk":
81
- if article["published_on"] >= datetime.now() - timedelta(minutes=interval):
82
- article["published_on"] = article.get("published_on").strftime(
83
- "%Y-%m-%d %H:%M:%S"
84
- )
85
- message = format_coindesk_article(article)
81
+ published_on = article.get("published_on")
82
+ if isinstance(
83
+ published_on, datetime
84
+ ) and published_on >= datetime.now() - timedelta(minutes=interval):
85
+ article["published_on"] = published_on.strftime("%Y-%m-%d %H:%M:%S")
86
+ message = format_coindesk_article(article)
86
87
  else:
87
88
  message = format_fmp_article(article)
88
89
  if message == "":
89
- return
90
+ continue
90
91
  await send_telegram_message(token, id, text=message)
91
92
 
92
93
 
93
- def send_news_feed(unknown):
94
+ def send_news_feed(unknown: List[str]) -> None:
94
95
  HELP_MSG = """
95
96
  Send news feed from Coindesk to Telegram channel.
96
97
  This script fetches the latest news articles from Coindesk, summarizes them,
@@ -145,19 +146,19 @@ def send_news_feed(unknown):
145
146
  logger.info(f"Starting the News Feed on {args.interval} minutes")
146
147
  while True:
147
148
  try:
148
- fmp_articles = []
149
+ fmp_articles: List[Dict[str, Any]] = []
149
150
  if fmp_news is not None:
150
151
  start = datetime.now() - timedelta(minutes=args.interval)
151
- start = start.strftime("%Y-%m-%d %H:%M:%S")
152
- end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
152
+ start_str = start.strftime("%Y-%m-%d %H:%M:%S")
153
+ end_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
153
154
  fmp_articles = fmp_news.get_latest_articles(
154
- save=True, start=start, end=end
155
+ save=True, start=start_str, end=end_str
155
156
  )
156
157
  coindesk_articles = news.get_coindesk_news(query=args.query)
157
- if len(coindesk_articles) != 0:
158
+ if coindesk_articles and isinstance(coindesk_articles, list):
158
159
  asyncio.run(
159
160
  send_articles(
160
- coindesk_articles,
161
+ coindesk_articles, # type: ignore
161
162
  args.token,
162
163
  args.id,
163
164
  "coindesk",
bbstrader/core/utils.py CHANGED
@@ -2,17 +2,16 @@ import configparser
2
2
  import importlib
3
3
  import importlib.util
4
4
  import os
5
- from typing import Any, Dict, List
5
+ from types import ModuleType
6
+ from typing import Any, Dict, List, Optional, Type, Union
6
7
 
7
8
  __all__ = ["load_module", "load_class"]
8
9
 
9
10
 
10
- def load_module(file_path):
11
+ def load_module(file_path: str) -> ModuleType:
11
12
  """Load a module from a file path.
12
-
13
13
  Args:
14
14
  file_path: Path to the file to load.
15
-
16
15
  Returns:
17
16
  The loaded module.
18
17
  """
@@ -21,14 +20,15 @@ def load_module(file_path):
21
20
  f"Strategy file {file_path} not found. Please create it."
22
21
  )
23
22
  spec = importlib.util.spec_from_file_location("bbstrader.cli", file_path)
23
+ if spec is None or spec.loader is None:
24
+ raise ImportError(f"Could not load spec for module at {file_path}")
24
25
  module = importlib.util.module_from_spec(spec)
25
26
  spec.loader.exec_module(module)
26
27
  return module
27
28
 
28
29
 
29
- def load_class(module, class_name, base_class):
30
+ def load_class(module: ModuleType, class_name: str, base_class: Type) -> Type:
30
31
  """Load a class from a module.
31
-
32
32
  Args:
33
33
  module: The module to load the class from.
34
34
  class_name: The name of the class to load.
@@ -42,7 +42,7 @@ def load_class(module, class_name, base_class):
42
42
  return class_
43
43
 
44
44
 
45
- def auto_convert(value):
45
+ def auto_convert(value: str) -> Union[bool, None, int, float, str]:
46
46
  """Convert string values to appropriate data types"""
47
47
  if value.lower() in {"true", "false"}: # Boolean
48
48
  return value.lower() == "true"
@@ -56,13 +56,13 @@ def auto_convert(value):
56
56
  return value
57
57
 
58
58
 
59
- def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]:
59
+ def dict_from_ini(
60
+ file_path: str, sections: Optional[Union[str, List[str]]] = None
61
+ ) -> Dict[str, Any]:
60
62
  """Reads an INI file and converts it to a dictionary with proper data types.
61
-
62
63
  Args:
63
64
  file_path: Path to the INI file to read.
64
65
  sections: Optional list of sections to read from the INI file.
65
-
66
66
  Returns:
67
67
  A dictionary containing the INI file contents with proper data types.
68
68
  """
@@ -71,7 +71,7 @@ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]
71
71
  config.read(file_path)
72
72
  except Exception:
73
73
  raise
74
- ini_dict = {}
74
+ ini_dict: Dict[str, Any] = {}
75
75
  for section in config.sections():
76
76
  ini_dict[section] = {
77
77
  key: auto_convert(value) for key, value in config.items(section)
@@ -83,10 +83,11 @@ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]
83
83
  except KeyError:
84
84
  raise KeyError(f"{sections} not found in the {file_path} file")
85
85
  if isinstance(sections, list):
86
- sect_dict = {}
86
+ sect_dict: Dict[str, Any] = {}
87
87
  for section in sections:
88
88
  try:
89
89
  sect_dict[section] = ini_dict[section]
90
90
  except KeyError:
91
91
  raise KeyError(f"{section} not found in the {file_path} file")
92
+ return sect_dict
92
93
  return ini_dict
@@ -971,11 +971,17 @@ class Mt5ExecutionEngine:
971
971
  msg = f"Handling period end actions, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
972
972
  self._print_exc(msg, e)
973
973
  pass
974
+
975
+ def select_symbols(self):
976
+ for symbol in self.symbols:
977
+ if not MT5.symbol_select(symbol, True):
978
+ logger.error(f"Failed to select symbol {symbol} error = {MT5.last_error()}")
974
979
 
975
980
  def run(self):
976
981
  while self._running and not self.shutdown_event.is_set():
977
982
  try:
978
983
  check_mt5_connection(**self.kwargs)
984
+ self.select_symbols()
979
985
  positions_orders = self._check_positions_orders()
980
986
  if self.show_positions_orders:
981
987
  self._display_positions_orders(positions_orders)