bbstrader 0.3.2__py3-none-any.whl → 0.3.4__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 +15 -7
- bbstrader/apps/__init__.py +0 -0
- bbstrader/apps/_copier.py +664 -0
- bbstrader/btengine/data.py +3 -3
- bbstrader/btengine/strategy.py +165 -90
- bbstrader/core/data.py +3 -1
- bbstrader/core/scripts.py +62 -19
- bbstrader/core/utils.py +5 -3
- bbstrader/metatrader/account.py +196 -42
- bbstrader/metatrader/analysis.py +7 -5
- bbstrader/metatrader/copier.py +325 -171
- bbstrader/metatrader/rates.py +2 -2
- bbstrader/metatrader/scripts.py +15 -2
- bbstrader/metatrader/trade.py +2 -2
- bbstrader/metatrader/utils.py +65 -5
- bbstrader/models/ml.py +8 -5
- bbstrader/models/nlp.py +16 -11
- bbstrader/trading/execution.py +100 -48
- bbstrader/tseries.py +0 -2
- {bbstrader-0.3.2.dist-info → bbstrader-0.3.4.dist-info}/METADATA +5 -5
- {bbstrader-0.3.2.dist-info → bbstrader-0.3.4.dist-info}/RECORD +25 -23
- {bbstrader-0.3.2.dist-info → bbstrader-0.3.4.dist-info}/WHEEL +0 -0
- {bbstrader-0.3.2.dist-info → bbstrader-0.3.4.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.3.2.dist-info → bbstrader-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.3.2.dist-info → bbstrader-0.3.4.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/rates.py
CHANGED
|
@@ -124,11 +124,11 @@ class Rates(object):
|
|
|
124
124
|
self.sd = session_duration
|
|
125
125
|
self.start_pos = self._get_start_pos(start_pos, timeframe)
|
|
126
126
|
self.count = count
|
|
127
|
-
self.
|
|
127
|
+
self.__initializ_mt5(**kwargs)
|
|
128
128
|
self.__account = Account(**kwargs)
|
|
129
129
|
self.__data = self.get_rates_from_pos()
|
|
130
130
|
|
|
131
|
-
def
|
|
131
|
+
def __initializ_mt5(self, **kwargs):
|
|
132
132
|
check_mt5_connection(**kwargs)
|
|
133
133
|
|
|
134
134
|
def _get_start_pos(self, index, time_frame):
|
bbstrader/metatrader/scripts.py
CHANGED
|
@@ -2,7 +2,7 @@ import argparse
|
|
|
2
2
|
import multiprocessing
|
|
3
3
|
import sys
|
|
4
4
|
|
|
5
|
-
from bbstrader.apps._copier import main as
|
|
5
|
+
from bbstrader.apps._copier import main as RunCopyApp
|
|
6
6
|
from bbstrader.metatrader.copier import RunCopier, config_copier, copier_worker_process
|
|
7
7
|
|
|
8
8
|
|
|
@@ -18,6 +18,15 @@ def copier_args(parser: argparse.ArgumentParser):
|
|
|
18
18
|
parser.add_argument(
|
|
19
19
|
"-s", "--source", type=str, nargs="?", default=None, help="Source section name"
|
|
20
20
|
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"-I", "--id", type=int, default=0, help="Source Account unique ID"
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"-U",
|
|
26
|
+
"--unique",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="Specify if the source account is only master",
|
|
29
|
+
)
|
|
21
30
|
parser.add_argument(
|
|
22
31
|
"-d",
|
|
23
32
|
"--destinations",
|
|
@@ -70,6 +79,8 @@ def copy_trades(unknown):
|
|
|
70
79
|
Options:
|
|
71
80
|
-m, --mode: CLI for terminal app and GUI for Desktop app
|
|
72
81
|
-s, --source: Source Account section name
|
|
82
|
+
-I, --id: Source Account unique ID
|
|
83
|
+
-U, --unique: Specify if the source account is only master
|
|
73
84
|
-d, --destinations: Destination Account section names (multiple allowed)
|
|
74
85
|
-i, --interval: Update interval in seconds
|
|
75
86
|
-M, --multiprocess: When set to True, each destination account runs in a separate process.
|
|
@@ -86,7 +97,7 @@ def copy_trades(unknown):
|
|
|
86
97
|
copy_args = copy_parser.parse_args(unknown)
|
|
87
98
|
|
|
88
99
|
if copy_args.mode == "GUI":
|
|
89
|
-
|
|
100
|
+
RunCopyApp()
|
|
90
101
|
|
|
91
102
|
elif copy_args.mode == "CLI":
|
|
92
103
|
source, destinations = config_copier(
|
|
@@ -94,6 +105,8 @@ def copy_trades(unknown):
|
|
|
94
105
|
dest_sections=copy_args.destinations,
|
|
95
106
|
inifile=copy_args.config,
|
|
96
107
|
)
|
|
108
|
+
source["id"] = copy_args.id
|
|
109
|
+
source["unique"] = copy_args.unique
|
|
97
110
|
if copy_args.multiprocess:
|
|
98
111
|
copier_processes = []
|
|
99
112
|
for dest_config in destinations:
|
bbstrader/metatrader/trade.py
CHANGED
|
@@ -590,7 +590,7 @@ class Trade(RiskManagement):
|
|
|
590
590
|
else:
|
|
591
591
|
raise ValueError("You need to set a price for pending orders")
|
|
592
592
|
else:
|
|
593
|
-
_price = self.get_tick_info(self.symbol).
|
|
593
|
+
_price = self.get_tick_info(self.symbol).bid
|
|
594
594
|
|
|
595
595
|
lot = volume or self.get_lot()
|
|
596
596
|
stop_loss = self.get_stop_loss()
|
|
@@ -683,7 +683,7 @@ class Trade(RiskManagement):
|
|
|
683
683
|
else:
|
|
684
684
|
raise ValueError("You need to set a price for pending orders")
|
|
685
685
|
else:
|
|
686
|
-
_price = self.get_tick_info(self.symbol).
|
|
686
|
+
_price = self.get_tick_info(self.symbol).ask
|
|
687
687
|
|
|
688
688
|
lot = volume or self.get_lot()
|
|
689
689
|
stop_loss = self.get_stop_loss()
|
bbstrader/metatrader/utils.py
CHANGED
|
@@ -2,6 +2,8 @@ from datetime import datetime
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import NamedTuple, Optional
|
|
4
4
|
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
5
7
|
try:
|
|
6
8
|
import MetaTrader5 as MT5
|
|
7
9
|
except ImportError:
|
|
@@ -28,6 +30,10 @@ __all__ = [
|
|
|
28
30
|
"HistoryNotFound",
|
|
29
31
|
"InvalidVersion",
|
|
30
32
|
"AuthFailed",
|
|
33
|
+
"RateInfo",
|
|
34
|
+
"RateDtype",
|
|
35
|
+
"TickDtype",
|
|
36
|
+
"TickFlag",
|
|
31
37
|
"UnsupportedMethod",
|
|
32
38
|
"AutoTradingDisabled",
|
|
33
39
|
"InternalFailSend",
|
|
@@ -285,6 +291,26 @@ class SymbolType(Enum):
|
|
|
285
291
|
unknown = "UNKNOWN" # Unknown or unsupported type
|
|
286
292
|
|
|
287
293
|
|
|
294
|
+
TickDtype = np.dtype(
|
|
295
|
+
[
|
|
296
|
+
("time", "<i8"),
|
|
297
|
+
("bid", "<f8"),
|
|
298
|
+
("ask", "<f8"),
|
|
299
|
+
("last", "<f8"),
|
|
300
|
+
("volume", "<u8"),
|
|
301
|
+
("time_msc", "<i8"),
|
|
302
|
+
("flags", "<u4"),
|
|
303
|
+
("volume_real", "<f8"),
|
|
304
|
+
]
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
TickFlag = {
|
|
308
|
+
"all": MT5.COPY_TICKS_ALL,
|
|
309
|
+
"info": MT5.COPY_TICKS_INFO,
|
|
310
|
+
"trade": MT5.COPY_TICKS_TRADE,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
288
314
|
class TickInfo(NamedTuple):
|
|
289
315
|
"""
|
|
290
316
|
Represents the last tick for the specified financial instrument.
|
|
@@ -308,6 +334,44 @@ class TickInfo(NamedTuple):
|
|
|
308
334
|
volume_real: float
|
|
309
335
|
|
|
310
336
|
|
|
337
|
+
RateDtype = np.dtype(
|
|
338
|
+
[
|
|
339
|
+
("time", "<i8"),
|
|
340
|
+
("open", "<f8"),
|
|
341
|
+
("high", "<f8"),
|
|
342
|
+
("low", "<f8"),
|
|
343
|
+
("close", "<f8"),
|
|
344
|
+
("tick_volume", "<u8"),
|
|
345
|
+
("spread", "<i4"),
|
|
346
|
+
("real_volume", "<u8"),
|
|
347
|
+
]
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class RateInfo(NamedTuple):
|
|
352
|
+
"""
|
|
353
|
+
Reprents a candle (bar) for a specified period.
|
|
354
|
+
* time: Time in seconds since 1970.01.01 00:00
|
|
355
|
+
* open: Open price
|
|
356
|
+
* high: High price
|
|
357
|
+
* low: Low price
|
|
358
|
+
* close: Close price
|
|
359
|
+
* tick_volume: Tick volume
|
|
360
|
+
* spread: Spread value
|
|
361
|
+
* real_volume: Real volume
|
|
362
|
+
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
time: int
|
|
366
|
+
open: float
|
|
367
|
+
high: float
|
|
368
|
+
low: float
|
|
369
|
+
close: float
|
|
370
|
+
tick_volume: float
|
|
371
|
+
spread: int
|
|
372
|
+
real_volume: float
|
|
373
|
+
|
|
374
|
+
|
|
311
375
|
class BookInfo(NamedTuple):
|
|
312
376
|
"""
|
|
313
377
|
Represents the structure of a book.
|
|
@@ -486,11 +550,7 @@ class MT5TerminalError(Exception):
|
|
|
486
550
|
self.code = code
|
|
487
551
|
self.message = message
|
|
488
552
|
|
|
489
|
-
def
|
|
490
|
-
# if self.message is None:
|
|
491
|
-
# return f"{self.__class__.__name__}"
|
|
492
|
-
# else:
|
|
493
|
-
# return f"{self.__class__.__name__}, {self.message}"
|
|
553
|
+
def __repr__(self) -> str:
|
|
494
554
|
msg_str = str(self.message) if self.message is not None else ""
|
|
495
555
|
return f"{self.code} - {self.__class__.__name__}: {msg_str}"
|
|
496
556
|
|
bbstrader/models/ml.py
CHANGED
|
@@ -1283,16 +1283,19 @@ class LightGBModel(object):
|
|
|
1283
1283
|
except Exception as e:
|
|
1284
1284
|
self.logger.error(f"Error getting last date: {e}")
|
|
1285
1285
|
try:
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1286
|
+
if now.weekday() == 0: # Monday
|
|
1287
|
+
expected_date = (now - pd.Timedelta(days=3)).normalize() # last Friday
|
|
1288
|
+
else:
|
|
1289
|
+
expected_date = (now - pd.Timedelta(days=1)).normalize() # yesterday
|
|
1290
|
+
|
|
1291
|
+
assert last_date == expected_date or last_date == now.normalize()
|
|
1289
1292
|
return True
|
|
1290
1293
|
except AssertionError:
|
|
1291
1294
|
yesterday = (now - pd.Timedelta(days=1)).normalize()
|
|
1292
1295
|
last_friday = (now - pd.Timedelta(days=now.weekday() + 3)).normalize()
|
|
1293
1296
|
self.logger.debug(
|
|
1294
|
-
f"Last date in predictions ({last_date}) is not equal to
|
|
1295
|
-
yesterday ({yesterday}) or last Friday ({last_friday})"
|
|
1297
|
+
f"Last date in predictions ({last_date}) is not equal to "
|
|
1298
|
+
f"yesterday ({yesterday}) or last Friday ({last_friday})"
|
|
1296
1299
|
)
|
|
1297
1300
|
return False
|
|
1298
1301
|
|
bbstrader/models/nlp.py
CHANGED
|
@@ -12,6 +12,7 @@ import matplotlib.pyplot as plt
|
|
|
12
12
|
import nltk
|
|
13
13
|
import pandas as pd
|
|
14
14
|
import plotly.express as px
|
|
15
|
+
from bbstrader.core.data import FinancialNews
|
|
15
16
|
from dash import dcc, html
|
|
16
17
|
from dash.dependencies import Input, Output
|
|
17
18
|
from nltk.corpus import stopwords
|
|
@@ -19,11 +20,10 @@ from nltk.tokenize import word_tokenize
|
|
|
19
20
|
from textblob import TextBlob
|
|
20
21
|
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
|
|
21
22
|
|
|
22
|
-
from bbstrader.core.data import FinancialNews
|
|
23
|
-
|
|
24
23
|
__all__ = [
|
|
25
24
|
"TopicModeler",
|
|
26
25
|
"SentimentAnalyzer",
|
|
26
|
+
"LEXICON",
|
|
27
27
|
"EQUITY_LEXICON",
|
|
28
28
|
"FOREX_LEXICON",
|
|
29
29
|
"COMMODITIES_LEXICON",
|
|
@@ -331,6 +331,17 @@ FINANCIAL_LEXICON = {
|
|
|
331
331
|
**BONDS_LEXICON,
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
LEXICON = {
|
|
335
|
+
"stock": EQUITY_LEXICON,
|
|
336
|
+
"etf": EQUITY_LEXICON,
|
|
337
|
+
"future": FINANCIAL_LEXICON,
|
|
338
|
+
"forex": FOREX_LEXICON,
|
|
339
|
+
"crypto": CRYPTO_LEXICON,
|
|
340
|
+
"index": EQUITY_LEXICON,
|
|
341
|
+
"bond": BONDS_LEXICON,
|
|
342
|
+
"commodity": COMMODITIES_LEXICON,
|
|
343
|
+
}
|
|
344
|
+
|
|
334
345
|
|
|
335
346
|
class TopicModeler(object):
|
|
336
347
|
def __init__(self):
|
|
@@ -379,11 +390,6 @@ class SentimentAnalyzer(object):
|
|
|
379
390
|
analysis using VADER (SentimentIntensityAnalyzer) and optional TextBlob
|
|
380
391
|
for enhanced polarity scoring.
|
|
381
392
|
|
|
382
|
-
Attributes:
|
|
383
|
-
nlp (spacy.Language): A SpaCy NLP pipeline for tokenization and lemmatization,
|
|
384
|
-
with Named Entity Recognition (NER) disabled.
|
|
385
|
-
analyzer (SentimentIntensityAnalyzer): An instance of VADER's sentiment analyzer
|
|
386
|
-
for financial sentiment scoring.
|
|
387
393
|
"""
|
|
388
394
|
|
|
389
395
|
def __init__(self):
|
|
@@ -395,8 +401,6 @@ class SentimentAnalyzer(object):
|
|
|
395
401
|
- Loads the `en_core_web_sm` SpaCy model with Named Entity Recognition (NER) disabled.
|
|
396
402
|
- Initializes VADER's SentimentIntensityAnalyzer for sentiment scoring.
|
|
397
403
|
|
|
398
|
-
Args:
|
|
399
|
-
use_spacy (bool): If True, uses SpaCy for lemmatization. Defaults to False.
|
|
400
404
|
"""
|
|
401
405
|
nltk.download("punkt", quiet=True)
|
|
402
406
|
nltk.download("stopwords", quiet=True)
|
|
@@ -617,8 +621,9 @@ class SentimentAnalyzer(object):
|
|
|
617
621
|
|
|
618
622
|
# Suppress stdout/stderr from underlying libraries during execution
|
|
619
623
|
with open(os.devnull, "w") as devnull:
|
|
620
|
-
with
|
|
621
|
-
devnull
|
|
624
|
+
with (
|
|
625
|
+
contextlib.redirect_stdout(devnull),
|
|
626
|
+
contextlib.redirect_stderr(devnull),
|
|
622
627
|
):
|
|
623
628
|
with ThreadPoolExecutor() as executor:
|
|
624
629
|
# Map each future to its ticker for easy result lookup
|
bbstrader/trading/execution.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import functools
|
|
1
3
|
import multiprocessing as mp
|
|
2
4
|
import sys
|
|
3
5
|
import time
|
|
4
6
|
from datetime import date, datetime
|
|
7
|
+
from multiprocessing.synchronize import Event
|
|
5
8
|
from typing import Callable, Dict, List, Literal, Optional
|
|
6
9
|
|
|
7
10
|
import pandas as pd
|
|
@@ -179,6 +182,8 @@ class Mt5ExecutionEngine:
|
|
|
179
182
|
mm: bool = True,
|
|
180
183
|
auto_trade: bool = True,
|
|
181
184
|
prompt_callback: Callable = None,
|
|
185
|
+
multithread: bool = False,
|
|
186
|
+
shutdown_event: Event = None,
|
|
182
187
|
optimizer: str = "equal",
|
|
183
188
|
trail: bool = True,
|
|
184
189
|
stop_trail: Optional[int] = None,
|
|
@@ -187,8 +192,8 @@ class Mt5ExecutionEngine:
|
|
|
187
192
|
show_positions_orders: bool = False,
|
|
188
193
|
iter_time: int | float = 5,
|
|
189
194
|
use_trade_time: bool = True,
|
|
190
|
-
period: Literal["24/7", "day", "week", "month"] = "
|
|
191
|
-
period_end_action: Literal["break", "sleep"] = "
|
|
195
|
+
period: Literal["24/7", "day", "week", "month"] = "month",
|
|
196
|
+
period_end_action: Literal["break", "sleep"] = "sleep",
|
|
192
197
|
closing_pnl: Optional[float] = None,
|
|
193
198
|
trading_days: Optional[List[str]] = None,
|
|
194
199
|
comment: Optional[str] = None,
|
|
@@ -207,6 +212,10 @@ class Mt5ExecutionEngine:
|
|
|
207
212
|
the user for confimation.
|
|
208
213
|
prompt_callback : Callback function to prompt the user for confirmation.
|
|
209
214
|
This is useful when integrating with GUI applications.
|
|
215
|
+
multithread : If True, use a thread pool to process signals in parallel.
|
|
216
|
+
If False, process them sequentially. Set this to True only if the engine
|
|
217
|
+
is running in a separate process. Default to False.
|
|
218
|
+
shutdown_event : Use to terminate the copy process when runs in a custum environment like web App or GUI.
|
|
210
219
|
show_positions_orders : Print open positions and orders. Defaults to False.
|
|
211
220
|
iter_time : Interval to check for signals and `mm`. Defaults to 5.
|
|
212
221
|
use_trade_time : Open trades after the time is completed. Defaults to True.
|
|
@@ -251,6 +260,7 @@ class Mt5ExecutionEngine:
|
|
|
251
260
|
self.mm = mm
|
|
252
261
|
self.auto_trade = auto_trade
|
|
253
262
|
self.prompt_callback = prompt_callback
|
|
263
|
+
self.multithread = multithread
|
|
254
264
|
self.optimizer = optimizer
|
|
255
265
|
self.trail = trail
|
|
256
266
|
self.stop_trail = stop_trail
|
|
@@ -274,6 +284,9 @@ class Mt5ExecutionEngine:
|
|
|
274
284
|
|
|
275
285
|
self._initialize_engine(**kwargs)
|
|
276
286
|
self.strategy = self._init_strategy(**kwargs)
|
|
287
|
+
self.shutdown_event = (
|
|
288
|
+
shutdown_event if shutdown_event is not None else mp.Event()
|
|
289
|
+
)
|
|
277
290
|
self._running = True
|
|
278
291
|
|
|
279
292
|
def __repr__(self):
|
|
@@ -316,6 +329,7 @@ class Mt5ExecutionEngine:
|
|
|
316
329
|
def _print_exc(self, msg: str, e: Exception):
|
|
317
330
|
if isinstance(e, KeyboardInterrupt):
|
|
318
331
|
logger.info("Stopping the Execution Engine ...")
|
|
332
|
+
self.stop()
|
|
319
333
|
sys.exit(0)
|
|
320
334
|
if self.debug_mode:
|
|
321
335
|
raise ValueError(msg).with_traceback(e.__traceback__)
|
|
@@ -617,12 +631,12 @@ class Mt5ExecutionEngine:
|
|
|
617
631
|
def _update_risk(self, weights):
|
|
618
632
|
try:
|
|
619
633
|
check_mt5_connection(**self.kwargs)
|
|
620
|
-
if weights is not None:
|
|
634
|
+
if weights is not None and not all(v == 0 for v in weights.values()):
|
|
635
|
+
assert self.daily_risk is not None
|
|
621
636
|
for symbol in self.symbols:
|
|
622
637
|
if symbol not in weights:
|
|
623
638
|
continue
|
|
624
639
|
trade = self.trades_instances[symbol]
|
|
625
|
-
assert self.daily_risk is not None
|
|
626
640
|
dailydd = round(weights[symbol] * self.daily_risk, 5)
|
|
627
641
|
trade.dailydd = dailydd
|
|
628
642
|
except Exception as e:
|
|
@@ -866,46 +880,78 @@ class Mt5ExecutionEngine:
|
|
|
866
880
|
f"(e.g., if time_frame is 15m, iter_time must be 1.5, 3, 5, 15 etc)"
|
|
867
881
|
)
|
|
868
882
|
|
|
869
|
-
def
|
|
883
|
+
def _handle_one_signal(self, signal, today, buys, sells):
|
|
870
884
|
try:
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
action
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if len(self.symbols) >= 10:
|
|
895
|
-
if symbol == self.symbols[-1]:
|
|
896
|
-
logger.info(
|
|
897
|
-
f"Not trading Time !!!, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
|
|
898
|
-
)
|
|
899
|
-
else:
|
|
885
|
+
symbol = signal.symbol
|
|
886
|
+
trade: Trade = self.trades_instances[symbol]
|
|
887
|
+
if trade.trading_time() and today in self.trading_days:
|
|
888
|
+
if signal.action is not None:
|
|
889
|
+
action = (
|
|
890
|
+
signal.action.value
|
|
891
|
+
if isinstance(signal.action, TradeAction)
|
|
892
|
+
else signal.action
|
|
893
|
+
)
|
|
894
|
+
self._run_trade_algorithm(
|
|
895
|
+
action,
|
|
896
|
+
symbol,
|
|
897
|
+
signal.id,
|
|
898
|
+
trade,
|
|
899
|
+
signal.price,
|
|
900
|
+
signal.stoplimit,
|
|
901
|
+
buys,
|
|
902
|
+
sells,
|
|
903
|
+
signal.comment or self.comment,
|
|
904
|
+
)
|
|
905
|
+
else:
|
|
906
|
+
if len(self.symbols) >= 10:
|
|
907
|
+
if symbol == self.symbols[-1]:
|
|
900
908
|
logger.info(
|
|
901
|
-
f"Not trading Time
|
|
909
|
+
f"Not trading Time !!!, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
|
|
902
910
|
)
|
|
903
|
-
|
|
911
|
+
else:
|
|
912
|
+
logger.info(
|
|
913
|
+
f"Not trading Time !!! SYMBOL={trade.symbol}, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
|
|
914
|
+
)
|
|
915
|
+
self._check(buys[symbol], sells[symbol], symbol)
|
|
904
916
|
|
|
905
917
|
except Exception as e:
|
|
906
|
-
msg =
|
|
918
|
+
msg = (
|
|
919
|
+
f"Error handling signal for SYMBOL={signal.symbol} (SIGNAL: {action}), "
|
|
920
|
+
f"STRATEGY={self.STRATEGY}, ACCOUNT={self.ACCOUNT}"
|
|
921
|
+
)
|
|
907
922
|
self._print_exc(msg, e)
|
|
908
|
-
|
|
923
|
+
|
|
924
|
+
def _handle_all_signals(self, today, signals, buys, sells, max_workers=50):
|
|
925
|
+
try:
|
|
926
|
+
check_mt5_connection(**self.kwargs)
|
|
927
|
+
except Exception as e:
|
|
928
|
+
msg = "Initial MT5 connection check failed. Aborting signal processing."
|
|
929
|
+
self._print_exc(msg, e)
|
|
930
|
+
return
|
|
931
|
+
|
|
932
|
+
if not signals:
|
|
933
|
+
return
|
|
934
|
+
|
|
935
|
+
# We want to create a temporary function that
|
|
936
|
+
# already has the 'today', 'buys', and 'sells' arguments filled in.
|
|
937
|
+
# This is necessary because executor.map only iterates over one sequence (signals).
|
|
938
|
+
signal_processor = functools.partial(
|
|
939
|
+
self._handle_one_signal, today=today, buys=buys, sells=sells
|
|
940
|
+
)
|
|
941
|
+
if self.multithread:
|
|
942
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
943
|
+
max_workers=max_workers
|
|
944
|
+
) as executor:
|
|
945
|
+
# 'map' will apply our worker function to every item in the 'signals' list.
|
|
946
|
+
# It will automatically manage the distribution of tasks to the worker threads.
|
|
947
|
+
# We wrap it in list() to ensure all tasks are complete before moving on.
|
|
948
|
+
list(executor.map(signal_processor, signals))
|
|
949
|
+
else:
|
|
950
|
+
for signal in signals:
|
|
951
|
+
try:
|
|
952
|
+
signal_processor(signal)
|
|
953
|
+
except Exception as e:
|
|
954
|
+
self._print_exc(f"Failed to process signal {signal}: ", e)
|
|
909
955
|
|
|
910
956
|
def _handle_period_end_actions(self, today):
|
|
911
957
|
try:
|
|
@@ -925,7 +971,7 @@ class Mt5ExecutionEngine:
|
|
|
925
971
|
pass
|
|
926
972
|
|
|
927
973
|
def run(self):
|
|
928
|
-
while self._running:
|
|
974
|
+
while self._running and not self.shutdown_event.is_set():
|
|
929
975
|
try:
|
|
930
976
|
check_mt5_connection(**self.kwargs)
|
|
931
977
|
positions_orders = self._check_positions_orders()
|
|
@@ -943,24 +989,25 @@ class Mt5ExecutionEngine:
|
|
|
943
989
|
self._check(buys[symbol], sells[symbol], symbol)
|
|
944
990
|
else:
|
|
945
991
|
self._update_risk(weights)
|
|
946
|
-
self.
|
|
992
|
+
self._handle_all_signals(today, signals, buys, sells)
|
|
947
993
|
self._sleep()
|
|
948
994
|
self._handle_period_end_actions(today)
|
|
949
995
|
except KeyboardInterrupt:
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
)
|
|
953
|
-
break
|
|
996
|
+
self.stop()
|
|
997
|
+
sys.exit(0)
|
|
954
998
|
except Exception as e:
|
|
955
999
|
msg = f"Running Execution Engine, STRATEGY={self.STRATEGY} , ACCOUNT={self.ACCOUNT}"
|
|
956
1000
|
self._print_exc(msg, e)
|
|
1001
|
+
self._sleep()
|
|
957
1002
|
|
|
958
1003
|
def stop(self):
|
|
959
1004
|
"""Stops the execution engine."""
|
|
960
|
-
self._running
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1005
|
+
if self._running:
|
|
1006
|
+
logger.info(
|
|
1007
|
+
f"Stopping Execution Engine for {self.STRATEGY} STRATEGY on {self.ACCOUNT} Account"
|
|
1008
|
+
)
|
|
1009
|
+
self._running = False
|
|
1010
|
+
self.shutdown_event.set()
|
|
964
1011
|
logger.info("Execution Engine stopped successfully.")
|
|
965
1012
|
|
|
966
1013
|
|
|
@@ -988,6 +1035,10 @@ def RunMt5Engine(account_id: str, **kwargs):
|
|
|
988
1035
|
symbol_list, trades_instances, strategy_cls, **kwargs
|
|
989
1036
|
)
|
|
990
1037
|
engine.run()
|
|
1038
|
+
except KeyboardInterrupt:
|
|
1039
|
+
log.info(f"Execution engine for {account_id} interrupted by user")
|
|
1040
|
+
engine.stop()
|
|
1041
|
+
sys.exit(0)
|
|
991
1042
|
except Exception as e:
|
|
992
1043
|
log.exception(f"Error running execution engine for {account_id}: {e}")
|
|
993
1044
|
finally:
|
|
@@ -1008,6 +1059,7 @@ def RunMt5Engines(accounts: Dict[str, Dict], start_delay: float = 1.0):
|
|
|
1008
1059
|
|
|
1009
1060
|
for account_id, params in accounts.items():
|
|
1010
1061
|
log.info(f"Starting process for {account_id}")
|
|
1062
|
+
params["multithread"] = True
|
|
1011
1063
|
process = mp.Process(target=RunMt5Engine, args=(account_id,), kwargs=params)
|
|
1012
1064
|
process.start()
|
|
1013
1065
|
processes[process] = account_id
|
bbstrader/tseries.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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/
|
|
@@ -37,8 +37,8 @@ Requires-Dist: ipython>=8.32.0
|
|
|
37
37
|
Requires-Dist: lightgbm>=4.5.0
|
|
38
38
|
Requires-Dist: nltk>=3.9.1
|
|
39
39
|
Requires-Dist: notify-py>=0.3.43
|
|
40
|
-
Requires-Dist: numpy
|
|
41
|
-
Requires-Dist: pandas_ta
|
|
40
|
+
Requires-Dist: numpy>=1.26.0
|
|
41
|
+
Requires-Dist: pandas_ta<=0.4.67b0
|
|
42
42
|
Requires-Dist: praw>=7.8.1
|
|
43
43
|
Requires-Dist: pyfiglet>=1.0.2
|
|
44
44
|
Requires-Dist: pykalman>=0.10.1
|
|
@@ -164,7 +164,7 @@ To begin using `bbstrader`, please ensure your system meets the following prereq
|
|
|
164
164
|
* **MetaTrader 5 (MT5)**:
|
|
165
165
|
* The MetaTrader 5 platform must be installed on your system (primarily for Windows users needing live trading or direct MT5 interaction).
|
|
166
166
|
* An active trading account with a MetaTrader 5 broker. `bbstrader` currently supports:
|
|
167
|
-
* [Admirals Group AS](https://
|
|
167
|
+
* [Admirals Group AS](https://one.justmarkets.link/a/tufvj0xugm/registration/trader) (for Stocks, ETFs, Indices, Commodities, Futures, Forex)
|
|
168
168
|
* [Just Global Markets Ltd.](https://one.justmarkets.link/a/tufvj0xugm/registration/trader) (for Stocks, Crypto, Indices, Commodities, Forex)
|
|
169
169
|
* [FTMO](https://trader.ftmo.com/?affiliates=JGmeuQqepAZLMcdOEQRp) (Proprietary Firm)
|
|
170
170
|
|
|
@@ -380,7 +380,7 @@ You may need to create the `~/.bbstrader/` directory and its subdirectories (lik
|
|
|
380
380
|
|
|
381
381
|
For comprehensive information, including detailed API references, tutorials, and advanced guides for each module, please refer to our full documentation hosted on ReadTheDocs:
|
|
382
382
|
|
|
383
|
-
[**View the Full Documentation
|
|
383
|
+
[**View the Full Documentation**](https://bbstrader.readthedocs.io/en/latest/)
|
|
384
384
|
|
|
385
385
|
Additionally, the codebase is commented and includes docstrings, which can be a valuable resource for understanding the implementation details of specific functions and classes.
|
|
386
386
|
|
|
@@ -1,47 +1,49 @@
|
|
|
1
1
|
bbstrader/__init__.py,sha256=4KVGBEYU3ao9zPVM3rMWqNuvCleCeA6C2MVe_AFc4rw,581
|
|
2
|
-
bbstrader/__main__.py,sha256=
|
|
2
|
+
bbstrader/__main__.py,sha256=RjUIJWaD2_Od8ZMSLL8dzc2ZuCkkzlvNaE7-LIu3RGU,2488
|
|
3
3
|
bbstrader/compat.py,sha256=djbHMvTvy0HYm1zyZ6Ttp_LMwP2PqTSVw1r7pqbz7So,487
|
|
4
4
|
bbstrader/config.py,sha256=riZxwb4hN0I-dSsWcjnROc5dWQpSJ9iKOMIp4PMGfko,3970
|
|
5
|
-
bbstrader/tseries.py,sha256=
|
|
5
|
+
bbstrader/tseries.py,sha256=SM_LTQHJ3ZXVkVJyZ51CefUDzJDl2TkJqBKMp_uM8s4,43833
|
|
6
|
+
bbstrader/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
bbstrader/apps/_copier.py,sha256=z6GhLSmFjHc_oh7pYRTflH42Xmpe3Z7B2fUyedzDQPo,25387
|
|
6
8
|
bbstrader/btengine/__init__.py,sha256=y1btjaEfhWsH8vuE7mBRpP9Tu-Azt9REhuVYsPCAfBU,2955
|
|
7
9
|
bbstrader/btengine/backtest.py,sha256=o3eoCVzpjykDx-9VgkxK6DKZdGDAuLVeeWLCa2U_zeY,14652
|
|
8
|
-
bbstrader/btengine/data.py,sha256=
|
|
10
|
+
bbstrader/btengine/data.py,sha256=WcBLtabboZkQdtOQS3SjbiJD9BcWc75sdhZ2voQ_lUw,27061
|
|
9
11
|
bbstrader/btengine/event.py,sha256=Ydl1avAXp9WAWOBXDAckcb9g1UkcnCO0rRzcJZwIq20,8714
|
|
10
12
|
bbstrader/btengine/execution.py,sha256=4MytWjcKg8J_w14P43emHqsvKOElkQTfhVYNakU6euQ,11190
|
|
11
13
|
bbstrader/btengine/performance.py,sha256=1ecWrTzHBQbk4ORvbTEKxwCzlL1brcXOEUwgbnjAwx4,12470
|
|
12
14
|
bbstrader/btengine/portfolio.py,sha256=z98M65HQeCyma8gMZkAxspxBA9jtIhzxMyJUHPPj34c,16128
|
|
13
15
|
bbstrader/btengine/scripts.py,sha256=8o66dq4Ex4DsH4s8xvJqUOFjLzZJSnbBvvNBzohtzoE,4837
|
|
14
|
-
bbstrader/btengine/strategy.py,sha256=
|
|
16
|
+
bbstrader/btengine/strategy.py,sha256=hDghr5sXNitWlWXXvgl8Vj-QHWaIuVlx54LUXAbQrHQ,36725
|
|
15
17
|
bbstrader/core/__init__.py,sha256=GIFzFSStPfE0XM2j7mDeZZQeMTh_AwPsDOQXwMVJLgw,97
|
|
16
|
-
bbstrader/core/data.py,sha256=
|
|
17
|
-
bbstrader/core/scripts.py,sha256=
|
|
18
|
-
bbstrader/core/utils.py,sha256=
|
|
18
|
+
bbstrader/core/data.py,sha256=5-ByClb-E3-iqDz8CBJ4om9wBIA7DmUWezu4A-tv5ys,25095
|
|
19
|
+
bbstrader/core/scripts.py,sha256=bMZ5I_hCTPCsAn9v9Sz9fQQ7JFkf_Zwtv7uUJwaqVBU,5466
|
|
20
|
+
bbstrader/core/utils.py,sha256=tHXQimmmlYZHktNnYNKn_wVq6v-85pI7DXF6xlJV7ps,2780
|
|
19
21
|
bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
22
|
bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
23
|
bbstrader/metatrader/__init__.py,sha256=A5Ye9tpc2sp9Xk5qjKw-EfYsoRcZtAt8nqvC3tCtZs8,333
|
|
22
|
-
bbstrader/metatrader/account.py,sha256=
|
|
23
|
-
bbstrader/metatrader/analysis.py,sha256=
|
|
24
|
-
bbstrader/metatrader/copier.py,sha256=
|
|
25
|
-
bbstrader/metatrader/rates.py,sha256=
|
|
24
|
+
bbstrader/metatrader/account.py,sha256=Ur0vKz_puUP9jmdJUpvdQq6W5ue0RfnhkL0JLPBQkOE,65200
|
|
25
|
+
bbstrader/metatrader/analysis.py,sha256=ywETmG3qxZ7ms_DCjR1GcQoUrQ0es5-n-CPDYKBAm8Q,3614
|
|
26
|
+
bbstrader/metatrader/copier.py,sha256=knGvLsiWUzM6eYe7CrKF6rTeReFdogdCQU5Q7p7UHmY,54186
|
|
27
|
+
bbstrader/metatrader/rates.py,sha256=w9mr6FB6E1zLcHCDtDGt-oMnw6sakIU6Qe3455KDsSg,20782
|
|
26
28
|
bbstrader/metatrader/risk.py,sha256=NhW8qtSg350Z6H9oLcDqOU_erqd_7Y7F5FwpfPN5Qso,27262
|
|
27
|
-
bbstrader/metatrader/scripts.py,sha256=
|
|
28
|
-
bbstrader/metatrader/trade.py,sha256=
|
|
29
|
-
bbstrader/metatrader/utils.py,sha256=
|
|
29
|
+
bbstrader/metatrader/scripts.py,sha256=8meq6_zz6jPSibNgtYtaO8Ba-uJZOoLkpqYUIjidk-U,4010
|
|
30
|
+
bbstrader/metatrader/trade.py,sha256=ezkALiUgtIu54R4m4blQjptWoRXNVG5wwuoctP2b90Y,80624
|
|
31
|
+
bbstrader/metatrader/utils.py,sha256=PnFZ8EuBSZgsYlvwZDOxj4vUTtt-hUYnnwFBmu7gxxw,20738
|
|
30
32
|
bbstrader/models/__init__.py,sha256=B-bn2h_SCK6gRAs2li6dDVnvV8jDT5suZimldk5xxcw,497
|
|
31
33
|
bbstrader/models/factors.py,sha256=Y1rjwhWU4aiSRd-jFOLnLZczFCY0bJUxauCo17HvOFY,12791
|
|
32
|
-
bbstrader/models/ml.py,sha256=
|
|
33
|
-
bbstrader/models/nlp.py,sha256=
|
|
34
|
+
bbstrader/models/ml.py,sha256=d_maEXwGOGuWawisjIbMO5FUsaSjlgmJu5XiMu28Wf8,48921
|
|
35
|
+
bbstrader/models/nlp.py,sha256=hcvz9d_8j1cIC1h3oqa1DBjExRIEd6WSiZb95Vr3NPo,32638
|
|
34
36
|
bbstrader/models/optimization.py,sha256=Fa4tdhynMmvKt5KHV9cH1TXmmJVJwU4QWpYkbeVq4aI,6395
|
|
35
37
|
bbstrader/models/portfolio.py,sha256=r-47Zrn2r7iKCHm5YVtwkbBJXAZGM3QYy-rXCWY9-Bg,8079
|
|
36
38
|
bbstrader/models/risk.py,sha256=MKCk53HtGIcivrNzH8Ikm5KMs1rXhFT5zkorUf30PyQ,506
|
|
37
39
|
bbstrader/trading/__init__.py,sha256=ycLyuuxN5SujqtzR9X0Q74UQfK93q2va-GGAXdr-KS8,457
|
|
38
|
-
bbstrader/trading/execution.py,sha256=
|
|
40
|
+
bbstrader/trading/execution.py,sha256=CYt4fageoqcpMFvdRH-jX4hexAGUiG_wE94i1qg7BFM,41479
|
|
39
41
|
bbstrader/trading/scripts.py,sha256=Tf5q33WqqygjpIv43_8nA82VZ3GM0qgb4Ggo3fHJ_wg,5744
|
|
40
42
|
bbstrader/trading/strategies.py,sha256=RZ6P4SfIyRW72v0OnPnrc4Hv8X00FdxR-_sD23xe_Pg,11756
|
|
41
43
|
bbstrader/trading/utils.py,sha256=57dKF9dcRu04oU2VRqydRrzW39dCW2wlDWhVt-sZdRw,1857
|
|
42
|
-
bbstrader-0.3.
|
|
43
|
-
bbstrader-0.3.
|
|
44
|
-
bbstrader-0.3.
|
|
45
|
-
bbstrader-0.3.
|
|
46
|
-
bbstrader-0.3.
|
|
47
|
-
bbstrader-0.3.
|
|
44
|
+
bbstrader-0.3.4.dist-info/licenses/LICENSE,sha256=ZwC_RqqGmOPBUiMDKqLyJZ5HBeHq53LpL7TMRzrJY8c,1094
|
|
45
|
+
bbstrader-0.3.4.dist-info/METADATA,sha256=_Zc1Yp1Exs0wUpD0KV1G41uDnR3v7mhHWawRDbkutSM,27089
|
|
46
|
+
bbstrader-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
bbstrader-0.3.4.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
|
|
48
|
+
bbstrader-0.3.4.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
49
|
+
bbstrader-0.3.4.dist-info/RECORD,,
|
|
File without changes
|