bbstrader 0.2.991__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bbstrader might be problematic. Click here for more details.

@@ -0,0 +1,130 @@
1
+ import argparse
2
+ import asyncio
3
+ import sys
4
+ import textwrap
5
+ import time
6
+ from datetime import datetime, timedelta
7
+
8
+ import nltk
9
+ from loguru import logger
10
+ from sumy.nlp.tokenizers import Tokenizer
11
+ from sumy.parsers.plaintext import PlaintextParser
12
+ from sumy.summarizers.text_rank import TextRankSummarizer
13
+
14
+ from bbstrader.core.data import FinancialNews
15
+ from bbstrader.trading.utils import send_telegram_message
16
+
17
+
18
+ def summarize_text(text, sentences_count=5):
19
+ """
20
+ Generate a summary using TextRank algorithm.
21
+ """
22
+ parser = PlaintextParser.from_string(text, Tokenizer("english"))
23
+ summarizer = TextRankSummarizer()
24
+ summary = summarizer(parser.document, sentences_count)
25
+ return " ".join(str(sentence) for sentence in summary)
26
+
27
+
28
+ def format_article_for_telegram(article: dict) -> str:
29
+ if not all(
30
+ k in article
31
+ for k in (
32
+ "body",
33
+ "title",
34
+ "published_on",
35
+ "sentiment",
36
+ "keywords",
37
+ "keywords",
38
+ "url",
39
+ )
40
+ ):
41
+ return ""
42
+ summary = summarize_text(article["body"])
43
+ text = (
44
+ f"📰 {article['title']}\n"
45
+ f"Published Date: {article['published_on']}\n"
46
+ f"Sentiment: {article['sentiment']}\n"
47
+ f"Status: {article['status']}\n"
48
+ f"Keywords: {article['keywords']}\n\n"
49
+ f"🔍 Summary\n"
50
+ f"{textwrap.fill(summary, width=80)}"
51
+ f"\n\n👉 Visit {article['url']} for full article."
52
+ )
53
+ return text
54
+
55
+
56
+ async def send_articles(articles: dict, token: str, id: str, interval=15):
57
+ 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)
66
+
67
+
68
+ def send_news_feed(unknown):
69
+ HELP_MSG = """
70
+ Send news feed from Coindesk to Telegram channel.
71
+ This script fetches the latest news articles from Coindesk, summarizes them,
72
+ and sends them to a specified Telegram channel at regular intervals.
73
+
74
+ Usage:
75
+ python -m bbstrader --run news_feed [options]
76
+
77
+ Options:
78
+ -q, --query: The news to look for (default: "")
79
+ -t, --token: Telegram bot token
80
+ -I, --id: Telegram Chat id
81
+ -i, --interval: Interval in minutes to fetch news (default: 15)
82
+
83
+ Note:
84
+ The script will run indefinitely, fetching news every 15 minutes.
85
+ Use Ctrl+C to stop the script.
86
+ """
87
+
88
+ if "-h" in unknown or "--help" in unknown:
89
+ print(HELP_MSG)
90
+ sys.exit(0)
91
+
92
+ parser = argparse.ArgumentParser()
93
+ parser.add_argument(
94
+ "-q", "--query", type=str, default="", help="The news to look for"
95
+ )
96
+ parser.add_argument(
97
+ "-t",
98
+ "--token",
99
+ type=str,
100
+ required=True,
101
+ help="Telegram bot token",
102
+ )
103
+ parser.add_argument("-I", "--id", type=str, required=True, help="Telegram Chat id")
104
+ parser.add_argument(
105
+ "-i",
106
+ "--interval",
107
+ type=int,
108
+ default=15,
109
+ help="Interval in minutes to fetch news (default: 15)",
110
+ )
111
+ args = parser.parse_args(unknown)
112
+
113
+ nltk.download("punkt", quiet=True)
114
+ news = FinancialNews()
115
+ logger.info(
116
+ f"Starting the News Feed on {args.interval} minutes"
117
+ )
118
+ while True:
119
+ 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))
125
+ time.sleep(args.interval * 60)
126
+ except KeyboardInterrupt:
127
+ logger.info("Stopping the News Feed ...")
128
+ exit(0)
129
+ except Exception as e:
130
+ logger.error(e)
@@ -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)}"
@@ -778,7 +761,7 @@ class Account(object):
778
761
  For other brokers use `get_symbols()` or this method will use it by default.
779
762
  """
780
763
  if self.broker not in [AdmiralMarktsGroup(), PepperstoneGroupLimited()]:
781
- return self.get_symbols(symbol_type="FX")
764
+ return self.get_symbols(symbol_type=SymbolType.FOREX)
782
765
  else:
783
766
  fx_categories = {
784
767
  "majors": r"\b(Majors?)\b",
@@ -787,7 +770,9 @@ class Account(object):
787
770
  "crosses": r"\b(Crosses?)\b",
788
771
  "ndfs": r"\b(NDFs?)\b",
789
772
  }
790
- return self._get_symbols_by_category("FX", category, fx_categories)
773
+ return self._get_symbols_by_category(
774
+ SymbolType.FOREX, category, fx_categories
775
+ )
791
776
 
792
777
  def get_stocks_from_country(
793
778
  self, country_code: str = "USA", etf=False
@@ -830,15 +815,17 @@ class Account(object):
830
815
  """
831
816
 
832
817
  if self.broker not in [AdmiralMarktsGroup(), PepperstoneGroupLimited()]:
833
- stocks = self.get_symbols(symbol_type="STK")
834
- return stocks
818
+ return self.get_symbols(symbol_type=SymbolType.STOCKS)
835
819
  else:
820
+ stocks, etfs = [], []
836
821
  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
822
+ stocks = self._get_symbols_by_category(
823
+ SymbolType.STOCKS, country_code, country_map
824
+ )
825
+ etfs = self._get_symbols_by_category(
826
+ SymbolType.ETFs, country_code, country_map
827
+ )
828
+ return stocks + etfs if etf else stocks
842
829
 
843
830
  def get_stocks_from_exchange(
844
831
  self, exchange_code: str = "XNYS", etf=True
@@ -882,15 +869,17 @@ class Account(object):
882
869
  For other brokers use `get_symbols()` or this method will use it by default.
883
870
  """
884
871
  if self.broker != AdmiralMarktsGroup():
885
- stocks = self.get_symbols(symbol_type="STK")
886
- return stocks
872
+ return self.get_symbols(symbol_type=SymbolType.STOCKS)
887
873
  else:
874
+ stocks, etfs = [], []
888
875
  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
876
+ stocks = self._get_symbols_by_category(
877
+ SymbolType.STOCKS, exchange_code, exchange_map
878
+ )
879
+ etfs = self._get_symbols_by_category(
880
+ SymbolType.ETFs, exchange_code, exchange_map
881
+ )
882
+ return stocks + etfs if etf else stocks
894
883
 
895
884
  def get_future_symbols(self, category: str = "ALL") -> List[str]:
896
885
  """
@@ -913,48 +902,42 @@ class Account(object):
913
902
  """
914
903
  category = category.lower()
915
904
  if self.broker != AdmiralMarktsGroup():
916
- return self.get_symbols(symbol_type="FUT")
905
+ return self.get_symbols(symbol_type=SymbolType.FUTURES)
917
906
  elif category in ["all", "index"]:
918
907
  categories = {
919
908
  "all": r"\b(Futures?)\b",
920
909
  "index": r"\b(Index)\b",
921
910
  }
922
- return self._get_symbols_by_category("FUT", category, categories)
911
+ return self._get_symbols_by_category(
912
+ SymbolType.FUTURES, category, categories
913
+ )
923
914
  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")
915
+ futures_types = {}
916
+ for future_type in ("metals", "energies", "agricultures", "bonds"):
917
+ futures_types[future_type] = []
918
+ commodities = self.get_symbols(symbol_type=SymbolType.COMMODITIES)
919
+ futures = self.get_symbols(symbol_type=SymbolType.FUTURES)
930
920
  for symbol in futures:
931
921
  info = self.get_symbol_info(symbol)
932
922
  if info.name.startswith("_"):
933
923
  if "XAU" in info.name:
934
- metals.append(info.name)
924
+ futures_types["metals"].append(info.name)
935
925
  if "oil" in info.name.lower():
936
- energies.append(info.name)
926
+ futures_types["energies"].append(info.name)
937
927
  name = info.name.split("_")[1]
938
928
  if name in commodities:
939
929
  _info = self.get_symbol_info(name)
940
930
  if "Metals" in _info.path:
941
- metals.append(info.name)
931
+ futures_types["metals"].append(info.name)
942
932
  elif "Energies" in _info.path:
943
- energies.append(info.name)
933
+ futures_types["energies"].append(info.name)
944
934
  elif "Agricultures" in _info.path:
945
- agricultures.append(info.name)
935
+ futures_types["agricultures"].append(info.name)
946
936
 
947
937
  elif info.name.startswith("#"):
948
938
  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
939
+ futures_types["bonds"].append(info.name)
940
+ return futures_types[category]
958
941
 
959
942
  def get_symbol_info(self, symbol: str) -> Union[SymbolInfo, None]:
960
943
  """Get symbol properties
@@ -978,12 +961,16 @@ class Account(object):
978
961
  return None
979
962
  else:
980
963
  symbol_info_dict = symbol_info._asdict()
981
- time = datetime.fromtimestamp(symbol_info.time)
964
+ time = (
965
+ datetime.fromtimestamp(symbol_info.time)
966
+ if isinstance(symbol_info.time, int)
967
+ else symbol_info.time
968
+ )
982
969
  symbol_info_dict["time"] = time
983
970
  return SymbolInfo(**symbol_info_dict)
984
971
  except Exception as e:
985
972
  msg = self._symbol_info_msg(symbol)
986
- raise_mt5_error(message=f"{e + msg}")
973
+ raise_mt5_error(message=f"{str(e)} {msg}")
987
974
 
988
975
  def show_symbol_info(self, symbol: str):
989
976
  """
@@ -1024,12 +1011,16 @@ class Account(object):
1024
1011
  return None
1025
1012
  else:
1026
1013
  info_dict = tick_info._asdict()
1027
- time = datetime.fromtimestamp(tick_info.time)
1014
+ time = (
1015
+ datetime.fromtimestamp(tick_info.time)
1016
+ if isinstance(tick_info.time, int)
1017
+ else tick_info.time
1018
+ )
1028
1019
  info_dict["time"] = time
1029
1020
  return TickInfo(**info_dict)
1030
1021
  except Exception as e:
1031
1022
  msg = self._symbol_info_msg(symbol)
1032
- raise_mt5_error(message=f"{e + msg}")
1023
+ raise_mt5_error(message=f"{str(e)} {msg}")
1033
1024
 
1034
1025
  def show_tick_info(self, symbol: str):
1035
1026
  """
@@ -1050,16 +1041,17 @@ class Account(object):
1050
1041
  Returns:
1051
1042
  The Market Depth content as a tuple from BookInfo entries featuring order type, price and volume in lots.
1052
1043
  Return None in case of an error.
1053
-
1044
+
1054
1045
  Raises:
1055
1046
  MT5TerminalError: A specific exception based on the error code.
1056
1047
  """
1057
1048
  try:
1058
1049
  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])
1050
+ return (
1051
+ None
1052
+ if book is None
1053
+ else tuple([BookInfo(**entry._asdict()) for entry in book])
1054
+ )
1063
1055
  except Exception as e:
1064
1056
  raise_mt5_error(e)
1065
1057
 
@@ -1083,10 +1075,7 @@ class Account(object):
1083
1075
  """
1084
1076
  actions = {"buy": mt5.ORDER_TYPE_BUY, "sell": mt5.ORDER_TYPE_SELL}
1085
1077
  try:
1086
- margin = mt5.order_calc_margin(actions[action], symbol, lot, price)
1087
- if margin is None:
1088
- return None
1089
- return margin
1078
+ return mt5.order_calc_margin(actions[action], symbol, lot, price)
1090
1079
  except Exception as e:
1091
1080
  raise_mt5_error(e)
1092
1081