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.
- bbstrader/__init__.py +1 -1
- bbstrader/__main__.py +35 -14
- bbstrader/btengine/data.py +4 -2
- bbstrader/btengine/execution.py +24 -14
- bbstrader/btengine/strategy.py +33 -15
- bbstrader/core/data.py +93 -30
- bbstrader/core/scripts.py +130 -0
- bbstrader/metatrader/account.py +115 -126
- bbstrader/metatrader/copier.py +114 -39
- bbstrader/metatrader/rates.py +14 -13
- bbstrader/metatrader/risk.py +13 -11
- bbstrader/metatrader/scripts.py +26 -12
- bbstrader/metatrader/trade.py +60 -54
- bbstrader/metatrader/utils.py +80 -26
- bbstrader/models/factors.py +3 -1
- bbstrader/models/ml.py +2 -1
- bbstrader/models/nlp.py +123 -70
- bbstrader/trading/execution.py +74 -36
- bbstrader/trading/strategies.py +15 -14
- bbstrader/tseries.py +8 -9
- bbstrader-0.3.1.dist-info/METADATA +466 -0
- bbstrader-0.3.1.dist-info/RECORD +47 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.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.1.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
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)}"
|
|
@@ -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=
|
|
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(
|
|
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
|
-
|
|
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(
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
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(
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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=
|
|
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(
|
|
911
|
+
return self._get_symbols_by_category(
|
|
912
|
+
SymbolType.FUTURES, category, categories
|
|
913
|
+
)
|
|
923
914
|
else:
|
|
924
|
-
|
|
925
|
-
energies
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
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
|
|