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.
- bbstrader/__main__.py +31 -14
- bbstrader/btengine/data.py +4 -2
- bbstrader/btengine/execution.py +24 -14
- bbstrader/btengine/strategy.py +33 -15
- bbstrader/core/data.py +1 -1
- bbstrader/core/scripts.py +130 -0
- bbstrader/metatrader/account.py +124 -129
- bbstrader/metatrader/copier.py +5 -6
- bbstrader/metatrader/rates.py +14 -13
- bbstrader/metatrader/risk.py +13 -11
- bbstrader/metatrader/trade.py +33 -22
- bbstrader/metatrader/utils.py +79 -26
- bbstrader/models/factors.py +3 -1
- bbstrader/models/ml.py +2 -1
- bbstrader/models/nlp.py +12 -2
- bbstrader/trading/execution.py +36 -19
- bbstrader/trading/strategies.py +15 -14
- bbstrader/tseries.py +8 -9
- bbstrader-0.3.0.dist-info/METADATA +469 -0
- bbstrader-0.3.0.dist-info/RECORD +47 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.0.dist-info}/WHEEL +1 -1
- bbstrader/__ini__.py +0 -20
- bbstrader-0.2.991.dist-info/METADATA +0 -191
- bbstrader-0.2.991.dist-info/RECORD +0 -47
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.0.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.0.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/account.py
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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__("
|
|
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__("
|
|
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
|
|
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__("
|
|
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
|
|
591
|
+
symbol_type (SymbolType | str): The type of financial instruments to retrieve.
|
|
589
592
|
- `ALL`: For all available symbols
|
|
590
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
"
|
|
677
|
-
|
|
678
|
-
"
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
- `
|
|
711
|
-
- `
|
|
712
|
-
- `
|
|
713
|
-
- `
|
|
714
|
-
- `
|
|
715
|
-
- `
|
|
716
|
-
|
|
717
|
-
|
|
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=
|
|
725
|
-
commodity = self.get_symbols(symbol_type=
|
|
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 [
|
|
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 =
|
|
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
|
|
720
|
+
return SymbolType.unknown
|
|
740
721
|
|
|
741
|
-
def _get_symbols_by_category(
|
|
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
|
|
781
|
-
|
|
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(
|
|
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
|
|
833
|
-
|
|
834
|
-
|
|
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(
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
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(
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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=
|
|
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(
|
|
917
|
+
return self._get_symbols_by_category(
|
|
918
|
+
SymbolType.FUTURES, category, categories
|
|
919
|
+
)
|
|
923
920
|
else:
|
|
924
|
-
|
|
925
|
-
energies
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
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
|
|
bbstrader/metatrader/copier.py
CHANGED
|
@@ -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
|
-
|
|
628
|
+
exit(0)
|
|
630
629
|
except Exception as e:
|
|
631
630
|
self.log_error(e)
|
|
632
631
|
time.sleep(self.sleeptime)
|
bbstrader/metatrader/rates.py
CHANGED
|
@@ -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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 ==
|
|
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 ==
|
|
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 ==
|
|
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 ==
|
|
271
|
+
elif symbol_type == SymbolType.FUTURES:
|
|
271
272
|
if "Index" in s_info.path:
|
|
272
273
|
calendar = get_calendar(
|
|
273
|
-
CALENDARS[
|
|
274
|
+
CALENDARS[SymbolType.INDICES][currencies["mc"]], side="right"
|
|
274
275
|
)
|
|
275
276
|
else:
|
|
276
277
|
for commodity, cal in COMD_CALENDARS.items():
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -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
|
|
278
|
+
if equity != 0:
|
|
279
279
|
risk_alowed = (((equity - initial_balance) / equity) * 100) * -1
|
|
280
280
|
return round(risk_alowed, 2)
|
|
281
|
-
|
|
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 ==
|
|
502
|
-
COMD = symbol_type ==
|
|
503
|
-
FUT = symbol_type ==
|
|
504
|
-
CRYPTO = symbol_type ==
|
|
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) ==
|
|
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) ==
|
|
662
|
-
or self.get_symbol_type(self.symbol) ==
|
|
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) ==
|
|
710
|
+
if self.get_symbol_type(self.symbol) == SymbolType.FOREX:
|
|
709
711
|
return AL
|
|
710
712
|
else:
|
|
711
713
|
s_info = self.symbol_info
|