bbstrader 0.3.1__tar.gz → 0.3.3__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 (59) hide show
  1. {bbstrader-0.3.1/bbstrader.egg-info → bbstrader-0.3.3}/PKG-INFO +36 -41
  2. {bbstrader-0.3.1 → bbstrader-0.3.3}/README.md +1 -1
  3. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/__init__.py +1 -1
  4. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/__main__.py +7 -5
  5. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/backtest.py +7 -8
  6. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/data.py +3 -3
  7. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/execution.py +2 -2
  8. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/strategy.py +70 -17
  9. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/config.py +2 -2
  10. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/core/data.py +3 -1
  11. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/core/scripts.py +62 -19
  12. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/account.py +108 -23
  13. bbstrader-0.3.3/bbstrader/metatrader/copier.py +1319 -0
  14. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/rates.py +2 -2
  15. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/risk.py +1 -0
  16. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/scripts.py +35 -9
  17. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/trade.py +60 -43
  18. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/utils.py +3 -5
  19. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/models/__init__.py +0 -1
  20. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/models/ml.py +55 -26
  21. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/models/nlp.py +159 -89
  22. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/models/optimization.py +1 -1
  23. bbstrader-0.3.3/bbstrader/models/risk.py +28 -0
  24. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/trading/execution.py +109 -50
  25. bbstrader-0.3.3/bbstrader/trading/strategies.py +292 -0
  26. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/tseries.py +39 -711
  27. {bbstrader-0.3.1 → bbstrader-0.3.3/bbstrader.egg-info}/PKG-INFO +36 -41
  28. bbstrader-0.3.3/bbstrader.egg-info/requires.txt +38 -0
  29. bbstrader-0.3.3/requirements.txt +35 -0
  30. {bbstrader-0.3.1 → bbstrader-0.3.3}/setup.py +1 -3
  31. bbstrader-0.3.1/bbstrader/metatrader/copier.py +0 -846
  32. bbstrader-0.3.1/bbstrader/models/risk.py +0 -398
  33. bbstrader-0.3.1/bbstrader/trading/strategies.py +0 -875
  34. bbstrader-0.3.1/bbstrader.egg-info/requires.txt +0 -43
  35. bbstrader-0.3.1/requirements.txt +0 -40
  36. {bbstrader-0.3.1 → bbstrader-0.3.3}/LICENSE +0 -0
  37. {bbstrader-0.3.1 → bbstrader-0.3.3}/MANIFEST.in +0 -0
  38. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/__init__.py +0 -0
  39. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/event.py +0 -0
  40. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/performance.py +0 -0
  41. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/portfolio.py +0 -0
  42. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/btengine/scripts.py +0 -0
  43. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/compat.py +0 -0
  44. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/core/__init__.py +0 -0
  45. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/core/utils.py +0 -0
  46. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/ibkr/__init__.py +0 -0
  47. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/ibkr/utils.py +0 -0
  48. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/__init__.py +0 -0
  49. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/metatrader/analysis.py +0 -0
  50. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/models/factors.py +0 -0
  51. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/models/portfolio.py +0 -0
  52. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/trading/__init__.py +0 -0
  53. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/trading/scripts.py +0 -0
  54. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader/trading/utils.py +0 -0
  55. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader.egg-info/SOURCES.txt +0 -0
  56. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader.egg-info/dependency_links.txt +0 -0
  57. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader.egg-info/entry_points.txt +0 -0
  58. {bbstrader-0.3.1 → bbstrader-0.3.3}/bbstrader.egg-info/top_level.txt +0 -0
  59. {bbstrader-0.3.1 → bbstrader-0.3.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bbstrader
3
- Version: 0.3.1
3
+ Version: 0.3.3
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/
@@ -24,46 +24,41 @@ Classifier: Operating System :: MacOS
24
24
  Classifier: License :: OSI Approved :: MIT License
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
+ Requires-Dist: alphalens-reloaded>=0.4.5
28
+ Requires-Dist: beautifulsoup4>=4.13.1
29
+ Requires-Dist: colorama>=0.4.6
30
+ Requires-Dist: CurrencyConverter>=0.18.2
31
+ Requires-Dist: dash>=2.18.2
32
+ Requires-Dist: eodhd>=1.0.32
33
+ Requires-Dist: exchange-calendars>=4.9
34
+ Requires-Dist: filterpy>=1.4.5
35
+ Requires-Dist: financetoolkit>=1.9.9
36
+ Requires-Dist: ipython>=8.32.0
37
+ Requires-Dist: lightgbm>=4.5.0
38
+ Requires-Dist: nltk>=3.9.1
39
+ Requires-Dist: notify-py>=0.3.43
27
40
  Requires-Dist: numpy<2.0.0,>=1.26.0
28
- Requires-Dist: scikit-learn
29
- Requires-Dist: statsmodels
30
- Requires-Dist: seaborn
31
- Requires-Dist: lightgbm
32
- Requires-Dist: dash
33
- Requires-Dist: nltk
34
- Requires-Dist: spacy
35
- Requires-Dist: pmdarima
36
- Requires-Dist: pyportfolioopt
37
- Requires-Dist: alphalens-reloaded
38
- Requires-Dist: pandas_ta
39
- Requires-Dist: yfinance
40
- Requires-Dist: hmmlearn
41
- Requires-Dist: arch
42
- Requires-Dist: hurst
43
- Requires-Dist: filterpy
44
- Requires-Dist: pykalman
45
- Requires-Dist: CurrencyConverter
46
- Requires-Dist: tabulate
47
- Requires-Dist: python-dotenv
48
- Requires-Dist: ipython
49
- Requires-Dist: QuantStats
50
- Requires-Dist: exchange-calendars
51
- Requires-Dist: tqdm
52
- Requires-Dist: notify-py
53
- Requires-Dist: python-telegram-bot
54
- Requires-Dist: eodhd
55
- Requires-Dist: financetoolkit
56
- Requires-Dist: PyYAML
57
- Requires-Dist: tables
58
- Requires-Dist: pyfiglet
59
- Requires-Dist: colorama
60
- Requires-Dist: praw
61
- Requires-Dist: tweepy
62
- Requires-Dist: beautifulsoup4
63
- Requires-Dist: textblob
64
- Requires-Dist: vaderSentiment
65
- Requires-Dist: pytest
66
- Requires-Dist: pytest-mock
41
+ Requires-Dist: pandas_ta>=0.3.14b0
42
+ Requires-Dist: praw>=7.8.1
43
+ Requires-Dist: pyfiglet>=1.0.2
44
+ Requires-Dist: pykalman>=0.10.1
45
+ Requires-Dist: pyportfolioopt>=1.5.6
46
+ Requires-Dist: python-dotenv>=1.0.1
47
+ Requires-Dist: python-telegram-bot>=21.10
48
+ Requires-Dist: PyYAML>=6.0.2
49
+ Requires-Dist: QuantStats>=0.0.64
50
+ Requires-Dist: scikit-learn>=1.6.1
51
+ Requires-Dist: seaborn>=0.13.2
52
+ Requires-Dist: spacy>=3.8.4
53
+ Requires-Dist: statsmodels>=0.14.4
54
+ Requires-Dist: sumy>=0.11.0
55
+ Requires-Dist: tables>=3.10.2
56
+ Requires-Dist: tabulate>=0.9.0
57
+ Requires-Dist: textblob>=0.19.0
58
+ Requires-Dist: tqdm>=4.67.1
59
+ Requires-Dist: tweepy>=4.15.0
60
+ Requires-Dist: vaderSentiment>=3.3.2
61
+ Requires-Dist: yfinance>=0.2.55
67
62
  Provides-Extra: mt5
68
63
  Requires-Dist: MetaTrader5; extra == "mt5"
69
64
  Dynamic: author
@@ -385,7 +380,7 @@ You may need to create the `~/.bbstrader/` directory and its subdirectories (lik
385
380
 
386
381
  For comprehensive information, including detailed API references, tutorials, and advanced guides for each module, please refer to our full documentation hosted on ReadTheDocs:
387
382
 
388
- [**View the Full Documentation on ReadTheDocs**](https://bbstrader.readthedocs.io/en/latest/)
383
+ [**View the Full Documentation**](https://bbstrader.readthedocs.io/en/latest/)
389
384
 
390
385
  Additionally, the codebase is commented and includes docstrings, which can be a valuable resource for understanding the implementation details of specific functions and classes.
391
386
 
@@ -301,7 +301,7 @@ You may need to create the `~/.bbstrader/` directory and its subdirectories (lik
301
301
 
302
302
  For comprehensive information, including detailed API references, tutorials, and advanced guides for each module, please refer to our full documentation hosted on ReadTheDocs:
303
303
 
304
- [**View the Full Documentation on ReadTheDocs**](https://bbstrader.readthedocs.io/en/latest/)
304
+ [**View the Full Documentation**](https://bbstrader.readthedocs.io/en/latest/)
305
305
 
306
306
  Additionally, the codebase is commented and includes docstrings, which can be a valuable resource for understanding the implementation details of specific functions and classes.
307
307
 
@@ -7,7 +7,7 @@ __author__ = "Bertin Balouki SIMYELI"
7
7
  __copyright__ = "2023-2025 Bertin Balouki SIMYELI"
8
8
  __email__ = "bertin@bbstrader.com"
9
9
  __license__ = "MIT"
10
- __version__ = "0.3.1"
10
+ __version__ = "0.3.2"
11
11
 
12
12
  from bbstrader import compat # noqa: F401
13
13
  from bbstrader import core # noqa: F401
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import multiprocessing
2
3
  import sys
3
4
  from enum import Enum
4
5
 
@@ -11,7 +12,7 @@ from bbstrader.metatrader.scripts import copy_trades
11
12
  from bbstrader.trading.scripts import execute_strategy
12
13
 
13
14
 
14
- class Module(Enum):
15
+ class _Module(Enum):
15
16
  COPIER = "copier"
16
17
  BACKTEST = "backtest"
17
18
  EXECUTION = "execution"
@@ -51,13 +52,13 @@ def main():
51
52
  sys.exit(0)
52
53
  try:
53
54
  match args.run:
54
- case Module.COPIER.value:
55
+ case _Module.COPIER.value:
55
56
  copy_trades(unknown)
56
- case Module.BACKTEST.value:
57
+ case _Module.BACKTEST.value:
57
58
  backtest(unknown)
58
- case Module.EXECUTION.value:
59
+ case _Module.EXECUTION.value:
59
60
  execute_strategy(unknown)
60
- case Module.NEWS_FEED.value:
61
+ case _Module.NEWS_FEED.value:
61
62
  send_news_feed(unknown)
62
63
  case _:
63
64
  print(Fore.RED + f"Unknown module: {args.run}")
@@ -69,4 +70,5 @@ def main():
69
70
 
70
71
 
71
72
  if __name__ == "__main__":
73
+ multiprocessing.freeze_support()
72
74
  main()
@@ -248,14 +248,13 @@ def run_backtest(
248
248
 
249
249
  start_date (datetime): Start date of the backtest.
250
250
 
251
- data_handler (DataHandler): An instance of the `DataHandler` class, responsible for managing
251
+ data_handler (DataHandler): A subclass of the `DataHandler` class, responsible for managing
252
252
  and processing market data. Available options include `CSVDataHandler`,
253
- `MT5DataHandler`, and `YFDataHandler`. Ensure that the `DataHandler`
254
- instance is initialized before passing it to the function.
253
+ `MT5DataHandler`, and `YFDataHandler`.
255
254
 
256
255
  strategy (Strategy): The trading strategy to be employed during the backtest.
257
- The strategy must be an instance of `Strategy` and should include the following attributes:
258
- - `bars` (DataHandler): The `DataHandler` instance for the strategy.
256
+ The strategy must be a subclass of `Strategy` and should include the following attributes:
257
+ - `bars` (DataHandler): The `DataHandler` class for the strategy.
259
258
  - `events` (Queue): Queue instance for managing events.
260
259
  - `symbol_list` (List[str]): List of symbols to trade.
261
260
  - `mode` (str): 'live' or 'backtest'.
@@ -307,9 +306,9 @@ def run_backtest(
307
306
  >>> run_backtest(
308
307
  ... symbol_list=symbol_list,
309
308
  ... start_date=start,
310
- ... data_handler=MT5DataHandler(),
311
- ... strategy=StockIndexSTBOTrading(),
312
- ... exc_handler=MT5ExecutionHandler(),
309
+ ... data_handler=MT5DataHandler,
310
+ ... strategy=StockIndexSTBOTrading,
311
+ ... exc_handler=MT5ExecutionHandler,
313
312
  ... initial_capital=100000.0,
314
313
  ... heartbeat=0.0,
315
314
  ... **kwargs
@@ -361,7 +361,7 @@ class CSVDataHandler(BaseCSVDataHandler):
361
361
  csv_dir (str): Absolute directory path to the CSV files.
362
362
 
363
363
  NOTE:
364
- All csv fille can be strored in 'Home/.bbstrader/csv_data'
364
+ All csv fille can be stored in 'Home/.bbstrader/data/csv_data'
365
365
 
366
366
  """
367
367
  csv_dir = kwargs.get("csv_dir")
@@ -580,7 +580,7 @@ class EODHDataHandler(BaseCSVDataHandler):
580
580
  to_unix_time=unix_end,
581
581
  )
582
582
 
583
- def _forma_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
583
+ def _format_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
584
584
  if isinstance(data, pd.DataFrame):
585
585
  if data.empty or len(data) == 0:
586
586
  raise ValueError("No data found.")
@@ -608,7 +608,7 @@ class EODHDataHandler(BaseCSVDataHandler):
608
608
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
609
609
  try:
610
610
  data = self._get_data(symbol, self.period)
611
- data = self._forma_data(data)
611
+ data = self._format_data(data)
612
612
  data.to_csv(filepath)
613
613
  except Exception as e:
614
614
  raise ValueError(f"Error downloading {symbol}: {e}")
@@ -98,7 +98,7 @@ class SimExecutionHandler(ExecutionHandler):
98
98
  self.events.put(fill_event)
99
99
  self.logger.info(
100
100
  f"{event.direction} ORDER FILLED: SYMBOL={event.symbol}, "
101
- f"QUANTITY={event.quantity}, PRICE @{event.price} EXCHANGE={fill_event.exchange}",
101
+ f"QUANTITY={event.quantity}, PRICE @{round(event.price, 5)} EXCHANGE={fill_event.exchange}",
102
102
  custom_time=fill_event.timeindex,
103
103
  )
104
104
 
@@ -264,7 +264,7 @@ class MT5ExecutionHandler(ExecutionHandler):
264
264
  self.events.put(fill_event)
265
265
  self.logger.info(
266
266
  f"{direction} ORDER FILLED: SYMBOL={symbol}, QUANTITY={quantity}, "
267
- f"PRICE @{price} EXCHANGE={fill_event.exchange}",
267
+ f"PRICE @{round(event.price, 5)} EXCHANGE={fill_event.exchange}",
268
268
  custom_time=fill_event.timeindex,
269
269
  )
270
270
 
@@ -12,17 +12,18 @@ from loguru import logger
12
12
  from bbstrader.btengine.data import DataHandler
13
13
  from bbstrader.btengine.event import Events, FillEvent, SignalEvent
14
14
  from bbstrader.config import BBSTRADER_DIR
15
- from bbstrader.metatrader.account import (
15
+ from bbstrader.metatrader import (
16
16
  Account,
17
17
  AdmiralMarktsGroup,
18
18
  PepperstoneGroupLimited,
19
+ TradeOrder,
20
+ Rates,
21
+ TradeSignal,
22
+ TradingMode,
23
+ SymbolType
19
24
  )
20
- from bbstrader.metatrader.utils import SymbolType
21
- from bbstrader.metatrader.rates import Rates
22
- from bbstrader.metatrader.trade import TradeSignal, TradingMode
23
25
  from bbstrader.models.optimization import optimized_weights
24
26
 
25
-
26
27
  __all__ = ["Strategy", "MT5Strategy"]
27
28
 
28
29
  logger.add(
@@ -71,8 +72,13 @@ class MT5Strategy(Strategy):
71
72
  calculate signals for the MetaTrader 5 trading platform. The signals
72
73
  are generated by the `MT5Strategy` object and sent to the the `Mt5ExecutionEngine`
73
74
  for live trading and `MT5BacktestEngine` objects for backtesting.
74
- """
75
75
 
76
+ # NOTE
77
+ It is recommanded that every strategy specfic method to be a private method
78
+ in order to avoid naming collusion.
79
+ """
80
+ tf: str
81
+ max_trades: Dict[str, int]
76
82
  def __init__(
77
83
  self,
78
84
  events: Queue = None,
@@ -107,9 +113,11 @@ class MT5Strategy(Strategy):
107
113
  self._initialize_portfolio()
108
114
  self.kwargs = kwargs
109
115
  self.periodes = 0
110
-
111
- @property
116
+
117
+ @property
112
118
  def account(self):
119
+ if self.mode != TradingMode.LIVE:
120
+ raise ValueError("account attribute is only allowed in Live mode")
113
121
  return Account(**self.kwargs)
114
122
 
115
123
  @property
@@ -127,7 +135,7 @@ class MT5Strategy(Strategy):
127
135
  @property
128
136
  def orders(self):
129
137
  if self.mode == TradingMode.LIVE:
130
- return self.account.get_orders()
138
+ return self.account.get_orders() or []
131
139
  return self._orders
132
140
 
133
141
  @property
@@ -139,7 +147,7 @@ class MT5Strategy(Strategy):
139
147
  @property
140
148
  def positions(self):
141
149
  if self.mode == TradingMode.LIVE:
142
- return self.account.get_positions()
150
+ return self.account.get_positions() or []
143
151
  return self._positions
144
152
 
145
153
  @property
@@ -345,7 +353,7 @@ class MT5Strategy(Strategy):
345
353
  log = True
346
354
  if log:
347
355
  self.logger.info(
348
- f"{signal} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={quantity}, PRICE @{price}",
356
+ f"{signal} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={quantity}, PRICE @{round(price, 5)}",
349
357
  custom_time=dtime,
350
358
  )
351
359
 
@@ -526,7 +534,7 @@ class MT5Strategy(Strategy):
526
534
  def logmsg(order, type, symbol, dtime):
527
535
  return self.logger.info(
528
536
  f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
529
- f"PRICE @ {order.price}",
537
+ f"PRICE @ {round(order.price, 5)}",
530
538
  custom_time=dtime,
531
539
  )
532
540
 
@@ -614,7 +622,7 @@ class MT5Strategy(Strategy):
614
622
  )
615
623
 
616
624
  @staticmethod
617
- def calculate_pct_change(current_price, lh_price):
625
+ def calculate_pct_change(current_price, lh_price) -> float:
618
626
  return ((current_price - lh_price) / lh_price) * 100
619
627
 
620
628
  def get_asset_values(
@@ -649,8 +657,8 @@ class MT5Strategy(Strategy):
649
657
  In Live mode, the `bbstrader.metatrader.rates.Rates` class is used to get the historical data
650
658
  so the value_type must be 'returns', 'open', 'high', 'low', 'close', 'adjclose', 'volume'.
651
659
  """
652
- if mode not in ["backtest", "live"]:
653
- raise ValueError("Mode must be either backtest or live.")
660
+ if mode not in [TradingMode.BACKTEST, TradingMode.LIVE]:
661
+ raise ValueError("Mode must be an instance of TradingMode")
654
662
  asset_values = {}
655
663
  if mode == TradingMode.BACKTEST:
656
664
  if bars is None:
@@ -696,7 +704,7 @@ class MT5Strategy(Strategy):
696
704
  if period_count == 0 or period_count is None:
697
705
  return True
698
706
  return period_count % signal_inverval == 0
699
-
707
+
700
708
  @staticmethod
701
709
  def stop_time(time_zone: str, stop_time: str) -> bool:
702
710
  now = datetime.now(pytz.timezone(time_zone)).time()
@@ -760,6 +768,49 @@ class MT5Strategy(Strategy):
760
768
  )
761
769
  return prices
762
770
  return np.array([])
771
+
772
+ def get_active_orders(self, symbol: str, strategy_id: int, order_type: int = None) -> List[TradeOrder]:
773
+ """
774
+ Get the active orders for a given symbol and strategy.
775
+
776
+ Args:
777
+ symbol : The symbol for the trade.
778
+ strategy_id : The unique identifier for the strategy.
779
+ order_type : The type of order to filter by (optional):
780
+ "BUY_LIMIT": 2
781
+ "SELL_LIMIT": 3
782
+ "BUY_STOP": 4
783
+ "SELL_STOP": 5
784
+ "BUY_STOP_LIMIT": 6
785
+ "SELL_STOP_LIMIT": 7
786
+
787
+ Returns:
788
+ List[TradeOrder] : A list of active orders for the given symbol and strategy.
789
+ """
790
+ orders = [o for o in self.orders if o.symbol == symbol and o.magic == strategy_id]
791
+ if order_type is not None and len(orders) > 0:
792
+ orders = [o for o in orders if o.type == order_type]
793
+ return orders
794
+
795
+ def exit_positions(self, position, prices, asset, th: float = 0.01):
796
+ if len(prices) == 0:
797
+ return False
798
+ tick_info = self.account.get_tick_info(asset)
799
+ bid, ask = tick_info.bid, tick_info.ask
800
+ price = None
801
+ if len(prices) == 1:
802
+ price = prices[0]
803
+ elif len(prices) in range(2, self.max_trades[asset] + 1):
804
+ price = np.mean(prices)
805
+ if price is not None:
806
+ if (
807
+ position == 0
808
+ and self.calculate_pct_change(ask, price) >= th
809
+ or position == 1
810
+ and abs(self.calculate_pct_change(bid, price)) >= th
811
+ ):
812
+ return True
813
+ return False
763
814
 
764
815
  @staticmethod
765
816
  def get_current_dt(time_zone: str = "US/Eastern") -> datetime:
@@ -796,7 +847,9 @@ class MT5Strategy(Strategy):
796
847
  return dt_to
797
848
 
798
849
  @staticmethod
799
- def get_mt5_equivalent(symbols, symbol_type: str | SymbolType = SymbolType.STOCKS, **kwargs) -> List[str]:
850
+ def get_mt5_equivalent(
851
+ symbols, symbol_type: str | SymbolType = SymbolType.STOCKS, **kwargs
852
+ ) -> List[str]:
800
853
  """
801
854
  Get the MetaTrader 5 equivalent symbols for the symbols in the list.
802
855
  This method is used to get the symbols that are available on the MetaTrader 5 platform.
@@ -3,8 +3,8 @@ from pathlib import Path
3
3
  from typing import List
4
4
 
5
5
 
6
- TERMINAL = "\\terminal64.exe"
7
- BASE_FOLDER = "C:\\Program Files\\"
6
+ TERMINAL = "/terminal64.exe"
7
+ BASE_FOLDER = "C:/Program Files/"
8
8
 
9
9
  AMG_PATH = BASE_FOLDER + "Admirals Group MT5 Terminal" + TERMINAL
10
10
  PGL_PATH = BASE_FOLDER + "Pepperstone MetaTrader 5" + TERMINAL
@@ -190,7 +190,9 @@ class FmpNews(object):
190
190
  0
191
191
  ] # if symbol is a yahoo finance ticker
192
192
  source_methods = {
193
- "articles": lambda: self.get_latest_articles(articles=articles, save=True),
193
+ "articles": lambda: self.get_latest_articles(
194
+ articles=articles, save=True, **kwargs
195
+ ),
194
196
  "releases": lambda: self.get_releases(symbol=symbol, **kwargs),
195
197
  "stock": lambda: self.get_stock_news(symbol=symbol, **kwargs),
196
198
  "crypto": lambda: self.get_crypto_news(symbol=symbol, **kwargs),
@@ -4,6 +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
8
 
8
9
  import nltk
9
10
  from loguru import logger
@@ -25,7 +26,7 @@ def summarize_text(text, sentences_count=5):
25
26
  return " ".join(str(sentence) for sentence in summary)
26
27
 
27
28
 
28
- def format_article_for_telegram(article: dict) -> str:
29
+ def format_coindesk_article(article: dict) -> str:
29
30
  if not all(
30
31
  k in article
31
32
  for k in (
@@ -53,16 +54,40 @@ def format_article_for_telegram(article: dict) -> str:
53
54
  return text
54
55
 
55
56
 
56
- async def send_articles(articles: dict, token: str, id: str, interval=15):
57
+ def format_fmp_article(article: dict) -> str:
58
+ if not all(k in article for k in ("title", "date", "content", "tickers")):
59
+ return ""
60
+ summary = summarize_text(article["content"])
61
+ text = (
62
+ f"📰 {article['title']}\n"
63
+ f"Published Date: {article['date']}\n"
64
+ f"Keywords: {article['tickers']}\n\n"
65
+ f"🔍 Summary\n"
66
+ f"{textwrap.fill(summary, width=80)}"
67
+ )
68
+ return text
69
+
70
+
71
+ async def send_articles(
72
+ articles: List[dict],
73
+ token: str,
74
+ id: str,
75
+ source: Literal["coindesk", "fmp"],
76
+ interval=15,
77
+ ):
57
78
  for article in articles:
58
- if article["published_on"] >= datetime.now() - timedelta(minutes=interval):
59
- article["published_on"] = article.get("published_on").strftime(
60
- "%Y-%m-%d %H:%M:%S"
61
- )
62
- message = format_article_for_telegram(article)
63
- if message == "":
64
- return
65
- await send_telegram_message(token, id, text=message)
79
+ message = ""
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)
86
+ else:
87
+ message = format_fmp_article(article)
88
+ if message == "":
89
+ return
90
+ await send_telegram_message(token, id, text=message)
66
91
 
67
92
 
68
93
  def send_news_feed(unknown):
@@ -78,6 +103,7 @@ def send_news_feed(unknown):
78
103
  -q, --query: The news to look for (default: "")
79
104
  -t, --token: Telegram bot token
80
105
  -I, --id: Telegram Chat id
106
+ --fmp: Financial Modeling Prop Api Key
81
107
  -i, --interval: Interval in minutes to fetch news (default: 15)
82
108
 
83
109
  Note:
@@ -101,6 +127,9 @@ def send_news_feed(unknown):
101
127
  help="Telegram bot token",
102
128
  )
103
129
  parser.add_argument("-I", "--id", type=str, required=True, help="Telegram Chat id")
130
+ parser.add_argument(
131
+ "--fmp", type=str, default="", help="Financial Modeling Prop Api Key"
132
+ )
104
133
  parser.add_argument(
105
134
  "-i",
106
135
  "--interval",
@@ -112,19 +141,33 @@ def send_news_feed(unknown):
112
141
 
113
142
  nltk.download("punkt", quiet=True)
114
143
  news = FinancialNews()
115
- logger.info(
116
- f"Starting the News Feed on {args.interval} minutes"
117
- )
144
+ logger.info(f"Starting the News Feed on {args.interval} minutes")
118
145
  while True:
119
146
  try:
120
- articles = news.get_coindesk_news(query=args.query)
121
- if len(articles) == 0:
122
- time.sleep(args.interval * 60)
123
- continue
124
- asyncio.run(send_articles(articles, args.token, args.id))
147
+ fmp_articles = []
148
+ coindesk_articles = news.get_coindesk_news(query=args.query)
149
+ if args.fmp:
150
+ 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")
153
+ fmp_articles = news.get_fmp_news(api=args.fmp).get_latest_articles(
154
+ save=True, start=start, end=end
155
+ )
156
+ if len(coindesk_articles) != 0:
157
+ asyncio.run(
158
+ send_articles(
159
+ coindesk_articles,
160
+ args.token,
161
+ args.id,
162
+ "coindesk",
163
+ interval=args.interval,
164
+ )
165
+ )
166
+ if len(fmp_articles) != 0:
167
+ asyncio.run(send_articles(fmp_articles, args.token, args.id, "fmp"))
125
168
  time.sleep(args.interval * 60)
126
169
  except KeyboardInterrupt:
127
170
  logger.info("Stopping the News Feed ...")
128
- exit(0)
171
+ sys.exit(0)
129
172
  except Exception as e:
130
173
  logger.error(e)