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

@@ -14,6 +14,7 @@ from bbstrader.metatrader.utils import (
14
14
  OrderCheckResult,
15
15
  OrderSentResult,
16
16
  SymbolInfo,
17
+ SymbolType,
17
18
  TerminalInfo,
18
19
  TickInfo,
19
20
  TradeDeal,
@@ -84,14 +85,14 @@ ftmo_url = _FTMO_URL_
84
85
 
85
86
 
86
87
  _SYMBOLS_TYPE_ = {
87
- "ETF": r"\b(ETFs?)\b",
88
- "BOND": r"\b(Treasuries?)\b",
89
- "FX": r"\b(Forex|Exotics?)\b",
90
- "FUT": r"\b(Futures?|Forwards)\b",
91
- "STK": r"\b(Stocks?|Equities?|Shares?)\b",
92
- "IDX": r"\b(?:Indices?|Cash|Index)\b(?!.*\\(?:UKOIL|USOIL))",
93
- "COMD": r"\b(Commodity|Commodities?|Metals?|Agricultures?|Energies?|OIL|Oil|USOIL|UKOIL)\b",
94
- "CRYPTO": r"\b(Cryptos?|Cryptocurrencies|Cryptocurrency)\b",
88
+ SymbolType.ETFs: r"\b(ETFs?)\b",
89
+ SymbolType.BONDS: r"\b(Treasuries?)\b",
90
+ SymbolType.FOREX: r"\b(Forex|Exotics?)\b",
91
+ SymbolType.FUTURES: r"\b(Futures?|Forwards)\b",
92
+ SymbolType.STOCKS: r"\b(Stocks?|Equities?|Shares?)\b",
93
+ SymbolType.INDICES: r"\b(?:Indices?|Cash|Index)\b(?!.*\\(?:UKOIL|USOIL))",
94
+ SymbolType.COMMODITIES: r"\b(Commodity|Commodities?|Metals?|Agricultures?|Energies?|OIL|Oil|USOIL|UKOIL)\b",
95
+ SymbolType.CRYPTO: r"\b(Cryptos?|Cryptocurrencies|Cryptocurrency)\b",
95
96
  }
96
97
 
97
98
  _COUNTRY_MAP_ = {
@@ -222,7 +223,7 @@ class Broker(object):
222
223
 
223
224
  class AdmiralMarktsGroup(Broker):
224
225
  def __init__(self, **kwargs):
225
- super().__init__("Admirals Group AS", **kwargs)
226
+ super().__init__(__BROKERS__["AMG"], **kwargs)
226
227
 
227
228
  @property
228
229
  def timezone(self) -> str:
@@ -231,7 +232,7 @@ class AdmiralMarktsGroup(Broker):
231
232
 
232
233
  class JustGlobalMarkets(Broker):
233
234
  def __init__(self, **kwargs):
234
- super().__init__("Just Global Markets Ltd.", **kwargs)
235
+ super().__init__(__BROKERS__["JGM"], **kwargs)
235
236
 
236
237
  @property
237
238
  def timezone(self) -> str:
@@ -240,7 +241,7 @@ class JustGlobalMarkets(Broker):
240
241
 
241
242
  class FTMO(Broker):
242
243
  def __init__(self, **kwargs):
243
- super().__init__("FTMO S.R.O.", **kwargs)
244
+ super().__init__(__BROKERS__["FTMO"], **kwargs)
244
245
 
245
246
  @property
246
247
  def timezone(self) -> str:
@@ -249,7 +250,7 @@ class FTMO(Broker):
249
250
 
250
251
  class PepperstoneGroupLimited(Broker):
251
252
  def __init__(self, **kwargs):
252
- super().__init__("Pepperstone Group Limited", **kwargs)
253
+ super().__init__(__BROKERS__["PGL"], **kwargs)
253
254
 
254
255
  @property
255
256
  def timezone(self) -> str:
@@ -316,6 +317,8 @@ class Account(object):
316
317
  def _check_brokers(self, **kwargs):
317
318
  if kwargs.get("copy", False):
318
319
  return
320
+ if kwargs.get("backtest", False):
321
+ return
319
322
  supported = BROKERS.copy()
320
323
  if self.broker not in supported.values():
321
324
  msg = (
@@ -574,7 +577,7 @@ class Account(object):
574
577
 
575
578
  def get_symbols(
576
579
  self,
577
- symbol_type="ALL",
580
+ symbol_type: SymbolType | str = "ALL",
578
581
  check_etf=False,
579
582
  save=False,
580
583
  file_name="symbols",
@@ -585,16 +588,9 @@ class Account(object):
585
588
  Get all specified financial instruments from the MetaTrader 5 terminal.
586
589
 
587
590
  Args:
588
- symbol_type (str) The category of instrument to get
591
+ symbol_type (SymbolType | str): The type of financial instruments to retrieve.
589
592
  - `ALL`: For all available symbols
590
- - `STK`: Stocks (e.g., 'GOOGL')
591
- - `ETF`: ETFs (e.g., 'QQQ')
592
- - `IDX`: Indices (e.g., 'SP500')
593
- - `FX`: Forex pairs (e.g., 'EURUSD')
594
- - `COMD`: Commodities (e.g., 'CRUDOIL', 'GOLD')
595
- - `FUT`: Futures (e.g., 'USTNote_U4'),
596
- - `CRYPTO`: Cryptocurrencies (e.g., 'BTC', 'ETH')
597
- - `BOND`: Bonds (e.g., 'USTN10YR')
593
+ - See `bbstrader.metatrader.utils.SymbolType` for more details.
598
594
 
599
595
  check_etf (bool): If True and symbol_type is 'etf', check if the
600
596
  ETF description contains 'ETF'.
@@ -621,9 +617,21 @@ class Account(object):
621
617
  patterns = _SYMBOLS_TYPE_
622
618
 
623
619
  if symbol_type != "ALL":
624
- if symbol_type not in patterns:
620
+ if not isinstance(symbol_type, SymbolType) or symbol_type not in patterns:
625
621
  raise ValueError(f"Unsupported symbol type: {symbol_type}")
626
622
 
623
+ def check_etfs(info):
624
+ err_msg = (
625
+ f"{info.name} doesn't have 'ETF' in its description. "
626
+ "If this is intended, set check_etf=False."
627
+ )
628
+ if (
629
+ symbol_type == SymbolType.ETFs
630
+ and check_etf
631
+ and "ETF" not in info.description
632
+ ):
633
+ raise ValueError(err_msg)
634
+
627
635
  if save:
628
636
  max_lengh = max([len(s.name) for s in symbols])
629
637
  file_path = f"{file_name}.txt"
@@ -637,15 +645,7 @@ class Account(object):
637
645
  pattern = re.compile(patterns[symbol_type])
638
646
  match = re.search(pattern, info.path)
639
647
  if match:
640
- if (
641
- symbol_type == "ETF"
642
- and check_etf
643
- and "ETF" not in info.description
644
- ):
645
- raise ValueError(
646
- f"{info.name} doesn't have 'ETF' in its description. "
647
- "If this is intended, set check_etf=False."
648
- )
648
+ check_etfs(info)
649
649
  self._write_symbol(file, info, include_desc, max_lengh)
650
650
  symbol_list.append(s.name)
651
651
 
@@ -658,31 +658,16 @@ class Account(object):
658
658
  pattern = re.compile(patterns[symbol_type]) # , re.IGNORECASE
659
659
  match = re.search(pattern, info.path)
660
660
  if match:
661
- if (
662
- symbol_type == "ETF"
663
- and check_etf
664
- and "ETF" not in info.description
665
- ):
666
- raise ValueError(
667
- f"{info.name} doesn't have 'ETF' in its description. "
668
- "If this is intended, set check_etf=False."
669
- )
661
+ check_etfs(info)
670
662
  symbol_list.append(s.name)
671
663
 
672
664
  # Print a summary of the retrieved symbols
673
665
  if display_total:
674
- names = {
675
- "ALL": "Symbols",
676
- "STK": "Stocks",
677
- "ETF": "ETFs",
678
- "IDX": "Indices",
679
- "FX": "Forex Paires",
680
- "COMD": "Commodities",
681
- "FUT": "Futures",
682
- "CRYPTO": "Cryptos Assets",
683
- "BOND": "Bonds",
684
- }
685
- print(f"Total {names[symbol_type]}: {len(symbol_list)}")
666
+ name = symbol_type if isinstance(symbol_type, str) else symbol_type.name
667
+ if symbol_type == "ALL":
668
+ print(f"Total number of symbols: {len(symbol_list)}")
669
+ else:
670
+ print(f"Total number of {name} symbols: {len(symbol_list)}")
686
671
 
687
672
  return symbol_list
688
673
 
@@ -694,9 +679,7 @@ class Account(object):
694
679
  else:
695
680
  file.write(info.name + "\n")
696
681
 
697
- def get_symbol_type(
698
- self, symbol: str
699
- ) -> Literal["STK", "ETF", "IDX", "FX", "COMD", "FUT", "CRYPTO", "BOND", "unknown"]:
682
+ def get_symbol_type(self, symbol: str) -> SymbolType:
700
683
  """
701
684
  Determines the type of a given financial instrument symbol.
702
685
 
@@ -704,41 +687,41 @@ class Account(object):
704
687
  symbol (str): The symbol of the financial instrument (e.g., `GOOGL`, `EURUSD`).
705
688
 
706
689
  Returns:
707
- Literal["STK", "ETF", "IDX", "FX", "COMD", "FUT", "CRYPTO", "BOND", "unknown"]:
708
- The type of the financial instrument, one of the following:
709
-
710
- - `STK`: For Stocks (e.g., `GOOGL`)
711
- - `ETF`: For ETFs (e.g., `QQQ`)
712
- - `IDX`: For Indices (e.g., `SP500`)
713
- - `FX` : For Forex pairs (e.g., `EURUSD`)
714
- - `COMD`: For Commodities (e.g., `CRUDOIL`, `GOLD`)
715
- - `FUT` : For Futures (e.g., `USTNote_U4`)
716
- - `CRYPTO`: For Cryptocurrencies (e.g., `BTC`, `ETH`)
717
- - `BOND`: For Bonds (e.g., `USTN10YR`)
718
-
719
- Returns `unknown` if the type cannot be determined.
690
+ SymbolType: The type of the financial instrument, one of the following:
691
+ - `SymbolType.ETFs`
692
+ - `SymbolType.BONDS`
693
+ - `SymbolType.FOREX`
694
+ - `SymbolType.FUTURES`
695
+ - `SymbolType.STOCKS`
696
+ - `SymbolType.INDICES`
697
+ - `SymbolType.COMMODITIES`
698
+ - `SymbolType.CRYPTO`
699
+ - `SymbolType.unknown` if the type cannot be determined.
700
+
720
701
  """
721
702
 
722
703
  patterns = _SYMBOLS_TYPE_
723
704
  info = self.get_symbol_info(symbol)
724
- indices = self.get_symbols(symbol_type="IDX")
725
- commodity = self.get_symbols(symbol_type="COMD")
705
+ indices = self.get_symbols(symbol_type=SymbolType.INDICES)
706
+ commodity = self.get_symbols(symbol_type=SymbolType.COMMODITIES)
726
707
  if info is not None:
727
708
  for symbol_type, pattern in patterns.items():
728
709
  if (
729
- symbol_type in ["IDX", "COMD"]
710
+ symbol_type in [SymbolType.COMMODITIES, SymbolType.INDICES]
730
711
  and self.broker == PepperstoneGroupLimited()
731
712
  and info.name.endswith("-F")
732
713
  and info.name in indices + commodity
733
714
  ):
734
- symbol_type = "FUT"
715
+ symbol_type = SymbolType.FUTURES
735
716
  pattern = r"\b(Forwards?)\b"
736
717
  search = re.compile(pattern)
737
718
  if re.search(search, info.path):
738
719
  return symbol_type
739
- return "unknown"
720
+ return SymbolType.unknown
740
721
 
741
- def _get_symbols_by_category(self, symbol_type, category, category_map):
722
+ def _get_symbols_by_category(
723
+ self, symbol_type: SymbolType | str, category, category_map
724
+ ):
742
725
  if category not in category_map:
743
726
  raise ValueError(
744
727
  f"Unsupported category: {category}. Choose from: {', '.join(category_map)}"
@@ -777,8 +760,11 @@ class Account(object):
777
760
  This mthods works primarly with Admirals Group AS products and Pepperstone Group Limited,
778
761
  For other brokers use `get_symbols()` or this method will use it by default.
779
762
  """
780
- if self.broker not in [AdmiralMarktsGroup(), PepperstoneGroupLimited()]:
781
- return self.get_symbols(symbol_type="FX")
763
+ if (
764
+ self.broker != AdmiralMarktsGroup()
765
+ or self.broker != PepperstoneGroupLimited()
766
+ ):
767
+ return self.get_symbols(symbol_type=SymbolType.FOREX)
782
768
  else:
783
769
  fx_categories = {
784
770
  "majors": r"\b(Majors?)\b",
@@ -787,7 +773,9 @@ class Account(object):
787
773
  "crosses": r"\b(Crosses?)\b",
788
774
  "ndfs": r"\b(NDFs?)\b",
789
775
  }
790
- return self._get_symbols_by_category("FX", category, fx_categories)
776
+ return self._get_symbols_by_category(
777
+ SymbolType.FOREX, category, fx_categories
778
+ )
791
779
 
792
780
  def get_stocks_from_country(
793
781
  self, country_code: str = "USA", etf=False
@@ -828,17 +816,22 @@ class Account(object):
828
816
  This mthods works primarly with Admirals Group AS products and Pepperstone Group Limited,
829
817
  For other brokers use `get_symbols()` or this method will use it by default.
830
818
  """
831
-
832
- if self.broker not in [AdmiralMarktsGroup(), PepperstoneGroupLimited()]:
833
- stocks = self.get_symbols(symbol_type="STK")
834
- return stocks
819
+
820
+ if (
821
+ self.broker != AdmiralMarktsGroup()
822
+ or self.broker != PepperstoneGroupLimited()
823
+ ):
824
+ return self.get_symbols(symbol_type=SymbolType.STOCKS)
835
825
  else:
826
+ stocks, etfs = [], []
836
827
  country_map = _COUNTRY_MAP_
837
- stocks = self._get_symbols_by_category("STK", country_code, country_map)
838
- if etf:
839
- etfs = self._get_symbols_by_category("ETF", country_code, country_map)
840
- return stocks + etfs
841
- return stocks
828
+ stocks = self._get_symbols_by_category(
829
+ SymbolType.STOCKS, country_code, country_map
830
+ )
831
+ etfs = self._get_symbols_by_category(
832
+ SymbolType.ETFs, country_code, country_map
833
+ )
834
+ return stocks + etfs if etf else stocks
842
835
 
843
836
  def get_stocks_from_exchange(
844
837
  self, exchange_code: str = "XNYS", etf=True
@@ -882,15 +875,17 @@ class Account(object):
882
875
  For other brokers use `get_symbols()` or this method will use it by default.
883
876
  """
884
877
  if self.broker != AdmiralMarktsGroup():
885
- stocks = self.get_symbols(symbol_type="STK")
886
- return stocks
878
+ return self.get_symbols(symbol_type=SymbolType.STOCKS)
887
879
  else:
880
+ stocks, etfs = [], []
888
881
  exchange_map = AMG_EXCHANGES
889
- stocks = self._get_symbols_by_category("STK", exchange_code, exchange_map)
890
- if etf:
891
- etfs = self._get_symbols_by_category("ETF", exchange_code, exchange_map)
892
- return stocks + etfs
893
- return stocks
882
+ stocks = self._get_symbols_by_category(
883
+ SymbolType.STOCKS, exchange_code, exchange_map
884
+ )
885
+ etfs = self._get_symbols_by_category(
886
+ SymbolType.ETFs, exchange_code, exchange_map
887
+ )
888
+ return stocks + etfs if etf else stocks
894
889
 
895
890
  def get_future_symbols(self, category: str = "ALL") -> List[str]:
896
891
  """
@@ -913,48 +908,42 @@ class Account(object):
913
908
  """
914
909
  category = category.lower()
915
910
  if self.broker != AdmiralMarktsGroup():
916
- return self.get_symbols(symbol_type="FUT")
911
+ return self.get_symbols(symbol_type=SymbolType.FUTURES)
917
912
  elif category in ["all", "index"]:
918
913
  categories = {
919
914
  "all": r"\b(Futures?)\b",
920
915
  "index": r"\b(Index)\b",
921
916
  }
922
- return self._get_symbols_by_category("FUT", category, categories)
917
+ return self._get_symbols_by_category(
918
+ SymbolType.FUTURES, category, categories
919
+ )
923
920
  else:
924
- metals = []
925
- energies = []
926
- agricultures = []
927
- bonds = []
928
- commodities = self.get_symbols(symbol_type="COMD")
929
- futures = self.get_symbols(symbol_type="FUT")
921
+ futures_types = {}
922
+ for future_type in ("metals", "energies", "agricultures", "bonds"):
923
+ futures_types[future_type] = []
924
+ commodities = self.get_symbols(symbol_type=SymbolType.COMMODITIES)
925
+ futures = self.get_symbols(symbol_type=SymbolType.FUTURES)
930
926
  for symbol in futures:
931
927
  info = self.get_symbol_info(symbol)
932
928
  if info.name.startswith("_"):
933
929
  if "XAU" in info.name:
934
- metals.append(info.name)
930
+ futures_types["metals"].append(info.name)
935
931
  if "oil" in info.name.lower():
936
- energies.append(info.name)
932
+ futures_types["energies"].append(info.name)
937
933
  name = info.name.split("_")[1]
938
934
  if name in commodities:
939
935
  _info = self.get_symbol_info(name)
940
936
  if "Metals" in _info.path:
941
- metals.append(info.name)
937
+ futures_types["metals"].append(info.name)
942
938
  elif "Energies" in _info.path:
943
- energies.append(info.name)
939
+ futures_types["energies"].append(info.name)
944
940
  elif "Agricultures" in _info.path:
945
- agricultures.append(info.name)
941
+ futures_types["agricultures"].append(info.name)
946
942
 
947
943
  elif info.name.startswith("#"):
948
944
  if "Index" not in info.path:
949
- bonds.append(info.name)
950
- if category == "metals":
951
- return metals
952
- elif category == "energies":
953
- return energies
954
- elif category == "agricultures":
955
- return agricultures
956
- elif category == "bonds":
957
- return bonds
945
+ futures_types["bonds"].append(info.name)
946
+ return futures_types[category]
958
947
 
959
948
  def get_symbol_info(self, symbol: str) -> Union[SymbolInfo, None]:
960
949
  """Get symbol properties
@@ -978,12 +967,16 @@ class Account(object):
978
967
  return None
979
968
  else:
980
969
  symbol_info_dict = symbol_info._asdict()
981
- time = datetime.fromtimestamp(symbol_info.time)
970
+ time = (
971
+ datetime.fromtimestamp(symbol_info.time)
972
+ if isinstance(symbol_info.time, int)
973
+ else symbol_info.time
974
+ )
982
975
  symbol_info_dict["time"] = time
983
976
  return SymbolInfo(**symbol_info_dict)
984
977
  except Exception as e:
985
978
  msg = self._symbol_info_msg(symbol)
986
- raise_mt5_error(message=f"{e + msg}")
979
+ raise_mt5_error(message=f"{str(e)} {msg}")
987
980
 
988
981
  def show_symbol_info(self, symbol: str):
989
982
  """
@@ -1024,12 +1017,16 @@ class Account(object):
1024
1017
  return None
1025
1018
  else:
1026
1019
  info_dict = tick_info._asdict()
1027
- time = datetime.fromtimestamp(tick_info.time)
1020
+ time = (
1021
+ datetime.fromtimestamp(tick_info.time)
1022
+ if isinstance(tick_info.time, int)
1023
+ else tick_info.time
1024
+ )
1028
1025
  info_dict["time"] = time
1029
1026
  return TickInfo(**info_dict)
1030
1027
  except Exception as e:
1031
1028
  msg = self._symbol_info_msg(symbol)
1032
- raise_mt5_error(message=f"{e + msg}")
1029
+ raise_mt5_error(message=f"{str(e)} {msg}")
1033
1030
 
1034
1031
  def show_tick_info(self, symbol: str):
1035
1032
  """
@@ -1050,16 +1047,17 @@ class Account(object):
1050
1047
  Returns:
1051
1048
  The Market Depth content as a tuple from BookInfo entries featuring order type, price and volume in lots.
1052
1049
  Return None in case of an error.
1053
-
1050
+
1054
1051
  Raises:
1055
1052
  MT5TerminalError: A specific exception based on the error code.
1056
1053
  """
1057
1054
  try:
1058
1055
  book = mt5.market_book_get(symbol)
1059
- if book is None:
1060
- return None
1061
- else:
1062
- return tuple([BookInfo(**entry._asdict()) for entry in book])
1056
+ return (
1057
+ None
1058
+ if book is None
1059
+ else tuple([BookInfo(**entry._asdict()) for entry in book])
1060
+ )
1063
1061
  except Exception as e:
1064
1062
  raise_mt5_error(e)
1065
1063
 
@@ -1083,10 +1081,7 @@ class Account(object):
1083
1081
  """
1084
1082
  actions = {"buy": mt5.ORDER_TYPE_BUY, "sell": mt5.ORDER_TYPE_SELL}
1085
1083
  try:
1086
- margin = mt5.order_calc_margin(actions[action], symbol, lot, price)
1087
- if margin is None:
1088
- return None
1089
- return margin
1084
+ return mt5.order_calc_margin(actions[action], symbol, lot, price)
1090
1085
  except Exception as e:
1091
1086
  raise_mt5_error(e)
1092
1087
 
@@ -302,10 +302,9 @@ class TradeCopier(object):
302
302
  if self.start_time is None or self.end_time is None:
303
303
  return True
304
304
  else:
305
- now = datetime.now()
306
305
  start_time = datetime.strptime(self.start_time, "%H:%M").time()
307
306
  end_time = datetime.strptime(self.end_time, "%H:%M").time()
308
- if start_time <= now.time() <= end_time:
307
+ if start_time <= datetime.now().time() <= end_time:
309
308
  return True
310
309
  return False
311
310
 
@@ -327,7 +326,7 @@ class TradeCopier(object):
327
326
  dest_eqty=Account(**destination).get_account_info().margin_free,
328
327
  )
329
328
 
330
- trade_instance = Trade(symbol=symbol, **destination, max_risk=100.0)
329
+ trade_instance = Trade(symbol=symbol, **destination, max_risk=100.0, logger=None)
331
330
  try:
332
331
  action = action_type[trade.type]
333
332
  except KeyError:
@@ -395,7 +394,7 @@ class TradeCopier(object):
395
394
 
396
395
  def remove_order(self, src_symbol, order: TradeOrder, destination: dict):
397
396
  check_mt5_connection(**destination)
398
- trade = Trade(symbol=order.symbol, **destination)
397
+ trade = Trade(symbol=order.symbol, **destination, logger=None)
399
398
  if trade.close_order(order.ticket, id=order.magic):
400
399
  logger.info(
401
400
  f"Close Order #{order.ticket} on @{destination.get('login')}::{order.symbol}, "
@@ -438,7 +437,7 @@ class TradeCopier(object):
438
437
 
439
438
  def remove_position(self, src_symbol, position: TradePosition, destination: dict):
440
439
  check_mt5_connection(**destination)
441
- trade = Trade(symbol=position.symbol, **destination)
440
+ trade = Trade(symbol=position.symbol, **destination, logger=None)
442
441
  if trade.close_position(position.ticket, id=position.magic):
443
442
  logger.info(
444
443
  f"Close Position #{position.ticket} on @{destination.get('login')}::{position.symbol}, "
@@ -626,7 +625,7 @@ class TradeCopier(object):
626
625
  time.sleep(0.1)
627
626
  except KeyboardInterrupt:
628
627
  logger.info("Stopping the Trade Copier ...")
629
- break
628
+ exit(0)
630
629
  except Exception as e:
631
630
  self.log_error(e)
632
631
  time.sleep(self.sleeptime)
@@ -7,7 +7,7 @@ from pandas.tseries.holiday import USFederalHolidayCalendar
7
7
  from pandas.tseries.offsets import CustomBusinessDay
8
8
 
9
9
  from bbstrader.metatrader.account import AMG_EXCHANGES, Account, check_mt5_connection
10
- from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error
10
+ from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error, SymbolType
11
11
 
12
12
  try:
13
13
  import MetaTrader5 as Mt5
@@ -46,13 +46,13 @@ COMD_CALENDARS = {
46
46
  }
47
47
 
48
48
  CALENDARS = {
49
- "FX": "us_futures",
50
- "STK": AMG_EXCHANGES,
51
- "ETF": AMG_EXCHANGES,
52
- "IDX": IDX_CALENDARS,
53
- "COMD": COMD_CALENDARS,
54
- "CRYPTO": "24/7",
55
- "FUT": None,
49
+ SymbolType.FOREX: "us_futures",
50
+ SymbolType.STOCKS: AMG_EXCHANGES,
51
+ SymbolType.ETFs: AMG_EXCHANGES,
52
+ SymbolType.INDICES: IDX_CALENDARS,
53
+ SymbolType.COMMODITIES: COMD_CALENDARS,
54
+ SymbolType.CRYPTO: "24/7",
55
+ SymbolType.FUTURES: None,
56
56
  }
57
57
 
58
58
  SESSION_TIMEFRAMES = [
@@ -206,6 +206,7 @@ class Rates(object):
206
206
  ) -> Union[pd.DataFrame, None]:
207
207
  """Fetches data from MT5 and returns a DataFrame or None."""
208
208
  try:
209
+ rates = None
209
210
  if isinstance(start, int) and isinstance(count, int):
210
211
  rates = Mt5.copy_rates_from_pos(
211
212
  self.symbol, self.time_frame, start, count
@@ -248,7 +249,7 @@ class Rates(object):
248
249
  currencies = self.__account.get_currency_rates(self.symbol)
249
250
  s_info = self.__account.get_symbol_info(self.symbol)
250
251
  if symbol_type in CALENDARS:
251
- if symbol_type == "STK" or symbol_type == "ETF":
252
+ if symbol_type == SymbolType.STOCKS or symbol_type == SymbolType.ETFs:
252
253
  for exchange in CALENDARS[symbol_type]:
253
254
  if exchange in get_calendar_names():
254
255
  symbols = self.__account.get_stocks_from_exchange(
@@ -257,20 +258,20 @@ class Rates(object):
257
258
  if self.symbol in symbols:
258
259
  calendar = get_calendar(exchange, side="right")
259
260
  break
260
- elif symbol_type == "IDX":
261
+ elif symbol_type == SymbolType.INDICES:
261
262
  calendar = get_calendar(
262
263
  CALENDARS[symbol_type][currencies["mc"]], side="right"
263
264
  )
264
- elif symbol_type == "COMD":
265
+ elif symbol_type == SymbolType.COMMODITIES:
265
266
  for commodity in CALENDARS[symbol_type]:
266
267
  if commodity in s_info.path:
267
268
  calendar = get_calendar(
268
269
  CALENDARS[symbol_type][commodity], side="right"
269
270
  )
270
- elif symbol_type == "FUT":
271
+ elif symbol_type == SymbolType.FUTURES:
271
272
  if "Index" in s_info.path:
272
273
  calendar = get_calendar(
273
- CALENDARS["IDX"][currencies["mc"]], side="right"
274
+ CALENDARS[SymbolType.INDICES][currencies["mc"]], side="right"
274
275
  )
275
276
  else:
276
277
  for commodity, cal in COMD_CALENDARS.items():
@@ -6,7 +6,7 @@ from scipy.stats import norm
6
6
 
7
7
  from bbstrader.metatrader.account import Account
8
8
  from bbstrader.metatrader.rates import Rates
9
- from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame
9
+ from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, SymbolType
10
10
 
11
11
  try:
12
12
  import MetaTrader5 as Mt5
@@ -275,10 +275,12 @@ class RiskManagement(Account):
275
275
  swap = df["swap"].sum()
276
276
  total_profit = commisions + fees + swap + profit
277
277
  initial_balance = balance - total_profit
278
- if balance != 0:
278
+ if equity != 0:
279
279
  risk_alowed = (((equity - initial_balance) / equity) * 100) * -1
280
280
  return round(risk_alowed, 2)
281
- return 0.0
281
+ else: # Handle equity is zero
282
+ return 0.0
283
+ return 0.0 # This is for the case where df is None
282
284
 
283
285
  def get_lot(self) -> float:
284
286
  """ "Get the approprite lot size for a trade"""
@@ -498,10 +500,10 @@ class RiskManagement(Account):
498
500
  av_price = (s_info.bid + s_info.ask) / 2
499
501
  trade_risk = self.get_trade_risk()
500
502
  symbol_type = self.get_symbol_type(self.symbol)
501
- FX = symbol_type == "FX"
502
- COMD = symbol_type == "COMD"
503
- FUT = symbol_type == "FUT"
504
- CRYPTO = symbol_type == "CRYPTO"
503
+ FX = symbol_type == SymbolType.FOREX
504
+ COMD = symbol_type == SymbolType.COMMODITIES
505
+ FUT = symbol_type == SymbolType.FUTURES
506
+ CRYPTO = symbol_type == SymbolType.CRYPTO
505
507
  if COMD:
506
508
  supported = _COMMD_SUPPORTED_
507
509
  if "." in self.symbol:
@@ -652,14 +654,14 @@ class RiskManagement(Account):
652
654
  lot = self._check_lot(_lot)
653
655
 
654
656
  volume = round(lot * size * av_price)
655
- if self.get_symbol_type(self.symbol) == "FX":
657
+ if self.get_symbol_type(self.symbol) == SymbolType.FOREX:
656
658
  volume = round((trade_loss * size) / loss)
657
659
  __lot = round((volume / size), 2)
658
660
  lot = self._check_lot(__lot)
659
661
 
660
662
  if (
661
- self.get_symbol_type(self.symbol) == "COMD"
662
- or self.get_symbol_type(self.symbol) == "CRYPTO"
663
+ self.get_symbol_type(self.symbol) == SymbolType.COMMODITIES
664
+ or self.get_symbol_type(self.symbol) == SymbolType.CRYPTO
663
665
  and size > 1
664
666
  ):
665
667
  lot = currency_risk / (sl * loss * size)
@@ -705,7 +707,7 @@ class RiskManagement(Account):
705
707
  if account:
706
708
  return AL
707
709
 
708
- if self.get_symbol_type(self.symbol) == "FX":
710
+ if self.get_symbol_type(self.symbol) == SymbolType.FOREX:
709
711
  return AL
710
712
  else:
711
713
  s_info = self.symbol_info