bbstrader 0.1.93__py3-none-any.whl → 0.1.94__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/btengine/data.py +230 -39
- bbstrader/metatrader/account.py +66 -12
- bbstrader/metatrader/rates.py +22 -18
- bbstrader/metatrader/risk.py +6 -3
- bbstrader/metatrader/trade.py +14 -9
- bbstrader/models/risk.py +9 -1
- bbstrader/trading/execution.py +19 -9
- bbstrader/trading/strategies.py +4 -4
- {bbstrader-0.1.93.dist-info → bbstrader-0.1.94.dist-info}/METADATA +5 -3
- {bbstrader-0.1.93.dist-info → bbstrader-0.1.94.dist-info}/RECORD +13 -13
- {bbstrader-0.1.93.dist-info → bbstrader-0.1.94.dist-info}/WHEEL +1 -1
- {bbstrader-0.1.93.dist-info → bbstrader-0.1.94.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.93.dist-info → bbstrader-0.1.94.dist-info}/top_level.txt +0 -0
bbstrader/btengine/data.py
CHANGED
|
@@ -10,13 +10,18 @@ from bbstrader.metatrader.rates import download_historical_data
|
|
|
10
10
|
from bbstrader.btengine.event import MarketEvent
|
|
11
11
|
from bbstrader.config import BBSTRADER_DIR
|
|
12
12
|
from datetime import datetime
|
|
13
|
+
from eodhd import APIClient
|
|
14
|
+
from financetoolkit import Toolkit
|
|
15
|
+
from pytz import timezone
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
__all__ = [
|
|
16
19
|
"DataHandler",
|
|
17
20
|
"CSVDataHandler",
|
|
18
21
|
"MT5DataHandler",
|
|
19
|
-
"YFDataHandler"
|
|
22
|
+
"YFDataHandler",
|
|
23
|
+
"EODHDataHandler",
|
|
24
|
+
"FMPDataHandler",
|
|
20
25
|
]
|
|
21
26
|
|
|
22
27
|
|
|
@@ -167,7 +172,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
167
172
|
os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
|
|
168
173
|
).columns.to_list()
|
|
169
174
|
new_names = self.columns or default_names
|
|
170
|
-
new_names = [name.lower().replace(' ', '_') for name in new_names]
|
|
175
|
+
new_names = [name.strip().lower().replace(' ', '_') for name in new_names]
|
|
171
176
|
self.columns = new_names
|
|
172
177
|
assert 'adj_close' in new_names or 'close' in new_names, \
|
|
173
178
|
"Column names must contain 'Adj Close' and 'Close' or adj_close and close"
|
|
@@ -340,7 +345,13 @@ class CSVDataHandler(BaseCSVDataHandler):
|
|
|
340
345
|
"""
|
|
341
346
|
csv_dir = kwargs.get("csv_dir")
|
|
342
347
|
csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
|
|
343
|
-
super().__init__(
|
|
348
|
+
super().__init__(
|
|
349
|
+
events,
|
|
350
|
+
symbol_list,
|
|
351
|
+
csv_dir,
|
|
352
|
+
columns =kwargs.get('columns'),
|
|
353
|
+
index_col=kwargs.get('index_col', 0)
|
|
354
|
+
)
|
|
344
355
|
|
|
345
356
|
|
|
346
357
|
class MT5DataHandler(BaseCSVDataHandler):
|
|
@@ -372,32 +383,41 @@ class MT5DataHandler(BaseCSVDataHandler):
|
|
|
372
383
|
See `bbstrader.metatrader.rates.Rates` for other arguments.
|
|
373
384
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
374
385
|
"""
|
|
375
|
-
self.tf
|
|
376
|
-
self.start
|
|
377
|
-
self.end
|
|
378
|
-
self.use_utc
|
|
379
|
-
self.filer
|
|
380
|
-
self.fill_na
|
|
386
|
+
self.tf = kwargs.get('time_frame', 'D1')
|
|
387
|
+
self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
|
|
388
|
+
self.end = kwargs.get('mt5_end', datetime.now())
|
|
389
|
+
self.use_utc = kwargs.get('use_utc', False)
|
|
390
|
+
self.filer = kwargs.get('filter', False)
|
|
391
|
+
self.fill_na = kwargs.get('fill_na', False)
|
|
381
392
|
self.lower_cols = kwargs.get('lower_cols', True)
|
|
382
|
-
self.data_dir
|
|
393
|
+
self.data_dir = kwargs.get('data_dir')
|
|
383
394
|
self.symbol_list = symbol_list
|
|
384
|
-
|
|
385
|
-
|
|
395
|
+
self.kwargs = kwargs
|
|
396
|
+
|
|
397
|
+
csv_dir = self._download_and_cache_data(self.data_dir)
|
|
398
|
+
super().__init__(
|
|
399
|
+
events,
|
|
400
|
+
symbol_list,
|
|
401
|
+
csv_dir,
|
|
402
|
+
columns =kwargs.get('columns'),
|
|
403
|
+
index_col=kwargs.get('index_col', 0)
|
|
404
|
+
)
|
|
386
405
|
|
|
387
|
-
def
|
|
388
|
-
data_dir = cache_dir or BBSTRADER_DIR / '
|
|
406
|
+
def _download_and_cache_data(self, cache_dir: str):
|
|
407
|
+
data_dir = cache_dir or BBSTRADER_DIR / 'mt5' / self.tf
|
|
389
408
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
390
409
|
for symbol in self.symbol_list:
|
|
391
410
|
try:
|
|
392
411
|
data = download_historical_data(
|
|
393
412
|
symbol=symbol,
|
|
394
|
-
|
|
413
|
+
timeframe=self.tf,
|
|
395
414
|
date_from=self.start,
|
|
396
|
-
date_to=self.end,
|
|
415
|
+
date_to=self.end,
|
|
397
416
|
utc=self.use_utc,
|
|
398
417
|
filter=self.filer,
|
|
399
418
|
fill_na=self.fill_na,
|
|
400
|
-
lower_colnames=self.lower_cols
|
|
419
|
+
lower_colnames=self.lower_cols,
|
|
420
|
+
**self.kwargs
|
|
401
421
|
)
|
|
402
422
|
if data is None:
|
|
403
423
|
raise ValueError(f"No data found for {symbol}")
|
|
@@ -432,15 +452,23 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
432
452
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
433
453
|
"""
|
|
434
454
|
self.symbol_list = symbol_list
|
|
435
|
-
self.start_date
|
|
436
|
-
self.end_date
|
|
437
|
-
self.cache_dir
|
|
455
|
+
self.start_date = kwargs.get('yf_start')
|
|
456
|
+
self.end_date = kwargs.get('yf_end', datetime.now())
|
|
457
|
+
self.cache_dir = kwargs.get('data_dir')
|
|
458
|
+
|
|
438
459
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
439
|
-
|
|
460
|
+
|
|
461
|
+
super().__init__(
|
|
462
|
+
events,
|
|
463
|
+
symbol_list,
|
|
464
|
+
csv_dir,
|
|
465
|
+
columns =kwargs.get('columns'),
|
|
466
|
+
index_col=kwargs.get('index_col', 0)
|
|
467
|
+
)
|
|
440
468
|
|
|
441
469
|
def _download_and_cache_data(self, cache_dir: str):
|
|
442
470
|
"""Downloads and caches historical data as CSV files."""
|
|
443
|
-
cache_dir = cache_dir or BBSTRADER_DIR / '
|
|
471
|
+
cache_dir = cache_dir or BBSTRADER_DIR / 'yfinance' / 'daily'
|
|
444
472
|
os.makedirs(cache_dir, exist_ok=True)
|
|
445
473
|
for symbol in self.symbol_list:
|
|
446
474
|
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
@@ -449,39 +477,202 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
449
477
|
symbol, start=self.start_date, end=self.end_date, multi_level_index=False)
|
|
450
478
|
if data.empty:
|
|
451
479
|
raise ValueError(f"No data found for {symbol}")
|
|
452
|
-
data.to_csv(filepath)
|
|
480
|
+
data.to_csv(filepath)
|
|
453
481
|
except Exception as e:
|
|
454
482
|
raise ValueError(f"Error downloading {symbol}: {e}")
|
|
455
483
|
return cache_dir
|
|
456
484
|
|
|
457
485
|
|
|
458
|
-
# TODO # Get data from EODHD
|
|
459
|
-
# https://eodhd.com/
|
|
460
486
|
class EODHDataHandler(BaseCSVDataHandler):
|
|
461
|
-
|
|
487
|
+
"""
|
|
488
|
+
Downloads historical data from EOD Historical Data.
|
|
489
|
+
Data is fetched using the `eodhd` library.
|
|
462
490
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
491
|
+
To use this class, you need to sign up for an API key at
|
|
492
|
+
https://eodhistoricaldata.com/ and provide the key as an argument.
|
|
493
|
+
"""
|
|
494
|
+
def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
|
|
495
|
+
"""
|
|
496
|
+
Args:
|
|
497
|
+
events (Queue): The Event Queue for passing market events.
|
|
498
|
+
symbol_list (list[str]): List of symbols to download data for.
|
|
499
|
+
eodhd_start (str): Start date for historical data (YYYY-MM-DD).
|
|
500
|
+
eodhd_end (str): End date for historical data (YYYY-MM-DD).
|
|
501
|
+
data_dir (str, optional): Directory for caching data .
|
|
502
|
+
eodhd_period (str, optional): Time period for historical data (e.g., 'd', 'w', 'm', '1m', '5m', '1h').
|
|
503
|
+
eodhd_api_key (str, optional): API key for EOD Historical Data.
|
|
467
504
|
|
|
505
|
+
Note:
|
|
506
|
+
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
507
|
+
"""
|
|
508
|
+
self.symbol_list = symbol_list
|
|
509
|
+
self.start_date = kwargs.get('eodhd_start')
|
|
510
|
+
self.end_date = kwargs.get('eodhd_end', datetime.now().strftime('%Y-%m-%d'))
|
|
511
|
+
self.period = kwargs.get('eodhd_period', 'd')
|
|
512
|
+
self.cache_dir = kwargs.get('data_dir')
|
|
513
|
+
self.__api_key = kwargs.get('eodhd_api_key', 'demo')
|
|
468
514
|
|
|
469
|
-
|
|
470
|
-
|
|
515
|
+
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
516
|
+
|
|
517
|
+
super().__init__(
|
|
518
|
+
events,
|
|
519
|
+
symbol_list,
|
|
520
|
+
csv_dir,
|
|
521
|
+
columns =kwargs.get('columns'),
|
|
522
|
+
index_col=kwargs.get('index_col', 0)
|
|
523
|
+
)
|
|
471
524
|
|
|
525
|
+
def _get_data(self, symbol: str, period) -> pd.DataFrame | List[Dict]:
|
|
526
|
+
if not self.__api_key:
|
|
527
|
+
raise ValueError("API key is required for EODHD data.")
|
|
528
|
+
client = APIClient(api_key=self.__api_key)
|
|
529
|
+
if period in ['d', 'w', 'm']:
|
|
530
|
+
return client.get_historical_data(
|
|
531
|
+
symbol=symbol,
|
|
532
|
+
interval=period,
|
|
533
|
+
iso8601_start=self.start_date,
|
|
534
|
+
iso8601_end=self.end_date,
|
|
535
|
+
)
|
|
536
|
+
elif period in ['1m', '5m', '1h']:
|
|
537
|
+
hms = ' 00:00:00'
|
|
538
|
+
fmt = '%Y-%m-%d %H:%M:%S'
|
|
539
|
+
startdt = datetime.strptime(self.start_date + hms, fmt)
|
|
540
|
+
enddt = datetime.strptime(self.end_date + hms, fmt)
|
|
541
|
+
startdt = startdt.replace(tzinfo=timezone('UTC'))
|
|
542
|
+
enddt = enddt.replace(tzinfo=timezone('UTC'))
|
|
543
|
+
unix_start = int(startdt.timestamp())
|
|
544
|
+
unix_end = int(enddt.timestamp())
|
|
545
|
+
return client.get_intraday_historical_data(
|
|
546
|
+
symbol=symbol,
|
|
547
|
+
interval=period,
|
|
548
|
+
from_unix_time=unix_start,
|
|
549
|
+
to_unix_time=unix_end,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def _forma_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
|
|
553
|
+
if isinstance(data, pd.DataFrame):
|
|
554
|
+
if data.empty or len(data) == 0:
|
|
555
|
+
raise ValueError("No data found.")
|
|
556
|
+
df = data.drop(labels=['symbol', 'interval'], axis=1)
|
|
557
|
+
df = df.rename(columns={'adjusted_close': 'adj_close'})
|
|
558
|
+
return df
|
|
559
|
+
|
|
560
|
+
elif isinstance(data, list):
|
|
561
|
+
if not data or len(data) == 0:
|
|
562
|
+
raise ValueError("No data found.")
|
|
563
|
+
df = pd.DataFrame(data)
|
|
564
|
+
df = df.drop(columns=['timestamp', 'gmtoffset'], axis=1)
|
|
565
|
+
df = df.rename(columns={'datetime': 'date'})
|
|
566
|
+
df['adj_close'] = df['close']
|
|
567
|
+
df = df[['date', 'open', 'high', 'low', 'close', 'adj_close', 'volume']]
|
|
568
|
+
df.date = pd.to_datetime(df.date)
|
|
569
|
+
df = df.set_index('date')
|
|
570
|
+
return df
|
|
571
|
+
|
|
572
|
+
def _download_and_cache_data(self, cache_dir: str):
|
|
573
|
+
"""Downloads and caches historical data as CSV files."""
|
|
574
|
+
cache_dir = cache_dir or BBSTRADER_DIR / 'eodhd' / self.period
|
|
575
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
576
|
+
for symbol in self.symbol_list:
|
|
577
|
+
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
578
|
+
try:
|
|
579
|
+
data = self._get_data(symbol, self.period)
|
|
580
|
+
data = self._forma_data(data)
|
|
581
|
+
data.to_csv(filepath)
|
|
582
|
+
except Exception as e:
|
|
583
|
+
raise ValueError(f"Error downloading {symbol}: {e}")
|
|
584
|
+
return cache_dir
|
|
472
585
|
|
|
473
|
-
|
|
586
|
+
|
|
587
|
+
class FMPDataHandler(BaseCSVDataHandler):
|
|
474
588
|
"""
|
|
475
|
-
|
|
476
|
-
|
|
589
|
+
Downloads historical data from Financial Modeling Prep (FMP).
|
|
590
|
+
Data is fetched using the `financetoolkit` library.
|
|
591
|
+
|
|
592
|
+
To use this class, you need to sign up for an API key at
|
|
593
|
+
https://financialmodelingprep.com/developer/docs/pricing and
|
|
594
|
+
provide the key as an argument.
|
|
595
|
+
|
|
477
596
|
"""
|
|
478
|
-
|
|
597
|
+
def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
|
|
598
|
+
"""
|
|
599
|
+
Args:
|
|
600
|
+
events (Queue): The Event Queue for passing market events.
|
|
601
|
+
symbol_list (list[str]): List of symbols to download data for.
|
|
602
|
+
fmp_start (str): Start date for historical data (YYYY-MM-DD).
|
|
603
|
+
fmp_end (str): End date for historical data (YYYY-MM-DD).
|
|
604
|
+
data_dir (str, optional): Directory for caching data .
|
|
605
|
+
fmp_period (str, optional): Time period for historical data
|
|
606
|
+
(e.g. daily, weekly, monthly, quarterly, yearly, "1min", "5min", "15min", "30min", "1hour").
|
|
607
|
+
fmp_api_key (str): API key for Financial Modeling Prep.
|
|
479
608
|
|
|
609
|
+
Note:
|
|
610
|
+
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
611
|
+
"""
|
|
612
|
+
self.symbol_list = symbol_list
|
|
613
|
+
self.start_date = kwargs.get('fmp_start')
|
|
614
|
+
self.end_date = kwargs.get('fmp_end', datetime.now().strftime('%Y-%m-%d'))
|
|
615
|
+
self.period = kwargs.get('fmp_period', 'daily')
|
|
616
|
+
self.cache_dir = kwargs.get('data_dir')
|
|
617
|
+
self.__api_key = kwargs.get('fmp_api_key')
|
|
480
618
|
|
|
481
|
-
|
|
482
|
-
|
|
619
|
+
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
620
|
+
|
|
621
|
+
super().__init__(
|
|
622
|
+
events,
|
|
623
|
+
symbol_list,
|
|
624
|
+
csv_dir,
|
|
625
|
+
columns =kwargs.get('columns'),
|
|
626
|
+
index_col=kwargs.get('index_col', 0)
|
|
627
|
+
)
|
|
483
628
|
|
|
484
|
-
|
|
629
|
+
def _get_data(self, symbol: str, period: str) -> pd.DataFrame:
|
|
630
|
+
if not self.__api_key:
|
|
631
|
+
raise ValueError("API key is required for FMP data.")
|
|
632
|
+
toolkit = Toolkit(
|
|
633
|
+
symbol,
|
|
634
|
+
api_key=self.__api_key,
|
|
635
|
+
start_date=self.start_date,
|
|
636
|
+
end_date=self.end_date,
|
|
637
|
+
benchmark_ticker=None
|
|
638
|
+
)
|
|
639
|
+
if period in ['daily', 'weekly', 'monthly', 'quarterly', 'yearly']:
|
|
640
|
+
return toolkit.get_historical_data(period=period)
|
|
641
|
+
elif period in ['1min', '5min', '15min', '30min', '1hour']:
|
|
642
|
+
return toolkit.get_intraday_data(period=period)
|
|
643
|
+
|
|
644
|
+
def _format_data(self, data: pd.DataFrame, period: str) -> pd.DataFrame:
|
|
645
|
+
if data.empty or len(data) == 0:
|
|
646
|
+
raise ValueError("No data found.")
|
|
647
|
+
if period[0].isnumeric():
|
|
648
|
+
data = data.drop(columns=['Return', 'Volatility', 'Cumulative Return'], axis=1)
|
|
649
|
+
else:
|
|
650
|
+
data = data.drop(columns=['Dividends', 'Return', 'Volatility',
|
|
651
|
+
'Excess Return', 'Excess Volatility',
|
|
652
|
+
'Cumulative Return'], axis=1)
|
|
653
|
+
data = data.reset_index()
|
|
654
|
+
if 'Adj Close' not in data.columns:
|
|
655
|
+
data['Adj Close'] = data['Close']
|
|
656
|
+
data['date'] = data['date'].dt.to_timestamp()
|
|
657
|
+
data['date'] = pd.to_datetime(data['date'])
|
|
658
|
+
data.set_index('date', inplace=True)
|
|
659
|
+
return data
|
|
660
|
+
|
|
661
|
+
def _download_and_cache_data(self, cache_dir: str):
|
|
662
|
+
"""Downloads and caches historical data as CSV files."""
|
|
663
|
+
cache_dir = cache_dir or BBSTRADER_DIR / 'fmp' / self.period
|
|
664
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
665
|
+
for symbol in self.symbol_list:
|
|
666
|
+
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
667
|
+
try:
|
|
668
|
+
data = self._get_data(symbol, self.period)
|
|
669
|
+
data = self._format_data(data, self.period)
|
|
670
|
+
data.to_csv(filepath)
|
|
671
|
+
except Exception as e:
|
|
672
|
+
raise ValueError(f"Error downloading {symbol}: {e}")
|
|
673
|
+
return cache_dir
|
|
485
674
|
|
|
486
675
|
|
|
487
676
|
# TODO Add data Handlers for Interactive Brokers
|
|
677
|
+
class TWSDataHandler(BaseCSVDataHandler):
|
|
678
|
+
...
|
bbstrader/metatrader/account.py
CHANGED
|
@@ -125,9 +125,57 @@ AMG_EXCHANGES = {
|
|
|
125
125
|
'XSWX': r"Switzerland.*\(SWX\)"
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
def check_mt5_connection():
|
|
128
|
+
def check_mt5_connection(**kwargs):
|
|
129
|
+
"""
|
|
130
|
+
Initialize the connection to the MetaTrader 5 terminal.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
path (str, optional): The path to the MetaTrader 5 terminal executable file.
|
|
134
|
+
Defaults to None (e.g., "C:\\Program Files\\MetaTrader 5\\terminal64.exe").
|
|
135
|
+
login (int, optional): The login ID of the trading account. Defaults to None.
|
|
136
|
+
password (str, optional): The password of the trading account. Defaults to None.
|
|
137
|
+
server (str, optional): The name of the trade server to which the client terminal is connected.
|
|
138
|
+
Defaults to None.
|
|
139
|
+
timeout (int, optional): Connection timeout in milliseconds. Defaults to 60_000.
|
|
140
|
+
portable (bool, optional): If True, the portable mode of the terminal is used.
|
|
141
|
+
Defaults to False (See https://www.metatrader5.com/en/terminal/help/start_advanced/start#portable).
|
|
142
|
+
|
|
143
|
+
Notes:
|
|
144
|
+
If you want to lunch multiple terminal instances:
|
|
145
|
+
- Follow these instructions to lunch each terminal in portable mode first:
|
|
146
|
+
https://www.metatrader5.com/en/terminal/help/start_advanced/start#configuration_file
|
|
147
|
+
"""
|
|
148
|
+
path = kwargs.get('path', None)
|
|
149
|
+
login = kwargs.get('login', None)
|
|
150
|
+
password = kwargs.get('password', None)
|
|
151
|
+
server = kwargs.get('server', None)
|
|
152
|
+
timeout = kwargs.get('timeout', 60_000)
|
|
153
|
+
portable = kwargs.get('portable', False)
|
|
154
|
+
|
|
155
|
+
if path is None and (login or password or server):
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"You must provide a path to the terminal executable file"
|
|
158
|
+
f"when providing login, password or server"
|
|
159
|
+
)
|
|
129
160
|
try:
|
|
130
|
-
|
|
161
|
+
if path is not None:
|
|
162
|
+
if (
|
|
163
|
+
login is not None and
|
|
164
|
+
password is not None and
|
|
165
|
+
server is not None
|
|
166
|
+
):
|
|
167
|
+
init = mt5.initialize(
|
|
168
|
+
path=path,
|
|
169
|
+
login=login,
|
|
170
|
+
password=password,
|
|
171
|
+
server=server,
|
|
172
|
+
timeout=timeout,
|
|
173
|
+
portable=portable
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
init = mt5.initialize(path=path)
|
|
177
|
+
else:
|
|
178
|
+
init = mt5.initialize()
|
|
131
179
|
if not init:
|
|
132
180
|
raise_mt5_error(INIT_MSG)
|
|
133
181
|
except Exception:
|
|
@@ -135,9 +183,9 @@ def check_mt5_connection():
|
|
|
135
183
|
|
|
136
184
|
|
|
137
185
|
class Broker(object):
|
|
138
|
-
def __init__(self, name: str=None):
|
|
186
|
+
def __init__(self, name: str=None, **kwargs):
|
|
139
187
|
if name is None:
|
|
140
|
-
check_mt5_connection()
|
|
188
|
+
check_mt5_connection(**kwargs)
|
|
141
189
|
self._name = mt5.account_info().company
|
|
142
190
|
else:
|
|
143
191
|
self._name = name
|
|
@@ -157,8 +205,8 @@ class Broker(object):
|
|
|
157
205
|
|
|
158
206
|
|
|
159
207
|
class AdmiralMarktsGroup(Broker):
|
|
160
|
-
def __init__(self):
|
|
161
|
-
super().__init__("Admirals Group AS")
|
|
208
|
+
def __init__(self, **kwargs):
|
|
209
|
+
super().__init__("Admirals Group AS", **kwargs)
|
|
162
210
|
|
|
163
211
|
@property
|
|
164
212
|
def timezone(self) -> str:
|
|
@@ -166,8 +214,8 @@ class AdmiralMarktsGroup(Broker):
|
|
|
166
214
|
|
|
167
215
|
|
|
168
216
|
class JustGlobalMarkets(Broker):
|
|
169
|
-
def __init__(self):
|
|
170
|
-
super().__init__("Just Global Markets Ltd.")
|
|
217
|
+
def __init__(self, **kwargs):
|
|
218
|
+
super().__init__("Just Global Markets Ltd.", **kwargs)
|
|
171
219
|
|
|
172
220
|
@property
|
|
173
221
|
def timezone(self) -> str:
|
|
@@ -175,8 +223,8 @@ class JustGlobalMarkets(Broker):
|
|
|
175
223
|
|
|
176
224
|
|
|
177
225
|
class FTMO(Broker):
|
|
178
|
-
def __init__(self):
|
|
179
|
-
super().__init__("FTMO S.R.O.")
|
|
226
|
+
def __init__(self, **kwargs):
|
|
227
|
+
super().__init__("FTMO S.R.O.", **kwargs)
|
|
180
228
|
|
|
181
229
|
@property
|
|
182
230
|
def timezone(self) -> str:
|
|
@@ -230,8 +278,14 @@ class Account(object):
|
|
|
230
278
|
>>> trade_history = account.get_trade_history(from_date, to_date)
|
|
231
279
|
"""
|
|
232
280
|
|
|
233
|
-
def __init__(self):
|
|
234
|
-
|
|
281
|
+
def __init__(self, **kwargs):
|
|
282
|
+
"""
|
|
283
|
+
Initialize the Account class.
|
|
284
|
+
|
|
285
|
+
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
check_mt5_connection(**kwargs)
|
|
235
289
|
self._check_brokers()
|
|
236
290
|
|
|
237
291
|
def _check_brokers(self):
|
bbstrader/metatrader/rates.py
CHANGED
|
@@ -82,6 +82,8 @@ class Rates(object):
|
|
|
82
82
|
2. The `open, high, low, close, adjclose, returns,
|
|
83
83
|
volume` properties returns data in Broker's timezone by default.
|
|
84
84
|
|
|
85
|
+
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
86
|
+
|
|
85
87
|
Example:
|
|
86
88
|
>>> rates = Rates("EURUSD", "1h")
|
|
87
89
|
>>> df = rates.get_historical_data(
|
|
@@ -94,17 +96,18 @@ class Rates(object):
|
|
|
94
96
|
def __init__(
|
|
95
97
|
self,
|
|
96
98
|
symbol: str,
|
|
97
|
-
|
|
99
|
+
timeframe: TimeFrame = 'D1',
|
|
98
100
|
start_pos: Union[int , str] = 0,
|
|
99
101
|
count: Optional[int] = MAX_BARS,
|
|
100
|
-
session_duration: Optional[float] = None
|
|
102
|
+
session_duration: Optional[float] = None,
|
|
103
|
+
**kwargs
|
|
101
104
|
):
|
|
102
105
|
"""
|
|
103
106
|
Initializes a new Rates instance.
|
|
104
107
|
|
|
105
108
|
Args:
|
|
106
109
|
symbol (str): Financial instrument symbol (e.g., "EURUSD").
|
|
107
|
-
|
|
110
|
+
timeframe (str): Timeframe string (e.g., "D1", "1h", "5m").
|
|
108
111
|
start_pos (int, | str): Starting index (int) or date (str) for data retrieval.
|
|
109
112
|
count (int, optional): Number of bars to retrieve default is
|
|
110
113
|
the maximum bars availble in the MT5 terminal.
|
|
@@ -118,16 +121,17 @@ class Rates(object):
|
|
|
118
121
|
For `session_duration` check your broker symbols details
|
|
119
122
|
"""
|
|
120
123
|
self.symbol = symbol
|
|
121
|
-
|
|
124
|
+
tf = kwargs.get('time_frame')
|
|
125
|
+
self.time_frame = self._validate_time_frame(timeframe)
|
|
122
126
|
self.sd = session_duration
|
|
123
|
-
self.start_pos = self._get_start_pos(start_pos,
|
|
127
|
+
self.start_pos = self._get_start_pos(start_pos, timeframe)
|
|
124
128
|
self.count = count
|
|
125
|
-
self._mt5_initialized()
|
|
126
|
-
self.__account = Account()
|
|
129
|
+
self._mt5_initialized(**kwargs)
|
|
130
|
+
self.__account = Account(**kwargs)
|
|
127
131
|
self.__data = self.get_rates_from_pos()
|
|
128
132
|
|
|
129
|
-
def _mt5_initialized(self):
|
|
130
|
-
check_mt5_connection()
|
|
133
|
+
def _mt5_initialized(self, **kwargs):
|
|
134
|
+
check_mt5_connection(**kwargs)
|
|
131
135
|
|
|
132
136
|
def _get_start_pos(self, index, time_frame):
|
|
133
137
|
if isinstance(index, int):
|
|
@@ -471,13 +475,13 @@ class Rates(object):
|
|
|
471
475
|
df.to_csv(f"{self.symbol}.csv")
|
|
472
476
|
return df
|
|
473
477
|
|
|
474
|
-
def download_historical_data(symbol,
|
|
478
|
+
def download_historical_data(symbol, timeframe, date_from,
|
|
475
479
|
date_to=pd.Timestamp.now(),lower_colnames=True,
|
|
476
|
-
utc=False, filter=False, fill_na=False, save_csv=False):
|
|
480
|
+
utc=False, filter=False, fill_na=False, save_csv=False, **kwargs):
|
|
477
481
|
"""Download historical data from MetaTrader 5 terminal.
|
|
478
482
|
See `Rates.get_historical_data` for more details.
|
|
479
483
|
"""
|
|
480
|
-
rates = Rates(symbol,
|
|
484
|
+
rates = Rates(symbol, timeframe, **kwargs)
|
|
481
485
|
data = rates.get_historical_data(
|
|
482
486
|
date_from=date_from,
|
|
483
487
|
date_to=date_to,
|
|
@@ -488,23 +492,23 @@ def download_historical_data(symbol, time_frame, date_from,
|
|
|
488
492
|
)
|
|
489
493
|
return data
|
|
490
494
|
|
|
491
|
-
def get_data_from_pos(symbol,
|
|
495
|
+
def get_data_from_pos(symbol, timeframe, start_pos=0, fill_na=False,
|
|
492
496
|
count=MAX_BARS, lower_colnames=False, utc=False, filter=False,
|
|
493
|
-
session_duration=23.0):
|
|
497
|
+
session_duration=23.0, **kwargs):
|
|
494
498
|
"""Get historical data from a specific position.
|
|
495
499
|
See `Rates.get_rates_from_pos` for more details.
|
|
496
500
|
"""
|
|
497
|
-
rates = Rates(symbol,
|
|
501
|
+
rates = Rates(symbol, timeframe, start_pos, count, session_duration, **kwargs)
|
|
498
502
|
data = rates.get_rates_from_pos(filter=filter, fill_na=fill_na,
|
|
499
503
|
lower_colnames=lower_colnames, utc=utc)
|
|
500
504
|
return data
|
|
501
505
|
|
|
502
|
-
def get_data_from_date(symbol,
|
|
503
|
-
lower_colnames=False, utc=False, filter=False):
|
|
506
|
+
def get_data_from_date(symbol, timeframe, date_from, count=MAX_BARS, fill_na=False,
|
|
507
|
+
lower_colnames=False, utc=False, filter=False, **kwargs):
|
|
504
508
|
"""Get historical data from a specific date.
|
|
505
509
|
See `Rates.get_rates_from` for more details.
|
|
506
510
|
"""
|
|
507
|
-
rates = Rates(symbol,
|
|
511
|
+
rates = Rates(symbol, timeframe, **kwargs)
|
|
508
512
|
data = rates.get_rates_from(date_from, count, filter=filter, fill_na=fill_na,
|
|
509
513
|
lower_colnames=lower_colnames, utc=utc)
|
|
510
514
|
return data
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -118,8 +118,10 @@ class RiskManagement(Account):
|
|
|
118
118
|
tp (int, optional): Take Profit in points, Must be a positive number.
|
|
119
119
|
be (int, optional): Break Even in points, Must be a positive number.
|
|
120
120
|
rr (float, optional): Risk reward ratio, Must be a positive number. Defaults to 1.5.
|
|
121
|
+
|
|
122
|
+
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
121
123
|
"""
|
|
122
|
-
super().__init__()
|
|
124
|
+
super().__init__(**kwargs)
|
|
123
125
|
|
|
124
126
|
# Validation
|
|
125
127
|
if daily_risk is not None and daily_risk < 0:
|
|
@@ -137,6 +139,7 @@ class RiskManagement(Account):
|
|
|
137
139
|
if var_time_frame not in TIMEFRAMES:
|
|
138
140
|
raise ValueError("Unsupported time frame {}".format(var_time_frame))
|
|
139
141
|
|
|
142
|
+
self.kwargs = kwargs
|
|
140
143
|
self.symbol = symbol
|
|
141
144
|
self.start_time = start_time
|
|
142
145
|
self.finishing_time = finishing_time
|
|
@@ -279,7 +282,7 @@ class RiskManagement(Account):
|
|
|
279
282
|
tf_int = self._convert_time_frame(self._tf)
|
|
280
283
|
interval = round((minutes / tf_int) * 252)
|
|
281
284
|
|
|
282
|
-
rate = Rates(self.symbol, self._tf, 0, interval)
|
|
285
|
+
rate = Rates(self.symbol, self._tf, 0, interval, **self.kwargs)
|
|
283
286
|
returns = rate.returns*100
|
|
284
287
|
std = returns.std()
|
|
285
288
|
point = self.get_symbol_info(self.symbol).point
|
|
@@ -333,7 +336,7 @@ class RiskManagement(Account):
|
|
|
333
336
|
tf_int = self._convert_time_frame(tf)
|
|
334
337
|
interval = round((minutes / tf_int) * 252)
|
|
335
338
|
|
|
336
|
-
rate = Rates(self.symbol, tf, 0, interval)
|
|
339
|
+
rate = Rates(self.symbol, tf, 0, interval, **self.kwargs)
|
|
337
340
|
returns = rate.returns*100
|
|
338
341
|
p = self.get_account_info().margin_free
|
|
339
342
|
mu = returns.mean()
|
bbstrader/metatrader/trade.py
CHANGED
|
@@ -138,6 +138,7 @@ class Trade(RiskManagement):
|
|
|
138
138
|
- tp
|
|
139
139
|
- be
|
|
140
140
|
See the RiskManagement class for more details on these parameters.
|
|
141
|
+
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
141
142
|
"""
|
|
142
143
|
# Call the parent class constructor first
|
|
143
144
|
super().__init__(
|
|
@@ -160,6 +161,7 @@ class Trade(RiskManagement):
|
|
|
160
161
|
self.console_log = console_log
|
|
161
162
|
self.logger = self._get_logger(logger, console_log)
|
|
162
163
|
self.tf = kwargs.get("time_frame", 'D1')
|
|
164
|
+
self.kwargs = kwargs
|
|
163
165
|
|
|
164
166
|
self.start_time_hour, self.start_time_minutes = self.start.split(":")
|
|
165
167
|
self.finishing_time_hour, self.finishing_time_minutes = self.finishing.split(
|
|
@@ -174,8 +176,8 @@ class Trade(RiskManagement):
|
|
|
174
176
|
self.break_even_points = {}
|
|
175
177
|
self.trail_after_points = []
|
|
176
178
|
|
|
177
|
-
self.initialize()
|
|
178
|
-
self.select_symbol()
|
|
179
|
+
self.initialize(**kwargs)
|
|
180
|
+
self.select_symbol(**kwargs)
|
|
179
181
|
self.prepare_symbol()
|
|
180
182
|
|
|
181
183
|
if self.verbose:
|
|
@@ -194,7 +196,7 @@ class Trade(RiskManagement):
|
|
|
194
196
|
return config_logger(f'{log_path}/{logger}', consol_log)
|
|
195
197
|
return logger
|
|
196
198
|
|
|
197
|
-
def initialize(self):
|
|
199
|
+
def initialize(self, **kwargs):
|
|
198
200
|
"""
|
|
199
201
|
Initializes the MetaTrader 5 (MT5) terminal for trading operations.
|
|
200
202
|
This method attempts to establish a connection with the MT5 terminal.
|
|
@@ -207,7 +209,7 @@ class Trade(RiskManagement):
|
|
|
207
209
|
try:
|
|
208
210
|
if self.verbose:
|
|
209
211
|
print("\nInitializing the basics.")
|
|
210
|
-
check_mt5_connection()
|
|
212
|
+
check_mt5_connection(**kwargs)
|
|
211
213
|
if self.verbose:
|
|
212
214
|
print(
|
|
213
215
|
f"You are running the @{self.expert_name} Expert advisor,"
|
|
@@ -216,7 +218,7 @@ class Trade(RiskManagement):
|
|
|
216
218
|
except Exception as e:
|
|
217
219
|
self.logger.error(f"During initialization: {e}")
|
|
218
220
|
|
|
219
|
-
def select_symbol(self):
|
|
221
|
+
def select_symbol(self, **kwargs):
|
|
220
222
|
"""
|
|
221
223
|
Selects the trading symbol in the MetaTrader 5 (MT5) terminal.
|
|
222
224
|
This method ensures that the specified trading
|
|
@@ -228,6 +230,7 @@ class Trade(RiskManagement):
|
|
|
228
230
|
MT5TerminalError: If symbole selection fails.
|
|
229
231
|
"""
|
|
230
232
|
try:
|
|
233
|
+
check_mt5_connection(**kwargs)
|
|
231
234
|
if not Mt5.symbol_select(self.symbol, True):
|
|
232
235
|
raise_mt5_error(message=INIT_MSG)
|
|
233
236
|
except Exception as e:
|
|
@@ -848,10 +851,11 @@ class Trade(RiskManagement):
|
|
|
848
851
|
positions = self.get_positions(symbol=self.symbol)
|
|
849
852
|
if positions is not None:
|
|
850
853
|
positions = [position for position in positions if position.magic == id]
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
854
|
+
|
|
855
|
+
if positions is not None:
|
|
856
|
+
profit = 0.0
|
|
857
|
+
balance = self.get_account_info().balance
|
|
858
|
+
target = round((balance * self.target)/100, 2)
|
|
855
859
|
for position in positions:
|
|
856
860
|
profit += position.profit
|
|
857
861
|
fees = self.get_stats()[0]["average_fee"] * len(positions)
|
|
@@ -1489,6 +1493,7 @@ def create_trade_instance(
|
|
|
1489
1493
|
daily_risk: Optional[Dict[str, float]] = None,
|
|
1490
1494
|
max_risk: Optional[Dict[str, float]] = None,
|
|
1491
1495
|
pchange_sl: Optional[Dict[str, float] | float] = None,
|
|
1496
|
+
**kwargs
|
|
1492
1497
|
) -> Dict[str, Trade]:
|
|
1493
1498
|
"""
|
|
1494
1499
|
Creates Trade instances for each symbol provided.
|
bbstrader/models/risk.py
CHANGED
|
@@ -310,7 +310,15 @@ class HMMRiskManager(RiskModel):
|
|
|
310
310
|
and data filtered up to the end date if specified.
|
|
311
311
|
"""
|
|
312
312
|
df = data_frame.copy()
|
|
313
|
-
|
|
313
|
+
if 'Returns' or 'returns' not in df.columns:
|
|
314
|
+
if 'Close' in df.columns:
|
|
315
|
+
df['Returns'] = df['Close'].pct_change()
|
|
316
|
+
elif 'Adj Close' in df.columns:
|
|
317
|
+
df['Returns'] = df['Adj Close'].pct_change()
|
|
318
|
+
else:
|
|
319
|
+
raise ValueError("No 'Close' or 'Adj Close' columns found.")
|
|
320
|
+
elif 'returns' in df.columns:
|
|
321
|
+
df.rename(columns={'returns': 'Returns'}, inplace=True)
|
|
314
322
|
if end is not None:
|
|
315
323
|
df = df[:end.strftime('%Y-%m-%d')]
|
|
316
324
|
df.dropna(inplace=True)
|
bbstrader/trading/execution.py
CHANGED
|
@@ -104,7 +104,9 @@ def _mt5_execution(
|
|
|
104
104
|
telegram=telegram, token=bot_token, chat_id=chat_id)
|
|
105
105
|
|
|
106
106
|
logger = trades_instances[symbols[0]].logger
|
|
107
|
-
max_trades = {symbol: mtrades[symbol]
|
|
107
|
+
max_trades = {symbol: mtrades[symbol]
|
|
108
|
+
if mtrades is not None and symbol in mtrades
|
|
109
|
+
else trades_instances[symbol].max_trade() for symbol in symbols}
|
|
108
110
|
if comment is None:
|
|
109
111
|
trade = trades_instances[symbols[0]]
|
|
110
112
|
comment = f"{trade.expert_name}@{trade.version}"
|
|
@@ -123,7 +125,7 @@ def _mt5_execution(
|
|
|
123
125
|
long_market = {symbol: False for symbol in symbols}
|
|
124
126
|
short_market = {symbol: False for symbol in symbols}
|
|
125
127
|
try:
|
|
126
|
-
check_mt5_connection()
|
|
128
|
+
check_mt5_connection(**kwargs)
|
|
127
129
|
strategy: MT5Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
|
|
128
130
|
except Exception as e:
|
|
129
131
|
logger.error(f"Initializing strategy, {e}, STRATEGY={STRATEGY}")
|
|
@@ -133,7 +135,7 @@ def _mt5_execution(
|
|
|
133
135
|
|
|
134
136
|
while True:
|
|
135
137
|
try:
|
|
136
|
-
check_mt5_connection()
|
|
138
|
+
check_mt5_connection(**kwargs)
|
|
137
139
|
current_date = datetime.now()
|
|
138
140
|
today = current_date.strftime("%A").lower()
|
|
139
141
|
time.sleep(0.5)
|
|
@@ -167,16 +169,18 @@ def _mt5_execution(
|
|
|
167
169
|
continue
|
|
168
170
|
time.sleep(0.5)
|
|
169
171
|
try:
|
|
170
|
-
check_mt5_connection()
|
|
172
|
+
check_mt5_connection(**kwargs)
|
|
171
173
|
signals = strategy.calculate_signals()
|
|
172
|
-
weights =
|
|
174
|
+
weights = None
|
|
175
|
+
if hasattr(strategy, 'apply_risk_management'):
|
|
176
|
+
weights = strategy.apply_risk_management(optimizer)
|
|
173
177
|
update_risk(weights)
|
|
174
178
|
except Exception as e:
|
|
175
179
|
logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
|
|
176
180
|
continue
|
|
177
181
|
for symbol in symbols:
|
|
178
182
|
try:
|
|
179
|
-
check_mt5_connection()
|
|
183
|
+
check_mt5_connection(**kwargs)
|
|
180
184
|
trade: Trade = trades_instances[symbol]
|
|
181
185
|
tfmsg = f"Time Frame Not completed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
182
186
|
riskmsg = f"Risk not allowed !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}"
|
|
@@ -269,7 +273,7 @@ def _mt5_execution(
|
|
|
269
273
|
)
|
|
270
274
|
try:
|
|
271
275
|
FRIDAY = 'friday'
|
|
272
|
-
check_mt5_connection()
|
|
276
|
+
check_mt5_connection(**kwargs)
|
|
273
277
|
day_end = all(trade.days_end() for trade in trades_instances.values())
|
|
274
278
|
if closing_pnl is not None:
|
|
275
279
|
closing = all(trade.positive_profit(id=trade.expert_id, th=closing_pnl)
|
|
@@ -461,7 +465,7 @@ class MT5ExecutionEngine():
|
|
|
461
465
|
trading_days: Optional[List[str]] = TradingDays,
|
|
462
466
|
comment: Optional[str] = None,
|
|
463
467
|
**kwargs
|
|
464
|
-
|
|
468
|
+
):
|
|
465
469
|
"""
|
|
466
470
|
Args:
|
|
467
471
|
symbol_list : List of symbols to trade
|
|
@@ -505,6 +509,7 @@ class MT5ExecutionEngine():
|
|
|
505
509
|
|
|
506
510
|
4. All strategies must generate signals for backtesting and live trading.
|
|
507
511
|
See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
|
|
512
|
+
See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
|
|
508
513
|
"""
|
|
509
514
|
self.symbol_list = symbol_list
|
|
510
515
|
self.trades_instances = trades_instances
|
|
@@ -525,8 +530,13 @@ class MT5ExecutionEngine():
|
|
|
525
530
|
self.comment = comment
|
|
526
531
|
self.kwargs = kwargs
|
|
527
532
|
|
|
533
|
+
def __repr__(self):
|
|
534
|
+
trades = self.trades_instances.keys()
|
|
535
|
+
s = self.strategy_cls.__name__
|
|
536
|
+
return f"MT5ExecutionEngine(Symbols={list(trades)}, Strategy={s})"
|
|
537
|
+
|
|
528
538
|
def run(self):
|
|
529
|
-
check_mt5_connection()
|
|
539
|
+
check_mt5_connection(**self.kwargs)
|
|
530
540
|
_mt5_execution(
|
|
531
541
|
self.symbol_list,
|
|
532
542
|
self.trades_instances,
|
bbstrader/trading/strategies.py
CHANGED
|
@@ -80,17 +80,17 @@ class SMAStrategy(Strategy):
|
|
|
80
80
|
self.symbol_list = symbol_list or self.bars.symbol_list
|
|
81
81
|
self.mode = mode
|
|
82
82
|
|
|
83
|
+
self.kwargs = kwargs
|
|
83
84
|
self.short_window = kwargs.get("short_window", 50)
|
|
84
85
|
self.long_window = kwargs.get("long_window", 200)
|
|
85
86
|
self.tf = kwargs.get("time_frame", 'D1')
|
|
86
87
|
self.qty = get_quantities(
|
|
87
88
|
kwargs.get('quantities', 100), self.symbol_list)
|
|
88
89
|
self.sd = kwargs.get("session_duration", 23.0)
|
|
89
|
-
self.risk_models = build_hmm_models(self.symbol_list, **kwargs)
|
|
90
|
+
self.risk_models = build_hmm_models(self.symbol_list, **self.kwargs)
|
|
90
91
|
self.risk_window = kwargs.get("risk_window", self.long_window)
|
|
91
92
|
self.bought = self._calculate_initial_bought()
|
|
92
93
|
|
|
93
|
-
|
|
94
94
|
def _calculate_initial_bought(self):
|
|
95
95
|
bought = {}
|
|
96
96
|
for s in self.symbol_list:
|
|
@@ -151,13 +151,13 @@ class SMAStrategy(Strategy):
|
|
|
151
151
|
signal = SignalEvent(
|
|
152
152
|
1, s, dt, 'SHORT', quantity=self.qty[s], price=price)
|
|
153
153
|
self.bought[s] = 'SHORT'
|
|
154
|
-
|
|
154
|
+
signals[s] = signal
|
|
155
155
|
return signals
|
|
156
156
|
|
|
157
157
|
def get_live_data(self):
|
|
158
158
|
symbol_data = {symbol: None for symbol in self.symbol_list}
|
|
159
159
|
for symbol in self.symbol_list:
|
|
160
|
-
sig_rate = Rates(symbol, self.tf, 0, self.risk_window)
|
|
160
|
+
sig_rate = Rates(symbol, self.tf, 0, self.risk_window+2, **self.kwargs)
|
|
161
161
|
hmm_data = sig_rate.returns.values
|
|
162
162
|
prices = sig_rate.close.values
|
|
163
163
|
current_regime = self.risk_models[symbol].which_trade_allowed(hmm_data)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.94
|
|
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/
|
|
@@ -23,7 +23,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: pandas
|
|
26
|
-
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: numpy==1.26.4
|
|
27
27
|
Requires-Dist: yfinance
|
|
28
28
|
Requires-Dist: scipy
|
|
29
29
|
Requires-Dist: hmmlearn
|
|
@@ -47,8 +47,10 @@ Requires-Dist: scikit-learn
|
|
|
47
47
|
Requires-Dist: notify-py
|
|
48
48
|
Requires-Dist: python-telegram-bot
|
|
49
49
|
Requires-Dist: pyportfolioopt
|
|
50
|
+
Requires-Dist: eodhd
|
|
51
|
+
Requires-Dist: financetoolkit
|
|
50
52
|
Provides-Extra: mt5
|
|
51
|
-
Requires-Dist: MetaTrader5
|
|
53
|
+
Requires-Dist: MetaTrader5; extra == "mt5"
|
|
52
54
|
|
|
53
55
|
# Simplified Investment & Trading Toolkit
|
|
54
56
|

|
|
@@ -3,30 +3,30 @@ bbstrader/config.py,sha256=_AD_Cd-w5zyabm1CBPNGhzcZuSjThB7jyzTcjbrIlUQ,3618
|
|
|
3
3
|
bbstrader/tseries.py,sha256=qJKLxHnPOjB7dXon-ITK7vU1fAuvl8evzET6lSSnijQ,53572
|
|
4
4
|
bbstrader/btengine/__init__.py,sha256=OaXZTjgDwqWrjPq-CNE4kJkmriKXt9t5pIghW1MDTeo,2911
|
|
5
5
|
bbstrader/btengine/backtest.py,sha256=A3S84jpGTE_zhguOEGoGu6H_4ws4Iq5sf0n7TZaUYfQ,14615
|
|
6
|
-
bbstrader/btengine/data.py,sha256=
|
|
6
|
+
bbstrader/btengine/data.py,sha256=3bdd50hMrszNmcOXd8BYtkjTjg56fiThW2EJsItsCJA,26509
|
|
7
7
|
bbstrader/btengine/event.py,sha256=zF_ST4tcjV5uJJVV1IbRXQgCLbca2R2fmE7A2MaIno4,8748
|
|
8
8
|
bbstrader/btengine/execution.py,sha256=Fs6Hk64DxEOEVzAjsQ3CIVvYifWLLgkDjOixSh_Ghsc,10282
|
|
9
9
|
bbstrader/btengine/performance.py,sha256=WTYzB50lUD5aShPIEebbQPlaC2NVW6VfxdgGHjcIIAw,10707
|
|
10
10
|
bbstrader/btengine/portfolio.py,sha256=wCRmGxaZvihUPlXIlZp9cQo9fqPP-Tk5oALjknMfnos,16055
|
|
11
11
|
bbstrader/btengine/strategy.py,sha256=6IN1KQ-a-IQgbCEOflKTtGh-ouztwsVjik6TuMg6CY0,30210
|
|
12
12
|
bbstrader/metatrader/__init__.py,sha256=OLVOB_EieEb1P72I8V4Vem8kQWJ__D_L3c_wfwqY-9k,211
|
|
13
|
-
bbstrader/metatrader/account.py,sha256=
|
|
14
|
-
bbstrader/metatrader/rates.py,sha256=
|
|
15
|
-
bbstrader/metatrader/risk.py,sha256=
|
|
16
|
-
bbstrader/metatrader/trade.py,sha256=
|
|
13
|
+
bbstrader/metatrader/account.py,sha256=tkcAEgFIYZwtFRQc07lXQQFWRqglzR4H4LjX2BDRvj8,56371
|
|
14
|
+
bbstrader/metatrader/rates.py,sha256=RLzeq26LJ8dzFccgTcMjSUut4lsnt7xrWPXf5Z8xVuQ,21073
|
|
15
|
+
bbstrader/metatrader/risk.py,sha256=uLarOF-g9-RBdJuKSmIfT5WrPn47bmrvMxP21pQg4xo,26793
|
|
16
|
+
bbstrader/metatrader/trade.py,sha256=xAdwe00qNP0qeDzXGCK1_i4lsa9LpUkhbj82XqwJe6Y,70464
|
|
17
17
|
bbstrader/metatrader/utils.py,sha256=BTaZun4DKWpCxBBzY0SLQqqz7n_7F_R1F59APfyaa3E,17666
|
|
18
18
|
bbstrader/models/__init__.py,sha256=mpxtXYEcE8hwNDbzJf8MRqnBIa2T1voraEk0U0ri53c,437
|
|
19
19
|
bbstrader/models/factors.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
bbstrader/models/ml.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
bbstrader/models/optimization.py,sha256=JlMsda9L-ADSgw4YPE4o3CsL1Yyxfeahf9kUb-EZqqM,6699
|
|
22
22
|
bbstrader/models/portfolios.py,sha256=dFTZ3maRVY_O3UOIoRlLCbAow3SiLTQYt1q5DNaRUxE,8223
|
|
23
|
-
bbstrader/models/risk.py,sha256=
|
|
23
|
+
bbstrader/models/risk.py,sha256=k1f78_a6oYk24kv-iURecsSfpgCTWi6IpLsoR4LwnWg,15530
|
|
24
24
|
bbstrader/trading/__init__.py,sha256=3CCzV5rQbH8NthjDJhD0_2FABvpiCmkeC9cVeoW7bi4,438
|
|
25
|
-
bbstrader/trading/execution.py,sha256=
|
|
25
|
+
bbstrader/trading/execution.py,sha256=EhrTtDVV1Dz7T7_RglY4he3ZZMzijgl5tcRhQvbfIWA,26545
|
|
26
26
|
bbstrader/trading/scripts.py,sha256=rQmnG_4F_MuUEc96RXpAQT4kXrC-FkscsgHKgDAR_-Y,1902
|
|
27
|
-
bbstrader/trading/strategies.py,sha256=
|
|
28
|
-
bbstrader-0.1.
|
|
29
|
-
bbstrader-0.1.
|
|
30
|
-
bbstrader-0.1.
|
|
31
|
-
bbstrader-0.1.
|
|
32
|
-
bbstrader-0.1.
|
|
27
|
+
bbstrader/trading/strategies.py,sha256=6bj-xXIEzhTfWrGHAkjWJg7U1OkARu7YzoaWeSrfuZY,36460
|
|
28
|
+
bbstrader-0.1.94.dist-info/LICENSE,sha256=1EudjwwP2oTJy8Vh0e-Kzv8VZZU95y-t6c3DYhR51uc,1115
|
|
29
|
+
bbstrader-0.1.94.dist-info/METADATA,sha256=JKdBaA3tjT_Deirxlf_2jSQkvlvZh6UxdVqWMph84mI,9983
|
|
30
|
+
bbstrader-0.1.94.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
31
|
+
bbstrader-0.1.94.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
32
|
+
bbstrader-0.1.94.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|