bbstrader 0.1.93__tar.gz → 0.2.0__tar.gz

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 (43) hide show
  1. {bbstrader-0.1.93/bbstrader.egg-info → bbstrader-0.2.0}/PKG-INFO +7 -1
  2. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/__ini__.py +2 -2
  3. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/data.py +241 -40
  4. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/strategy.py +12 -8
  5. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/config.py +4 -0
  6. bbstrader-0.2.0/bbstrader/core/data.py +23 -0
  7. bbstrader-0.2.0/bbstrader/ibkr/__init__.py +0 -0
  8. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/metatrader/account.py +66 -12
  9. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/metatrader/rates.py +24 -20
  10. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/metatrader/risk.py +6 -3
  11. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/metatrader/trade.py +31 -13
  12. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/models/__init__.py +1 -1
  13. bbstrader-0.2.0/bbstrader/models/factors.py +275 -0
  14. bbstrader-0.2.0/bbstrader/models/ml.py +1026 -0
  15. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/models/optimization.py +17 -16
  16. bbstrader-0.1.93/bbstrader/models/portfolios.py → bbstrader-0.2.0/bbstrader/models/portfolio.py +20 -11
  17. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/models/risk.py +10 -2
  18. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/trading/execution.py +67 -35
  19. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/trading/strategies.py +5 -5
  20. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/tseries.py +412 -63
  21. {bbstrader-0.1.93 → bbstrader-0.2.0/bbstrader.egg-info}/PKG-INFO +7 -1
  22. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader.egg-info/SOURCES.txt +5 -1
  23. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader.egg-info/requires.txt +7 -0
  24. {bbstrader-0.1.93 → bbstrader-0.2.0}/requirements.txt +5 -1
  25. {bbstrader-0.1.93 → bbstrader-0.2.0}/setup.py +4 -1
  26. {bbstrader-0.1.93 → bbstrader-0.2.0}/LICENSE +0 -0
  27. {bbstrader-0.1.93 → bbstrader-0.2.0}/MANIFEST.in +0 -0
  28. {bbstrader-0.1.93 → bbstrader-0.2.0}/README.md +0 -0
  29. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/__init__.py +0 -0
  30. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/backtest.py +0 -0
  31. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/event.py +0 -0
  32. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/execution.py +0 -0
  33. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/performance.py +0 -0
  34. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/btengine/portfolio.py +0 -0
  35. /bbstrader-0.1.93/bbstrader/models/factors.py → /bbstrader-0.2.0/bbstrader/core/__init__.py +0 -0
  36. /bbstrader-0.1.93/bbstrader/models/ml.py → /bbstrader-0.2.0/bbstrader/core/utils.py +0 -0
  37. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/metatrader/__init__.py +0 -0
  38. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/metatrader/utils.py +0 -0
  39. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/trading/__init__.py +0 -0
  40. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader/trading/scripts.py +0 -0
  41. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader.egg-info/dependency_links.txt +0 -0
  42. {bbstrader-0.1.93 → bbstrader-0.2.0}/bbstrader.egg-info/top_level.txt +0 -0
  43. {bbstrader-0.1.93 → bbstrader-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bbstrader
3
- Version: 0.1.93
3
+ Version: 0.2.0
4
4
  Summary: Simplified Investment & Trading Toolkit
5
5
  Home-page: https://github.com/bbalouki/bbstrader
6
6
  Download-URL: https://pypi.org/project/bbstrader/
@@ -47,8 +47,14 @@ Requires-Dist: scikit-learn
47
47
  Requires-Dist: notify-py
48
48
  Requires-Dist: python-telegram-bot
49
49
  Requires-Dist: pyportfolioopt
50
+ Requires-Dist: eodhd
51
+ Requires-Dist: financetoolkit
52
+ Requires-Dist: lightgbm
53
+ Requires-Dist: alphalens-reloaded
50
54
  Provides-Extra: mt5
51
55
  Requires-Dist: MetaTrader5; extra == "mt5"
56
+ Provides-Extra: ta
57
+ Requires-Dist: ta-lib; extra == "ta"
52
58
 
53
59
  # Simplified Investment & Trading Toolkit
54
60
  ![bbstrader](https://github.com/bbalouki/bbstrader/blob/main/assets/bbstrader_logo.png?raw=true)
@@ -5,9 +5,9 @@ Simplified Investment & Trading Toolkit
5
5
  """
6
6
  __author__ = "Bertin Balouki SIMYELI"
7
7
  __copyright__ = "2023-2024 Bertin Balouki SIMYELI"
8
- __email__ = "bbalouki@outlook.com"
8
+ __email__ = "bertin@bbstrader.com"
9
9
  __license__ = "MIT"
10
- __version__ = '0.1.91'
10
+ __version__ = '0.2.0'
11
11
 
12
12
 
13
13
  from bbstrader import btengine
@@ -10,13 +10,18 @@ from bbstrader.metatrader.rates import download_historical_data
10
10
  from bbstrader.btengine.event import MarketEvent
11
11
  from bbstrader.config import BBSTRADER_DIR
12
12
  from datetime import datetime
13
+ from eodhd import APIClient
14
+ from financetoolkit import Toolkit
15
+ from pytz import timezone
13
16
 
14
17
 
15
18
  __all__ = [
16
19
  "DataHandler",
17
20
  "CSVDataHandler",
18
21
  "MT5DataHandler",
19
- "YFDataHandler"
22
+ "YFDataHandler",
23
+ "EODHDataHandler",
24
+ "FMPDataHandler",
20
25
  ]
21
26
 
22
27
 
@@ -148,12 +153,19 @@ class BaseCSVDataHandler(DataHandler):
148
153
  @property
149
154
  def symbols(self)-> List[str]:
150
155
  return self.symbol_list
156
+
151
157
  @property
152
158
  def data(self)-> Dict[str, pd.DataFrame]:
153
159
  return self.symbol_data
160
+
161
+ @property
162
+ def datadir(self)-> str:
163
+ return self.csv_dir
164
+
154
165
  @property
155
166
  def labels(self)-> List[str]:
156
167
  return self.columns
168
+
157
169
  @property
158
170
  def index(self)-> str | List[str]:
159
171
  return self._index
@@ -167,7 +179,7 @@ class BaseCSVDataHandler(DataHandler):
167
179
  os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
168
180
  ).columns.to_list()
169
181
  new_names = self.columns or default_names
170
- new_names = [name.lower().replace(' ', '_') for name in new_names]
182
+ new_names = [name.strip().lower().replace(' ', '_') for name in new_names]
171
183
  self.columns = new_names
172
184
  assert 'adj_close' in new_names or 'close' in new_names, \
173
185
  "Column names must contain 'Adj Close' and 'Close' or adj_close and close"
@@ -198,6 +210,7 @@ class BaseCSVDataHandler(DataHandler):
198
210
  'adj_close' if 'adj_close' in new_names else 'close'
199
211
  ].pct_change().dropna()
200
212
  self._index = self.symbol_data[s].index.name
213
+ self.symbol_data[s].to_csv(os.path.join(self.csv_dir, f'{s}.csv'))
201
214
  if self.events is not None:
202
215
  self.symbol_data[s] = self.symbol_data[s].iterrows()
203
216
 
@@ -340,7 +353,13 @@ class CSVDataHandler(BaseCSVDataHandler):
340
353
  """
341
354
  csv_dir = kwargs.get("csv_dir")
342
355
  csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
343
- super().__init__(events, symbol_list, csv_dir)
356
+ super().__init__(
357
+ events,
358
+ symbol_list,
359
+ csv_dir,
360
+ columns =kwargs.get('columns'),
361
+ index_col=kwargs.get('index_col', 0)
362
+ )
344
363
 
345
364
 
346
365
  class MT5DataHandler(BaseCSVDataHandler):
@@ -372,32 +391,41 @@ class MT5DataHandler(BaseCSVDataHandler):
372
391
  See `bbstrader.metatrader.rates.Rates` for other arguments.
373
392
  See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
374
393
  """
375
- self.tf = kwargs.get('time_frame', 'D1')
376
- self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
377
- self.end = kwargs.get('mt5_end', datetime.now())
378
- self.use_utc = kwargs.get('use_utc', False)
379
- self.filer = kwargs.get('filter', False)
380
- self.fill_na = kwargs.get('fill_na', False)
394
+ self.tf = kwargs.get('time_frame', 'D1')
395
+ self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
396
+ self.end = kwargs.get('mt5_end', datetime.now())
397
+ self.use_utc = kwargs.get('use_utc', False)
398
+ self.filer = kwargs.get('filter', False)
399
+ self.fill_na = kwargs.get('fill_na', False)
381
400
  self.lower_cols = kwargs.get('lower_cols', True)
382
- self.data_dir = kwargs.get('data_dir')
401
+ self.data_dir = kwargs.get('data_dir')
383
402
  self.symbol_list = symbol_list
384
- csv_dir = self._download_data(self.data_dir)
385
- super().__init__(events, symbol_list, csv_dir)
403
+ self.kwargs = kwargs
404
+
405
+ csv_dir = self._download_and_cache_data(self.data_dir)
406
+ super().__init__(
407
+ events,
408
+ symbol_list,
409
+ csv_dir,
410
+ columns =kwargs.get('columns'),
411
+ index_col=kwargs.get('index_col', 0)
412
+ )
386
413
 
387
- def _download_data(self, cache_dir: str):
388
- data_dir = cache_dir or BBSTRADER_DIR / 'mt5_data' / self.tf
414
+ def _download_and_cache_data(self, cache_dir: str):
415
+ data_dir = cache_dir or BBSTRADER_DIR / 'mt5' / self.tf
389
416
  data_dir.mkdir(parents=True, exist_ok=True)
390
417
  for symbol in self.symbol_list:
391
418
  try:
392
419
  data = download_historical_data(
393
420
  symbol=symbol,
394
- time_frame=self.tf,
421
+ timeframe=self.tf,
395
422
  date_from=self.start,
396
- date_to=self.end,
423
+ date_to=self.end,
397
424
  utc=self.use_utc,
398
425
  filter=self.filer,
399
426
  fill_na=self.fill_na,
400
- lower_colnames=self.lower_cols
427
+ lower_colnames=self.lower_cols,
428
+ **self.kwargs
401
429
  )
402
430
  if data is None:
403
431
  raise ValueError(f"No data found for {symbol}")
@@ -432,56 +460,229 @@ class YFDataHandler(BaseCSVDataHandler):
432
460
  See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
433
461
  """
434
462
  self.symbol_list = symbol_list
435
- self.start_date = kwargs.get('yf_start', '2000-01-01')
436
- self.end_date = kwargs.get('yf_end', datetime.now().strftime('%Y-%m-%d'))
437
- self.cache_dir = kwargs.get('data_dir')
463
+ self.start_date = kwargs.get('yf_start')
464
+ self.end_date = kwargs.get('yf_end', datetime.now())
465
+ self.cache_dir = kwargs.get('data_dir')
466
+
438
467
  csv_dir = self._download_and_cache_data(self.cache_dir)
439
- super().__init__(events, symbol_list, csv_dir)
468
+
469
+ super().__init__(
470
+ events,
471
+ symbol_list,
472
+ csv_dir,
473
+ columns =kwargs.get('columns'),
474
+ index_col=kwargs.get('index_col', 0)
475
+ )
440
476
 
441
477
  def _download_and_cache_data(self, cache_dir: str):
442
478
  """Downloads and caches historical data as CSV files."""
443
- cache_dir = cache_dir or BBSTRADER_DIR / 'yf_data' / 'daily'
479
+ cache_dir = cache_dir or BBSTRADER_DIR / 'yfinance' / 'daily'
444
480
  os.makedirs(cache_dir, exist_ok=True)
445
481
  for symbol in self.symbol_list:
446
482
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
447
483
  try:
448
484
  data = yf.download(
449
- symbol, start=self.start_date, end=self.end_date, multi_level_index=False)
485
+ symbol, start=self.start_date, end=self.end_date,
486
+ multi_level_index=False, progress=False)
450
487
  if data.empty:
451
488
  raise ValueError(f"No data found for {symbol}")
452
- data.to_csv(filepath) # Cache the data
489
+ data.to_csv(filepath)
453
490
  except Exception as e:
454
491
  raise ValueError(f"Error downloading {symbol}: {e}")
455
492
  return cache_dir
456
493
 
457
494
 
458
- # TODO # Get data from EODHD
459
- # https://eodhd.com/
460
495
  class EODHDataHandler(BaseCSVDataHandler):
461
- ...
496
+ """
497
+ Downloads historical data from EOD Historical Data.
498
+ Data is fetched using the `eodhd` library.
462
499
 
463
- # TODO # Get data from FMP using Financialtoolkit API
464
- # https://github.com/bbalouki/FinanceToolkit
465
- class FMPDataHandler(BaseCSVDataHandler):
466
- ...
500
+ To use this class, you need to sign up for an API key at
501
+ https://eodhistoricaldata.com/ and provide the key as an argument.
502
+ """
503
+ def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
504
+ """
505
+ Args:
506
+ events (Queue): The Event Queue for passing market events.
507
+ symbol_list (list[str]): List of symbols to download data for.
508
+ eodhd_start (str): Start date for historical data (YYYY-MM-DD).
509
+ eodhd_end (str): End date for historical data (YYYY-MM-DD).
510
+ data_dir (str, optional): Directory for caching data .
511
+ eodhd_period (str, optional): Time period for historical data (e.g., 'd', 'w', 'm', '1m', '5m', '1h').
512
+ eodhd_api_key (str, optional): API key for EOD Historical Data.
467
513
 
514
+ Note:
515
+ See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
516
+ """
517
+ self.symbol_list = symbol_list
518
+ self.start_date = kwargs.get('eodhd_start')
519
+ self.end_date = kwargs.get('eodhd_end', datetime.now().strftime('%Y-%m-%d'))
520
+ self.period = kwargs.get('eodhd_period', 'd')
521
+ self.cache_dir = kwargs.get('data_dir')
522
+ self.__api_key = kwargs.get('eodhd_api_key', 'demo')
523
+
524
+ csv_dir = self._download_and_cache_data(self.cache_dir)
525
+
526
+ super().__init__(
527
+ events,
528
+ symbol_list,
529
+ csv_dir,
530
+ columns =kwargs.get('columns'),
531
+ index_col=kwargs.get('index_col', 0)
532
+ )
468
533
 
469
- class AlgoseekDataHandler(BaseCSVDataHandler):
470
- ...
534
+ def _get_data(self, symbol: str, period) -> pd.DataFrame | List[Dict]:
535
+ if not self.__api_key:
536
+ raise ValueError("API key is required for EODHD data.")
537
+ client = APIClient(api_key=self.__api_key)
538
+ if period in ['d', 'w', 'm']:
539
+ return client.get_historical_data(
540
+ symbol=symbol,
541
+ interval=period,
542
+ iso8601_start=self.start_date,
543
+ iso8601_end=self.end_date,
544
+ )
545
+ elif period in ['1m', '5m', '1h']:
546
+ hms = ' 00:00:00'
547
+ fmt = '%Y-%m-%d %H:%M:%S'
548
+ startdt = datetime.strptime(self.start_date + hms, fmt)
549
+ enddt = datetime.strptime(self.end_date + hms, fmt)
550
+ startdt = startdt.replace(tzinfo=timezone('UTC'))
551
+ enddt = enddt.replace(tzinfo=timezone('UTC'))
552
+ unix_start = int(startdt.timestamp())
553
+ unix_end = int(enddt.timestamp())
554
+ return client.get_intraday_historical_data(
555
+ symbol=symbol,
556
+ interval=period,
557
+ from_unix_time=unix_start,
558
+ to_unix_time=unix_end,
559
+ )
560
+
561
+ def _forma_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
562
+ if isinstance(data, pd.DataFrame):
563
+ if data.empty or len(data) == 0:
564
+ raise ValueError("No data found.")
565
+ df = data.drop(labels=['symbol', 'interval'], axis=1)
566
+ df = df.rename(columns={'adjusted_close': 'adj_close'})
567
+ return df
568
+
569
+ elif isinstance(data, list):
570
+ if not data or len(data) == 0:
571
+ raise ValueError("No data found.")
572
+ df = pd.DataFrame(data)
573
+ df = df.drop(columns=['timestamp', 'gmtoffset'], axis=1)
574
+ df = df.rename(columns={'datetime': 'date'})
575
+ df['adj_close'] = df['close']
576
+ df = df[['date', 'open', 'high', 'low', 'close', 'adj_close', 'volume']]
577
+ df.date = pd.to_datetime(df.date)
578
+ df = df.set_index('date')
579
+ return df
580
+
581
+ def _download_and_cache_data(self, cache_dir: str):
582
+ """Downloads and caches historical data as CSV files."""
583
+ cache_dir = cache_dir or BBSTRADER_DIR / 'eodhd' / self.period
584
+ os.makedirs(cache_dir, exist_ok=True)
585
+ for symbol in self.symbol_list:
586
+ filepath = os.path.join(cache_dir, f"{symbol}.csv")
587
+ try:
588
+ data = self._get_data(symbol, self.period)
589
+ data = self._forma_data(data)
590
+ data.to_csv(filepath)
591
+ except Exception as e:
592
+ raise ValueError(f"Error downloading {symbol}: {e}")
593
+ return cache_dir
471
594
 
472
595
 
473
- class BaseFMPDataHanler(object):
596
+ class FMPDataHandler(BaseCSVDataHandler):
474
597
  """
475
- This will serve as the base class for all other FMP data
476
- that is not historical data and does not have an OHLC structure.
598
+ Downloads historical data from Financial Modeling Prep (FMP).
599
+ Data is fetched using the `financetoolkit` library.
600
+
601
+ To use this class, you need to sign up for an API key at
602
+ https://financialmodelingprep.com/developer/docs/pricing and
603
+ provide the key as an argument.
604
+
477
605
  """
478
- ...
606
+ def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
607
+ """
608
+ Args:
609
+ events (Queue): The Event Queue for passing market events.
610
+ symbol_list (list[str]): List of symbols to download data for.
611
+ fmp_start (str): Start date for historical data (YYYY-MM-DD).
612
+ fmp_end (str): End date for historical data (YYYY-MM-DD).
613
+ data_dir (str, optional): Directory for caching data .
614
+ fmp_period (str, optional): Time period for historical data
615
+ (e.g. daily, weekly, monthly, quarterly, yearly, "1min", "5min", "15min", "30min", "1hour").
616
+ fmp_api_key (str): API key for Financial Modeling Prep.
479
617
 
618
+ Note:
619
+ See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
620
+ """
621
+ self.symbol_list = symbol_list
622
+ self.start_date = kwargs.get('fmp_start')
623
+ self.end_date = kwargs.get('fmp_end', datetime.now().strftime('%Y-%m-%d'))
624
+ self.period = kwargs.get('fmp_period', 'daily')
625
+ self.cache_dir = kwargs.get('data_dir')
626
+ self.__api_key = kwargs.get('fmp_api_key')
480
627
 
481
- class FMPFundamentalDataHandler(BaseFMPDataHanler):
482
- ...
628
+ csv_dir = self._download_and_cache_data(self.cache_dir)
629
+
630
+ super().__init__(
631
+ events,
632
+ symbol_list,
633
+ csv_dir,
634
+ columns =kwargs.get('columns'),
635
+ index_col=kwargs.get('index_col', 0)
636
+ )
483
637
 
484
- # TODO Add other Handlers for FMP
638
+ def _get_data(self, symbol: str, period: str) -> pd.DataFrame:
639
+ if not self.__api_key:
640
+ raise ValueError("API key is required for FMP data.")
641
+ toolkit = Toolkit(
642
+ symbol,
643
+ api_key=self.__api_key,
644
+ start_date=self.start_date,
645
+ end_date=self.end_date,
646
+ benchmark_ticker=None,
647
+ progress_bar=False
648
+ )
649
+ if period in ['daily', 'weekly', 'monthly', 'quarterly', 'yearly']:
650
+ return toolkit.get_historical_data(period=period, progress_bar=False)
651
+ elif period in ['1min', '5min', '15min', '30min', '1hour']:
652
+ return toolkit.get_intraday_data(period=period, progress_bar=False)
653
+
654
+ def _format_data(self, data: pd.DataFrame, period: str) -> pd.DataFrame:
655
+ if data.empty or len(data) == 0:
656
+ raise ValueError("No data found.")
657
+ if period[0].isnumeric():
658
+ data = data.drop(columns=['Return', 'Volatility', 'Cumulative Return'], axis=1)
659
+ else:
660
+ data = data.drop(columns=['Dividends', 'Return', 'Volatility',
661
+ 'Excess Return', 'Excess Volatility',
662
+ 'Cumulative Return'], axis=1)
663
+ data = data.reset_index()
664
+ if 'Adj Close' not in data.columns:
665
+ data['Adj Close'] = data['Close']
666
+ data['date'] = data['date'].dt.to_timestamp()
667
+ data['date'] = pd.to_datetime(data['date'])
668
+ data.set_index('date', inplace=True)
669
+ return data
670
+
671
+ def _download_and_cache_data(self, cache_dir: str):
672
+ """Downloads and caches historical data as CSV files."""
673
+ cache_dir = cache_dir or BBSTRADER_DIR / 'fmp' / self.period
674
+ os.makedirs(cache_dir, exist_ok=True)
675
+ for symbol in self.symbol_list:
676
+ filepath = os.path.join(cache_dir, f"{symbol}.csv")
677
+ try:
678
+ data = self._get_data(symbol, self.period)
679
+ data = self._format_data(data, self.period)
680
+ data.to_csv(filepath)
681
+ except Exception as e:
682
+ raise ValueError(f"Error downloading {symbol}: {e}")
683
+ return cache_dir
485
684
 
486
685
 
487
686
  # TODO Add data Handlers for Interactive Brokers
687
+ class TWSDataHandler(BaseCSVDataHandler):
688
+ ...
@@ -86,6 +86,7 @@ class MT5Strategy(Strategy):
86
86
  self.tf = kwargs.get("time_frame", 'D1')
87
87
  self.logger = kwargs.get("logger")
88
88
  self._initialize_portfolio()
89
+ self.kwargs = kwargs
89
90
 
90
91
  @property
91
92
  def cash(self) -> float:
@@ -201,19 +202,22 @@ class MT5Strategy(Strategy):
201
202
  - ``action``: The action to take for the symbol (LONG, SHORT, EXIT, etc.)
202
203
  - ``price``: The price at which to execute the action.
203
204
  - ``stoplimit``: The stop-limit price for STOP-LIMIT orders.
205
+ - ``id``: The unique identifier for the strategy or order.
204
206
 
205
- The dictionary can be use for pending orders (limit, stop, stop-limit) where the price is required.
207
+ The dictionary can be use for pending orders (limit, stop, stop-limit) where the price is required
208
+ or for executing orders where the each order has a unique identifier.
206
209
  """
207
210
  pass
208
211
 
209
- def apply_risk_management(self, optimer, freq=252) -> Dict[str, float] | None:
212
+ def apply_risk_management(self, optimer, symbols=None, freq=252) -> Dict[str, float] | None:
210
213
  """
211
214
  Apply risk management rules to the strategy.
212
215
  """
213
216
  if optimer is None:
214
217
  return None
218
+ symbols = symbols or self.symbols
215
219
  prices = self.get_asset_values(
216
- symbol_list=self.symbols, bars=self.data, mode=self.mode,
220
+ symbol_list=symbols, bars=self.data, mode=self.mode,
217
221
  window=freq, value_type='close', array=False, tf=self.tf
218
222
  )
219
223
  prices = pd.DataFrame(prices)
@@ -222,7 +226,7 @@ class MT5Strategy(Strategy):
222
226
  weights = optimized_weights(prices=prices, freq=freq, method=optimer)
223
227
  return {symbol: weight for symbol, weight in weights.items()}
224
228
  except Exception:
225
- return {symbol: 0.0 for symbol in self.symbols}
229
+ return {symbol: 0.0 for symbol in symbols}
226
230
 
227
231
  def get_quantity(self, symbol, weight, price=None, volume=None, maxqty=None) -> int:
228
232
  """
@@ -530,7 +534,7 @@ class MT5Strategy(Strategy):
530
534
  asset_values[asset] = getattr(values, value_type)
531
535
  elif mode == 'live':
532
536
  for asset in symbol_list:
533
- rates = Rates(symbol=asset, time_frame=tf, count=window + 1)
537
+ rates = Rates(asset, timeframe=tf, count=window + 1, **self.kwargs)
534
538
  if array:
535
539
  values = getattr(rates, value_type).values
536
540
  asset_values[asset] = values[~np.isnan(values)]
@@ -575,11 +579,11 @@ class MT5Strategy(Strategy):
575
579
  Returns:
576
580
  bool : True if there are open positions, False otherwise
577
581
  """
578
- account = account or Account()
582
+ account = account or Account(**self.kwargs)
579
583
  positions = account.get_positions(symbol=symbol)
580
584
  if positions is not None:
581
585
  open_positions = [
582
- pos for pos in positions if pos.type == position
586
+ pos.ticket for pos in positions if pos.type == position
583
587
  and pos.magic == strategy_id
584
588
  ]
585
589
  if one_true:
@@ -600,7 +604,7 @@ class MT5Strategy(Strategy):
600
604
  Returns:
601
605
  prices : numpy array of buy or sell prices for open positions if any or an empty array.
602
606
  """
603
- account = account or Account()
607
+ account = account or Account(**self.kwargs)
604
608
  positions = account.get_positions(symbol=symbol)
605
609
  if positions is not None:
606
610
  prices = np.array([
@@ -3,6 +3,10 @@ from typing import List
3
3
  from pathlib import Path
4
4
  from datetime import datetime
5
5
 
6
+
7
+ ADMIRAL_PATH = "C:\\Program Files\\Admirals Group MT5 Terminal\\terminal64.exe"
8
+ FTMO_PATH = "C:\\Program Files\\FTMO MetaTrader 5\\terminal64.exe"
9
+
6
10
  def get_config_dir(name: str=".bbstrader") -> Path:
7
11
  """
8
12
  Get the path to the configuration directory.
@@ -0,0 +1,23 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from financetoolkit import Toolkit
4
+
5
+
6
+ __all__ = [
7
+ 'FMP',
8
+ ]
9
+
10
+ class FMP(Toolkit):
11
+ """
12
+ FMPData class for fetching data from Financial Modeling Prep API
13
+ using the Toolkit class from financetoolkit package.
14
+
15
+ See `financetoolkit` for more details.
16
+
17
+ """
18
+ def __init__(self, api_key: str ='', symbols: str | list = 'AAPL'):
19
+ super().__init__(tickers=symbols, api_key=api_key)
20
+
21
+
22
+ class DataBendo:
23
+ ...
File without changes
@@ -125,9 +125,57 @@ AMG_EXCHANGES = {
125
125
  'XSWX': r"Switzerland.*\(SWX\)"
126
126
  }
127
127
 
128
- def check_mt5_connection():
128
+ def check_mt5_connection(**kwargs):
129
+ """
130
+ Initialize the connection to the MetaTrader 5 terminal.
131
+
132
+ Args:
133
+ path (str, optional): The path to the MetaTrader 5 terminal executable file.
134
+ Defaults to None (e.g., "C:\\Program Files\\MetaTrader 5\\terminal64.exe").
135
+ login (int, optional): The login ID of the trading account. Defaults to None.
136
+ password (str, optional): The password of the trading account. Defaults to None.
137
+ server (str, optional): The name of the trade server to which the client terminal is connected.
138
+ Defaults to None.
139
+ timeout (int, optional): Connection timeout in milliseconds. Defaults to 60_000.
140
+ portable (bool, optional): If True, the portable mode of the terminal is used.
141
+ Defaults to False (See https://www.metatrader5.com/en/terminal/help/start_advanced/start#portable).
142
+
143
+ Notes:
144
+ If you want to lunch multiple terminal instances:
145
+ - Follow these instructions to lunch each terminal in portable mode first:
146
+ https://www.metatrader5.com/en/terminal/help/start_advanced/start#configuration_file
147
+ """
148
+ path = kwargs.get('path', None)
149
+ login = kwargs.get('login', None)
150
+ password = kwargs.get('password', None)
151
+ server = kwargs.get('server', None)
152
+ timeout = kwargs.get('timeout', 60_000)
153
+ portable = kwargs.get('portable', False)
154
+
155
+ if path is None and (login or password or server):
156
+ raise ValueError(
157
+ f"You must provide a path to the terminal executable file"
158
+ f"when providing login, password or server"
159
+ )
129
160
  try:
130
- init = mt5.initialize()
161
+ if path is not None:
162
+ if (
163
+ login is not None and
164
+ password is not None and
165
+ server is not None
166
+ ):
167
+ init = mt5.initialize(
168
+ path=path,
169
+ login=login,
170
+ password=password,
171
+ server=server,
172
+ timeout=timeout,
173
+ portable=portable
174
+ )
175
+ else:
176
+ init = mt5.initialize(path=path)
177
+ else:
178
+ init = mt5.initialize()
131
179
  if not init:
132
180
  raise_mt5_error(INIT_MSG)
133
181
  except Exception:
@@ -135,9 +183,9 @@ def check_mt5_connection():
135
183
 
136
184
 
137
185
  class Broker(object):
138
- def __init__(self, name: str=None):
186
+ def __init__(self, name: str=None, **kwargs):
139
187
  if name is None:
140
- check_mt5_connection()
188
+ check_mt5_connection(**kwargs)
141
189
  self._name = mt5.account_info().company
142
190
  else:
143
191
  self._name = name
@@ -157,8 +205,8 @@ class Broker(object):
157
205
 
158
206
 
159
207
  class AdmiralMarktsGroup(Broker):
160
- def __init__(self):
161
- super().__init__("Admirals Group AS")
208
+ def __init__(self, **kwargs):
209
+ super().__init__("Admirals Group AS", **kwargs)
162
210
 
163
211
  @property
164
212
  def timezone(self) -> str:
@@ -166,8 +214,8 @@ class AdmiralMarktsGroup(Broker):
166
214
 
167
215
 
168
216
  class JustGlobalMarkets(Broker):
169
- def __init__(self):
170
- super().__init__("Just Global Markets Ltd.")
217
+ def __init__(self, **kwargs):
218
+ super().__init__("Just Global Markets Ltd.", **kwargs)
171
219
 
172
220
  @property
173
221
  def timezone(self) -> str:
@@ -175,8 +223,8 @@ class JustGlobalMarkets(Broker):
175
223
 
176
224
 
177
225
  class FTMO(Broker):
178
- def __init__(self):
179
- super().__init__("FTMO S.R.O.")
226
+ def __init__(self, **kwargs):
227
+ super().__init__("FTMO S.R.O.", **kwargs)
180
228
 
181
229
  @property
182
230
  def timezone(self) -> str:
@@ -230,8 +278,14 @@ class Account(object):
230
278
  >>> trade_history = account.get_trade_history(from_date, to_date)
231
279
  """
232
280
 
233
- def __init__(self):
234
- check_mt5_connection()
281
+ def __init__(self, **kwargs):
282
+ """
283
+ Initialize the Account class.
284
+
285
+ See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
286
+
287
+ """
288
+ check_mt5_connection(**kwargs)
235
289
  self._check_brokers()
236
290
 
237
291
  def _check_brokers(self):