bbstrader 0.1.94__py3-none-any.whl → 0.2.1__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.

Files changed (38) hide show
  1. bbstrader/__ini__.py +9 -9
  2. bbstrader/btengine/__init__.py +7 -7
  3. bbstrader/btengine/backtest.py +30 -26
  4. bbstrader/btengine/data.py +100 -79
  5. bbstrader/btengine/event.py +2 -1
  6. bbstrader/btengine/execution.py +18 -16
  7. bbstrader/btengine/performance.py +11 -7
  8. bbstrader/btengine/portfolio.py +35 -36
  9. bbstrader/btengine/strategy.py +119 -94
  10. bbstrader/config.py +14 -8
  11. bbstrader/core/__init__.py +0 -0
  12. bbstrader/core/data.py +22 -0
  13. bbstrader/core/utils.py +57 -0
  14. bbstrader/ibkr/__init__.py +0 -0
  15. bbstrader/ibkr/utils.py +0 -0
  16. bbstrader/metatrader/__init__.py +5 -5
  17. bbstrader/metatrader/account.py +117 -121
  18. bbstrader/metatrader/rates.py +83 -80
  19. bbstrader/metatrader/risk.py +23 -37
  20. bbstrader/metatrader/trade.py +169 -140
  21. bbstrader/metatrader/utils.py +3 -3
  22. bbstrader/models/__init__.py +5 -5
  23. bbstrader/models/factors.py +280 -0
  24. bbstrader/models/ml.py +1092 -0
  25. bbstrader/models/optimization.py +31 -28
  26. bbstrader/models/{portfolios.py → portfolio.py} +64 -46
  27. bbstrader/models/risk.py +15 -9
  28. bbstrader/trading/__init__.py +2 -2
  29. bbstrader/trading/execution.py +252 -164
  30. bbstrader/trading/scripts.py +8 -4
  31. bbstrader/trading/strategies.py +79 -66
  32. bbstrader/tseries.py +482 -107
  33. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
  34. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/METADATA +6 -1
  35. bbstrader-0.2.1.dist-info/RECORD +37 -0
  36. bbstrader-0.1.94.dist-info/RECORD +0 -32
  37. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
  38. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/top_level.txt +0 -0
bbstrader/__ini__.py CHANGED
@@ -4,15 +4,15 @@ Simplified Investment & Trading Toolkit
4
4
 
5
5
  """
6
6
  __author__ = "Bertin Balouki SIMYELI"
7
- __copyright__ = "2023-2024 Bertin Balouki SIMYELI"
8
- __email__ = "bbalouki@outlook.com"
7
+ __copyright__ = "2023-2025 Bertin Balouki SIMYELI"
8
+ __email__ = "bertin@bbstrader.com"
9
9
  __license__ = "MIT"
10
- __version__ = '0.1.91'
10
+ __version__ = '0.2.0'
11
11
 
12
12
 
13
- from bbstrader import btengine
14
- from bbstrader import metatrader
15
- from bbstrader import models
16
- from bbstrader import trading
17
- from bbstrader import tseries
18
- from .config import config_logger
13
+ from bbstrader import btengine # noqa: F401
14
+ from bbstrader import metatrader # noqa: F401
15
+ from bbstrader import models # noqa: F401
16
+ from bbstrader import trading # noqa: F401
17
+ from bbstrader import tseries # noqa: F401
18
+ from .config import config_logger # noqa: F401
@@ -45,10 +45,10 @@ Notes
45
45
 
46
46
  See `bbstrader.btengine.backtest.run_backtest` for more details on the backtesting process and its parameters.
47
47
  """
48
- from bbstrader.btengine.data import *
49
- from bbstrader.btengine.event import *
50
- from bbstrader.btengine.execution import *
51
- from bbstrader.btengine.performance import *
52
- from bbstrader.btengine.backtest import *
53
- from bbstrader.btengine.strategy import *
54
- from bbstrader.btengine.portfolio import *
48
+ from bbstrader.btengine.backtest import * # noqa: F403
49
+ from bbstrader.btengine.data import * # noqa: F403
50
+ from bbstrader.btengine.event import * # noqa: F403
51
+ from bbstrader.btengine.execution import * # noqa: F403
52
+ from bbstrader.btengine.performance import * # noqa: F403
53
+ from bbstrader.btengine.portfolio import * # noqa: F403
54
+ from bbstrader.btengine.strategy import * # noqa: F403
@@ -1,16 +1,14 @@
1
- import pprint
2
1
  import queue
3
2
  import time
4
- import yfinance as yf
5
- from queue import Queue
6
3
  from datetime import datetime
7
- from bbstrader.btengine.data import *
8
- from bbstrader.btengine.execution import *
4
+ from typing import List, Literal, Optional
5
+
6
+ from tabulate import tabulate
7
+
8
+ from bbstrader.btengine.data import DataHandler
9
+ from bbstrader.btengine.execution import ExecutionHandler, SimExecutionHandler
9
10
  from bbstrader.btengine.portfolio import Portfolio
10
- from bbstrader.btengine.event import SignalEvent
11
11
  from bbstrader.btengine.strategy import Strategy
12
- from typing import Literal, Optional, List
13
- from tabulate import tabulate
14
12
 
15
13
  __all__ = [
16
14
  "Backtest",
@@ -18,6 +16,7 @@ __all__ = [
18
16
  "run_backtest"
19
17
  ]
20
18
 
19
+
21
20
  class Backtest(object):
22
21
  """
23
22
  The `Backtest()` object encapsulates the event-handling logic and essentially
@@ -60,6 +59,7 @@ class Backtest(object):
60
59
 
61
60
  class BacktestEngine(Backtest):
62
61
  __doc__ = Backtest.__doc__
62
+
63
63
  def __init__(
64
64
  self,
65
65
  symbol_list: List[str],
@@ -141,7 +141,7 @@ class BacktestEngine(Backtest):
141
141
  while True:
142
142
  i += 1
143
143
  value = self.portfolio.all_holdings[-1]['Total']
144
- if self.data_handler.continue_backtest == True:
144
+ if self.data_handler.continue_backtest is True:
145
145
  # Update the market bars
146
146
  self.data_handler.update_bars()
147
147
  self.strategy.check_pending_orders()
@@ -152,7 +152,8 @@ class BacktestEngine(Backtest):
152
152
  self.strategy.cash = value
153
153
  else:
154
154
  print("\n[======= BACKTEST COMPLETED =======]")
155
- print(f"END DATE: {self.data_handler.get_latest_bar_datetime(self.symbol_list[0])}")
155
+ print(
156
+ f"END DATE: {self.data_handler.get_latest_bar_datetime(self.symbol_list[0])}")
156
157
  print(f"TOTAL BARS: {i} ")
157
158
  print(f"PORFOLIO VALUE: {round(value, 2)}")
158
159
  break
@@ -198,7 +199,8 @@ class BacktestEngine(Backtest):
198
199
  stat2['Orders'] = self.orders
199
200
  stat2['Fills'] = self.fills
200
201
  stats.extend(stat2.items())
201
- tab_stats = tabulate(stats, headers=["Metric", "Value"], tablefmt="outline")
202
+ tab_stats = tabulate(
203
+ stats, headers=["Metric", "Value"], tablefmt="outline")
202
204
  print(tab_stats, "\n")
203
205
  if self.stats_file:
204
206
  with open(self.stats_file, 'a') as f:
@@ -211,16 +213,16 @@ class BacktestEngine(Backtest):
211
213
  print("\n[======= PORTFOLIO SUMMARY =======]")
212
214
  print(
213
215
  tabulate(
214
- self.portfolio.equity_curve.tail(10),
215
- headers="keys",
216
- tablefmt="outline"),
217
- "\n"
218
- )
219
-
216
+ self.portfolio.equity_curve.tail(10),
217
+ headers="keys",
218
+ tablefmt="outline"),
219
+ "\n"
220
+ )
221
+
220
222
  def simulate_trading(self):
221
223
  """
222
224
  Simulates the backtest and outputs portfolio performance.
223
-
225
+
224
226
  Returns:
225
227
  pd.DataFrame: The portfilio values over time (capital, equity, returns etc.)
226
228
  """
@@ -244,9 +246,9 @@ def run_backtest(
244
246
 
245
247
  Args:
246
248
  symbol_list (List[str]): List of symbol strings for the assets to be backtested.
247
-
249
+
248
250
  start_date (datetime): Start date of the backtest.
249
-
251
+
250
252
  data_handler (DataHandler): An instance of the `DataHandler` class, responsible for managing
251
253
  and processing market data. Available options include `CSVDataHandler`,
252
254
  `MT5DataHandler`, and `YFDataHandler`. Ensure that the `DataHandler`
@@ -275,7 +277,7 @@ def run_backtest(
275
277
 
276
278
  **kwargs: Additional parameters passed to the `Backtest` instance, which may include strategy-specific,
277
279
  data handler, portfolio, or execution handler options.
278
-
280
+
279
281
  Returns:
280
282
  pd.DataFrame: The portfolio values over time (capital, equities, returns etc.).
281
283
 
@@ -326,10 +328,12 @@ def run_backtest(
326
328
  return portfolio
327
329
 
328
330
 
329
- class CerebroEngine:...
331
+ class CerebroEngine:
332
+ ...
330
333
 
331
334
 
332
- class ZiplineEngine:...
335
+ class ZiplineEngine:
336
+ ...
333
337
 
334
338
 
335
339
  def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwargs):
@@ -347,8 +351,8 @@ def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwar
347
351
  **kwargs
348
352
  )
349
353
  elif engine == "cerebro":
350
- #TODO:
354
+ # TODO:
351
355
  pass
352
356
  elif engine == "zipline":
353
- #TODO:
354
- pass
357
+ # TODO:
358
+ pass
@@ -1,19 +1,19 @@
1
1
  import os.path
2
+ from abc import ABCMeta, abstractmethod
3
+ from datetime import datetime
4
+ from queue import Queue
5
+ from typing import Dict, List
6
+
2
7
  import numpy as np
3
8
  import pandas as pd
4
9
  import yfinance as yf
5
- from pathlib import Path
6
- from typing import List, Dict
7
- from queue import Queue
8
- from abc import ABCMeta, abstractmethod
9
- from bbstrader.metatrader.rates import download_historical_data
10
- from bbstrader.btengine.event import MarketEvent
11
- from bbstrader.config import BBSTRADER_DIR
12
- from datetime import datetime
13
10
  from eodhd import APIClient
14
11
  from financetoolkit import Toolkit
15
12
  from pytz import timezone
16
13
 
14
+ from bbstrader.btengine.event import MarketEvent
15
+ from bbstrader.config import BBSTRADER_DIR
16
+ from bbstrader.metatrader.rates import download_historical_data
17
17
 
18
18
  __all__ = [
19
19
  "DataHandler",
@@ -47,12 +47,15 @@ class DataHandler(metaclass=ABCMeta):
47
47
  @property
48
48
  def symbols(self) -> List[str]:
49
49
  pass
50
+
50
51
  @property
51
52
  def data(self) -> Dict[str, pd.DataFrame]:
52
53
  pass
54
+
53
55
  @property
54
56
  def labels(self) -> List[str]:
55
57
  pass
58
+
56
59
  @property
57
60
  def index(self) -> str | List[str]:
58
61
  pass
@@ -122,16 +125,15 @@ class BaseCSVDataHandler(DataHandler):
122
125
 
123
126
  """
124
127
 
125
- def __init__(self, events: Queue,
126
- symbol_list: List[str],
127
- csv_dir: str,
128
- columns: List[str]=None,
128
+ def __init__(self, events: Queue,
129
+ symbol_list: List[str],
130
+ csv_dir: str,
131
+ columns: List[str] = None,
129
132
  index_col: str | int | List[str] | List[int] = 0):
130
-
131
133
  """
132
134
  Initialises the data handler by requesting the location of the CSV files
133
135
  and a list of symbols.
134
-
136
+
135
137
  Args:
136
138
  events : The Event Queue.
137
139
  symbol_list : A list of symbol strings.
@@ -151,28 +153,36 @@ class BaseCSVDataHandler(DataHandler):
151
153
  self._load_and_process_data()
152
154
 
153
155
  @property
154
- def symbols(self)-> List[str]:
156
+ def symbols(self) -> List[str]:
155
157
  return self.symbol_list
158
+
156
159
  @property
157
- def data(self)-> Dict[str, pd.DataFrame]:
160
+ def data(self) -> Dict[str, pd.DataFrame]:
158
161
  return self.symbol_data
162
+
159
163
  @property
160
- def labels(self)-> List[str]:
164
+ def datadir(self) -> str:
165
+ return self.csv_dir
166
+
167
+ @property
168
+ def labels(self) -> List[str]:
161
169
  return self.columns
170
+
162
171
  @property
163
- def index(self)-> str | List[str]:
172
+ def index(self) -> str | List[str]:
164
173
  return self._index
165
-
174
+
166
175
  def _load_and_process_data(self):
167
176
  """
168
177
  Opens the CSV files from the data directory, converting
169
178
  them into pandas DataFrames within a symbol dictionary.
170
179
  """
171
180
  default_names = pd.read_csv(
172
- os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
173
- ).columns.to_list()
181
+ os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
182
+ ).columns.to_list()
174
183
  new_names = self.columns or default_names
175
- new_names = [name.strip().lower().replace(' ', '_') for name in new_names]
184
+ new_names = [name.strip().lower().replace(' ', '_')
185
+ for name in new_names]
176
186
  self.columns = new_names
177
187
  assert 'adj_close' in new_names or 'close' in new_names, \
178
188
  "Column names must contain 'Adj Close' and 'Close' or adj_close and close"
@@ -203,6 +213,7 @@ class BaseCSVDataHandler(DataHandler):
203
213
  'adj_close' if 'adj_close' in new_names else 'close'
204
214
  ].pct_change().dropna()
205
215
  self._index = self.symbol_data[s].index.name
216
+ self.symbol_data[s].to_csv(os.path.join(self.csv_dir, f'{s}.csv'))
206
217
  if self.events is not None:
207
218
  self.symbol_data[s] = self.symbol_data[s].iterrows()
208
219
 
@@ -280,7 +291,8 @@ class BaseCSVDataHandler(DataHandler):
280
291
  try:
281
292
  return getattr(bars_list[-1][1], val_type)
282
293
  except AttributeError:
283
- print(f"Value type {val_type} not available in the historical data set.")
294
+ print(
295
+ f"Value type {val_type} not available in the historical data set.")
284
296
  raise
285
297
 
286
298
  def get_latest_bars_values(self, symbol: str, val_type: str, N=1) -> np.ndarray:
@@ -297,7 +309,8 @@ class BaseCSVDataHandler(DataHandler):
297
309
  try:
298
310
  return np.array([getattr(b[1], val_type) for b in bars_list])
299
311
  except AttributeError:
300
- print(f"Value type {val_type} not available in the historical data set.")
312
+ print(
313
+ f"Value type {val_type} not available in the historical data set.")
301
314
  raise
302
315
 
303
316
  def update_bars(self):
@@ -338,18 +351,18 @@ class CSVDataHandler(BaseCSVDataHandler):
338
351
  events (Queue): The Event Queue.
339
352
  symbol_list (List[str]): A list of symbol strings.
340
353
  csv_dir (str): Absolute directory path to the CSV files.
341
-
354
+
342
355
  NOTE:
343
356
  All csv fille can be strored in 'Home/.bbstrader/csv_data'
344
357
 
345
358
  """
346
359
  csv_dir = kwargs.get("csv_dir")
347
- csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
360
+ csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
348
361
  super().__init__(
349
- events,
350
- symbol_list,
362
+ events,
363
+ symbol_list,
351
364
  csv_dir,
352
- columns =kwargs.get('columns'),
365
+ columns=kwargs.get('columns'),
353
366
  index_col=kwargs.get('index_col', 0)
354
367
  )
355
368
 
@@ -383,35 +396,35 @@ class MT5DataHandler(BaseCSVDataHandler):
383
396
  See `bbstrader.metatrader.rates.Rates` for other arguments.
384
397
  See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
385
398
  """
386
- self.tf = kwargs.get('time_frame', 'D1')
387
- self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
388
- self.end = kwargs.get('mt5_end', datetime.now())
389
- self.use_utc = kwargs.get('use_utc', False)
390
- self.filer = kwargs.get('filter', False)
391
- self.fill_na = kwargs.get('fill_na', False)
399
+ self.tf = kwargs.get('time_frame', 'D1')
400
+ self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
401
+ self.end = kwargs.get('mt5_end', datetime.now())
402
+ self.use_utc = kwargs.get('use_utc', False)
403
+ self.filer = kwargs.get('filter', False)
404
+ self.fill_na = kwargs.get('fill_na', False)
392
405
  self.lower_cols = kwargs.get('lower_cols', True)
393
- self.data_dir = kwargs.get('data_dir')
406
+ self.data_dir = kwargs.get('data_dir')
394
407
  self.symbol_list = symbol_list
395
408
  self.kwargs = kwargs
396
-
409
+
397
410
  csv_dir = self._download_and_cache_data(self.data_dir)
398
411
  super().__init__(
399
412
  events,
400
413
  symbol_list,
401
414
  csv_dir,
402
- columns =kwargs.get('columns'),
415
+ columns=kwargs.get('columns'),
403
416
  index_col=kwargs.get('index_col', 0)
404
417
  )
405
418
 
406
419
  def _download_and_cache_data(self, cache_dir: str):
407
- data_dir = cache_dir or BBSTRADER_DIR / 'mt5' / self.tf
420
+ data_dir = cache_dir or BBSTRADER_DIR / 'mt5' / self.tf
408
421
  data_dir.mkdir(parents=True, exist_ok=True)
409
422
  for symbol in self.symbol_list:
410
423
  try:
411
424
  data = download_historical_data(
412
425
  symbol=symbol,
413
426
  timeframe=self.tf,
414
- date_from=self.start,
427
+ date_from=self.start,
415
428
  date_to=self.end,
416
429
  utc=self.use_utc,
417
430
  filter=self.filer,
@@ -452,17 +465,17 @@ class YFDataHandler(BaseCSVDataHandler):
452
465
  See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
453
466
  """
454
467
  self.symbol_list = symbol_list
455
- self.start_date = kwargs.get('yf_start')
456
- self.end_date = kwargs.get('yf_end', datetime.now())
457
- self.cache_dir = kwargs.get('data_dir')
458
-
468
+ self.start_date = kwargs.get('yf_start')
469
+ self.end_date = kwargs.get('yf_end', datetime.now())
470
+ self.cache_dir = kwargs.get('data_dir')
471
+
459
472
  csv_dir = self._download_and_cache_data(self.cache_dir)
460
-
473
+
461
474
  super().__init__(
462
475
  events,
463
476
  symbol_list,
464
477
  csv_dir,
465
- columns =kwargs.get('columns'),
478
+ columns=kwargs.get('columns'),
466
479
  index_col=kwargs.get('index_col', 0)
467
480
  )
468
481
 
@@ -474,7 +487,8 @@ class YFDataHandler(BaseCSVDataHandler):
474
487
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
475
488
  try:
476
489
  data = yf.download(
477
- symbol, start=self.start_date, end=self.end_date, multi_level_index=False)
490
+ symbol, start=self.start_date, end=self.end_date,
491
+ multi_level_index=False, progress=False)
478
492
  if data.empty:
479
493
  raise ValueError(f"No data found for {symbol}")
480
494
  data.to_csv(filepath)
@@ -491,6 +505,7 @@ class EODHDataHandler(BaseCSVDataHandler):
491
505
  To use this class, you need to sign up for an API key at
492
506
  https://eodhistoricaldata.com/ and provide the key as an argument.
493
507
  """
508
+
494
509
  def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
495
510
  """
496
511
  Args:
@@ -506,19 +521,20 @@ class EODHDataHandler(BaseCSVDataHandler):
506
521
  See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
507
522
  """
508
523
  self.symbol_list = symbol_list
509
- self.start_date = kwargs.get('eodhd_start')
510
- self.end_date = kwargs.get('eodhd_end', datetime.now().strftime('%Y-%m-%d'))
511
- self.period = kwargs.get('eodhd_period', 'd')
512
- self.cache_dir = kwargs.get('data_dir')
513
- self.__api_key = kwargs.get('eodhd_api_key', 'demo')
524
+ self.start_date = kwargs.get('eodhd_start')
525
+ self.end_date = kwargs.get(
526
+ 'eodhd_end', datetime.now().strftime('%Y-%m-%d'))
527
+ self.period = kwargs.get('eodhd_period', 'd')
528
+ self.cache_dir = kwargs.get('data_dir')
529
+ self.__api_key = kwargs.get('eodhd_api_key', 'demo')
514
530
 
515
531
  csv_dir = self._download_and_cache_data(self.cache_dir)
516
-
532
+
517
533
  super().__init__(
518
- events,
534
+ events,
519
535
  symbol_list,
520
536
  csv_dir,
521
- columns =kwargs.get('columns'),
537
+ columns=kwargs.get('columns'),
522
538
  index_col=kwargs.get('index_col', 0)
523
539
  )
524
540
 
@@ -529,7 +545,7 @@ class EODHDataHandler(BaseCSVDataHandler):
529
545
  if period in ['d', 'w', 'm']:
530
546
  return client.get_historical_data(
531
547
  symbol=symbol,
532
- interval=period,
548
+ interval=period,
533
549
  iso8601_start=self.start_date,
534
550
  iso8601_end=self.end_date,
535
551
  )
@@ -544,11 +560,11 @@ class EODHDataHandler(BaseCSVDataHandler):
544
560
  unix_end = int(enddt.timestamp())
545
561
  return client.get_intraday_historical_data(
546
562
  symbol=symbol,
547
- interval=period,
563
+ interval=period,
548
564
  from_unix_time=unix_start,
549
565
  to_unix_time=unix_end,
550
566
  )
551
-
567
+
552
568
  def _forma_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
553
569
  if isinstance(data, pd.DataFrame):
554
570
  if data.empty or len(data) == 0:
@@ -556,7 +572,7 @@ class EODHDataHandler(BaseCSVDataHandler):
556
572
  df = data.drop(labels=['symbol', 'interval'], axis=1)
557
573
  df = df.rename(columns={'adjusted_close': 'adj_close'})
558
574
  return df
559
-
575
+
560
576
  elif isinstance(data, list):
561
577
  if not data or len(data) == 0:
562
578
  raise ValueError("No data found.")
@@ -564,11 +580,12 @@ class EODHDataHandler(BaseCSVDataHandler):
564
580
  df = df.drop(columns=['timestamp', 'gmtoffset'], axis=1)
565
581
  df = df.rename(columns={'datetime': 'date'})
566
582
  df['adj_close'] = df['close']
567
- df = df[['date', 'open', 'high', 'low', 'close', 'adj_close', 'volume']]
583
+ df = df[['date', 'open', 'high', 'low',
584
+ 'close', 'adj_close', 'volume']]
568
585
  df.date = pd.to_datetime(df.date)
569
586
  df = df.set_index('date')
570
587
  return df
571
-
588
+
572
589
  def _download_and_cache_data(self, cache_dir: str):
573
590
  """Downloads and caches historical data as CSV files."""
574
591
  cache_dir = cache_dir or BBSTRADER_DIR / 'eodhd' / self.period
@@ -594,6 +611,7 @@ class FMPDataHandler(BaseCSVDataHandler):
594
611
  provide the key as an argument.
595
612
 
596
613
  """
614
+
597
615
  def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
598
616
  """
599
617
  Args:
@@ -610,19 +628,20 @@ class FMPDataHandler(BaseCSVDataHandler):
610
628
  See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
611
629
  """
612
630
  self.symbol_list = symbol_list
613
- self.start_date = kwargs.get('fmp_start')
614
- self.end_date = kwargs.get('fmp_end', datetime.now().strftime('%Y-%m-%d'))
615
- self.period = kwargs.get('fmp_period', 'daily')
616
- self.cache_dir = kwargs.get('data_dir')
617
- self.__api_key = kwargs.get('fmp_api_key')
631
+ self.start_date = kwargs.get('fmp_start')
632
+ self.end_date = kwargs.get(
633
+ 'fmp_end', datetime.now().strftime('%Y-%m-%d'))
634
+ self.period = kwargs.get('fmp_period', 'daily')
635
+ self.cache_dir = kwargs.get('data_dir')
636
+ self.__api_key = kwargs.get('fmp_api_key')
618
637
 
619
638
  csv_dir = self._download_and_cache_data(self.cache_dir)
620
-
639
+
621
640
  super().__init__(
622
641
  events,
623
642
  symbol_list,
624
643
  csv_dir,
625
- columns =kwargs.get('columns'),
644
+ columns=kwargs.get('columns'),
626
645
  index_col=kwargs.get('index_col', 0)
627
646
  )
628
647
 
@@ -632,24 +651,26 @@ class FMPDataHandler(BaseCSVDataHandler):
632
651
  toolkit = Toolkit(
633
652
  symbol,
634
653
  api_key=self.__api_key,
635
- start_date=self.start_date,
654
+ start_date=self.start_date,
636
655
  end_date=self.end_date,
637
- benchmark_ticker=None
656
+ benchmark_ticker=None,
657
+ progress_bar=False
638
658
  )
639
659
  if period in ['daily', 'weekly', 'monthly', 'quarterly', 'yearly']:
640
- return toolkit.get_historical_data(period=period)
660
+ return toolkit.get_historical_data(period=period, progress_bar=False)
641
661
  elif period in ['1min', '5min', '15min', '30min', '1hour']:
642
- return toolkit.get_intraday_data(period=period)
643
-
662
+ return toolkit.get_intraday_data(period=period, progress_bar=False)
663
+
644
664
  def _format_data(self, data: pd.DataFrame, period: str) -> pd.DataFrame:
645
665
  if data.empty or len(data) == 0:
646
666
  raise ValueError("No data found.")
647
667
  if period[0].isnumeric():
648
- data = data.drop(columns=['Return', 'Volatility', 'Cumulative Return'], axis=1)
668
+ data = data.drop(
669
+ columns=['Return', 'Volatility', 'Cumulative Return'], axis=1)
649
670
  else:
650
- data = data.drop(columns=['Dividends', 'Return', 'Volatility',
651
- 'Excess Return', 'Excess Volatility',
652
- 'Cumulative Return'], axis=1)
671
+ data = data.drop(columns=['Dividends', 'Return', 'Volatility',
672
+ 'Excess Return', 'Excess Volatility',
673
+ 'Cumulative Return'], axis=1)
653
674
  data = data.reset_index()
654
675
  if 'Adj Close' not in data.columns:
655
676
  data['Adj Close'] = data['Close']
@@ -657,7 +678,7 @@ class FMPDataHandler(BaseCSVDataHandler):
657
678
  data['date'] = pd.to_datetime(data['date'])
658
679
  data.set_index('date', inplace=True)
659
680
  return data
660
-
681
+
661
682
  def _download_and_cache_data(self, cache_dir: str):
662
683
  """Downloads and caches historical data as CSV files."""
663
684
  cache_dir = cache_dir or BBSTRADER_DIR / 'fmp' / self.period
@@ -675,4 +696,4 @@ class FMPDataHandler(BaseCSVDataHandler):
675
696
 
676
697
  # TODO Add data Handlers for Interactive Brokers
677
698
  class TWSDataHandler(BaseCSVDataHandler):
678
- ...
699
+ ...
@@ -135,7 +135,8 @@ class OrderEvent(Event):
135
135
  """
136
136
  print(
137
137
  "Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s, Price=%s" %
138
- (self.symbol, self.order_type, self.quantity, self.direction, self.price)
138
+ (self.symbol, self.order_type,
139
+ self.quantity, self.direction, self.price)
139
140
  )
140
141
 
141
142