bbstrader 0.2.92__py3-none-any.whl → 0.2.93__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 +1 -1
- bbstrader/btengine/data.py +11 -9
- bbstrader/compat.py +1 -1
- bbstrader/core/utils.py +2 -2
- bbstrader/metatrader/account.py +3 -3
- bbstrader/metatrader/copier.py +41 -31
- bbstrader/metatrader/scripts.py +6 -6
- bbstrader/metatrader/trade.py +13 -3
- bbstrader/models/ml.py +19 -12
- bbstrader/trading/execution.py +5 -1
- bbstrader/trading/script.py +1 -1
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.93.dist-info}/METADATA +1 -1
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.93.dist-info}/RECORD +17 -17
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.93.dist-info}/WHEEL +1 -1
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.93.dist-info}/LICENSE +0 -0
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.93.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.92.dist-info → bbstrader-0.2.93.dist-info}/top_level.txt +0 -0
bbstrader/__main__.py
CHANGED
|
@@ -18,7 +18,7 @@ USAGE_TEXT = """
|
|
|
18
18
|
backtest: Backtest a strategy, see bbstrader.btengine.backtest.run_backtest
|
|
19
19
|
execution: Execute a strategy, see bbstrader.trading.execution.MT5ExecutionEngine
|
|
20
20
|
|
|
21
|
-
run <module> --help for more information on the module
|
|
21
|
+
python -m bbstrader --run <module> --help for more information on the module
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
FONT = pyfiglet.figlet_format("BBSTRADER", font="big")
|
bbstrader/btengine/data.py
CHANGED
|
@@ -175,9 +175,9 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
175
175
|
new_names = self.columns or default_names
|
|
176
176
|
new_names = [name.strip().lower().replace(" ", "_") for name in new_names]
|
|
177
177
|
self.columns = new_names
|
|
178
|
-
assert (
|
|
179
|
-
"
|
|
180
|
-
)
|
|
178
|
+
assert "adj_close" in new_names or "close" in new_names, (
|
|
179
|
+
"Column names must contain 'Adj Close' and 'Close' or adj_close and close"
|
|
180
|
+
)
|
|
181
181
|
comb_index = None
|
|
182
182
|
for s in self.symbol_list:
|
|
183
183
|
# Load the CSV file with no header information,
|
|
@@ -207,7 +207,9 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
207
207
|
self.columns.append("adj_close")
|
|
208
208
|
self.symbol_data[s]["adj_close"] = self.symbol_data[s]["close"]
|
|
209
209
|
self.symbol_data[s]["returns"] = (
|
|
210
|
-
self.symbol_data[s][
|
|
210
|
+
self.symbol_data[s][
|
|
211
|
+
"adj_close" if "adj_close" in new_names else "close"
|
|
212
|
+
]
|
|
211
213
|
.pct_change()
|
|
212
214
|
.dropna()
|
|
213
215
|
)
|
|
@@ -362,7 +364,7 @@ class CSVDataHandler(BaseCSVDataHandler):
|
|
|
362
364
|
|
|
363
365
|
"""
|
|
364
366
|
csv_dir = kwargs.get("csv_dir")
|
|
365
|
-
csv_dir = csv_dir or BBSTRADER_DIR / "csv_data"
|
|
367
|
+
csv_dir = csv_dir or BBSTRADER_DIR / "data" / "csv_data"
|
|
366
368
|
super().__init__(
|
|
367
369
|
events,
|
|
368
370
|
symbol_list,
|
|
@@ -422,7 +424,7 @@ class MT5DataHandler(BaseCSVDataHandler):
|
|
|
422
424
|
)
|
|
423
425
|
|
|
424
426
|
def _download_and_cache_data(self, cache_dir: str):
|
|
425
|
-
data_dir = cache_dir or BBSTRADER_DIR / "mt5" / self.tf
|
|
427
|
+
data_dir = cache_dir or BBSTRADER_DIR / "data" / "mt5" / self.tf
|
|
426
428
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
427
429
|
for symbol in self.symbol_list:
|
|
428
430
|
try:
|
|
@@ -486,7 +488,7 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
486
488
|
|
|
487
489
|
def _download_and_cache_data(self, cache_dir: str):
|
|
488
490
|
"""Downloads and caches historical data as CSV files."""
|
|
489
|
-
cache_dir = cache_dir or BBSTRADER_DIR / "yfinance" / "daily"
|
|
491
|
+
cache_dir = cache_dir or BBSTRADER_DIR / "data" / "yfinance" / "daily"
|
|
490
492
|
os.makedirs(cache_dir, exist_ok=True)
|
|
491
493
|
for symbol in self.symbol_list:
|
|
492
494
|
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
@@ -598,7 +600,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
598
600
|
|
|
599
601
|
def _download_and_cache_data(self, cache_dir: str):
|
|
600
602
|
"""Downloads and caches historical data as CSV files."""
|
|
601
|
-
cache_dir = cache_dir or BBSTRADER_DIR / "eodhd" / self.period
|
|
603
|
+
cache_dir = cache_dir or BBSTRADER_DIR / "data" / "eodhd" / self.period
|
|
602
604
|
os.makedirs(cache_dir, exist_ok=True)
|
|
603
605
|
for symbol in self.symbol_list:
|
|
604
606
|
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
@@ -699,7 +701,7 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
699
701
|
|
|
700
702
|
def _download_and_cache_data(self, cache_dir: str):
|
|
701
703
|
"""Downloads and caches historical data as CSV files."""
|
|
702
|
-
cache_dir = cache_dir or BBSTRADER_DIR / "fmp" / self.period
|
|
704
|
+
cache_dir = cache_dir or BBSTRADER_DIR / "data" / "fmp" / self.period
|
|
703
705
|
os.makedirs(cache_dir, exist_ok=True)
|
|
704
706
|
for symbol in self.symbol_list:
|
|
705
707
|
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
bbstrader/compat.py
CHANGED
bbstrader/core/utils.py
CHANGED
|
@@ -20,7 +20,7 @@ def load_module(file_path):
|
|
|
20
20
|
raise FileNotFoundError(
|
|
21
21
|
f"Strategy file {file_path} not found. Please create it."
|
|
22
22
|
)
|
|
23
|
-
spec = importlib.util.spec_from_file_location("
|
|
23
|
+
spec = importlib.util.spec_from_file_location("bbstrader.cli", file_path)
|
|
24
24
|
module = importlib.util.module_from_spec(spec)
|
|
25
25
|
spec.loader.exec_module(module)
|
|
26
26
|
return module
|
|
@@ -66,7 +66,7 @@ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]
|
|
|
66
66
|
Returns:
|
|
67
67
|
A dictionary containing the INI file contents with proper data types.
|
|
68
68
|
"""
|
|
69
|
-
config = configparser.ConfigParser()
|
|
69
|
+
config = configparser.ConfigParser(interpolation=None)
|
|
70
70
|
config.read(file_path)
|
|
71
71
|
|
|
72
72
|
ini_dict = {}
|
bbstrader/metatrader/account.py
CHANGED
|
@@ -326,7 +326,7 @@ class Account(object):
|
|
|
326
326
|
|
|
327
327
|
def shutdown(self):
|
|
328
328
|
"""Close the connection to the MetaTrader 5 terminal."""
|
|
329
|
-
|
|
329
|
+
shutdown_mt5()
|
|
330
330
|
|
|
331
331
|
@property
|
|
332
332
|
def broker(self) -> Broker:
|
|
@@ -960,7 +960,7 @@ class Account(object):
|
|
|
960
960
|
symbol (str): Symbol name
|
|
961
961
|
|
|
962
962
|
Returns:
|
|
963
|
-
-
|
|
963
|
+
- SymbolInfo in the form of a NamedTuple().
|
|
964
964
|
- None in case of an error.
|
|
965
965
|
|
|
966
966
|
Raises:
|
|
@@ -1006,7 +1006,7 @@ class Account(object):
|
|
|
1006
1006
|
symbol (str): Symbol name
|
|
1007
1007
|
|
|
1008
1008
|
Returns:
|
|
1009
|
-
-
|
|
1009
|
+
- TickInfo in the form of a NamedTuple().
|
|
1010
1010
|
- None in case of an error.
|
|
1011
1011
|
|
|
1012
1012
|
Raises:
|
bbstrader/metatrader/copier.py
CHANGED
|
@@ -294,11 +294,11 @@ class TradeCopier(object):
|
|
|
294
294
|
return False
|
|
295
295
|
|
|
296
296
|
def iscopy_time(self):
|
|
297
|
-
start_hour, start_minutes = self.start_time.split(":")
|
|
298
|
-
end_hour, end_minutes = self.end_time.split(":")
|
|
299
297
|
if self.start_time is None or self.end_time is None:
|
|
300
298
|
return True
|
|
301
299
|
else:
|
|
300
|
+
start_hour, start_minutes = self.start_time.split(":")
|
|
301
|
+
end_hour, end_minutes = self.end_time.split(":")
|
|
302
302
|
if int(start_hour) < datetime.now().hour < int(end_hour):
|
|
303
303
|
return True
|
|
304
304
|
elif datetime.now().hour == int(start_hour):
|
|
@@ -346,13 +346,13 @@ class TradeCopier(object):
|
|
|
346
346
|
comment=destination.get("comment", trade.comment + "#Copied"),
|
|
347
347
|
):
|
|
348
348
|
logger.info(
|
|
349
|
-
f"Copy {action} Order #{trade.ticket} from @{self.source.get('login')} "
|
|
350
|
-
f"to @{destination.get('login')}
|
|
349
|
+
f"Copy {action} Order #{trade.ticket} from @{self.source.get('login')}::{trade.symbol} "
|
|
350
|
+
f"to @{destination.get('login')}::{symbol}"
|
|
351
351
|
)
|
|
352
352
|
else:
|
|
353
353
|
logger.error(
|
|
354
|
-
f"Error copying {action} Order #{trade.ticket} from @{self.source.get('login')} "
|
|
355
|
-
f"to @{destination.get('login')}
|
|
354
|
+
f"Error copying {action} Order #{trade.ticket} from @{self.source.get('login')}::{trade.symbol} "
|
|
355
|
+
f"to @{destination.get('login')}::{symbol}"
|
|
356
356
|
)
|
|
357
357
|
except Exception as e:
|
|
358
358
|
self.log_error(e, symbol=symbol)
|
|
@@ -384,27 +384,27 @@ class TradeCopier(object):
|
|
|
384
384
|
if result.retcode != Mt5.TRADE_RETCODE_DONE:
|
|
385
385
|
msg = trade_retcode_message(result.retcode)
|
|
386
386
|
logger.error(
|
|
387
|
-
f"Error modifying Order #{ticket} on @{destination.get('login')}
|
|
388
|
-
f"SOURCE=@{self.source.get('login')}
|
|
387
|
+
f"Error modifying Order #{ticket} on @{destination.get('login')}::{symbol}, {msg}, "
|
|
388
|
+
f"SOURCE=@{self.source.get('login')}::{source_order.symbol}"
|
|
389
389
|
)
|
|
390
390
|
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
391
391
|
logger.info(
|
|
392
|
-
f"Modify Order #{ticket} on @{destination.get('login')}, "
|
|
393
|
-
f"SOURCE=@{self.source.get('login')}
|
|
392
|
+
f"Modify Order #{ticket} on @{destination.get('login')}::{symbol}, "
|
|
393
|
+
f"SOURCE=@{self.source.get('login')}::{source_order.symbol}"
|
|
394
394
|
)
|
|
395
395
|
|
|
396
|
-
def remove_order(self, order: TradeOrder, destination: dict):
|
|
396
|
+
def remove_order(self, src_symbol, order: TradeOrder, destination: dict):
|
|
397
397
|
check_mt5_connection(**destination)
|
|
398
398
|
trade = Trade(symbol=order.symbol, **destination)
|
|
399
399
|
if trade.close_order(order.ticket, id=order.magic):
|
|
400
400
|
logger.info(
|
|
401
|
-
f"Close Order #{order.ticket} on @{destination.get('login')}, "
|
|
402
|
-
f"SOURCE=@{self.source.get('login')}
|
|
401
|
+
f"Close Order #{order.ticket} on @{destination.get('login')}::{order.symbol}, "
|
|
402
|
+
f"SOURCE=@{self.source.get('login')}::{src_symbol}"
|
|
403
403
|
)
|
|
404
404
|
else:
|
|
405
405
|
logger.error(
|
|
406
|
-
f"Error closing Order #{order.ticket} on @{destination.get('login')}, "
|
|
407
|
-
f"SOURCE=@{self.source.get('login')}
|
|
406
|
+
f"Error closing Order #{order.ticket} on @{destination.get('login')}::{order.symbol}, "
|
|
407
|
+
f"SOURCE=@{self.source.get('login')}::{src_symbol}"
|
|
408
408
|
)
|
|
409
409
|
|
|
410
410
|
def copy_new_position(self, position: TradePosition, destination: dict):
|
|
@@ -427,27 +427,27 @@ class TradeCopier(object):
|
|
|
427
427
|
if result.retcode != Mt5.TRADE_RETCODE_DONE:
|
|
428
428
|
msg = trade_retcode_message(result.retcode)
|
|
429
429
|
logger.error(
|
|
430
|
-
f"Error modifying Position #{ticket} on @{destination.get('login')}
|
|
431
|
-
f"SOURCE=@{self.source.get('login')}
|
|
430
|
+
f"Error modifying Position #{ticket} on @{destination.get('login')}::{symbol}, {msg}, "
|
|
431
|
+
f"SOURCE=@{self.source.get('login')}::{source_pos.symbol}"
|
|
432
432
|
)
|
|
433
433
|
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
434
434
|
logger.info(
|
|
435
|
-
f"Modify Position #{ticket} on @{destination.get('login')}, "
|
|
436
|
-
f"SOURCE=@{self.source.get('login')}
|
|
435
|
+
f"Modify Position #{ticket} on @{destination.get('login')}::{symbol}, "
|
|
436
|
+
f"SOURCE=@{self.source.get('login')}::{source_pos.symbol}"
|
|
437
437
|
)
|
|
438
438
|
|
|
439
|
-
def remove_position(self, position: TradePosition, destination: dict):
|
|
439
|
+
def remove_position(self, src_symbol, position: TradePosition, destination: dict):
|
|
440
440
|
check_mt5_connection(**destination)
|
|
441
441
|
trade = Trade(symbol=position.symbol, **destination)
|
|
442
442
|
if trade.close_position(position.ticket, id=position.magic):
|
|
443
443
|
logger.info(
|
|
444
|
-
f"Close Position #{position.ticket} on @{destination.get('login')}, "
|
|
445
|
-
f"SOURCE=@{self.source.get('login')}
|
|
444
|
+
f"Close Position #{position.ticket} on @{destination.get('login')}::{position.symbol}, "
|
|
445
|
+
f"SOURCE=@{self.source.get('login')}::{src_symbol}"
|
|
446
446
|
)
|
|
447
447
|
else:
|
|
448
448
|
logger.error(
|
|
449
|
-
f"Error closing Position #{position.ticket} on @{destination.get('login')}, "
|
|
450
|
-
f"SOURCE=@{self.source.get('login')}
|
|
449
|
+
f"Error closing Position #{position.ticket} on @{destination.get('login')}::{position.symbol}, "
|
|
450
|
+
f"SOURCE=@{self.source.get('login')}::{src_symbol}"
|
|
451
451
|
)
|
|
452
452
|
|
|
453
453
|
def filter_positions_and_orders(self, pos_or_orders, symbols=None):
|
|
@@ -514,7 +514,10 @@ class TradeCopier(object):
|
|
|
514
514
|
source_ids = [order.ticket for order in source_orders]
|
|
515
515
|
for destination_order in destination_orders:
|
|
516
516
|
if destination_order.magic not in source_ids:
|
|
517
|
-
|
|
517
|
+
for order in source_orders:
|
|
518
|
+
if order.ticket == destination_order.magic:
|
|
519
|
+
src_symbol = order.ticket
|
|
520
|
+
self.remove_order(src_symbol, destination_order, destination)
|
|
518
521
|
|
|
519
522
|
# Check if order are triggered on source account
|
|
520
523
|
# and not on destination account or vice versa
|
|
@@ -523,7 +526,9 @@ class TradeCopier(object):
|
|
|
523
526
|
for source_position in source_positions:
|
|
524
527
|
for destination_order in destination_orders:
|
|
525
528
|
if source_position.ticket == destination_order.magic:
|
|
526
|
-
self.remove_order(
|
|
529
|
+
self.remove_order(
|
|
530
|
+
source_position.symbol, destination_order, destination
|
|
531
|
+
)
|
|
527
532
|
if what in ["all", "positions"]:
|
|
528
533
|
if not self.slippage(source_position, destination):
|
|
529
534
|
self.copy_new_position(source_position, destination)
|
|
@@ -533,7 +538,9 @@ class TradeCopier(object):
|
|
|
533
538
|
for destination_position in destination_positions:
|
|
534
539
|
for source_order in source_orders:
|
|
535
540
|
if destination_position.magic == source_order.ticket:
|
|
536
|
-
self.remove_position(
|
|
541
|
+
self.remove_position(
|
|
542
|
+
source_order.symbol, destination_position, destination
|
|
543
|
+
)
|
|
537
544
|
if not self.slippage(source_order, destination):
|
|
538
545
|
self.copy_new_order(source_order, destination)
|
|
539
546
|
Mt5.shutdown()
|
|
@@ -569,7 +576,10 @@ class TradeCopier(object):
|
|
|
569
576
|
source_ids = [pos.ticket for pos in source_positions]
|
|
570
577
|
for destination_position in destination_positions:
|
|
571
578
|
if destination_position.magic not in source_ids:
|
|
572
|
-
|
|
579
|
+
for position in source_positions:
|
|
580
|
+
if position.ticket == destination_position.magic:
|
|
581
|
+
src_symbol = position.ticket
|
|
582
|
+
self.remove_position(src_symbol, destination_position, destination)
|
|
573
583
|
Mt5.shutdown()
|
|
574
584
|
|
|
575
585
|
def log_error(self, e, symbol=None):
|
|
@@ -607,7 +617,7 @@ def RunCopier(
|
|
|
607
617
|
|
|
608
618
|
|
|
609
619
|
def RunMultipleCopier(
|
|
610
|
-
accounts: List[
|
|
620
|
+
accounts: List[dict],
|
|
611
621
|
sleeptime: float = 0.1,
|
|
612
622
|
start_delay: float = 1.0,
|
|
613
623
|
start_time: str = None,
|
|
@@ -664,7 +674,7 @@ def config_copier(
|
|
|
664
674
|
Example:
|
|
665
675
|
```python
|
|
666
676
|
from pathlib import Path
|
|
667
|
-
config_file =
|
|
677
|
+
config_file = ~/.bbstrader/copier/copier.ini
|
|
668
678
|
source, destinations = config_copier(config_file, "SOURCE", ["DEST1", "DEST2"])
|
|
669
679
|
```
|
|
670
680
|
"""
|
|
@@ -675,7 +685,7 @@ def config_copier(
|
|
|
675
685
|
return dict(item.split(":") for item in string.split(","))
|
|
676
686
|
|
|
677
687
|
if not inifile:
|
|
678
|
-
inifile = Path().home() / ".bbstrader" / "copier.ini"
|
|
688
|
+
inifile = Path().home() / ".bbstrader" / "copier" / "copier.ini"
|
|
679
689
|
if not inifile.exists() or not inifile.is_file():
|
|
680
690
|
raise FileNotFoundError(f"{inifile} not found")
|
|
681
691
|
|
bbstrader/metatrader/scripts.py
CHANGED
|
@@ -6,10 +6,10 @@ from bbstrader.metatrader.copier import RunCopier, config_copier
|
|
|
6
6
|
|
|
7
7
|
def copier_args(parser: argparse.ArgumentParser):
|
|
8
8
|
parser.add_argument(
|
|
9
|
-
"-
|
|
9
|
+
"-s", "--source", type=str, nargs="?", default=None, help="Source section name"
|
|
10
10
|
)
|
|
11
11
|
parser.add_argument(
|
|
12
|
-
"-
|
|
12
|
+
"-d",
|
|
13
13
|
"--destinations",
|
|
14
14
|
type=str,
|
|
15
15
|
nargs="*",
|
|
@@ -17,10 +17,10 @@ def copier_args(parser: argparse.ArgumentParser):
|
|
|
17
17
|
help="Destination section names",
|
|
18
18
|
)
|
|
19
19
|
parser.add_argument(
|
|
20
|
-
"-
|
|
20
|
+
"-i", "--interval", type=float, default=0.1, help="Update interval in seconds"
|
|
21
21
|
)
|
|
22
22
|
parser.add_argument(
|
|
23
|
-
"-
|
|
23
|
+
"-c",
|
|
24
24
|
"--config",
|
|
25
25
|
nargs="?",
|
|
26
26
|
default=None,
|
|
@@ -28,7 +28,7 @@ def copier_args(parser: argparse.ArgumentParser):
|
|
|
28
28
|
help="Config file name or path",
|
|
29
29
|
)
|
|
30
30
|
parser.add_argument(
|
|
31
|
-
"-
|
|
31
|
+
"-t",
|
|
32
32
|
"--start",
|
|
33
33
|
type=str,
|
|
34
34
|
nargs="?",
|
|
@@ -36,7 +36,7 @@ def copier_args(parser: argparse.ArgumentParser):
|
|
|
36
36
|
help="Start time in HH:MM format",
|
|
37
37
|
)
|
|
38
38
|
parser.add_argument(
|
|
39
|
-
"-
|
|
39
|
+
"-e",
|
|
40
40
|
"--end",
|
|
41
41
|
type=str,
|
|
42
42
|
nargs="?",
|
bbstrader/metatrader/trade.py
CHANGED
|
@@ -422,6 +422,7 @@ class Trade(RiskManagement):
|
|
|
422
422
|
price: Optional[float] = None,
|
|
423
423
|
stoplimit: Optional[float] = None,
|
|
424
424
|
mm: bool = True,
|
|
425
|
+
trail: bool = True,
|
|
425
426
|
id: Optional[int] = None,
|
|
426
427
|
comment: Optional[str] = None,
|
|
427
428
|
symbol: Optional[str] = None,
|
|
@@ -440,6 +441,7 @@ class Trade(RiskManagement):
|
|
|
440
441
|
The pending order is not passed to the trading system until that moment
|
|
441
442
|
id (int): The strategy id or expert Id
|
|
442
443
|
mm (bool): Weither to put stop loss and tp or not
|
|
444
|
+
trail (bool): Weither to trail the stop loss or not
|
|
443
445
|
comment (str): The comment for the opening position
|
|
444
446
|
"""
|
|
445
447
|
Id = id if id is not None else self.expert_id
|
|
@@ -484,7 +486,7 @@ class Trade(RiskManagement):
|
|
|
484
486
|
if mm:
|
|
485
487
|
request["sl"] = sl or mm_price - stop_loss * point
|
|
486
488
|
request["tp"] = tp or mm_price + take_profit * point
|
|
487
|
-
self.break_even(mm=mm, id=Id)
|
|
489
|
+
self.break_even(mm=mm, id=Id, trail=trail)
|
|
488
490
|
if self.check(comment):
|
|
489
491
|
return self.request_result(_price, request, action)
|
|
490
492
|
return False
|
|
@@ -510,6 +512,7 @@ class Trade(RiskManagement):
|
|
|
510
512
|
price: Optional[float] = None,
|
|
511
513
|
stoplimit: Optional[float] = None,
|
|
512
514
|
mm: bool = True,
|
|
515
|
+
trail: bool = True,
|
|
513
516
|
id: Optional[int] = None,
|
|
514
517
|
comment: Optional[str] = None,
|
|
515
518
|
symbol: Optional[str] = None,
|
|
@@ -528,6 +531,7 @@ class Trade(RiskManagement):
|
|
|
528
531
|
The pending order is not passed to the trading system until that moment
|
|
529
532
|
id (int): The strategy id or expert Id
|
|
530
533
|
mm (bool): Weither to put stop loss and tp or not
|
|
534
|
+
trail (bool): Weither to trail the stop loss or not
|
|
531
535
|
comment (str): The comment for the closing position
|
|
532
536
|
symbol (str): The symbol to trade
|
|
533
537
|
volume (float): The volume (lot) to trade
|
|
@@ -576,7 +580,7 @@ class Trade(RiskManagement):
|
|
|
576
580
|
if mm:
|
|
577
581
|
request["sl"] = sl or mm_price + stop_loss * point
|
|
578
582
|
request["tp"] = tp or mm_price - take_profit * point
|
|
579
|
-
self.break_even(mm=mm, id=Id)
|
|
583
|
+
self.break_even(mm=mm, id=Id, trail=trail)
|
|
580
584
|
if self.check(comment):
|
|
581
585
|
return self.request_result(_price, request, action)
|
|
582
586
|
return False
|
|
@@ -723,6 +727,7 @@ class Trade(RiskManagement):
|
|
|
723
727
|
stoplimit: Optional[float] = None,
|
|
724
728
|
id: Optional[int] = None,
|
|
725
729
|
mm: bool = True,
|
|
730
|
+
trail: bool = True,
|
|
726
731
|
comment: Optional[str] = None,
|
|
727
732
|
symbol: Optional[str] = None,
|
|
728
733
|
volume: Optional[float] = None,
|
|
@@ -740,6 +745,7 @@ class Trade(RiskManagement):
|
|
|
740
745
|
The pending order is not passed to the trading system until that moment
|
|
741
746
|
id (int): The strategy id or expert Id
|
|
742
747
|
mm (bool): Weither to put stop loss and tp or not
|
|
748
|
+
trail (bool): Weither to trail the stop loss or not
|
|
743
749
|
comment (str): The comment for the closing position
|
|
744
750
|
symbol (str): The symbol to trade
|
|
745
751
|
volume (float): The volume (lot) to trade
|
|
@@ -755,6 +761,7 @@ class Trade(RiskManagement):
|
|
|
755
761
|
stoplimit=stoplimit,
|
|
756
762
|
id=id,
|
|
757
763
|
mm=mm,
|
|
764
|
+
trail=trail,
|
|
758
765
|
comment=comment,
|
|
759
766
|
symbol=symbol,
|
|
760
767
|
volume=volume,
|
|
@@ -768,9 +775,12 @@ class Trade(RiskManagement):
|
|
|
768
775
|
stoplimit=stoplimit,
|
|
769
776
|
id=id,
|
|
770
777
|
mm=mm,
|
|
778
|
+
trail=trail,
|
|
771
779
|
comment=comment,
|
|
772
780
|
symbol=symbol,
|
|
773
781
|
volume=volume,
|
|
782
|
+
sl=sl,
|
|
783
|
+
tp=tp,
|
|
774
784
|
)
|
|
775
785
|
else:
|
|
776
786
|
raise ValueError(
|
|
@@ -1242,7 +1252,7 @@ class Trade(RiskManagement):
|
|
|
1242
1252
|
try:
|
|
1243
1253
|
min_be = round((fees / risk)) + 2
|
|
1244
1254
|
except ZeroDivisionError:
|
|
1245
|
-
min_be = self.symbol_info
|
|
1255
|
+
min_be = self.symbol_info.spread
|
|
1246
1256
|
be = self.get_break_even()
|
|
1247
1257
|
if th is not None:
|
|
1248
1258
|
win_be = th
|
bbstrader/models/ml.py
CHANGED
|
@@ -634,14 +634,13 @@ class LightGBModel(object):
|
|
|
634
634
|
|
|
635
635
|
# set up cross-validation
|
|
636
636
|
n_splits = int(2 * YEAR / test_length)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
637
|
+
print(
|
|
638
|
+
f"Lookahead: {lookahead:2.0f} | "
|
|
639
|
+
f"Train: {train_length:3.0f} | "
|
|
640
|
+
f"Test: {test_length:2.0f} | "
|
|
641
|
+
f"Params: {len(cv_params_):3.0f} | "
|
|
642
|
+
f"Train configs: {len(test_params)}"
|
|
643
|
+
)
|
|
645
644
|
|
|
646
645
|
# time-series cross-validation
|
|
647
646
|
cv = MultipleTimeSeriesCV(
|
|
@@ -1235,7 +1234,10 @@ class LightGBModel(object):
|
|
|
1235
1234
|
.to_frame("prediction")
|
|
1236
1235
|
)
|
|
1237
1236
|
tickers = predictions.index.get_level_values("symbol").unique().tolist()
|
|
1238
|
-
|
|
1237
|
+
try:
|
|
1238
|
+
return (predictions.unstack("symbol").prediction.tz_convert("UTC")), tickers
|
|
1239
|
+
except TypeError:
|
|
1240
|
+
return (predictions.unstack("symbol").prediction.tz_localize("UTC")), tickers
|
|
1239
1241
|
|
|
1240
1242
|
def assert_last_date(self, predictions: pd.DataFrame):
|
|
1241
1243
|
"""
|
|
@@ -1243,9 +1245,14 @@ class LightGBModel(object):
|
|
|
1243
1245
|
is the previous day, so it predicts today's returns.
|
|
1244
1246
|
"""
|
|
1245
1247
|
last_date = predictions.index.get_level_values("date").max()
|
|
1246
|
-
|
|
1247
|
-
last_date
|
|
1248
|
-
|
|
1248
|
+
try:
|
|
1249
|
+
if last_date.tzinfo is None:
|
|
1250
|
+
last_date = last_date.tz_localize("UTC")
|
|
1251
|
+
else:
|
|
1252
|
+
last_date = last_date.tz_convert("UTC")
|
|
1253
|
+
last_date = last_date.normalize()
|
|
1254
|
+
except Exception as e:
|
|
1255
|
+
print(f"Error getting last date: {e}")
|
|
1249
1256
|
try:
|
|
1250
1257
|
days = 3 if datetime.now().strftime("%A") == "Monday" else 1
|
|
1251
1258
|
td = (
|
bbstrader/trading/execution.py
CHANGED
|
@@ -221,7 +221,7 @@ def _mt5_execution(
|
|
|
221
221
|
desc = account.get_symbol_info(symbol).description
|
|
222
222
|
sigmsg = (
|
|
223
223
|
f"SIGNAL = {signal}, \nSYMBOL={symbol}, \nTYPE={symbol_type}, \nDESCRIPTION={desc}, "
|
|
224
|
-
f"\nPRICE={price}, \nSTOPLIMIT={stoplimit}, \nSTRATEGY={STRATEGY}, \nTIMEFRAME={time_frame}"
|
|
224
|
+
f"\nPRICE={price}, \nSTOPLIMIT={stoplimit}, \nSTRATEGY={STRATEGY}, \nTIMEFRAME={time_frame},"
|
|
225
225
|
f"\nBROKER={account.broker.name}, \nTIMESTAMP={datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
226
226
|
)
|
|
227
227
|
msg = f"Sending {signal} Order ... SYMBOL={symbol}, STRATEGY={STRATEGY} , ACCOUNT={ACCOUNT}"
|
|
@@ -258,6 +258,7 @@ def _mt5_execution(
|
|
|
258
258
|
stoplimit=stoplimit,
|
|
259
259
|
id=id,
|
|
260
260
|
mm=mm,
|
|
261
|
+
trail=trail,
|
|
261
262
|
comment=comment,
|
|
262
263
|
)
|
|
263
264
|
else:
|
|
@@ -274,6 +275,7 @@ def _mt5_execution(
|
|
|
274
275
|
stoplimit=stoplimit,
|
|
275
276
|
id=id,
|
|
276
277
|
mm=mm,
|
|
278
|
+
trail=trail,
|
|
277
279
|
comment=comment,
|
|
278
280
|
)
|
|
279
281
|
|
|
@@ -293,6 +295,7 @@ def _mt5_execution(
|
|
|
293
295
|
stoplimit=stoplimit,
|
|
294
296
|
id=id,
|
|
295
297
|
mm=mm,
|
|
298
|
+
trail=trail,
|
|
296
299
|
comment=comment,
|
|
297
300
|
)
|
|
298
301
|
else:
|
|
@@ -309,6 +312,7 @@ def _mt5_execution(
|
|
|
309
312
|
stoplimit=stoplimit,
|
|
310
313
|
id=id,
|
|
311
314
|
mm=mm,
|
|
315
|
+
trail=trail,
|
|
312
316
|
comment=comment,
|
|
313
317
|
)
|
|
314
318
|
|
bbstrader/trading/script.py
CHANGED
|
@@ -143,7 +143,7 @@ def execute_strategy(unknown):
|
|
|
143
143
|
parser.add_argument("-a", "--account", type=str, nargs="*", default=[])
|
|
144
144
|
parser.add_argument("-p", "--path", type=str, default=EXECUTION_PATH)
|
|
145
145
|
parser.add_argument("-c", "--config", type=str, default=CONFIG_PATH)
|
|
146
|
-
parser.add_argument("-l", "--parallel",
|
|
146
|
+
parser.add_argument("-l", "--parallel", action="store_true")
|
|
147
147
|
parser.add_argument(
|
|
148
148
|
"-t", "--terminal", type=str, default="MT5", choices=["MT5", "TWS"]
|
|
149
149
|
)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
bbstrader/__ini__.py,sha256=ahTl0_LzPsSMLr0Ae_hV78IuMFvMFDwPOBQh4kQltes,641
|
|
2
|
-
bbstrader/__main__.py,sha256=
|
|
3
|
-
bbstrader/compat.py,sha256=
|
|
2
|
+
bbstrader/__main__.py,sha256=JvWLoIN03J1VKwmrJN0Z8m7uFfAL3F-6c_Yij_CUAgM,1534
|
|
3
|
+
bbstrader/compat.py,sha256=YYubimfqHj8yPFhfqCDvz9eddMK9JJUYvxQDle6-tNU,506
|
|
4
4
|
bbstrader/config.py,sha256=YMH9O_b0eJdfTwlw6GNbjRo0ieQMTH893crgwbbW5fQ,4110
|
|
5
5
|
bbstrader/tseries.py,sha256=lbsbEl7jNyKHqkHdzX_DqAlZ7z46dGOJWB6kG2Y60qg,70181
|
|
6
6
|
bbstrader/btengine/__init__.py,sha256=FL0kC0NcsnlTH-yuTv4lu6AexY1wZKN1AQ9rv9MZagQ,3009
|
|
7
7
|
bbstrader/btengine/backtest.py,sha256=ZzGhoN-_g0cF-OCyk173imze2OXEhykHTUiJ9MowDO8,14582
|
|
8
|
-
bbstrader/btengine/data.py,sha256=
|
|
8
|
+
bbstrader/btengine/data.py,sha256=UpHW27o7cb9ibQJ6JYovfG_zyxA9SCwR0-Zv7zUQxSM,27296
|
|
9
9
|
bbstrader/btengine/event.py,sha256=38mhZH9d53C4x7bZER2B0O6M18txzS4u7zveKyxeP5Y,8603
|
|
10
10
|
bbstrader/btengine/execution.py,sha256=Ij5dLc9mGgtWp2dKAH5oURplA3RS_ZtwTwSrp9IYfpk,10644
|
|
11
11
|
bbstrader/btengine/performance.py,sha256=1ecWrTzHBQbk4ORvbTEKxwCzlL1brcXOEUwgbnjAwx4,12470
|
|
@@ -14,31 +14,31 @@ bbstrader/btengine/scripts.py,sha256=Dff0cMvaZBynczVagY91XvHxQR7v53lMXyHMF69n3DY
|
|
|
14
14
|
bbstrader/btengine/strategy.py,sha256=c-wvotJdhHu5FWAlPkv33LfjoW-7ID2G0Di_hc7CYMM,33217
|
|
15
15
|
bbstrader/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
bbstrader/core/data.py,sha256=3_odj9jxokyKU15j1aHTlgLQDjW75tKqGpCUfkpYq2Q,452
|
|
17
|
-
bbstrader/core/utils.py,sha256=
|
|
17
|
+
bbstrader/core/utils.py,sha256=yC4_BqymZ4_t5J6y3bRY5J8hCT_0ooSPD8rqwj_aYdM,4225
|
|
18
18
|
bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
bbstrader/metatrader/__init__.py,sha256=_vDoSBDTQ9JVhN6HsSYmTBx8womTvbJ222aqTRG5rmE,339
|
|
21
|
-
bbstrader/metatrader/account.py,sha256=
|
|
22
|
-
bbstrader/metatrader/copier.py,sha256=
|
|
21
|
+
bbstrader/metatrader/account.py,sha256=As2LtWUbVnobM5I1WPNAYv5_w_7mMm5xF31jaOtI8IU,58009
|
|
22
|
+
bbstrader/metatrader/copier.py,sha256=eoztbIlzBAJ-zjNpcq4NN7QnFkGJ3VSRqVmr6oGhUtA,32412
|
|
23
23
|
bbstrader/metatrader/rates.py,sha256=DzjObLlas38TnprPsw4xPxTupCRwy_jZzLezCU_4p0I,21169
|
|
24
24
|
bbstrader/metatrader/risk.py,sha256=jLrLiQWb_v8Dl480jXaUbA-yV4ck8ES4PArTRAWILE0,27680
|
|
25
|
-
bbstrader/metatrader/scripts.py,sha256=
|
|
26
|
-
bbstrader/metatrader/trade.py,sha256=
|
|
25
|
+
bbstrader/metatrader/scripts.py,sha256=zgY94YGL7l_tzVnbkZWlj5YRRogM1jHY9i3Vxxmh6qQ,2196
|
|
26
|
+
bbstrader/metatrader/trade.py,sha256=f1fETc83bmLru0hXtu5VpV73lB3dGMVJrHf8cdsirdI,78616
|
|
27
27
|
bbstrader/metatrader/utils.py,sha256=1d7A8XhUmiLGpG4ImcBhCgs7xHrmYe_YJhynWIdLr30,17748
|
|
28
28
|
bbstrader/models/__init__.py,sha256=SnGBMQ-zcUIpms3oNeqg7EVDFpg-7OPjNAD8kvi_Q84,508
|
|
29
29
|
bbstrader/models/factors.py,sha256=dWuXh83hLkwxUp3XwjgUl-r3_cjVcV_s0aFRlSLIfo8,13332
|
|
30
|
-
bbstrader/models/ml.py,sha256=
|
|
30
|
+
bbstrader/models/ml.py,sha256=dSoKL409RPEKUc0lQkzMh66--x7JsiB69BZKF1RzGoU,48791
|
|
31
31
|
bbstrader/models/optimization.py,sha256=gp0n9a9vwbUldaNiZUYry_4RP2NW0VFZ2k5NoOkz30c,6581
|
|
32
32
|
bbstrader/models/portfolio.py,sha256=-Zq9cmzyOZUlGq9RWfAxClpX0KJZqYZYpc5EGNTcPGI,8302
|
|
33
33
|
bbstrader/models/risk.py,sha256=IFQoHXxpBwJiifANRgwyAUOp7EgTWBAhfJFCO1sGR3g,15405
|
|
34
34
|
bbstrader/trading/__init__.py,sha256=2VoxbzfP1XBLVuxJtjRhjEBCtnv9HqwQzfMV4B5mM7M,468
|
|
35
|
-
bbstrader/trading/execution.py,sha256=
|
|
36
|
-
bbstrader/trading/script.py,sha256=
|
|
35
|
+
bbstrader/trading/execution.py,sha256=kRkvrX2HjGvIWgtGR3ar8FSkJmqPDFAWExYOaQMUrZQ,35495
|
|
36
|
+
bbstrader/trading/script.py,sha256=No6XW_Cz4vc28CtfcMhCDokb_ctXK-D4sGiOjxwMCXg,5875
|
|
37
37
|
bbstrader/trading/scripts.py,sha256=rYK-LF2rmiHwpImfJ3P2HTMUcVU372Fxqj5UP6ypTJ0,1926
|
|
38
38
|
bbstrader/trading/strategies.py,sha256=rMvLIhX_8MQg7_Lbo127UqdTRxBUof2m3jgRQTm55p0,37019
|
|
39
|
-
bbstrader-0.2.
|
|
40
|
-
bbstrader-0.2.
|
|
41
|
-
bbstrader-0.2.
|
|
42
|
-
bbstrader-0.2.
|
|
43
|
-
bbstrader-0.2.
|
|
44
|
-
bbstrader-0.2.
|
|
39
|
+
bbstrader-0.2.93.dist-info/LICENSE,sha256=P3PBO9RuYPzl6-PkjysTNnwmwMB64ph36Bz9DBj8MS4,1115
|
|
40
|
+
bbstrader-0.2.93.dist-info/METADATA,sha256=IUHCIzp-HO_VPJPubFU_IWVhaoeHMM41_ytwOcSBM4M,11546
|
|
41
|
+
bbstrader-0.2.93.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
42
|
+
bbstrader-0.2.93.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
|
|
43
|
+
bbstrader-0.2.93.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
|
|
44
|
+
bbstrader-0.2.93.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|