bbstrader 0.2.991__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbstrader might be problematic. Click here for more details.
- bbstrader/__init__.py +1 -1
- bbstrader/__main__.py +35 -14
- bbstrader/btengine/data.py +4 -2
- bbstrader/btengine/execution.py +24 -14
- bbstrader/btengine/strategy.py +33 -15
- bbstrader/core/data.py +93 -30
- bbstrader/core/scripts.py +130 -0
- bbstrader/metatrader/account.py +115 -126
- bbstrader/metatrader/copier.py +114 -39
- bbstrader/metatrader/rates.py +14 -13
- bbstrader/metatrader/risk.py +13 -11
- bbstrader/metatrader/scripts.py +26 -12
- bbstrader/metatrader/trade.py +60 -54
- bbstrader/metatrader/utils.py +80 -26
- bbstrader/models/factors.py +3 -1
- bbstrader/models/ml.py +2 -1
- bbstrader/models/nlp.py +123 -70
- bbstrader/trading/execution.py +74 -36
- bbstrader/trading/strategies.py +15 -14
- bbstrader/tseries.py +8 -9
- bbstrader-0.3.1.dist-info/METADATA +466 -0
- bbstrader-0.3.1.dist-info/RECORD +47 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.dist-info}/WHEEL +1 -1
- bbstrader/__ini__.py +0 -20
- bbstrader-0.2.991.dist-info/METADATA +0 -191
- bbstrader-0.2.991.dist-info/RECORD +0 -47
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.dist-info}/entry_points.txt +0 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {bbstrader-0.2.991.dist-info → bbstrader-0.3.1.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/copier.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import multiprocessing
|
|
2
|
+
import threading
|
|
2
3
|
import time
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Dict, List, Literal, Tuple
|
|
6
7
|
|
|
7
|
-
from loguru import logger
|
|
8
|
+
from loguru import logger as log
|
|
8
9
|
|
|
9
10
|
from bbstrader.config import BBSTRADER_DIR
|
|
10
11
|
from bbstrader.metatrader.account import Account, check_mt5_connection
|
|
@@ -20,12 +21,14 @@ except ImportError:
|
|
|
20
21
|
__all__ = ["TradeCopier", "RunCopier", "RunMultipleCopier", "config_copier"]
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
log.add(
|
|
24
25
|
f"{BBSTRADER_DIR}/logs/copier.log",
|
|
25
26
|
enqueue=True,
|
|
26
27
|
level="INFO",
|
|
27
28
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
|
|
28
29
|
)
|
|
30
|
+
global logger
|
|
31
|
+
logger = log
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
def fix_lot(fixed):
|
|
@@ -113,11 +116,21 @@ def calculate_copy_lot(
|
|
|
113
116
|
raise ValueError("Invalid mode selected")
|
|
114
117
|
|
|
115
118
|
|
|
116
|
-
def get_copy_symbols(destination: dict
|
|
119
|
+
def get_copy_symbols(destination: dict, source: dict):
|
|
117
120
|
symbols = destination.get("symbols", "all")
|
|
118
|
-
|
|
121
|
+
src_account = Account(**source)
|
|
122
|
+
dest_account = Account(**destination)
|
|
119
123
|
if symbols == "all" or symbols == "*":
|
|
120
|
-
|
|
124
|
+
src_symbols = src_account.get_symbols()
|
|
125
|
+
dest_symbols = dest_account.get_symbols()
|
|
126
|
+
for s in src_symbols:
|
|
127
|
+
if s not in dest_symbols:
|
|
128
|
+
err_msg = (
|
|
129
|
+
f"To use 'all' or '*', Source account@{src_account.number} "
|
|
130
|
+
f"and destination account@{dest_account.number} "
|
|
131
|
+
f"must be the same type and have the same symbols"
|
|
132
|
+
)
|
|
133
|
+
raise ValueError(err_msg)
|
|
121
134
|
elif isinstance(symbols, (list, dict)):
|
|
122
135
|
return symbols
|
|
123
136
|
elif isinstance(symbols, str):
|
|
@@ -127,22 +140,6 @@ def get_copy_symbols(destination: dict = None):
|
|
|
127
140
|
return symbols.split()
|
|
128
141
|
|
|
129
142
|
|
|
130
|
-
def get_copy_symbol(symbol, destination: dict = None, type="destination"):
|
|
131
|
-
symbols = get_copy_symbols(destination)
|
|
132
|
-
if isinstance(symbols, list):
|
|
133
|
-
if symbol in symbols:
|
|
134
|
-
return symbol
|
|
135
|
-
if isinstance(symbols, dict):
|
|
136
|
-
if type == "destination":
|
|
137
|
-
if symbol in symbols.keys():
|
|
138
|
-
return symbols[symbol]
|
|
139
|
-
if type == "source":
|
|
140
|
-
for k, v in symbols.items():
|
|
141
|
-
if v == symbol:
|
|
142
|
-
return k
|
|
143
|
-
raise ValueError(f"Symbol {symbol} not found in {type} account")
|
|
144
|
-
|
|
145
|
-
|
|
146
143
|
class TradeCopier(object):
|
|
147
144
|
"""
|
|
148
145
|
``TradeCopier`` responsible for copying trading orders and positions from a source account to multiple destination accounts.
|
|
@@ -159,7 +156,10 @@ class TradeCopier(object):
|
|
|
159
156
|
"sleeptime",
|
|
160
157
|
"start_time",
|
|
161
158
|
"end_time",
|
|
159
|
+
"shutdown_event",
|
|
160
|
+
"custom_logger",
|
|
162
161
|
)
|
|
162
|
+
shutdown_event: threading.Event
|
|
163
163
|
|
|
164
164
|
def __init__(
|
|
165
165
|
self,
|
|
@@ -168,6 +168,8 @@ class TradeCopier(object):
|
|
|
168
168
|
sleeptime: float = 0.1,
|
|
169
169
|
start_time: str = None,
|
|
170
170
|
end_time: str = None,
|
|
171
|
+
shutdown_event=None,
|
|
172
|
+
custom_logger=None,
|
|
171
173
|
):
|
|
172
174
|
"""
|
|
173
175
|
Initializes the ``TradeCopier`` instance, setting up the source and destination trading accounts for trade copying.
|
|
@@ -245,8 +247,15 @@ class TradeCopier(object):
|
|
|
245
247
|
self.sleeptime = sleeptime
|
|
246
248
|
self.start_time = start_time
|
|
247
249
|
self.end_time = end_time
|
|
248
|
-
self.
|
|
250
|
+
self.shutdown_event = shutdown_event
|
|
251
|
+
self._add_logger(custom_logger)
|
|
249
252
|
self._add_copy()
|
|
253
|
+
self.errors = set()
|
|
254
|
+
|
|
255
|
+
def _add_logger(self, custom_logger):
|
|
256
|
+
if custom_logger:
|
|
257
|
+
global logger
|
|
258
|
+
logger = custom_logger
|
|
250
259
|
|
|
251
260
|
def _add_copy(self):
|
|
252
261
|
self.source["copy"] = True
|
|
@@ -269,6 +278,21 @@ class TradeCopier(object):
|
|
|
269
278
|
check_mt5_connection(**destination)
|
|
270
279
|
return Account(**destination).get_positions(symbol=symbol)
|
|
271
280
|
|
|
281
|
+
def get_copy_symbol(self, symbol, destination: dict = None, type="destination"):
|
|
282
|
+
symbols = get_copy_symbols(destination, self.source)
|
|
283
|
+
if isinstance(symbols, list):
|
|
284
|
+
if symbol in symbols:
|
|
285
|
+
return symbol
|
|
286
|
+
if isinstance(symbols, dict):
|
|
287
|
+
if type == "destination":
|
|
288
|
+
if symbol in symbols.keys():
|
|
289
|
+
return symbols[symbol]
|
|
290
|
+
if type == "source":
|
|
291
|
+
for k, v in symbols.items():
|
|
292
|
+
if v == symbol:
|
|
293
|
+
return k
|
|
294
|
+
raise ValueError(f"Symbol {symbol} not found in {type} account")
|
|
295
|
+
|
|
272
296
|
def isorder_modified(self, source: TradeOrder, dest: TradeOrder):
|
|
273
297
|
if source.type == dest.type and source.ticket == dest.magic:
|
|
274
298
|
return (
|
|
@@ -302,10 +326,9 @@ class TradeCopier(object):
|
|
|
302
326
|
if self.start_time is None or self.end_time is None:
|
|
303
327
|
return True
|
|
304
328
|
else:
|
|
305
|
-
now = datetime.now()
|
|
306
329
|
start_time = datetime.strptime(self.start_time, "%H:%M").time()
|
|
307
330
|
end_time = datetime.strptime(self.end_time, "%H:%M").time()
|
|
308
|
-
if start_time <= now.time() <= end_time:
|
|
331
|
+
if start_time <= datetime.now().time() <= end_time:
|
|
309
332
|
return True
|
|
310
333
|
return False
|
|
311
334
|
|
|
@@ -316,7 +339,7 @@ class TradeCopier(object):
|
|
|
316
339
|
return
|
|
317
340
|
check_mt5_connection(**destination)
|
|
318
341
|
volume = trade.volume if hasattr(trade, "volume") else trade.volume_initial
|
|
319
|
-
symbol = get_copy_symbol(trade.symbol, destination)
|
|
342
|
+
symbol = self.get_copy_symbol(trade.symbol, destination)
|
|
320
343
|
lot = calculate_copy_lot(
|
|
321
344
|
volume,
|
|
322
345
|
symbol,
|
|
@@ -327,7 +350,9 @@ class TradeCopier(object):
|
|
|
327
350
|
dest_eqty=Account(**destination).get_account_info().margin_free,
|
|
328
351
|
)
|
|
329
352
|
|
|
330
|
-
trade_instance = Trade(
|
|
353
|
+
trade_instance = Trade(
|
|
354
|
+
symbol=symbol, **destination, max_risk=100.0, logger=None
|
|
355
|
+
)
|
|
331
356
|
try:
|
|
332
357
|
action = action_type[trade.type]
|
|
333
358
|
except KeyError:
|
|
@@ -395,7 +420,7 @@ class TradeCopier(object):
|
|
|
395
420
|
|
|
396
421
|
def remove_order(self, src_symbol, order: TradeOrder, destination: dict):
|
|
397
422
|
check_mt5_connection(**destination)
|
|
398
|
-
trade = Trade(symbol=order.symbol, **destination)
|
|
423
|
+
trade = Trade(symbol=order.symbol, **destination, logger=None)
|
|
399
424
|
if trade.close_order(order.ticket, id=order.magic):
|
|
400
425
|
logger.info(
|
|
401
426
|
f"Close Order #{order.ticket} on @{destination.get('login')}::{order.symbol}, "
|
|
@@ -438,7 +463,7 @@ class TradeCopier(object):
|
|
|
438
463
|
|
|
439
464
|
def remove_position(self, src_symbol, position: TradePosition, destination: dict):
|
|
440
465
|
check_mt5_connection(**destination)
|
|
441
|
-
trade = Trade(symbol=position.symbol, **destination)
|
|
466
|
+
trade = Trade(symbol=position.symbol, **destination, logger=None)
|
|
442
467
|
if trade.close_position(position.ticket, id=position.magic):
|
|
443
468
|
logger.info(
|
|
444
469
|
f"Close Position #{position.ticket} on @{destination.get('login')}::{position.symbol}, "
|
|
@@ -464,7 +489,7 @@ class TradeCopier(object):
|
|
|
464
489
|
|
|
465
490
|
def get_positions(self, destination: dict):
|
|
466
491
|
source_positions = self.source_positions() or []
|
|
467
|
-
dest_symbols = get_copy_symbols(destination)
|
|
492
|
+
dest_symbols = get_copy_symbols(destination, self.source)
|
|
468
493
|
dest_positions = self.destination_positions(destination) or []
|
|
469
494
|
source_positions = self.filter_positions_and_orders(
|
|
470
495
|
source_positions, symbols=dest_symbols
|
|
@@ -476,7 +501,7 @@ class TradeCopier(object):
|
|
|
476
501
|
|
|
477
502
|
def get_orders(self, destination: dict):
|
|
478
503
|
source_orders = self.source_orders() or []
|
|
479
|
-
dest_symbols = get_copy_symbols(destination)
|
|
504
|
+
dest_symbols = get_copy_symbols(destination, self.source)
|
|
480
505
|
dest_orders = self.destination_orders(destination) or []
|
|
481
506
|
source_orders = self.filter_positions_and_orders(
|
|
482
507
|
source_orders, symbols=dest_symbols
|
|
@@ -512,7 +537,7 @@ class TradeCopier(object):
|
|
|
512
537
|
source_ids = [order.ticket for order in source_orders]
|
|
513
538
|
for destination_order in destination_orders:
|
|
514
539
|
if destination_order.magic not in source_ids:
|
|
515
|
-
src_symbol = get_copy_symbol(
|
|
540
|
+
src_symbol = self.get_copy_symbol(
|
|
516
541
|
destination_order.symbol, destination, type="source"
|
|
517
542
|
)
|
|
518
543
|
self.remove_order(src_symbol, destination_order, destination)
|
|
@@ -548,7 +573,7 @@ class TradeCopier(object):
|
|
|
548
573
|
if not destination.get("copy", False):
|
|
549
574
|
raise ValueError("Destination account not set to copy mode")
|
|
550
575
|
return destination.get("copy_what", "all")
|
|
551
|
-
|
|
576
|
+
|
|
552
577
|
def copy_orders(self, destination: dict):
|
|
553
578
|
what = self._copy_what(destination)
|
|
554
579
|
if what not in ["all", "orders"]:
|
|
@@ -588,7 +613,7 @@ class TradeCopier(object):
|
|
|
588
613
|
source_ids = [pos.ticket for pos in source_positions]
|
|
589
614
|
for destination_position in destination_positions:
|
|
590
615
|
if destination_position.magic not in source_ids:
|
|
591
|
-
src_symbol = get_copy_symbol(
|
|
616
|
+
src_symbol = self.get_copy_symbol(
|
|
592
617
|
destination_position.symbol, destination, type="source"
|
|
593
618
|
)
|
|
594
619
|
self.remove_position(src_symbol, destination_position, destination)
|
|
@@ -613,8 +638,15 @@ class TradeCopier(object):
|
|
|
613
638
|
logger.info("Trade Copier Running ...")
|
|
614
639
|
logger.info(f"Source Account: {self.source.get('login')}")
|
|
615
640
|
while True:
|
|
641
|
+
if self.shutdown_event and self.shutdown_event.is_set():
|
|
642
|
+
logger.info(
|
|
643
|
+
"Shutdown event received, stopping Trade Copier gracefully."
|
|
644
|
+
)
|
|
645
|
+
break
|
|
616
646
|
try:
|
|
617
647
|
for destination in self.destinations:
|
|
648
|
+
if self.shutdown_event and self.shutdown_event.is_set():
|
|
649
|
+
break
|
|
618
650
|
if destination.get("path") == self.source.get("path"):
|
|
619
651
|
err_msg = "Source and destination accounts are on the same \
|
|
620
652
|
MetaTrader 5 installation which is not allowed."
|
|
@@ -624,18 +656,52 @@ class TradeCopier(object):
|
|
|
624
656
|
self.copy_positions(destination)
|
|
625
657
|
Mt5.shutdown()
|
|
626
658
|
time.sleep(0.1)
|
|
659
|
+
|
|
660
|
+
if self.shutdown_event and self.shutdown_event.is_set():
|
|
661
|
+
logger.info(
|
|
662
|
+
"Shutdown event received during destination processing, exiting."
|
|
663
|
+
)
|
|
664
|
+
break
|
|
665
|
+
|
|
627
666
|
except KeyboardInterrupt:
|
|
628
|
-
logger.info("
|
|
667
|
+
logger.info("KeyboardInterrupt received, stopping the Trade Copier ...")
|
|
668
|
+
if self.shutdown_event:
|
|
669
|
+
self.shutdown_event.set()
|
|
629
670
|
break
|
|
630
671
|
except Exception as e:
|
|
631
672
|
self.log_error(e)
|
|
673
|
+
if self.shutdown_event and self.shutdown_event.is_set():
|
|
674
|
+
logger.error(
|
|
675
|
+
"Error occurred after shutdown signaled, exiting loop."
|
|
676
|
+
)
|
|
677
|
+
break
|
|
678
|
+
|
|
679
|
+
# Check shutdown event before sleeping
|
|
680
|
+
if self.shutdown_event and self.shutdown_event.is_set():
|
|
681
|
+
logger.info("Shutdown event checked before sleep, exiting.")
|
|
682
|
+
break
|
|
632
683
|
time.sleep(self.sleeptime)
|
|
684
|
+
logger.info("Trade Copier has shut down.")
|
|
633
685
|
|
|
634
686
|
|
|
635
687
|
def RunCopier(
|
|
636
|
-
source: dict,
|
|
688
|
+
source: dict,
|
|
689
|
+
destinations: list,
|
|
690
|
+
sleeptime: float,
|
|
691
|
+
start_time: str,
|
|
692
|
+
end_time: str,
|
|
693
|
+
shutdown_event=None,
|
|
694
|
+
custom_logger=None,
|
|
637
695
|
):
|
|
638
|
-
copier = TradeCopier(
|
|
696
|
+
copier = TradeCopier(
|
|
697
|
+
source,
|
|
698
|
+
destinations,
|
|
699
|
+
sleeptime,
|
|
700
|
+
start_time,
|
|
701
|
+
end_time,
|
|
702
|
+
shutdown_event,
|
|
703
|
+
custom_logger,
|
|
704
|
+
)
|
|
639
705
|
copier.run()
|
|
640
706
|
|
|
641
707
|
|
|
@@ -645,6 +711,8 @@ def RunMultipleCopier(
|
|
|
645
711
|
start_delay: float = 1.0,
|
|
646
712
|
start_time: str = None,
|
|
647
713
|
end_time: str = None,
|
|
714
|
+
shutdown_event=None,
|
|
715
|
+
custom_logger=None,
|
|
648
716
|
):
|
|
649
717
|
processes = []
|
|
650
718
|
|
|
@@ -662,10 +730,16 @@ def RunMultipleCopier(
|
|
|
662
730
|
)
|
|
663
731
|
continue
|
|
664
732
|
logger.info(f"Starting process for source account @{source.get('login')}")
|
|
665
|
-
|
|
666
733
|
process = multiprocessing.Process(
|
|
667
734
|
target=RunCopier,
|
|
668
|
-
args=(
|
|
735
|
+
args=(
|
|
736
|
+
source,
|
|
737
|
+
destinations,
|
|
738
|
+
sleeptime,
|
|
739
|
+
start_time,
|
|
740
|
+
end_time,
|
|
741
|
+
),
|
|
742
|
+
kwargs=dict(shutdown_event=shutdown_event, custom_logger=custom_logger),
|
|
669
743
|
)
|
|
670
744
|
processes.append(process)
|
|
671
745
|
process.start()
|
|
@@ -673,13 +747,14 @@ def RunMultipleCopier(
|
|
|
673
747
|
if start_delay:
|
|
674
748
|
time.sleep(start_delay)
|
|
675
749
|
|
|
676
|
-
# Wait for all processes to complete
|
|
677
750
|
for process in processes:
|
|
678
751
|
process.join()
|
|
679
752
|
|
|
680
753
|
|
|
681
754
|
def _strtodict(string: str) -> dict:
|
|
682
755
|
string = string.strip().replace("\n", "").replace(" ", "").replace('"""', "")
|
|
756
|
+
if string.endswith(","):
|
|
757
|
+
string = string[:-1]
|
|
683
758
|
return dict(item.split(":") for item in string.split(","))
|
|
684
759
|
|
|
685
760
|
|
bbstrader/metatrader/rates.py
CHANGED
|
@@ -7,7 +7,7 @@ from pandas.tseries.holiday import USFederalHolidayCalendar
|
|
|
7
7
|
from pandas.tseries.offsets import CustomBusinessDay
|
|
8
8
|
|
|
9
9
|
from bbstrader.metatrader.account import AMG_EXCHANGES, Account, check_mt5_connection
|
|
10
|
-
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error
|
|
10
|
+
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error, SymbolType
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
import MetaTrader5 as Mt5
|
|
@@ -46,13 +46,13 @@ COMD_CALENDARS = {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
CALENDARS = {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
SymbolType.FOREX: "us_futures",
|
|
50
|
+
SymbolType.STOCKS: AMG_EXCHANGES,
|
|
51
|
+
SymbolType.ETFs: AMG_EXCHANGES,
|
|
52
|
+
SymbolType.INDICES: IDX_CALENDARS,
|
|
53
|
+
SymbolType.COMMODITIES: COMD_CALENDARS,
|
|
54
|
+
SymbolType.CRYPTO: "24/7",
|
|
55
|
+
SymbolType.FUTURES: None,
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
SESSION_TIMEFRAMES = [
|
|
@@ -206,6 +206,7 @@ class Rates(object):
|
|
|
206
206
|
) -> Union[pd.DataFrame, None]:
|
|
207
207
|
"""Fetches data from MT5 and returns a DataFrame or None."""
|
|
208
208
|
try:
|
|
209
|
+
rates = None
|
|
209
210
|
if isinstance(start, int) and isinstance(count, int):
|
|
210
211
|
rates = Mt5.copy_rates_from_pos(
|
|
211
212
|
self.symbol, self.time_frame, start, count
|
|
@@ -248,7 +249,7 @@ class Rates(object):
|
|
|
248
249
|
currencies = self.__account.get_currency_rates(self.symbol)
|
|
249
250
|
s_info = self.__account.get_symbol_info(self.symbol)
|
|
250
251
|
if symbol_type in CALENDARS:
|
|
251
|
-
if symbol_type ==
|
|
252
|
+
if symbol_type == SymbolType.STOCKS or symbol_type == SymbolType.ETFs:
|
|
252
253
|
for exchange in CALENDARS[symbol_type]:
|
|
253
254
|
if exchange in get_calendar_names():
|
|
254
255
|
symbols = self.__account.get_stocks_from_exchange(
|
|
@@ -257,20 +258,20 @@ class Rates(object):
|
|
|
257
258
|
if self.symbol in symbols:
|
|
258
259
|
calendar = get_calendar(exchange, side="right")
|
|
259
260
|
break
|
|
260
|
-
elif symbol_type ==
|
|
261
|
+
elif symbol_type == SymbolType.INDICES:
|
|
261
262
|
calendar = get_calendar(
|
|
262
263
|
CALENDARS[symbol_type][currencies["mc"]], side="right"
|
|
263
264
|
)
|
|
264
|
-
elif symbol_type ==
|
|
265
|
+
elif symbol_type == SymbolType.COMMODITIES:
|
|
265
266
|
for commodity in CALENDARS[symbol_type]:
|
|
266
267
|
if commodity in s_info.path:
|
|
267
268
|
calendar = get_calendar(
|
|
268
269
|
CALENDARS[symbol_type][commodity], side="right"
|
|
269
270
|
)
|
|
270
|
-
elif symbol_type ==
|
|
271
|
+
elif symbol_type == SymbolType.FUTURES:
|
|
271
272
|
if "Index" in s_info.path:
|
|
272
273
|
calendar = get_calendar(
|
|
273
|
-
CALENDARS[
|
|
274
|
+
CALENDARS[SymbolType.INDICES][currencies["mc"]], side="right"
|
|
274
275
|
)
|
|
275
276
|
else:
|
|
276
277
|
for commodity, cal in COMD_CALENDARS.items():
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -6,7 +6,7 @@ from scipy.stats import norm
|
|
|
6
6
|
|
|
7
7
|
from bbstrader.metatrader.account import Account
|
|
8
8
|
from bbstrader.metatrader.rates import Rates
|
|
9
|
-
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame
|
|
9
|
+
from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, SymbolType
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
12
|
import MetaTrader5 as Mt5
|
|
@@ -275,10 +275,12 @@ class RiskManagement(Account):
|
|
|
275
275
|
swap = df["swap"].sum()
|
|
276
276
|
total_profit = commisions + fees + swap + profit
|
|
277
277
|
initial_balance = balance - total_profit
|
|
278
|
-
if
|
|
278
|
+
if equity != 0:
|
|
279
279
|
risk_alowed = (((equity - initial_balance) / equity) * 100) * -1
|
|
280
280
|
return round(risk_alowed, 2)
|
|
281
|
-
|
|
281
|
+
else: # Handle equity is zero
|
|
282
|
+
return 0.0
|
|
283
|
+
return 0.0 # This is for the case where df is None
|
|
282
284
|
|
|
283
285
|
def get_lot(self) -> float:
|
|
284
286
|
""" "Get the approprite lot size for a trade"""
|
|
@@ -498,10 +500,10 @@ class RiskManagement(Account):
|
|
|
498
500
|
av_price = (s_info.bid + s_info.ask) / 2
|
|
499
501
|
trade_risk = self.get_trade_risk()
|
|
500
502
|
symbol_type = self.get_symbol_type(self.symbol)
|
|
501
|
-
FX = symbol_type ==
|
|
502
|
-
COMD = symbol_type ==
|
|
503
|
-
FUT = symbol_type ==
|
|
504
|
-
CRYPTO = symbol_type ==
|
|
503
|
+
FX = symbol_type == SymbolType.FOREX
|
|
504
|
+
COMD = symbol_type == SymbolType.COMMODITIES
|
|
505
|
+
FUT = symbol_type == SymbolType.FUTURES
|
|
506
|
+
CRYPTO = symbol_type == SymbolType.CRYPTO
|
|
505
507
|
if COMD:
|
|
506
508
|
supported = _COMMD_SUPPORTED_
|
|
507
509
|
if "." in self.symbol:
|
|
@@ -652,14 +654,14 @@ class RiskManagement(Account):
|
|
|
652
654
|
lot = self._check_lot(_lot)
|
|
653
655
|
|
|
654
656
|
volume = round(lot * size * av_price)
|
|
655
|
-
if self.get_symbol_type(self.symbol) ==
|
|
657
|
+
if self.get_symbol_type(self.symbol) == SymbolType.FOREX:
|
|
656
658
|
volume = round((trade_loss * size) / loss)
|
|
657
659
|
__lot = round((volume / size), 2)
|
|
658
660
|
lot = self._check_lot(__lot)
|
|
659
661
|
|
|
660
662
|
if (
|
|
661
|
-
self.get_symbol_type(self.symbol) ==
|
|
662
|
-
or self.get_symbol_type(self.symbol) ==
|
|
663
|
+
self.get_symbol_type(self.symbol) == SymbolType.COMMODITIES
|
|
664
|
+
or self.get_symbol_type(self.symbol) == SymbolType.CRYPTO
|
|
663
665
|
and size > 1
|
|
664
666
|
):
|
|
665
667
|
lot = currency_risk / (sl * loss * size)
|
|
@@ -705,7 +707,7 @@ class RiskManagement(Account):
|
|
|
705
707
|
if account:
|
|
706
708
|
return AL
|
|
707
709
|
|
|
708
|
-
if self.get_symbol_type(self.symbol) ==
|
|
710
|
+
if self.get_symbol_type(self.symbol) == SymbolType.FOREX:
|
|
709
711
|
return AL
|
|
710
712
|
else:
|
|
711
713
|
s_info = self.symbol_info
|
bbstrader/metatrader/scripts.py
CHANGED
|
@@ -2,9 +2,18 @@ import argparse
|
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
4
|
from bbstrader.metatrader.copier import RunCopier, config_copier
|
|
5
|
+
from bbstrader.apps._copier import main as RunCopyAPP
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def copier_args(parser: argparse.ArgumentParser):
|
|
9
|
+
parser.add_argument(
|
|
10
|
+
"-m",
|
|
11
|
+
"--mode",
|
|
12
|
+
type=str,
|
|
13
|
+
default="CLI",
|
|
14
|
+
choices=("CLI", "GUI"),
|
|
15
|
+
help="Run the copier in the terminal or using the GUI",
|
|
16
|
+
)
|
|
8
17
|
parser.add_argument(
|
|
9
18
|
"-s", "--source", type=str, nargs="?", default=None, help="Source section name"
|
|
10
19
|
)
|
|
@@ -52,6 +61,7 @@ def copy_trades(unknown):
|
|
|
52
61
|
python -m bbstrader --run copier [options]
|
|
53
62
|
|
|
54
63
|
Options:
|
|
64
|
+
-m, --mode: CLI for terminal app and GUI for Desktop app
|
|
55
65
|
-s, --source: Source Account section name
|
|
56
66
|
-d, --destinations: Destination Account section names (multiple allowed)
|
|
57
67
|
-i, --interval: Update interval in seconds
|
|
@@ -67,15 +77,19 @@ def copy_trades(unknown):
|
|
|
67
77
|
copy_parser = copier_args(copy_parser)
|
|
68
78
|
copy_args = copy_parser.parse_args(unknown)
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
if copy_args.mode == "GUI":
|
|
81
|
+
RunCopyAPP()
|
|
82
|
+
|
|
83
|
+
elif copy_args.mode == "CLI":
|
|
84
|
+
source, destinations = config_copier(
|
|
85
|
+
source_section=copy_args.source,
|
|
86
|
+
dest_sections=copy_args.destinations,
|
|
87
|
+
inifile=copy_args.config,
|
|
88
|
+
)
|
|
89
|
+
RunCopier(
|
|
90
|
+
source,
|
|
91
|
+
destinations,
|
|
92
|
+
copy_args.interval,
|
|
93
|
+
copy_args.start,
|
|
94
|
+
copy_args.end,
|
|
95
|
+
)
|