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

@@ -95,27 +95,16 @@ class TradeSignal:
95
95
  """
96
96
  Represents a trading signal generated by a trading system or strategy.
97
97
 
98
+ Notes
99
+ -----
98
100
  Attributes:
99
- id (int):
100
- A unique identifier for the trade signal or the strategy.
101
101
 
102
- symbol (str):
103
- The trading symbol (e.g., stock ticker, forex pair, crypto asset)
104
- related to the signal.
105
-
106
- action (TradeAction):
107
- The trading action to perform.
108
- Must be an instance of the `TradeAction` enum (e.g., BUY, SELL).
109
-
110
- price (float, optional):
111
- The price at which the trade should be executed.
112
-
113
- stoplimit (float, optional):
114
- A stop-limit price for the trade.
115
- Must not be set without specifying a price.
116
-
117
- comment (str, optional):
118
- An optional comment or description related to the trade signal.
102
+ - id (int): A unique identifier for the trade signal or the strategy.
103
+ - symbol (str): The trading symbol (e.g., stock ticker, forex pair, crypto asset).
104
+ - action (TradeAction): The trading action to perform. Must be an instance of the ``TradeAction`` enum (e.g., BUY, SELL).
105
+ - price (float, optional): The price at which the trade should be executed.
106
+ - stoplimit (float, optional): A stop-limit price for the trade. Must not be set without specifying a price.
107
+ - comment (str, optional): An optional comment or description related to the trade signal.
119
108
  """
120
109
 
121
110
  id: int
@@ -781,14 +770,14 @@ class Trade(RiskManagement):
781
770
  self.check_order(request)
782
771
  result = self.send_order(request)
783
772
  except Exception as e:
784
- msg = trade_retcode_message(result.retcode) if result else "N/A"
773
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
785
774
  LOGGER.error(f"Trade Order Request, {msg}{addtionnal}, {e}")
786
775
  if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
787
776
  if result.retcode == Mt5.TRADE_RETCODE_INVALID_FILL: # 10030
788
777
  for fill in FILLING_TYPE:
789
778
  request["type_filling"] = fill
790
779
  result = self.send_order(request)
791
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
780
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
792
781
  break
793
782
  elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME: # 10014
794
783
  new_volume = int(request["volume"])
@@ -812,14 +801,14 @@ class Trade(RiskManagement):
812
801
  self.check_order(request)
813
802
  result = self.send_order(request)
814
803
  except Exception as e:
815
- msg = trade_retcode_message(result.retcode) if result else "N/A"
804
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
816
805
  LOGGER.error(f"Trade Order Request, {msg}{addtionnal}, {e}")
817
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
806
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
818
807
  break
819
808
  tries += 1
820
809
  # Print the result
821
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
822
- msg = trade_retcode_message(result.retcode)
810
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
811
+ msg = trade_retcode_message(result.retcode)
823
812
  LOGGER.info(f"Trade Order {msg}{addtionnal}")
824
813
  if type != "BMKT" or type != "SMKT":
825
814
  self.opened_orders.append(result.order)
@@ -855,7 +844,7 @@ class Trade(RiskManagement):
855
844
  LOGGER.info(pos_info)
856
845
  return True
857
846
  else:
858
- msg = trade_retcode_message(result.retcode) if result else "N/A"
847
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
859
848
  LOGGER.error(
860
849
  f"Unable to Open Position, RETCODE={result.retcode}: {msg}{addtionnal}"
861
850
  )
@@ -1332,10 +1321,10 @@ class Trade(RiskManagement):
1332
1321
  self.check_order(request)
1333
1322
  result = self.send_order(request)
1334
1323
  except Exception as e:
1335
- msg = trade_retcode_message(result.retcode) if result else "N/A"
1324
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
1336
1325
  LOGGER.error(f"Break-Even Order Request, {msg}{addtionnal}, Error: {e}")
1337
- if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
1338
- msg = trade_retcode_message(result.retcode)
1326
+ if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
1327
+ msg = trade_retcode_message(result.retcode)
1339
1328
  if result.retcode != Mt5.TRADE_RETCODE_NO_CHANGES:
1340
1329
  LOGGER.error(
1341
1330
  f"Break-Even Order Request, Position: #{tiket}, RETCODE={result.retcode}: {msg}{addtionnal}"
@@ -1350,15 +1339,15 @@ class Trade(RiskManagement):
1350
1339
  self.check_order(request)
1351
1340
  result = self.send_order(request)
1352
1341
  except Exception as e:
1353
- msg = trade_retcode_message(result.retcode) if result else "N/A"
1342
+ msg = trade_retcode_message(result.retcode) if result else "N/A"
1354
1343
  LOGGER.error(
1355
1344
  f"Break-Even Order Request, {msg}{addtionnal}, Error: {e}"
1356
1345
  )
1357
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1346
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1358
1347
  break
1359
1348
  tries += 1
1360
- if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1361
- msg = trade_retcode_message(result.retcode)
1349
+ if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
1350
+ msg = trade_retcode_message(result.retcode)
1362
1351
  LOGGER.info(f"Break-Even Order {msg}{addtionnal}")
1363
1352
  info = f"Stop loss set to Break-even, Position: #{tiket}, Symbol: {self.symbol}, Price: @{round(price, 5)}"
1364
1353
  LOGGER.info(info)
@@ -1434,7 +1423,7 @@ class Trade(RiskManagement):
1434
1423
  """
1435
1424
  ticket = request[type]
1436
1425
  addtionnal = f", SYMBOL={self.symbol}"
1437
- result = None
1426
+ result = None
1438
1427
  try:
1439
1428
  self.check_order(request)
1440
1429
  result = self.send_order(request)
@@ -1839,15 +1828,16 @@ class Trade(RiskManagement):
1839
1828
 
1840
1829
  def sleep_time(self, weekend=False):
1841
1830
  if weekend:
1842
- # claculate number of minute from the friday and to monday start
1843
- friday_time = datetime.strptime(self.current_time(), "%H:%M")
1831
+ # calculate number of minute from now and monday start
1832
+ multiplyer = {"friday": 3, "saturday": 2, "sunday": 1}
1833
+ current_time = datetime.strptime(self.current_time(), "%H:%M")
1844
1834
  monday_time = datetime.strptime(self.start, "%H:%M")
1845
- intra_day_diff = (monday_time - friday_time).total_seconds() // 60
1846
- inter_day_diff = 3 * 24 * 60
1835
+ intra_day_diff = (monday_time - current_time).total_seconds() // 60
1836
+ inter_day_diff = multiplyer[datetime.now().strftime("%A").lower()] * 24 * 60
1847
1837
  total_minutes = inter_day_diff + intra_day_diff
1848
1838
  return total_minutes
1849
1839
  else:
1850
- # claculate number of minute from the end to the start
1840
+ # calculate number of minute from the end to the start
1851
1841
  start = datetime.strptime(self.start, "%H:%M")
1852
1842
  end = datetime.strptime(self.current_time(), "%H:%M")
1853
1843
  minutes = (end - start).total_seconds() // 60
@@ -607,7 +607,8 @@ class AutoTradingDisabled(MT5TerminalError):
607
607
  class InternalFailError(MT5TerminalError):
608
608
  """Base exception class for internal IPC errors."""
609
609
 
610
- pass
610
+ def __init__(self, code, message):
611
+ super().__init__(code, message)
611
612
 
612
613
 
613
614
  class InternalFailSend(InternalFailError):
@@ -700,9 +701,9 @@ def raise_mt5_error(message: Optional[str] = None):
700
701
  """
701
702
  if message and isinstance(message, Exception):
702
703
  message = str(message)
703
- error = _ERROR_CODE_TO_EXCEPTION_.get(MT5.last_error()[0])
704
- if error is not None:
705
- raise Exception(f"{error(None)} {message or MT5.last_error()[1]}")
704
+ exception = _ERROR_CODE_TO_EXCEPTION_.get(MT5.last_error()[0])
705
+ if exception is not None:
706
+ raise exception(f"{message or MT5.last_error()[1]}")
706
707
  else:
707
708
  raise Exception(f"{message or MT5.last_error()[1]}")
708
709
 
bbstrader/models/nlp.py CHANGED
@@ -577,44 +577,55 @@ class SentimentAnalyzer(object):
577
577
  **kwargs,
578
578
  ) -> Dict[str, float]:
579
579
  """
580
- Computes sentiment scores for a list of financial tickers based on news and social media data.
581
-
582
- Process:
583
- 1. Collects news articles and posts related to each ticker from various sources:
584
- - Yahoo Finance News
585
- - Google Finance News
586
- - Reddit posts
587
- - Financial Modeling Prep (FMP) news
588
- 2. Analyzes sentiment from each source:
589
- - Uses VADER for Yahoo and Google Finance news.
590
- - Uses TextBlob for Reddit and FMP news.
591
- 3. Computes an overall sentiment score using a weighted average approach.
592
-
593
- Args:
594
- tickers (List[str] | List[Tuple[str, str]]): A list of asset tickers to analyze
595
- - if using tuples, the first element is the ticker and the second is the asset type.
596
- - if using a single string, the asset type must be specified or the default is "stock".
597
- lexicon (dict, optional): A custom sentiment lexicon to update VADER's default lexicon.
598
- asset_type (str, optional): The type of asset, Defaults to "stock",
599
- supported types include:
600
- - "stock": Stock symbols (e.g., AAPL, MSFT)
601
- - "etf": Exchange-traded funds (e.g., SPY, QQQ)
602
- - "future": Futures contracts (e.g., CL=F for crude oil)
603
- - "forex": Forex pairs (e.g., EURUSD=X, USDJPY=X)
604
- - "crypto": Cryptocurrency pairs (e.g., BTC-USD, ETH-USD)
605
- - "index": Stock market indices (e.g., ^GSPC for S&P 500)
606
- top_news (int, optional): Number of news articles/posts to fetch per source. Defaults to 10.
607
- **kwargs: Additional parameters for API authentication and data retrieval, including:
608
- - fmp_api (str): API key for Financial Modeling Prep.
609
- - client_id, client_secret, user_agent (str): Credentials for accessing Reddit API.
610
-
611
- Returns:
612
- Dict[str, float]: A dictionary mapping each ticker to its overall sentiment score.
613
- - Positive values indicate positive sentiment.
614
- - Negative values indicate negative sentiment.
615
- - Zero indicates neutral sentiment.
616
- Notes:
617
- The tickers names must follow yahoo finance conventions.
580
+ Compute sentiment scores for a list of financial tickers based on news and social media data.
581
+
582
+ Process
583
+ -------
584
+ 1. Collect news articles and posts related to each ticker from various sources:
585
+ * Yahoo Finance News
586
+ * Google Finance News
587
+ * Reddit posts
588
+ * Financial Modeling Prep (FMP) news
589
+ 2. Analyze sentiment from each source:
590
+ * Uses VADER for Yahoo and Google Finance news.
591
+ * Uses TextBlob for Reddit and FMP news.
592
+ 3. Compute an overall sentiment score using a weighted average approach.
593
+
594
+ Parameters
595
+ ----------
596
+ tickers : list of str or list of tuple
597
+ A list of asset tickers to analyze.
598
+ * If using tuples, the first element is the ticker and the second is the asset type.
599
+ * If using a single string, the asset type must be specified or defaults to "stock".
600
+ lexicon : dict, optional
601
+ A custom sentiment lexicon to update VADER's default lexicon. Default is None.
602
+ asset_type : str, optional
603
+ The type of asset. Default is "stock".
604
+ Supported types include:
605
+ * "stock": Stock symbols (e.g., AAPL, MSFT)
606
+ * "etf": Exchange-traded funds (e.g., SPY, QQQ)
607
+ * "future": Futures contracts (e.g., CL=F for crude oil)
608
+ * "forex": Forex pairs (e.g., EURUSD=X, USDJPY=X)
609
+ * "crypto": Cryptocurrency pairs (e.g., BTC-USD, ETH-USD)
610
+ * "index": Stock market indices (e.g., ^GSPC for S&P 500)
611
+ top_news : int, optional
612
+ Number of news articles/posts to fetch per source. Default is 10.
613
+ **kwargs : dict
614
+ Additional parameters for API authentication and data retrieval. Must include:
615
+ * fmp_api (str): API key for Financial Modeling Prep.
616
+ * client_id, client_secret, user_agent (str): Credentials for Reddit API.
617
+
618
+ Returns
619
+ -------
620
+ dict of str to float
621
+ A dictionary mapping each ticker to its overall sentiment score.
622
+ * Positive values indicate positive sentiment.
623
+ * Negative values indicate negative sentiment.
624
+ * Zero indicates neutral sentiment.
625
+
626
+ Notes
627
+ -----
628
+ Ticker names must follow Yahoo Finance conventions.
618
629
  """
619
630
 
620
631
  sentiment_results = {}
@@ -761,30 +772,36 @@ class SentimentAnalyzer(object):
761
772
  The dashboard visualizes sentiment scores for given tickers using interactive
762
773
  bar and scatter plots. It fetches new sentiment data at specified intervals.
763
774
 
764
- Args:
765
- tickers (List[str] | List[Tuple[str, str]]):
766
- A list of financial asset tickers to analyze.
767
- - If using tuples, the first element is the ticker and the second is the asset type.
768
- - If using a single string, the asset type must be specified or defaults to "stock".
769
- asset_type (str, optional):
770
- The type of financial asset ("stock", "forex", "crypto"). Defaults to "stock".
771
- lexicon (dict, optional):
772
- A custom sentiment lexicon. Defaults to None.
773
- interval (int, optional):
774
- The refresh interval (in milliseconds) for sentiment data updates. Defaults to 100000.
775
- top_n (int, optional):
776
- The number of top and bottom assets to display in the sentiment bar chart. Defaults to 20.
777
- **kwargs (dict):
778
- Additional arguments required for fetching sentiment data. Must include:
779
- - client_id (str): Reddit API client ID.
780
- - client_secret (str): Reddit API client secret.
781
- - user_agent (str): User agent for Reddit API.
782
- - fmp_api (str): Financial Modeling Prep (FMP) API key.
783
-
784
- Returns:
785
- None: The function does not return anything but starts a real-time interactive dashboard.
775
+ Parameters
776
+ ----------
777
+ tickers : list of str or list of tuple
778
+ A list of financial asset tickers to analyze.
779
+ * If using tuples, the first element is the ticker and the second is the asset type.
780
+ * If using a single string, the asset type must be specified or defaults to "stock".
781
+ asset_type : str, optional
782
+ The type of financial asset ("stock", "forex", "crypto"). Default is "stock".
783
+ lexicon : dict, optional
784
+ A custom sentiment lexicon. Default is None.
785
+ interval : int, optional
786
+ The refresh interval (in milliseconds) for sentiment data updates. Default is 100000.
787
+ top_n : int, optional
788
+ The number of top and bottom assets to display in the sentiment bar chart. Default is 20.
789
+ **kwargs : dict
790
+ Additional arguments required for fetching sentiment data. Must include:
791
+ * client_id (str): Reddit API client ID.
792
+ * client_secret (str): Reddit API client secret.
793
+ * user_agent (str): User agent for Reddit API.
794
+ * fmp_api (str): Financial Modeling Prep (FMP) API key.
795
+
796
+ Returns
797
+ -------
798
+ None
799
+ Starts a real-time interactive dashboard. Does not return any value.
800
+
801
+ Example
802
+ -------
803
+ .. code-block:: python
786
804
 
787
- Example Usage:
788
805
  sa = SentimentAnalyzer()
789
806
  sa.display_sentiment_dashboard(
790
807
  tickers=["AAPL", "TSLA", "GOOGL"],
@@ -799,12 +816,12 @@ class SentimentAnalyzer(object):
799
816
  fmp_api="your_fmp_api_key",
800
817
  )
801
818
 
802
- Notes:
803
- - Sentiment analysis is performed using financial news and social media discussions.
804
- - The dashboard updates in real-time at the specified interval.
805
- - The dashboard will keep running unless manually stopped (Ctrl+C).
819
+ Notes
820
+ -----
821
+ * Sentiment analysis is performed using financial news and social media discussions.
822
+ * The dashboard updates in real-time at the specified interval.
823
+ * The dashboard will keep running unless manually stopped (Ctrl+C).
806
824
  """
807
-
808
825
  app = dash.Dash(__name__)
809
826
 
810
827
  sentiment_history = {ticker: [] for ticker in tickers}
@@ -45,6 +45,7 @@ MT5_ENGINE_TIMEFRAMES = list(_TF_MAPPING.keys())
45
45
  TradingDays = ["monday", "tuesday", "wednesday", "thursday", "friday"]
46
46
  WEEK_DAYS = TradingDays + ["saturday", "sunday"]
47
47
  FRIDAY = "friday"
48
+ WEEK_ENDS = ["friday", "saturday", "sunday"]
48
49
 
49
50
  BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
50
51
  SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
@@ -513,17 +514,12 @@ class Mt5ExecutionEngine:
513
514
  logger.info(sessionmsg)
514
515
 
515
516
  def _check_is_day_ends(self, trade: Trade, symbol, period_type, today, closing):
516
- if trade.days_end():
517
- self._logmsgif("Day", symbol) if today != FRIDAY else self._logmsgif(
517
+ if trade.days_end() or (today in WEEK_ENDS and today != FRIDAY):
518
+ self._logmsgif("Day", symbol) if today not in WEEK_ENDS else self._logmsgif(
518
519
  "Week", symbol
519
520
  )
520
521
  if (
521
- (
522
- period_type == "month"
523
- and today == FRIDAY
524
- and self._is_month_ends()
525
- and closing
526
- )
522
+ (period_type == "month" and self._is_month_ends() and closing)
527
523
  or (period_type == "week" and today == FRIDAY and closing)
528
524
  or (period_type == "day" and closing)
529
525
  or (period_type == "24/7" and closing)
@@ -546,15 +542,17 @@ class Mt5ExecutionEngine:
546
542
  self.strategy.perform_period_end_checks()
547
543
  if self.period_end_action == "break" and closing:
548
544
  sys.exit(0)
549
- elif self.period_end_action == "sleep" and today != FRIDAY or not closing:
545
+ elif (
546
+ self.period_end_action == "sleep" and today not in WEEK_ENDS or not closing
547
+ ):
550
548
  self._sleep_over_night(sessionmsg)
551
- elif self.period_end_action == "sleep" and today == FRIDAY:
549
+ elif self.period_end_action == "sleep" and today in WEEK_ENDS:
552
550
  self._sleep_over_weekend(sessionmsg)
553
551
 
554
552
  def _weekly_end_checks(self, today, closing, sessionmsg):
555
- if today != FRIDAY:
553
+ if today not in WEEK_ENDS:
556
554
  self._sleep_over_night(sessionmsg)
557
- elif today == FRIDAY:
555
+ else:
558
556
  self.strategy.perform_period_end_checks()
559
557
  if self.period_end_action == "break" and closing:
560
558
  sys.exit(0)
@@ -562,9 +560,9 @@ class Mt5ExecutionEngine:
562
560
  self._sleep_over_weekend(sessionmsg)
563
561
 
564
562
  def _monthly_end_cheks(self, today, closing, sessionmsg):
565
- if today != FRIDAY:
563
+ if today not in WEEK_ENDS and not self._is_month_ends():
566
564
  self._sleep_over_night(sessionmsg)
567
- elif today == FRIDAY and self._is_month_ends() and closing:
565
+ elif self._is_month_ends() and closing:
568
566
  self.strategy.perform_period_end_checks()
569
567
  sys.exit(0)
570
568
  else:
@@ -956,7 +954,11 @@ class Mt5ExecutionEngine:
956
954
  def _handle_period_end_actions(self, today):
957
955
  try:
958
956
  check_mt5_connection(**self.kwargs)
959
- day_end = all(trade.days_end() for trade in self.trades_instances.values())
957
+ day_end = (
958
+ all(trade.days_end() for trade in self.trades_instances.values())
959
+ or (today in WEEK_ENDS and today != FRIDAY)
960
+ and self.period != "24/7"
961
+ )
960
962
  closing = self._is_closing()
961
963
  sessionmsg = f"{self.ACCOUNT} STARTING NEW TRADING SESSION ...\n"
962
964
  self._perform_period_end_actions(
@@ -1012,13 +1014,28 @@ class Mt5ExecutionEngine:
1012
1014
 
1013
1015
 
1014
1016
  def RunMt5Engine(account_id: str, **kwargs):
1015
- """Starts an MT5 execution engine for a given account.
1016
- Args:
1017
- account_id: Account ID to run the execution engine on.
1018
- **kwargs: Additional keyword arguments
1019
- _ symbol_list : List of symbols to trade.
1020
- - trades_instances : Dictionary of Trade instances.
1021
- - strategy_cls : Strategy class to use for trading.
1017
+ """
1018
+ Start an MT5 execution engine for a given account.
1019
+
1020
+ Parameters
1021
+ ----------
1022
+ account_id : str
1023
+ Account ID to run the execution engine on.
1024
+
1025
+ **kwargs : dict
1026
+ Additional keyword arguments. Possible keys include:
1027
+
1028
+ * symbol_list : list
1029
+ List of symbols to trade.
1030
+ * trades_instances : dict
1031
+ Dictionary of Trade instances.
1032
+ * strategy_cls : class
1033
+ Strategy class to use for trading.
1034
+
1035
+ Returns
1036
+ -------
1037
+ None
1038
+ Initializes and runs the MT5 execution engine.
1022
1039
  """
1023
1040
  log.info(f"Starting execution engine for {account_id}")
1024
1041