bbstrader 0.2.96__tar.gz → 0.2.98__tar.gz
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-0.2.96/bbstrader.egg-info → bbstrader-0.2.98}/PKG-INFO +12 -15
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/__main__.py +2 -1
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/data.py +6 -6
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/execution.py +1 -1
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/strategy.py +90 -101
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/core/utils.py +0 -58
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/account.py +30 -6
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/copier.py +7 -11
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/trade.py +186 -68
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/utils.py +16 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/risk.py +2 -2
- bbstrader-0.2.98/bbstrader/trading/execution.py +990 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/trading/scripts.py +10 -11
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/trading/strategies.py +6 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98/bbstrader.egg-info}/PKG-INFO +12 -15
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader.egg-info/requires.txt +11 -14
- {bbstrader-0.2.96 → bbstrader-0.2.98}/requirements.txt +11 -15
- {bbstrader-0.2.96 → bbstrader-0.2.98}/setup.py +1 -1
- bbstrader-0.2.96/bbstrader/trading/execution.py +0 -849
- {bbstrader-0.2.96 → bbstrader-0.2.98}/LICENSE +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/MANIFEST.in +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/README.md +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/__ini__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/__init__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/backtest.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/event.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/performance.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/portfolio.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/btengine/scripts.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/compat.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/config.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/core/__init__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/core/data.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/ibkr/__init__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/ibkr/utils.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/__init__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/rates.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/risk.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/metatrader/scripts.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/__init__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/factors.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/ml.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/nlp.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/optimization.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/models/portfolio.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/trading/__init__.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/trading/utils.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader/tseries.py +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader.egg-info/SOURCES.txt +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader.egg-info/dependency_links.txt +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader.egg-info/entry_points.txt +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/bbstrader.egg-info/top_level.txt +0 -0
- {bbstrader-0.2.96 → bbstrader-0.2.98}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.98
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
5
|
Home-page: https://github.com/bbalouki/bbstrader
|
|
6
6
|
Download-URL: https://pypi.org/project/bbstrader/
|
|
@@ -24,21 +24,24 @@ Classifier: Operating System :: MacOS
|
|
|
24
24
|
Classifier: License :: OSI Approved :: MIT License
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE
|
|
27
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: numpy<2.0.0,>=1.26.0
|
|
28
|
+
Requires-Dist: scikit-learn
|
|
29
|
+
Requires-Dist: statsmodels
|
|
30
|
+
Requires-Dist: seaborn
|
|
31
|
+
Requires-Dist: lightgbm
|
|
32
|
+
Requires-Dist: dash
|
|
33
|
+
Requires-Dist: nltk
|
|
34
|
+
Requires-Dist: spacy
|
|
35
|
+
Requires-Dist: pmdarima
|
|
36
|
+
Requires-Dist: pyportfolioopt
|
|
37
|
+
Requires-Dist: alphalens-reloaded
|
|
28
38
|
Requires-Dist: pandas_ta
|
|
29
|
-
Requires-Dist: numpy<2.0.0
|
|
30
39
|
Requires-Dist: yfinance
|
|
31
|
-
Requires-Dist: scipy
|
|
32
40
|
Requires-Dist: hmmlearn
|
|
33
|
-
Requires-Dist: pmdarima
|
|
34
41
|
Requires-Dist: arch
|
|
35
42
|
Requires-Dist: hurst
|
|
36
|
-
Requires-Dist: seaborn
|
|
37
|
-
Requires-Dist: statsmodels
|
|
38
|
-
Requires-Dist: matplotlib
|
|
39
43
|
Requires-Dist: filterpy
|
|
40
44
|
Requires-Dist: pykalman
|
|
41
|
-
Requires-Dist: pytest
|
|
42
45
|
Requires-Dist: CurrencyConverter
|
|
43
46
|
Requires-Dist: tabulate
|
|
44
47
|
Requires-Dist: python-dotenv
|
|
@@ -46,23 +49,17 @@ Requires-Dist: ipython
|
|
|
46
49
|
Requires-Dist: QuantStats
|
|
47
50
|
Requires-Dist: exchange-calendars
|
|
48
51
|
Requires-Dist: tqdm
|
|
49
|
-
Requires-Dist: scikit-learn
|
|
50
52
|
Requires-Dist: notify-py
|
|
51
53
|
Requires-Dist: python-telegram-bot
|
|
52
|
-
Requires-Dist: pyportfolioopt
|
|
53
54
|
Requires-Dist: eodhd
|
|
54
55
|
Requires-Dist: financetoolkit
|
|
55
56
|
Requires-Dist: PyYAML
|
|
56
57
|
Requires-Dist: tables
|
|
57
|
-
Requires-Dist: lightgbm
|
|
58
|
-
Requires-Dist: alphalens-reloaded
|
|
59
58
|
Requires-Dist: pyfiglet
|
|
60
59
|
Requires-Dist: colorama
|
|
61
60
|
Requires-Dist: praw
|
|
62
61
|
Requires-Dist: tweepy
|
|
63
62
|
Requires-Dist: beautifulsoup4
|
|
64
|
-
Requires-Dist: dash
|
|
65
|
-
Requires-Dist: nltk
|
|
66
63
|
Requires-Dist: textblob
|
|
67
64
|
Requires-Dist: vaderSentiment
|
|
68
65
|
Provides-Extra: mt5
|
|
@@ -16,7 +16,7 @@ USAGE_TEXT = """
|
|
|
16
16
|
Modules:
|
|
17
17
|
copier: Copy trades from one MetaTrader account to another or multiple accounts
|
|
18
18
|
backtest: Backtest a strategy, see bbstrader.btengine.backtest.run_backtest
|
|
19
|
-
execution: Execute a strategy, see bbstrader.trading.execution.
|
|
19
|
+
execution: Execute a strategy, see bbstrader.trading.execution.Mt5ExecutionEngine
|
|
20
20
|
|
|
21
21
|
python -m bbstrader --run <module> --help for more information on the module
|
|
22
22
|
"""
|
|
@@ -26,6 +26,7 @@ FONT = pyfiglet.figlet_format("BBSTRADER", font="big")
|
|
|
26
26
|
|
|
27
27
|
def main():
|
|
28
28
|
print(Fore.BLUE + FONT)
|
|
29
|
+
print(Fore.WHITE + "")
|
|
29
30
|
parser = argparse.ArgumentParser(
|
|
30
31
|
prog="BBSTRADER",
|
|
31
32
|
usage=USAGE_TEXT,
|
|
@@ -66,21 +66,21 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
66
66
|
"""
|
|
67
67
|
Returns the last bar updated.
|
|
68
68
|
"""
|
|
69
|
-
|
|
69
|
+
pass
|
|
70
70
|
|
|
71
71
|
@abstractmethod
|
|
72
72
|
def get_latest_bars(self, symbol, N=1, df=True) -> pd.DataFrame | List[pd.Series]:
|
|
73
73
|
"""
|
|
74
74
|
Returns the last N bars updated.
|
|
75
75
|
"""
|
|
76
|
-
|
|
76
|
+
pass
|
|
77
77
|
|
|
78
78
|
@abstractmethod
|
|
79
79
|
def get_latest_bar_datetime(self, symbol) -> datetime | pd.Timestamp:
|
|
80
80
|
"""
|
|
81
81
|
Returns a Python datetime object for the last bar.
|
|
82
82
|
"""
|
|
83
|
-
|
|
83
|
+
pass
|
|
84
84
|
|
|
85
85
|
@abstractmethod
|
|
86
86
|
def get_latest_bar_value(self, symbol, val_type) -> float:
|
|
@@ -88,7 +88,7 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
88
88
|
Returns one of the Open, High, Low, Close, Adj Close, Volume or Returns
|
|
89
89
|
from the last bar.
|
|
90
90
|
"""
|
|
91
|
-
|
|
91
|
+
pass
|
|
92
92
|
|
|
93
93
|
@abstractmethod
|
|
94
94
|
def get_latest_bars_values(self, symbol, val_type, N=1) -> np.ndarray:
|
|
@@ -96,7 +96,7 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
96
96
|
Returns the last N bar values from the
|
|
97
97
|
latest_symbol list, or N-k if less available.
|
|
98
98
|
"""
|
|
99
|
-
|
|
99
|
+
pass
|
|
100
100
|
|
|
101
101
|
@abstractmethod
|
|
102
102
|
def update_bars(self):
|
|
@@ -105,7 +105,7 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
105
105
|
in a tuple OHLCVI format: (datetime, Open, High, Low,
|
|
106
106
|
Close, Adj Close, Volume, Retruns).
|
|
107
107
|
"""
|
|
108
|
-
|
|
108
|
+
pass
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
class BaseCSVDataHandler(DataHandler):
|
|
@@ -12,13 +12,13 @@ from loguru import logger
|
|
|
12
12
|
from bbstrader.btengine.data import DataHandler
|
|
13
13
|
from bbstrader.btengine.event import FillEvent, SignalEvent
|
|
14
14
|
from bbstrader.config import BBSTRADER_DIR
|
|
15
|
-
from bbstrader.core.utils import TradeSignal
|
|
16
15
|
from bbstrader.metatrader.account import (
|
|
17
16
|
Account,
|
|
18
17
|
AdmiralMarktsGroup,
|
|
19
18
|
PepperstoneGroupLimited,
|
|
20
19
|
)
|
|
21
20
|
from bbstrader.metatrader.rates import Rates
|
|
21
|
+
from bbstrader.metatrader.trade import TradeSignal
|
|
22
22
|
from bbstrader.models.optimization import optimized_weights
|
|
23
23
|
|
|
24
24
|
__all__ = ["Strategy", "MT5Strategy"]
|
|
@@ -55,7 +55,7 @@ class Strategy(metaclass=ABCMeta):
|
|
|
55
55
|
|
|
56
56
|
@abstractmethod
|
|
57
57
|
def calculate_signals(self, *args, **kwargs) -> List[TradeSignal]:
|
|
58
|
-
|
|
58
|
+
pass
|
|
59
59
|
|
|
60
60
|
def check_pending_orders(self, *args, **kwargs): ...
|
|
61
61
|
def get_update_from_portfolio(self, *args, **kwargs): ...
|
|
@@ -67,7 +67,7 @@ class MT5Strategy(Strategy):
|
|
|
67
67
|
"""
|
|
68
68
|
A `MT5Strategy()` object is a subclass of `Strategy` that is used to
|
|
69
69
|
calculate signals for the MetaTrader 5 trading platform. The signals
|
|
70
|
-
are generated by the `MT5Strategy` object and sent to the the `
|
|
70
|
+
are generated by the `MT5Strategy` object and sent to the the `Mt5ExecutionEngine`
|
|
71
71
|
for live trading and `MT5BacktestEngine` objects for backtesting.
|
|
72
72
|
"""
|
|
73
73
|
|
|
@@ -146,9 +146,9 @@ class MT5Strategy(Strategy):
|
|
|
146
146
|
def _initialize_portfolio(self):
|
|
147
147
|
positions = ["LONG", "SHORT"]
|
|
148
148
|
orders = ["BLMT", "BSTP", "BSTPLMT", "SLMT", "SSTP", "SSTPLMT"]
|
|
149
|
+
self._orders: Dict[str, Dict[str, List[SignalEvent]]] = {}
|
|
149
150
|
self._positions: Dict[str, Dict[str, int | float]] = {}
|
|
150
151
|
self._trades: Dict[str, Dict[str, int]] = {}
|
|
151
|
-
self._orders: Dict[str, Dict[str, List[SignalEvent]]] = {}
|
|
152
152
|
for symbol in self.symbols:
|
|
153
153
|
self._positions[symbol] = {}
|
|
154
154
|
self._orders[symbol] = {}
|
|
@@ -206,6 +206,7 @@ class MT5Strategy(Strategy):
|
|
|
206
206
|
- ``price``: The price at which to execute the action, used for pending orders.
|
|
207
207
|
- ``stoplimit``: The stop-limit price for STOP-LIMIT orders, used for pending stop limit orders.
|
|
208
208
|
- ``id``: The unique identifier for the strategy or order.
|
|
209
|
+
- ``comment``: An optional comment or description related to the trade signal.
|
|
209
210
|
"""
|
|
210
211
|
pass
|
|
211
212
|
|
|
@@ -503,108 +504,96 @@ class MT5Strategy(Strategy):
|
|
|
503
504
|
"""
|
|
504
505
|
Check for pending orders and handle them accordingly.
|
|
505
506
|
"""
|
|
506
|
-
for symbol in self.symbols:
|
|
507
|
-
dtime = self.data.get_latest_bar_datetime(symbol)
|
|
508
507
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
508
|
+
def logmsg(order, type, symbol, dtime):
|
|
509
|
+
return self.logger.info(
|
|
510
|
+
f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
|
|
511
|
+
f"PRICE @ {order.price}",
|
|
512
|
+
custom_time=dtime,
|
|
513
|
+
)
|
|
515
514
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
)
|
|
515
|
+
def process_orders(order_type, condition, execute_fn, log_label, symbol, dtime):
|
|
516
|
+
for order in self._orders[symbol][order_type].copy():
|
|
517
|
+
if condition(order):
|
|
518
|
+
execute_fn(order)
|
|
521
519
|
try:
|
|
522
|
-
self._orders[symbol][
|
|
523
|
-
assert order not in self._orders[symbol][
|
|
524
|
-
logmsg(order, "BUY LIMIT")
|
|
520
|
+
self._orders[symbol][order_type].remove(order)
|
|
521
|
+
assert order not in self._orders[symbol][order_type]
|
|
525
522
|
except AssertionError:
|
|
526
|
-
self._orders[symbol][
|
|
527
|
-
o for o in self._orders[symbol][
|
|
523
|
+
self._orders[symbol][order_type] = [
|
|
524
|
+
o for o in self._orders[symbol][order_type] if o != order
|
|
528
525
|
]
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
self.
|
|
561
|
-
|
|
562
|
-
)
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
symbol,
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
self._orders[symbol]["SSTPLMT"].remove(order)
|
|
601
|
-
assert order not in self._orders[symbol]["SSTPLMT"]
|
|
602
|
-
logmsg(order, "SELL STOP LIMIT")
|
|
603
|
-
except AssertionError:
|
|
604
|
-
self._orders[symbol]["SSTPLMT"] = [
|
|
605
|
-
o for o in self._orders[symbol]["SSTPLMT"] if o != order
|
|
606
|
-
]
|
|
607
|
-
logmsg(order, "SELL STOP LIMIT")
|
|
526
|
+
logmsg(order, log_label, symbol, dtime)
|
|
527
|
+
|
|
528
|
+
for symbol in self.symbols:
|
|
529
|
+
dtime = self.data.get_latest_bar_datetime(symbol)
|
|
530
|
+
latest_close = self.data.get_latest_bar_value(symbol, "close")
|
|
531
|
+
|
|
532
|
+
process_orders(
|
|
533
|
+
"BLMT",
|
|
534
|
+
lambda o: latest_close <= o.price,
|
|
535
|
+
lambda o: self.buy_mkt(
|
|
536
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
537
|
+
),
|
|
538
|
+
"BUY LIMIT",
|
|
539
|
+
symbol,
|
|
540
|
+
dtime,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
process_orders(
|
|
544
|
+
"SLMT",
|
|
545
|
+
lambda o: latest_close >= o.price,
|
|
546
|
+
lambda o: self.sell_mkt(
|
|
547
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
548
|
+
),
|
|
549
|
+
"SELL LIMIT",
|
|
550
|
+
symbol,
|
|
551
|
+
dtime,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
process_orders(
|
|
555
|
+
"BSTP",
|
|
556
|
+
lambda o: latest_close >= o.price,
|
|
557
|
+
lambda o: self.buy_mkt(
|
|
558
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
559
|
+
),
|
|
560
|
+
"BUY STOP",
|
|
561
|
+
symbol,
|
|
562
|
+
dtime,
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
process_orders(
|
|
566
|
+
"SSTP",
|
|
567
|
+
lambda o: latest_close <= o.price,
|
|
568
|
+
lambda o: self.sell_mkt(
|
|
569
|
+
o.strategy_id, symbol, o.price, o.quantity, dtime
|
|
570
|
+
),
|
|
571
|
+
"SELL STOP",
|
|
572
|
+
symbol,
|
|
573
|
+
dtime,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
process_orders(
|
|
577
|
+
"BSTPLMT",
|
|
578
|
+
lambda o: latest_close >= o.price,
|
|
579
|
+
lambda o: self.buy_limit(
|
|
580
|
+
o.strategy_id, symbol, o.stoplimit, o.quantity, dtime
|
|
581
|
+
),
|
|
582
|
+
"BUY STOP LIMIT",
|
|
583
|
+
symbol,
|
|
584
|
+
dtime,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
process_orders(
|
|
588
|
+
"SSTPLMT",
|
|
589
|
+
lambda o: latest_close <= o.price,
|
|
590
|
+
lambda o: self.sell_limit(
|
|
591
|
+
o.strategy_id, symbol, o.stoplimit, o.quantity, dtime
|
|
592
|
+
),
|
|
593
|
+
"SELL STOP LIMIT",
|
|
594
|
+
symbol,
|
|
595
|
+
dtime,
|
|
596
|
+
)
|
|
608
597
|
|
|
609
598
|
@staticmethod
|
|
610
599
|
def calculate_pct_change(current_price, lh_price):
|
|
@@ -2,8 +2,6 @@ import configparser
|
|
|
2
2
|
import importlib
|
|
3
3
|
import importlib.util
|
|
4
4
|
import os
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from enum import Enum
|
|
7
5
|
from typing import Any, Dict, List
|
|
8
6
|
|
|
9
7
|
__all__ = ["load_module", "load_class"]
|
|
@@ -90,59 +88,3 @@ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]
|
|
|
90
88
|
except KeyError:
|
|
91
89
|
raise KeyError(f"{section} not found in the {file_path} file")
|
|
92
90
|
return ini_dict
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class TradeAction(Enum):
|
|
96
|
-
"""
|
|
97
|
-
An enumeration class for trade actions.
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
BUY = "LONG"
|
|
101
|
-
LONG = "LONG"
|
|
102
|
-
SELL = "SHORT"
|
|
103
|
-
EXIT = "EXIT"
|
|
104
|
-
BMKT = "BMKT"
|
|
105
|
-
SMKT = "SMKT"
|
|
106
|
-
BLMT = "BLMT"
|
|
107
|
-
SLMT = "SLMT"
|
|
108
|
-
BSTP = "BSTP"
|
|
109
|
-
SSTP = "SSTP"
|
|
110
|
-
SHORT = "SHORT"
|
|
111
|
-
BSTPLMT = "BSTPLMT"
|
|
112
|
-
SSTPLMT = "SSTPLMT"
|
|
113
|
-
EXIT_LONG = "EXIT_LONG"
|
|
114
|
-
EXIT_SHORT = "EXIT_SHORT"
|
|
115
|
-
EXIT_STOP = "EXIT_STOP"
|
|
116
|
-
EXIT_LIMIT = "EXIT_LIMIT"
|
|
117
|
-
EXIT_LONG_STOP = "EXIT_LONG_STOP"
|
|
118
|
-
EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
|
|
119
|
-
EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
|
|
120
|
-
EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
|
|
121
|
-
EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
|
|
122
|
-
EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
|
|
123
|
-
EXIT_PROFITABLES = "EXIT_PROFITABLES"
|
|
124
|
-
EXIT_LOSINGS = "EXIT_LOSINGS"
|
|
125
|
-
EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
|
|
126
|
-
EXIT_ALL_ORDERS = "EXIT_ALL_ORDERS"
|
|
127
|
-
|
|
128
|
-
def __str__(self):
|
|
129
|
-
return self.value
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@dataclass()
|
|
133
|
-
class TradeSignal:
|
|
134
|
-
"""
|
|
135
|
-
A dataclass for storing trading signal.
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
id: int
|
|
139
|
-
symbol: str
|
|
140
|
-
action: TradeAction
|
|
141
|
-
price: float = None
|
|
142
|
-
stoplimit: float = None
|
|
143
|
-
|
|
144
|
-
def __repr__(self):
|
|
145
|
-
return (
|
|
146
|
-
f"TradeSignal(id={self.id}, symbol='{self.symbol}', "
|
|
147
|
-
f"action='{self.action.value}', price={self.price}, stoplimit={self.stoplimit})"
|
|
148
|
-
)
|
|
@@ -9,6 +9,7 @@ from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
|
|
|
9
9
|
|
|
10
10
|
from bbstrader.metatrader.utils import (
|
|
11
11
|
AccountInfo,
|
|
12
|
+
BookInfo,
|
|
12
13
|
InvalidBroker,
|
|
13
14
|
OrderCheckResult,
|
|
14
15
|
OrderSentResult,
|
|
@@ -188,10 +189,12 @@ def check_mt5_connection(**kwargs):
|
|
|
188
189
|
except Exception:
|
|
189
190
|
raise_mt5_error(INIT_MSG)
|
|
190
191
|
|
|
192
|
+
|
|
191
193
|
def shutdown_mt5():
|
|
192
194
|
"""Close the connection to the MetaTrader 5 terminal."""
|
|
193
195
|
mt5.shutdown()
|
|
194
|
-
|
|
196
|
+
|
|
197
|
+
|
|
195
198
|
class Broker(object):
|
|
196
199
|
def __init__(self, name: str = None, **kwargs):
|
|
197
200
|
if name is None:
|
|
@@ -323,7 +326,7 @@ class Account(object):
|
|
|
323
326
|
f"For {supported['FTMO'].name}, See [{ftmo_url}]\n"
|
|
324
327
|
)
|
|
325
328
|
raise InvalidBroker(message=msg)
|
|
326
|
-
|
|
329
|
+
|
|
327
330
|
def shutdown(self):
|
|
328
331
|
"""Close the connection to the MetaTrader 5 terminal."""
|
|
329
332
|
shutdown_mt5()
|
|
@@ -1037,6 +1040,29 @@ class Account(object):
|
|
|
1037
1040
|
"""
|
|
1038
1041
|
self._show_info(self.get_tick_info, "tick", symbol=symbol)
|
|
1039
1042
|
|
|
1043
|
+
def get_market_book(self, symbol: str) -> Union[Tuple[BookInfo], None]:
|
|
1044
|
+
"""
|
|
1045
|
+
Get the Market Depth content for a specific symbol.
|
|
1046
|
+
Args:
|
|
1047
|
+
symbol (str): Financial instrument name. Required unnamed parameter.
|
|
1048
|
+
The symbol name should be specified in the same format as in the Market Watch window.
|
|
1049
|
+
|
|
1050
|
+
Returns:
|
|
1051
|
+
The Market Depth content as a tuple from BookInfo entries featuring order type, price and volume in lots.
|
|
1052
|
+
Return None in case of an error.
|
|
1053
|
+
|
|
1054
|
+
Raises:
|
|
1055
|
+
MT5TerminalError: A specific exception based on the error code.
|
|
1056
|
+
"""
|
|
1057
|
+
try:
|
|
1058
|
+
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])
|
|
1063
|
+
except Exception as e:
|
|
1064
|
+
raise_mt5_error(e)
|
|
1065
|
+
|
|
1040
1066
|
def calculate_margin(
|
|
1041
1067
|
self, action: Literal["buy", "sell"], symbol: str, lot: float, price: float
|
|
1042
1068
|
) -> float:
|
|
@@ -1299,8 +1325,7 @@ class Account(object):
|
|
|
1299
1325
|
df.drop(["time_msc", "external_id"], axis=1, inplace=True)
|
|
1300
1326
|
df.set_index("time", inplace=True)
|
|
1301
1327
|
if save:
|
|
1302
|
-
|
|
1303
|
-
df.to_csv(file)
|
|
1328
|
+
df.to_csv("trades_history.csv")
|
|
1304
1329
|
if to_df:
|
|
1305
1330
|
return df
|
|
1306
1331
|
else:
|
|
@@ -1507,8 +1532,7 @@ class Account(object):
|
|
|
1507
1532
|
df["time_done"] = pd.to_datetime(df["time_done"], unit="s")
|
|
1508
1533
|
|
|
1509
1534
|
if save:
|
|
1510
|
-
|
|
1511
|
-
df.to_csv(file)
|
|
1535
|
+
df.to_csv("orders_history.csv")
|
|
1512
1536
|
if to_df:
|
|
1513
1537
|
return df
|
|
1514
1538
|
else:
|
|
@@ -302,16 +302,11 @@ 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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
305
|
+
now = datetime.now()
|
|
306
|
+
start_time = datetime.strptime(self.start_time, "%H:%M").time()
|
|
307
|
+
end_time = datetime.strptime(self.end_time, "%H:%M").time()
|
|
308
|
+
if start_time <= now.time() <= end_time:
|
|
308
309
|
return True
|
|
309
|
-
elif datetime.now().hour == int(start_hour):
|
|
310
|
-
if datetime.now().minute >= int(start_minutes):
|
|
311
|
-
return True
|
|
312
|
-
elif datetime.now().hour == int(end_hour):
|
|
313
|
-
if datetime.now().minute < int(end_minutes):
|
|
314
|
-
return True
|
|
315
310
|
return False
|
|
316
311
|
|
|
317
312
|
def copy_new_trade(
|
|
@@ -607,10 +602,11 @@ class TradeCopier(object):
|
|
|
607
602
|
continue
|
|
608
603
|
self.copy_orders(destination)
|
|
609
604
|
self.copy_positions(destination)
|
|
610
|
-
|
|
611
|
-
self.log_error(e)
|
|
605
|
+
time.sleep(0.1)
|
|
612
606
|
except KeyboardInterrupt:
|
|
613
607
|
break
|
|
608
|
+
except Exception as e:
|
|
609
|
+
self.log_error(e)
|
|
614
610
|
time.sleep(self.sleeptime)
|
|
615
611
|
|
|
616
612
|
|