bbstrader 0.3.5__py3-none-any.whl → 0.3.6__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 +10 -1
- bbstrader/__main__.py +5 -0
- bbstrader/apps/_copier.py +3 -3
- bbstrader/btengine/strategy.py +113 -38
- bbstrader/metatrader/account.py +51 -26
- bbstrader/metatrader/analysis.py +30 -16
- bbstrader/metatrader/copier.py +75 -40
- bbstrader/metatrader/trade.py +29 -39
- bbstrader/metatrader/utils.py +5 -4
- bbstrader/models/nlp.py +83 -66
- bbstrader/trading/execution.py +39 -22
- bbstrader/tseries.py +103 -127
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.6.dist-info}/METADATA +7 -21
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.6.dist-info}/RECORD +31 -18
- bbstrader-0.3.6.dist-info/top_level.txt +3 -0
- docs/conf.py +56 -0
- tests/__init__.py +0 -0
- tests/engine/__init__.py +1 -0
- tests/engine/test_backtest.py +58 -0
- tests/engine/test_data.py +536 -0
- tests/engine/test_events.py +300 -0
- tests/engine/test_execution.py +219 -0
- tests/engine/test_portfolio.py +307 -0
- tests/metatrader/__init__.py +0 -0
- tests/metatrader/test_account.py +1769 -0
- tests/metatrader/test_rates.py +292 -0
- tests/metatrader/test_risk_management.py +700 -0
- tests/metatrader/test_trade.py +439 -0
- bbstrader-0.3.5.dist-info/top_level.txt +0 -1
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.6.dist-info}/WHEEL +0 -0
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.6.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.3.5.dist-info → bbstrader-0.3.6.dist-info}/licenses/LICENSE +0 -0
bbstrader/metatrader/trade.py
CHANGED
|
@@ -95,27 +95,16 @@ class TradeSignal:
|
|
|
95
95
|
"""
|
|
96
96
|
Represents a trading signal generated by a trading system or strategy.
|
|
97
97
|
|
|
98
|
+
Notes
|
|
99
|
+
-----
|
|
98
100
|
Attributes:
|
|
99
|
-
id (int):
|
|
100
|
-
A unique identifier for the trade signal or the strategy.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Must be an instance of the `TradeAction` enum (e.g., BUY, SELL).
|
|
109
|
-
|
|
110
|
-
price (float, optional):
|
|
111
|
-
The price at which the trade should be executed.
|
|
112
|
-
|
|
113
|
-
stoplimit (float, optional):
|
|
114
|
-
A stop-limit price for the trade.
|
|
115
|
-
Must not be set without specifying a price.
|
|
116
|
-
|
|
117
|
-
comment (str, optional):
|
|
118
|
-
An optional comment or description related to the trade signal.
|
|
102
|
+
- id (int): A unique identifier for the trade signal or the strategy.
|
|
103
|
+
- symbol (str): The trading symbol (e.g., stock ticker, forex pair, crypto asset).
|
|
104
|
+
- action (TradeAction): The trading action to perform. Must be an instance of the ``TradeAction`` enum (e.g., BUY, SELL).
|
|
105
|
+
- price (float, optional): The price at which the trade should be executed.
|
|
106
|
+
- stoplimit (float, optional): A stop-limit price for the trade. Must not be set without specifying a price.
|
|
107
|
+
- comment (str, optional): An optional comment or description related to the trade signal.
|
|
119
108
|
"""
|
|
120
109
|
|
|
121
110
|
id: int
|
|
@@ -781,14 +770,14 @@ class Trade(RiskManagement):
|
|
|
781
770
|
self.check_order(request)
|
|
782
771
|
result = self.send_order(request)
|
|
783
772
|
except Exception as e:
|
|
784
|
-
msg = trade_retcode_message(result.retcode)
|
|
773
|
+
msg = trade_retcode_message(result.retcode) if result else "N/A"
|
|
785
774
|
LOGGER.error(f"Trade Order Request, {msg}{addtionnal}, {e}")
|
|
786
775
|
if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
|
|
787
776
|
if result.retcode == Mt5.TRADE_RETCODE_INVALID_FILL: # 10030
|
|
788
777
|
for fill in FILLING_TYPE:
|
|
789
778
|
request["type_filling"] = fill
|
|
790
779
|
result = self.send_order(request)
|
|
791
|
-
if result and
|
|
780
|
+
if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
792
781
|
break
|
|
793
782
|
elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME: # 10014
|
|
794
783
|
new_volume = int(request["volume"])
|
|
@@ -812,14 +801,14 @@ class Trade(RiskManagement):
|
|
|
812
801
|
self.check_order(request)
|
|
813
802
|
result = self.send_order(request)
|
|
814
803
|
except Exception as e:
|
|
815
|
-
msg = trade_retcode_message(result.retcode)
|
|
804
|
+
msg = trade_retcode_message(result.retcode) if result else "N/A"
|
|
816
805
|
LOGGER.error(f"Trade Order Request, {msg}{addtionnal}, {e}")
|
|
817
|
-
if result and
|
|
806
|
+
if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
818
807
|
break
|
|
819
808
|
tries += 1
|
|
820
809
|
# Print the result
|
|
821
|
-
if result and
|
|
822
|
-
msg = trade_retcode_message(result.retcode)
|
|
810
|
+
if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
811
|
+
msg = trade_retcode_message(result.retcode)
|
|
823
812
|
LOGGER.info(f"Trade Order {msg}{addtionnal}")
|
|
824
813
|
if type != "BMKT" or type != "SMKT":
|
|
825
814
|
self.opened_orders.append(result.order)
|
|
@@ -855,7 +844,7 @@ class Trade(RiskManagement):
|
|
|
855
844
|
LOGGER.info(pos_info)
|
|
856
845
|
return True
|
|
857
846
|
else:
|
|
858
|
-
msg = trade_retcode_message(result.retcode)
|
|
847
|
+
msg = trade_retcode_message(result.retcode) if result else "N/A"
|
|
859
848
|
LOGGER.error(
|
|
860
849
|
f"Unable to Open Position, RETCODE={result.retcode}: {msg}{addtionnal}"
|
|
861
850
|
)
|
|
@@ -1332,10 +1321,10 @@ class Trade(RiskManagement):
|
|
|
1332
1321
|
self.check_order(request)
|
|
1333
1322
|
result = self.send_order(request)
|
|
1334
1323
|
except Exception as e:
|
|
1335
|
-
msg = trade_retcode_message(result.retcode)
|
|
1324
|
+
msg = trade_retcode_message(result.retcode) if result else "N/A"
|
|
1336
1325
|
LOGGER.error(f"Break-Even Order Request, {msg}{addtionnal}, Error: {e}")
|
|
1337
|
-
if result and
|
|
1338
|
-
msg = trade_retcode_message(result.retcode)
|
|
1326
|
+
if result and result.retcode != Mt5.TRADE_RETCODE_DONE:
|
|
1327
|
+
msg = trade_retcode_message(result.retcode)
|
|
1339
1328
|
if result.retcode != Mt5.TRADE_RETCODE_NO_CHANGES:
|
|
1340
1329
|
LOGGER.error(
|
|
1341
1330
|
f"Break-Even Order Request, Position: #{tiket}, RETCODE={result.retcode}: {msg}{addtionnal}"
|
|
@@ -1350,15 +1339,15 @@ class Trade(RiskManagement):
|
|
|
1350
1339
|
self.check_order(request)
|
|
1351
1340
|
result = self.send_order(request)
|
|
1352
1341
|
except Exception as e:
|
|
1353
|
-
msg = trade_retcode_message(result.retcode)
|
|
1342
|
+
msg = trade_retcode_message(result.retcode) if result else "N/A"
|
|
1354
1343
|
LOGGER.error(
|
|
1355
1344
|
f"Break-Even Order Request, {msg}{addtionnal}, Error: {e}"
|
|
1356
1345
|
)
|
|
1357
|
-
if result and
|
|
1346
|
+
if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
1358
1347
|
break
|
|
1359
1348
|
tries += 1
|
|
1360
|
-
if result and
|
|
1361
|
-
msg = trade_retcode_message(result.retcode)
|
|
1349
|
+
if result and result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
1350
|
+
msg = trade_retcode_message(result.retcode)
|
|
1362
1351
|
LOGGER.info(f"Break-Even Order {msg}{addtionnal}")
|
|
1363
1352
|
info = f"Stop loss set to Break-even, Position: #{tiket}, Symbol: {self.symbol}, Price: @{round(price, 5)}"
|
|
1364
1353
|
LOGGER.info(info)
|
|
@@ -1434,7 +1423,7 @@ class Trade(RiskManagement):
|
|
|
1434
1423
|
"""
|
|
1435
1424
|
ticket = request[type]
|
|
1436
1425
|
addtionnal = f", SYMBOL={self.symbol}"
|
|
1437
|
-
result = None
|
|
1426
|
+
result = None
|
|
1438
1427
|
try:
|
|
1439
1428
|
self.check_order(request)
|
|
1440
1429
|
result = self.send_order(request)
|
|
@@ -1839,15 +1828,16 @@ class Trade(RiskManagement):
|
|
|
1839
1828
|
|
|
1840
1829
|
def sleep_time(self, weekend=False):
|
|
1841
1830
|
if weekend:
|
|
1842
|
-
#
|
|
1843
|
-
|
|
1831
|
+
# calculate number of minute from now and monday start
|
|
1832
|
+
multiplyer = {"friday": 3, "saturday": 2, "sunday": 1}
|
|
1833
|
+
current_time = datetime.strptime(self.current_time(), "%H:%M")
|
|
1844
1834
|
monday_time = datetime.strptime(self.start, "%H:%M")
|
|
1845
|
-
intra_day_diff = (monday_time -
|
|
1846
|
-
inter_day_diff =
|
|
1835
|
+
intra_day_diff = (monday_time - current_time).total_seconds() // 60
|
|
1836
|
+
inter_day_diff = multiplyer[datetime.now().strftime("%A").lower()] * 24 * 60
|
|
1847
1837
|
total_minutes = inter_day_diff + intra_day_diff
|
|
1848
1838
|
return total_minutes
|
|
1849
1839
|
else:
|
|
1850
|
-
#
|
|
1840
|
+
# calculate number of minute from the end to the start
|
|
1851
1841
|
start = datetime.strptime(self.start, "%H:%M")
|
|
1852
1842
|
end = datetime.strptime(self.current_time(), "%H:%M")
|
|
1853
1843
|
minutes = (end - start).total_seconds() // 60
|
bbstrader/metatrader/utils.py
CHANGED
|
@@ -607,7 +607,8 @@ class AutoTradingDisabled(MT5TerminalError):
|
|
|
607
607
|
class InternalFailError(MT5TerminalError):
|
|
608
608
|
"""Base exception class for internal IPC errors."""
|
|
609
609
|
|
|
610
|
-
|
|
610
|
+
def __init__(self, code, message):
|
|
611
|
+
super().__init__(code, message)
|
|
611
612
|
|
|
612
613
|
|
|
613
614
|
class InternalFailSend(InternalFailError):
|
|
@@ -700,9 +701,9 @@ def raise_mt5_error(message: Optional[str] = None):
|
|
|
700
701
|
"""
|
|
701
702
|
if message and isinstance(message, Exception):
|
|
702
703
|
message = str(message)
|
|
703
|
-
|
|
704
|
-
if
|
|
705
|
-
raise
|
|
704
|
+
exception = _ERROR_CODE_TO_EXCEPTION_.get(MT5.last_error()[0])
|
|
705
|
+
if exception is not None:
|
|
706
|
+
raise exception(f"{message or MT5.last_error()[1]}")
|
|
706
707
|
else:
|
|
707
708
|
raise Exception(f"{message or MT5.last_error()[1]}")
|
|
708
709
|
|
bbstrader/models/nlp.py
CHANGED
|
@@ -577,44 +577,55 @@ class SentimentAnalyzer(object):
|
|
|
577
577
|
**kwargs,
|
|
578
578
|
) -> Dict[str, float]:
|
|
579
579
|
"""
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
Process
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
580
|
+
Compute sentiment scores for a list of financial tickers based on news and social media data.
|
|
581
|
+
|
|
582
|
+
Process
|
|
583
|
+
-------
|
|
584
|
+
1. Collect news articles and posts related to each ticker from various sources:
|
|
585
|
+
* Yahoo Finance News
|
|
586
|
+
* Google Finance News
|
|
587
|
+
* Reddit posts
|
|
588
|
+
* Financial Modeling Prep (FMP) news
|
|
589
|
+
2. Analyze sentiment from each source:
|
|
590
|
+
* Uses VADER for Yahoo and Google Finance news.
|
|
591
|
+
* Uses TextBlob for Reddit and FMP news.
|
|
592
|
+
3. Compute an overall sentiment score using a weighted average approach.
|
|
593
|
+
|
|
594
|
+
Parameters
|
|
595
|
+
----------
|
|
596
|
+
tickers : list of str or list of tuple
|
|
597
|
+
A list of asset tickers to analyze.
|
|
598
|
+
* If using tuples, the first element is the ticker and the second is the asset type.
|
|
599
|
+
* If using a single string, the asset type must be specified or defaults to "stock".
|
|
600
|
+
lexicon : dict, optional
|
|
601
|
+
A custom sentiment lexicon to update VADER's default lexicon. Default is None.
|
|
602
|
+
asset_type : str, optional
|
|
603
|
+
The type of asset. Default is "stock".
|
|
604
|
+
Supported types include:
|
|
605
|
+
* "stock": Stock symbols (e.g., AAPL, MSFT)
|
|
606
|
+
* "etf": Exchange-traded funds (e.g., SPY, QQQ)
|
|
607
|
+
* "future": Futures contracts (e.g., CL=F for crude oil)
|
|
608
|
+
* "forex": Forex pairs (e.g., EURUSD=X, USDJPY=X)
|
|
609
|
+
* "crypto": Cryptocurrency pairs (e.g., BTC-USD, ETH-USD)
|
|
610
|
+
* "index": Stock market indices (e.g., ^GSPC for S&P 500)
|
|
611
|
+
top_news : int, optional
|
|
612
|
+
Number of news articles/posts to fetch per source. Default is 10.
|
|
613
|
+
**kwargs : dict
|
|
614
|
+
Additional parameters for API authentication and data retrieval. Must include:
|
|
615
|
+
* fmp_api (str): API key for Financial Modeling Prep.
|
|
616
|
+
* client_id, client_secret, user_agent (str): Credentials for Reddit API.
|
|
617
|
+
|
|
618
|
+
Returns
|
|
619
|
+
-------
|
|
620
|
+
dict of str to float
|
|
621
|
+
A dictionary mapping each ticker to its overall sentiment score.
|
|
622
|
+
* Positive values indicate positive sentiment.
|
|
623
|
+
* Negative values indicate negative sentiment.
|
|
624
|
+
* Zero indicates neutral sentiment.
|
|
625
|
+
|
|
626
|
+
Notes
|
|
627
|
+
-----
|
|
628
|
+
Ticker names must follow Yahoo Finance conventions.
|
|
618
629
|
"""
|
|
619
630
|
|
|
620
631
|
sentiment_results = {}
|
|
@@ -761,30 +772,36 @@ class SentimentAnalyzer(object):
|
|
|
761
772
|
The dashboard visualizes sentiment scores for given tickers using interactive
|
|
762
773
|
bar and scatter plots. It fetches new sentiment data at specified intervals.
|
|
763
774
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
775
|
+
Parameters
|
|
776
|
+
----------
|
|
777
|
+
tickers : list of str or list of tuple
|
|
778
|
+
A list of financial asset tickers to analyze.
|
|
779
|
+
* If using tuples, the first element is the ticker and the second is the asset type.
|
|
780
|
+
* If using a single string, the asset type must be specified or defaults to "stock".
|
|
781
|
+
asset_type : str, optional
|
|
782
|
+
The type of financial asset ("stock", "forex", "crypto"). Default is "stock".
|
|
783
|
+
lexicon : dict, optional
|
|
784
|
+
A custom sentiment lexicon. Default is None.
|
|
785
|
+
interval : int, optional
|
|
786
|
+
The refresh interval (in milliseconds) for sentiment data updates. Default is 100000.
|
|
787
|
+
top_n : int, optional
|
|
788
|
+
The number of top and bottom assets to display in the sentiment bar chart. Default is 20.
|
|
789
|
+
**kwargs : dict
|
|
790
|
+
Additional arguments required for fetching sentiment data. Must include:
|
|
791
|
+
* client_id (str): Reddit API client ID.
|
|
792
|
+
* client_secret (str): Reddit API client secret.
|
|
793
|
+
* user_agent (str): User agent for Reddit API.
|
|
794
|
+
* fmp_api (str): Financial Modeling Prep (FMP) API key.
|
|
795
|
+
|
|
796
|
+
Returns
|
|
797
|
+
-------
|
|
798
|
+
None
|
|
799
|
+
Starts a real-time interactive dashboard. Does not return any value.
|
|
800
|
+
|
|
801
|
+
Example
|
|
802
|
+
-------
|
|
803
|
+
.. code-block:: python
|
|
786
804
|
|
|
787
|
-
Example Usage:
|
|
788
805
|
sa = SentimentAnalyzer()
|
|
789
806
|
sa.display_sentiment_dashboard(
|
|
790
807
|
tickers=["AAPL", "TSLA", "GOOGL"],
|
|
@@ -799,12 +816,12 @@ class SentimentAnalyzer(object):
|
|
|
799
816
|
fmp_api="your_fmp_api_key",
|
|
800
817
|
)
|
|
801
818
|
|
|
802
|
-
Notes
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
819
|
+
Notes
|
|
820
|
+
-----
|
|
821
|
+
* Sentiment analysis is performed using financial news and social media discussions.
|
|
822
|
+
* The dashboard updates in real-time at the specified interval.
|
|
823
|
+
* The dashboard will keep running unless manually stopped (Ctrl+C).
|
|
806
824
|
"""
|
|
807
|
-
|
|
808
825
|
app = dash.Dash(__name__)
|
|
809
826
|
|
|
810
827
|
sentiment_history = {ticker: [] for ticker in tickers}
|
bbstrader/trading/execution.py
CHANGED
|
@@ -45,6 +45,7 @@ MT5_ENGINE_TIMEFRAMES = list(_TF_MAPPING.keys())
|
|
|
45
45
|
TradingDays = ["monday", "tuesday", "wednesday", "thursday", "friday"]
|
|
46
46
|
WEEK_DAYS = TradingDays + ["saturday", "sunday"]
|
|
47
47
|
FRIDAY = "friday"
|
|
48
|
+
WEEK_ENDS = ["friday", "saturday", "sunday"]
|
|
48
49
|
|
|
49
50
|
BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
|
|
50
51
|
SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
|
|
@@ -513,17 +514,12 @@ class Mt5ExecutionEngine:
|
|
|
513
514
|
logger.info(sessionmsg)
|
|
514
515
|
|
|
515
516
|
def _check_is_day_ends(self, trade: Trade, symbol, period_type, today, closing):
|
|
516
|
-
if trade.days_end():
|
|
517
|
-
self._logmsgif("Day", symbol) if today
|
|
517
|
+
if trade.days_end() or (today in WEEK_ENDS and today != FRIDAY):
|
|
518
|
+
self._logmsgif("Day", symbol) if today not in WEEK_ENDS else self._logmsgif(
|
|
518
519
|
"Week", symbol
|
|
519
520
|
)
|
|
520
521
|
if (
|
|
521
|
-
(
|
|
522
|
-
period_type == "month"
|
|
523
|
-
and today == FRIDAY
|
|
524
|
-
and self._is_month_ends()
|
|
525
|
-
and closing
|
|
526
|
-
)
|
|
522
|
+
(period_type == "month" and self._is_month_ends() and closing)
|
|
527
523
|
or (period_type == "week" and today == FRIDAY and closing)
|
|
528
524
|
or (period_type == "day" and closing)
|
|
529
525
|
or (period_type == "24/7" and closing)
|
|
@@ -546,15 +542,17 @@ class Mt5ExecutionEngine:
|
|
|
546
542
|
self.strategy.perform_period_end_checks()
|
|
547
543
|
if self.period_end_action == "break" and closing:
|
|
548
544
|
sys.exit(0)
|
|
549
|
-
elif
|
|
545
|
+
elif (
|
|
546
|
+
self.period_end_action == "sleep" and today not in WEEK_ENDS or not closing
|
|
547
|
+
):
|
|
550
548
|
self._sleep_over_night(sessionmsg)
|
|
551
|
-
elif self.period_end_action == "sleep" and today
|
|
549
|
+
elif self.period_end_action == "sleep" and today in WEEK_ENDS:
|
|
552
550
|
self._sleep_over_weekend(sessionmsg)
|
|
553
551
|
|
|
554
552
|
def _weekly_end_checks(self, today, closing, sessionmsg):
|
|
555
|
-
if today
|
|
553
|
+
if today not in WEEK_ENDS:
|
|
556
554
|
self._sleep_over_night(sessionmsg)
|
|
557
|
-
|
|
555
|
+
else:
|
|
558
556
|
self.strategy.perform_period_end_checks()
|
|
559
557
|
if self.period_end_action == "break" and closing:
|
|
560
558
|
sys.exit(0)
|
|
@@ -562,9 +560,9 @@ class Mt5ExecutionEngine:
|
|
|
562
560
|
self._sleep_over_weekend(sessionmsg)
|
|
563
561
|
|
|
564
562
|
def _monthly_end_cheks(self, today, closing, sessionmsg):
|
|
565
|
-
if today
|
|
563
|
+
if today not in WEEK_ENDS and not self._is_month_ends():
|
|
566
564
|
self._sleep_over_night(sessionmsg)
|
|
567
|
-
elif
|
|
565
|
+
elif self._is_month_ends() and closing:
|
|
568
566
|
self.strategy.perform_period_end_checks()
|
|
569
567
|
sys.exit(0)
|
|
570
568
|
else:
|
|
@@ -956,7 +954,11 @@ class Mt5ExecutionEngine:
|
|
|
956
954
|
def _handle_period_end_actions(self, today):
|
|
957
955
|
try:
|
|
958
956
|
check_mt5_connection(**self.kwargs)
|
|
959
|
-
day_end =
|
|
957
|
+
day_end = (
|
|
958
|
+
all(trade.days_end() for trade in self.trades_instances.values())
|
|
959
|
+
or (today in WEEK_ENDS and today != FRIDAY)
|
|
960
|
+
and self.period != "24/7"
|
|
961
|
+
)
|
|
960
962
|
closing = self._is_closing()
|
|
961
963
|
sessionmsg = f"{self.ACCOUNT} STARTING NEW TRADING SESSION ...\n"
|
|
962
964
|
self._perform_period_end_actions(
|
|
@@ -1012,13 +1014,28 @@ class Mt5ExecutionEngine:
|
|
|
1012
1014
|
|
|
1013
1015
|
|
|
1014
1016
|
def RunMt5Engine(account_id: str, **kwargs):
|
|
1015
|
-
"""
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1017
|
+
"""
|
|
1018
|
+
Start an MT5 execution engine for a given account.
|
|
1019
|
+
|
|
1020
|
+
Parameters
|
|
1021
|
+
----------
|
|
1022
|
+
account_id : str
|
|
1023
|
+
Account ID to run the execution engine on.
|
|
1024
|
+
|
|
1025
|
+
**kwargs : dict
|
|
1026
|
+
Additional keyword arguments. Possible keys include:
|
|
1027
|
+
|
|
1028
|
+
* symbol_list : list
|
|
1029
|
+
List of symbols to trade.
|
|
1030
|
+
* trades_instances : dict
|
|
1031
|
+
Dictionary of Trade instances.
|
|
1032
|
+
* strategy_cls : class
|
|
1033
|
+
Strategy class to use for trading.
|
|
1034
|
+
|
|
1035
|
+
Returns
|
|
1036
|
+
-------
|
|
1037
|
+
None
|
|
1038
|
+
Initializes and runs the MT5 execution engine.
|
|
1022
1039
|
"""
|
|
1023
1040
|
log.info(f"Starting execution engine for {account_id}")
|
|
1024
1041
|
|