bbstrader 0.2.99__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.

@@ -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)}"
@@ -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)