bbstrader 0.2.91__py3-none-any.whl → 0.2.92__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 +50 -0
- bbstrader/btengine/data.py +1 -0
- bbstrader/btengine/execution.py +13 -2
- bbstrader/btengine/performance.py +50 -1
- bbstrader/btengine/scripts.py +157 -0
- bbstrader/btengine/strategy.py +12 -2
- bbstrader/compat.py +1 -1
- bbstrader/config.py +2 -4
- bbstrader/core/utils.py +90 -1
- bbstrader/metatrader/__init__.py +2 -1
- bbstrader/metatrader/account.py +27 -37
- bbstrader/metatrader/copier.py +735 -0
- bbstrader/metatrader/rates.py +6 -3
- bbstrader/metatrader/risk.py +19 -8
- bbstrader/metatrader/scripts.py +81 -0
- bbstrader/metatrader/trade.py +165 -63
- bbstrader/metatrader/utils.py +5 -2
- bbstrader/models/ml.py +1 -0
- bbstrader/trading/execution.py +145 -32
- bbstrader/trading/script.py +155 -0
- bbstrader/trading/scripts.py +2 -0
- bbstrader/tseries.py +33 -7
- {bbstrader-0.2.91.dist-info → bbstrader-0.2.92.dist-info}/METADATA +6 -2
- bbstrader-0.2.92.dist-info/RECORD +44 -0
- bbstrader-0.2.92.dist-info/entry_points.txt +2 -0
- bbstrader-0.2.91.dist-info/RECORD +0 -38
- {bbstrader-0.2.91.dist-info → bbstrader-0.2.92.dist-info}/LICENSE +0 -0
- {bbstrader-0.2.91.dist-info → bbstrader-0.2.92.dist-info}/WHEEL +0 -0
- {bbstrader-0.2.91.dist-info → bbstrader-0.2.92.dist-info}/top_level.txt +0 -0
bbstrader/metatrader/trade.py
CHANGED
|
@@ -2,15 +2,15 @@ import os
|
|
|
2
2
|
import time
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from logging import Logger
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
|
|
6
7
|
|
|
7
|
-
from bbstrader import compat # noqa: F401
|
|
8
|
-
import MetaTrader5 as Mt5
|
|
9
8
|
import pandas as pd
|
|
9
|
+
from loguru import logger as log
|
|
10
10
|
from tabulate import tabulate
|
|
11
11
|
|
|
12
12
|
from bbstrader.btengine.performance import create_sharpe_ratio
|
|
13
|
-
from bbstrader.config import config_logger
|
|
13
|
+
from bbstrader.config import BBSTRADER_DIR, config_logger
|
|
14
14
|
from bbstrader.metatrader.account import INIT_MSG, check_mt5_connection
|
|
15
15
|
from bbstrader.metatrader.risk import RiskManagement
|
|
16
16
|
from bbstrader.metatrader.utils import (
|
|
@@ -19,6 +19,12 @@ from bbstrader.metatrader.utils import (
|
|
|
19
19
|
trade_retcode_message,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
try:
|
|
23
|
+
import MetaTrader5 as Mt5
|
|
24
|
+
except ImportError:
|
|
25
|
+
import bbstrader.compat # noqa: F401
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
__all__ = [
|
|
23
29
|
"Trade",
|
|
24
30
|
"create_trade_instance",
|
|
@@ -30,6 +36,16 @@ FILLING_TYPE = [
|
|
|
30
36
|
Mt5.ORDER_FILLING_BOC,
|
|
31
37
|
]
|
|
32
38
|
|
|
39
|
+
log.add(
|
|
40
|
+
f"{BBSTRADER_DIR}/logs/trade.log",
|
|
41
|
+
enqueue=True,
|
|
42
|
+
level="INFO",
|
|
43
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
global LOGGER
|
|
47
|
+
LOGGER = log
|
|
48
|
+
|
|
33
49
|
|
|
34
50
|
class Trade(RiskManagement):
|
|
35
51
|
"""
|
|
@@ -92,9 +108,9 @@ class Trade(RiskManagement):
|
|
|
92
108
|
expert_id: int = 9818,
|
|
93
109
|
version: str = "2.0",
|
|
94
110
|
target: float = 5.0,
|
|
95
|
-
start_time: str = "
|
|
96
|
-
finishing_time: str = "23:
|
|
97
|
-
ending_time: str = "23:
|
|
111
|
+
start_time: str = "0:00",
|
|
112
|
+
finishing_time: str = "23:59",
|
|
113
|
+
ending_time: str = "23:59",
|
|
98
114
|
verbose: Optional[bool] = None,
|
|
99
115
|
console_log: Optional[bool] = False,
|
|
100
116
|
logger: Logger | str = "bbstrader.log",
|
|
@@ -152,7 +168,6 @@ class Trade(RiskManagement):
|
|
|
152
168
|
self.end = ending_time
|
|
153
169
|
self.finishing = finishing_time
|
|
154
170
|
self.console_log = console_log
|
|
155
|
-
self.logger = self._get_logger(logger, console_log)
|
|
156
171
|
self.tf = kwargs.get("time_frame", "D1")
|
|
157
172
|
self.kwargs = kwargs
|
|
158
173
|
|
|
@@ -171,6 +186,7 @@ class Trade(RiskManagement):
|
|
|
171
186
|
self.trail_after_points = []
|
|
172
187
|
self._retcodes = []
|
|
173
188
|
|
|
189
|
+
self._get_logger(logger, console_log)
|
|
174
190
|
self.initialize(**kwargs)
|
|
175
191
|
self.select_symbol(**kwargs)
|
|
176
192
|
self.prepare_symbol()
|
|
@@ -187,11 +203,19 @@ class Trade(RiskManagement):
|
|
|
187
203
|
"""Return all the retcodes"""
|
|
188
204
|
return self._retcodes
|
|
189
205
|
|
|
190
|
-
|
|
206
|
+
@property
|
|
207
|
+
def logger(self):
|
|
208
|
+
return LOGGER
|
|
209
|
+
|
|
210
|
+
def _get_logger(self, loger: Any, consol_log: bool):
|
|
191
211
|
"""Get the logger object"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
212
|
+
global LOGGER
|
|
213
|
+
if loger is None:
|
|
214
|
+
... # Do nothing
|
|
215
|
+
elif isinstance(loger, (str, Path)):
|
|
216
|
+
LOGGER = config_logger(f"{BBSTRADER_DIR}/logs/{loger}", consol_log)
|
|
217
|
+
elif isinstance(loger, (Logger, type(log))):
|
|
218
|
+
LOGGER = loger
|
|
195
219
|
|
|
196
220
|
def initialize(self, **kwargs):
|
|
197
221
|
"""
|
|
@@ -213,7 +237,7 @@ class Trade(RiskManagement):
|
|
|
213
237
|
f" Version @{self.version}, on {self.symbol}."
|
|
214
238
|
)
|
|
215
239
|
except Exception as e:
|
|
216
|
-
|
|
240
|
+
LOGGER.error(f"During initialization: {e}")
|
|
217
241
|
|
|
218
242
|
def select_symbol(self, **kwargs):
|
|
219
243
|
"""
|
|
@@ -231,7 +255,7 @@ class Trade(RiskManagement):
|
|
|
231
255
|
if not Mt5.symbol_select(self.symbol, True):
|
|
232
256
|
raise_mt5_error(message=INIT_MSG)
|
|
233
257
|
except Exception as e:
|
|
234
|
-
|
|
258
|
+
LOGGER.error(f"Selecting symbol '{self.symbol}': {e}")
|
|
235
259
|
|
|
236
260
|
def prepare_symbol(self):
|
|
237
261
|
"""
|
|
@@ -253,7 +277,7 @@ class Trade(RiskManagement):
|
|
|
253
277
|
if self.verbose:
|
|
254
278
|
print("Initialization successfully completed.")
|
|
255
279
|
except Exception as e:
|
|
256
|
-
|
|
280
|
+
LOGGER.error(f"Preparing symbol '{self.symbol}': {e}")
|
|
257
281
|
|
|
258
282
|
def summary(self):
|
|
259
283
|
"""Show a brief description about the trading program"""
|
|
@@ -388,7 +412,7 @@ class Trade(RiskManagement):
|
|
|
388
412
|
filename = f"{symbol}_{today_date}@{self.expert_id}.csv"
|
|
389
413
|
filepath = os.path.join(dir, filename)
|
|
390
414
|
stats_df.to_csv(filepath, index=False)
|
|
391
|
-
|
|
415
|
+
LOGGER.info(f"Session statistics saved to {filepath}")
|
|
392
416
|
|
|
393
417
|
Buys = Literal["BMKT", "BLMT", "BSTP", "BSTPLMT"]
|
|
394
418
|
|
|
@@ -400,6 +424,10 @@ class Trade(RiskManagement):
|
|
|
400
424
|
mm: bool = True,
|
|
401
425
|
id: Optional[int] = None,
|
|
402
426
|
comment: Optional[str] = None,
|
|
427
|
+
symbol: Optional[str] = None,
|
|
428
|
+
volume: Optional[float] = None,
|
|
429
|
+
sl: Optional[float] = None,
|
|
430
|
+
tp: Optional[float] = None,
|
|
403
431
|
):
|
|
404
432
|
"""
|
|
405
433
|
Open a Buy positin
|
|
@@ -424,19 +452,19 @@ class Trade(RiskManagement):
|
|
|
424
452
|
else:
|
|
425
453
|
_price = self.get_tick_info(self.symbol).ask
|
|
426
454
|
|
|
427
|
-
lot = self.get_lot()
|
|
455
|
+
lot = volume or self.get_lot()
|
|
428
456
|
stop_loss = self.get_stop_loss()
|
|
429
457
|
take_profit = self.get_take_profit()
|
|
430
458
|
deviation = self.get_deviation()
|
|
431
459
|
request = {
|
|
432
460
|
"action": Mt5.TRADE_ACTION_DEAL,
|
|
433
|
-
"symbol": self.symbol,
|
|
461
|
+
"symbol": symbol or self.symbol,
|
|
434
462
|
"volume": float(lot),
|
|
435
463
|
"type": Mt5.ORDER_TYPE_BUY,
|
|
436
464
|
"price": _price,
|
|
437
465
|
"deviation": deviation,
|
|
438
466
|
"magic": Id,
|
|
439
|
-
"comment": f"@{self.expert_name}"
|
|
467
|
+
"comment": comment or f"@{self.expert_name}",
|
|
440
468
|
"type_time": Mt5.ORDER_TIME_GTC,
|
|
441
469
|
"type_filling": Mt5.ORDER_FILLING_FOK,
|
|
442
470
|
}
|
|
@@ -454,11 +482,12 @@ class Trade(RiskManagement):
|
|
|
454
482
|
request["stoplimit"] = stoplimit
|
|
455
483
|
mm_price = stoplimit
|
|
456
484
|
if mm:
|
|
457
|
-
request["sl"] = mm_price - stop_loss * point
|
|
458
|
-
request["tp"] = mm_price + take_profit * point
|
|
485
|
+
request["sl"] = sl or mm_price - stop_loss * point
|
|
486
|
+
request["tp"] = tp or mm_price + take_profit * point
|
|
459
487
|
self.break_even(mm=mm, id=Id)
|
|
460
488
|
if self.check(comment):
|
|
461
|
-
self.request_result(_price, request, action)
|
|
489
|
+
return self.request_result(_price, request, action)
|
|
490
|
+
return False
|
|
462
491
|
|
|
463
492
|
def _order_type(self):
|
|
464
493
|
type = {
|
|
@@ -483,6 +512,10 @@ class Trade(RiskManagement):
|
|
|
483
512
|
mm: bool = True,
|
|
484
513
|
id: Optional[int] = None,
|
|
485
514
|
comment: Optional[str] = None,
|
|
515
|
+
symbol: Optional[str] = None,
|
|
516
|
+
volume: Optional[float] = None,
|
|
517
|
+
sl: Optional[float] = None,
|
|
518
|
+
tp: Optional[float] = None,
|
|
486
519
|
):
|
|
487
520
|
"""
|
|
488
521
|
Open a sell positin
|
|
@@ -496,6 +529,10 @@ class Trade(RiskManagement):
|
|
|
496
529
|
id (int): The strategy id or expert Id
|
|
497
530
|
mm (bool): Weither to put stop loss and tp or not
|
|
498
531
|
comment (str): The comment for the closing position
|
|
532
|
+
symbol (str): The symbol to trade
|
|
533
|
+
volume (float): The volume (lot) to trade
|
|
534
|
+
sl (float): The stop loss in points
|
|
535
|
+
tp (float): The take profit in points
|
|
499
536
|
"""
|
|
500
537
|
Id = id if id is not None else self.expert_id
|
|
501
538
|
point = self.get_symbol_info(self.symbol).point
|
|
@@ -507,19 +544,19 @@ class Trade(RiskManagement):
|
|
|
507
544
|
else:
|
|
508
545
|
_price = self.get_tick_info(self.symbol).bid
|
|
509
546
|
|
|
510
|
-
lot = self.get_lot()
|
|
547
|
+
lot = volume or self.get_lot()
|
|
511
548
|
stop_loss = self.get_stop_loss()
|
|
512
549
|
take_profit = self.get_take_profit()
|
|
513
550
|
deviation = self.get_deviation()
|
|
514
551
|
request = {
|
|
515
552
|
"action": Mt5.TRADE_ACTION_DEAL,
|
|
516
|
-
"symbol": self.symbol,
|
|
553
|
+
"symbol": symbol or self.symbol,
|
|
517
554
|
"volume": float(lot),
|
|
518
555
|
"type": Mt5.ORDER_TYPE_SELL,
|
|
519
556
|
"price": _price,
|
|
520
557
|
"deviation": deviation,
|
|
521
558
|
"magic": Id,
|
|
522
|
-
"comment": f"@{self.expert_name}"
|
|
559
|
+
"comment": comment or f"@{self.expert_name}",
|
|
523
560
|
"type_time": Mt5.ORDER_TIME_GTC,
|
|
524
561
|
"type_filling": Mt5.ORDER_FILLING_FOK,
|
|
525
562
|
}
|
|
@@ -537,11 +574,12 @@ class Trade(RiskManagement):
|
|
|
537
574
|
request["stoplimit"] = stoplimit
|
|
538
575
|
mm_price = stoplimit
|
|
539
576
|
if mm:
|
|
540
|
-
request["sl"] = mm_price + stop_loss * point
|
|
541
|
-
request["tp"] = mm_price - take_profit * point
|
|
577
|
+
request["sl"] = sl or mm_price + stop_loss * point
|
|
578
|
+
request["tp"] = tp or mm_price - take_profit * point
|
|
542
579
|
self.break_even(mm=mm, id=Id)
|
|
543
580
|
if self.check(comment):
|
|
544
|
-
self.request_result(_price, request, action)
|
|
581
|
+
return self.request_result(_price, request, action)
|
|
582
|
+
return False
|
|
545
583
|
|
|
546
584
|
def check(self, comment):
|
|
547
585
|
"""
|
|
@@ -555,10 +593,10 @@ class Trade(RiskManagement):
|
|
|
555
593
|
if self.days_end():
|
|
556
594
|
return False
|
|
557
595
|
elif not self.trading_time():
|
|
558
|
-
|
|
596
|
+
LOGGER.info(f"Not Trading time, SYMBOL={self.symbol}")
|
|
559
597
|
return False
|
|
560
598
|
elif not self.is_risk_ok():
|
|
561
|
-
|
|
599
|
+
LOGGER.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
|
|
562
600
|
self._check(comment)
|
|
563
601
|
return False
|
|
564
602
|
elif self.profit_target():
|
|
@@ -571,7 +609,7 @@ class Trade(RiskManagement):
|
|
|
571
609
|
or self.get_current_positions() is None
|
|
572
610
|
):
|
|
573
611
|
self.close_positions(position_type="all")
|
|
574
|
-
|
|
612
|
+
LOGGER.info(txt)
|
|
575
613
|
time.sleep(5)
|
|
576
614
|
self.statistics(save=True)
|
|
577
615
|
|
|
@@ -613,7 +651,7 @@ class Trade(RiskManagement):
|
|
|
613
651
|
elif result.retcode not in self._retcodes:
|
|
614
652
|
self._retcodes.append(result.retcode)
|
|
615
653
|
msg = trade_retcode_message(result.retcode)
|
|
616
|
-
|
|
654
|
+
LOGGER.error(
|
|
617
655
|
f"Trade Order Request, RETCODE={result.retcode}: {msg}{addtionnal}"
|
|
618
656
|
)
|
|
619
657
|
elif result.retcode in [
|
|
@@ -637,7 +675,7 @@ class Trade(RiskManagement):
|
|
|
637
675
|
# Print the result
|
|
638
676
|
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
639
677
|
msg = trade_retcode_message(result.retcode)
|
|
640
|
-
|
|
678
|
+
LOGGER.info(f"Trade Order {msg}{addtionnal}")
|
|
641
679
|
if type != "BMKT" or type != "SMKT":
|
|
642
680
|
self.opened_orders.append(result.order)
|
|
643
681
|
long_msg = (
|
|
@@ -645,7 +683,7 @@ class Trade(RiskManagement):
|
|
|
645
683
|
f"Lot(s): {result.volume}, Sl: {self.get_stop_loss()}, "
|
|
646
684
|
f"Tp: {self.get_take_profit()}"
|
|
647
685
|
)
|
|
648
|
-
|
|
686
|
+
LOGGER.info(long_msg)
|
|
649
687
|
time.sleep(0.1)
|
|
650
688
|
if type == "BMKT" or type == "SMKT":
|
|
651
689
|
self.opened_positions.append(result.order)
|
|
@@ -664,17 +702,19 @@ class Trade(RiskManagement):
|
|
|
664
702
|
f"2. {order_type} Position Opened, Symbol: {self.symbol}, Price: @{round(position.price_open, 5)}, "
|
|
665
703
|
f"Sl: @{position.sl} Tp: @{position.tp}"
|
|
666
704
|
)
|
|
667
|
-
|
|
705
|
+
LOGGER.info(order_info)
|
|
668
706
|
pos_info = (
|
|
669
707
|
f"3. [OPEN POSITIONS ON {self.symbol} = {len(positions)}, ACCOUNT OPEN PnL = {profit} "
|
|
670
708
|
f"{self.get_account_info().currency}]\n"
|
|
671
709
|
)
|
|
672
|
-
|
|
710
|
+
LOGGER.info(pos_info)
|
|
711
|
+
return True
|
|
673
712
|
else:
|
|
674
713
|
msg = trade_retcode_message(result.retcode)
|
|
675
|
-
|
|
714
|
+
LOGGER.error(
|
|
676
715
|
f"Unable to Open Position, RETCODE={result.retcode}: {msg}{addtionnal}"
|
|
677
716
|
)
|
|
717
|
+
return False
|
|
678
718
|
|
|
679
719
|
def open_position(
|
|
680
720
|
self,
|
|
@@ -684,6 +724,10 @@ class Trade(RiskManagement):
|
|
|
684
724
|
id: Optional[int] = None,
|
|
685
725
|
mm: bool = True,
|
|
686
726
|
comment: Optional[str] = None,
|
|
727
|
+
symbol: Optional[str] = None,
|
|
728
|
+
volume: Optional[float] = None,
|
|
729
|
+
sl: Optional[float] = None,
|
|
730
|
+
tp: Optional[float] = None,
|
|
687
731
|
):
|
|
688
732
|
"""
|
|
689
733
|
Open a buy or sell position.
|
|
@@ -697,26 +741,36 @@ class Trade(RiskManagement):
|
|
|
697
741
|
id (int): The strategy id or expert Id
|
|
698
742
|
mm (bool): Weither to put stop loss and tp or not
|
|
699
743
|
comment (str): The comment for the closing position
|
|
744
|
+
symbol (str): The symbol to trade
|
|
745
|
+
volume (float): The volume (lot) to trade
|
|
746
|
+
sl (float): The stop loss in points
|
|
747
|
+
tp (float): The take profit in points
|
|
700
748
|
"""
|
|
701
749
|
BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
|
|
702
750
|
SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
|
|
703
751
|
if action in BUYS:
|
|
704
|
-
self.open_buy_position(
|
|
752
|
+
return self.open_buy_position(
|
|
705
753
|
action=action,
|
|
706
754
|
price=price,
|
|
707
755
|
stoplimit=stoplimit,
|
|
708
756
|
id=id,
|
|
709
757
|
mm=mm,
|
|
710
758
|
comment=comment,
|
|
759
|
+
symbol=symbol,
|
|
760
|
+
volume=volume,
|
|
761
|
+
sl=sl,
|
|
762
|
+
tp=tp,
|
|
711
763
|
)
|
|
712
764
|
elif action in SELLS:
|
|
713
|
-
self.open_sell_position(
|
|
765
|
+
return self.open_sell_position(
|
|
714
766
|
action=action,
|
|
715
767
|
price=price,
|
|
716
768
|
stoplimit=stoplimit,
|
|
717
769
|
id=id,
|
|
718
770
|
mm=mm,
|
|
719
771
|
comment=comment,
|
|
772
|
+
symbol=symbol,
|
|
773
|
+
volume=volume,
|
|
720
774
|
)
|
|
721
775
|
else:
|
|
722
776
|
raise ValueError(
|
|
@@ -939,7 +993,7 @@ class Trade(RiskManagement):
|
|
|
939
993
|
mm=True,
|
|
940
994
|
id: Optional[int] = None,
|
|
941
995
|
trail: Optional[bool] = True,
|
|
942
|
-
stop_trail:
|
|
996
|
+
stop_trail: int | str = None,
|
|
943
997
|
trail_after_points: int | str = None,
|
|
944
998
|
be_plus_points: Optional[int] = None,
|
|
945
999
|
):
|
|
@@ -1096,7 +1150,6 @@ class Trade(RiskManagement):
|
|
|
1096
1150
|
# Set the stop loss to break even
|
|
1097
1151
|
request = {
|
|
1098
1152
|
"action": Mt5.TRADE_ACTION_SLTP,
|
|
1099
|
-
"type": Mt5.ORDER_TYPE_SELL_STOP,
|
|
1100
1153
|
"position": position.ticket,
|
|
1101
1154
|
"sl": round(_price, digits),
|
|
1102
1155
|
"tp": position.tp,
|
|
@@ -1115,7 +1168,6 @@ class Trade(RiskManagement):
|
|
|
1115
1168
|
# Set the stop loss to break even
|
|
1116
1169
|
request = {
|
|
1117
1170
|
"action": Mt5.TRADE_ACTION_SLTP,
|
|
1118
|
-
"type": Mt5.ORDER_TYPE_BUY_STOP,
|
|
1119
1171
|
"position": position.ticket,
|
|
1120
1172
|
"sl": round(_price, digits),
|
|
1121
1173
|
"tp": position.tp,
|
|
@@ -1144,7 +1196,7 @@ class Trade(RiskManagement):
|
|
|
1144
1196
|
if result.retcode != Mt5.TRADE_RETCODE_DONE:
|
|
1145
1197
|
msg = trade_retcode_message(result.retcode)
|
|
1146
1198
|
if result.retcode != Mt5.TRADE_RETCODE_NO_CHANGES:
|
|
1147
|
-
|
|
1199
|
+
LOGGER.error(
|
|
1148
1200
|
f"Break-Even Order Request, Position: #{tiket}, RETCODE={result.retcode}: {msg}{addtionnal}"
|
|
1149
1201
|
)
|
|
1150
1202
|
tries = 0
|
|
@@ -1166,9 +1218,9 @@ class Trade(RiskManagement):
|
|
|
1166
1218
|
tries += 1
|
|
1167
1219
|
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
1168
1220
|
msg = trade_retcode_message(result.retcode)
|
|
1169
|
-
|
|
1221
|
+
LOGGER.info(f"Break-Even Order {msg}{addtionnal}")
|
|
1170
1222
|
info = f"Stop loss set to Break-even, Position: #{tiket}, Symbol: {self.symbol}, Price: @{price}"
|
|
1171
|
-
|
|
1223
|
+
LOGGER.info(info)
|
|
1172
1224
|
self.break_even_status.append(tiket)
|
|
1173
1225
|
|
|
1174
1226
|
def win_trade(self, position: TradePosition, th: Optional[int] = None) -> bool:
|
|
@@ -1256,7 +1308,7 @@ class Trade(RiskManagement):
|
|
|
1256
1308
|
elif result.retcode not in self._retcodes:
|
|
1257
1309
|
self._retcodes.append(result.retcode)
|
|
1258
1310
|
msg = trade_retcode_message(result.retcode)
|
|
1259
|
-
|
|
1311
|
+
LOGGER.error(
|
|
1260
1312
|
f"Closing Order Request, {type.capitalize()}: #{ticket}, RETCODE={result.retcode}: {msg}{addtionnal}"
|
|
1261
1313
|
)
|
|
1262
1314
|
else:
|
|
@@ -1276,13 +1328,59 @@ class Trade(RiskManagement):
|
|
|
1276
1328
|
tries += 1
|
|
1277
1329
|
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
1278
1330
|
msg = trade_retcode_message(result.retcode)
|
|
1279
|
-
|
|
1331
|
+
LOGGER.info(f"Closing Order {msg}{addtionnal}")
|
|
1280
1332
|
info = f"{type.capitalize()} #{ticket} closed, Symbol: {self.symbol}, Price: @{request.get('price', 0.0)}"
|
|
1281
|
-
|
|
1333
|
+
LOGGER.info(info)
|
|
1282
1334
|
return True
|
|
1283
1335
|
else:
|
|
1284
1336
|
return False
|
|
1285
1337
|
|
|
1338
|
+
def modify_order(
|
|
1339
|
+
self,
|
|
1340
|
+
ticket: int,
|
|
1341
|
+
price: Optional[float] = None,
|
|
1342
|
+
stoplimit: Optional[float] = None,
|
|
1343
|
+
sl: Optional[float] = None,
|
|
1344
|
+
tp: Optional[float] = None,
|
|
1345
|
+
):
|
|
1346
|
+
"""
|
|
1347
|
+
Modify an open order by it ticket
|
|
1348
|
+
|
|
1349
|
+
Args:
|
|
1350
|
+
ticket (int): Order ticket to modify (e.g TradeOrder.ticket)
|
|
1351
|
+
price (float): The price at which to modify the order
|
|
1352
|
+
stoplimit (float): A price a pending Limit order is set at when the price reaches the 'price' value (this condition is mandatory).
|
|
1353
|
+
The pending order is not passed to the trading system until that moment
|
|
1354
|
+
sl (float): The stop loss in points
|
|
1355
|
+
tp (float): The take profit in points
|
|
1356
|
+
"""
|
|
1357
|
+
orders = self.get_orders(ticket=ticket) or []
|
|
1358
|
+
if len(orders) == 0:
|
|
1359
|
+
LOGGER.error(
|
|
1360
|
+
f"Order #{ticket} not found, SYMBOL={self.symbol}, PRICE={price}"
|
|
1361
|
+
)
|
|
1362
|
+
return
|
|
1363
|
+
order = orders[0]
|
|
1364
|
+
request = {
|
|
1365
|
+
"action": Mt5.TRADE_ACTION_MODIFY,
|
|
1366
|
+
"order": ticket,
|
|
1367
|
+
"price": price or order.price_open,
|
|
1368
|
+
"sl": sl or order.sl,
|
|
1369
|
+
"tp": tp or order.tp,
|
|
1370
|
+
"stoplimit": stoplimit or order.price_stoplimit,
|
|
1371
|
+
}
|
|
1372
|
+
self.check_order(request)
|
|
1373
|
+
result = self.send_order(request)
|
|
1374
|
+
if result.retcode == Mt5.TRADE_RETCODE_DONE:
|
|
1375
|
+
LOGGER.info(
|
|
1376
|
+
f"Order #{ticket} modified, SYMBOL={self.symbol}, PRICE={price}, SL={sl}, TP={tp}, STOP_LIMIT={stoplimit}"
|
|
1377
|
+
)
|
|
1378
|
+
else:
|
|
1379
|
+
msg = trade_retcode_message(result.retcode)
|
|
1380
|
+
LOGGER.error(
|
|
1381
|
+
f"Unable to modify Order #{ticket}, RETCODE={result.retcode}: {msg}, SYMBOL={self.symbol}"
|
|
1382
|
+
)
|
|
1383
|
+
|
|
1286
1384
|
def close_order(
|
|
1287
1385
|
self, ticket: int, id: Optional[int] = None, comment: Optional[str] = None
|
|
1288
1386
|
):
|
|
@@ -1312,6 +1410,7 @@ class Trade(RiskManagement):
|
|
|
1312
1410
|
id: Optional[int] = None,
|
|
1313
1411
|
pct: Optional[float] = 1.0,
|
|
1314
1412
|
comment: Optional[str] = None,
|
|
1413
|
+
symbol: Optional[str] = None,
|
|
1315
1414
|
) -> bool:
|
|
1316
1415
|
"""
|
|
1317
1416
|
Close an open position by it ticket
|
|
@@ -1327,10 +1426,11 @@ class Trade(RiskManagement):
|
|
|
1327
1426
|
"""
|
|
1328
1427
|
# get all Actives positions
|
|
1329
1428
|
time.sleep(0.1)
|
|
1429
|
+
symbol = symbol or self.symbol
|
|
1330
1430
|
Id = id if id is not None else self.expert_id
|
|
1331
1431
|
positions = self.get_positions(ticket=ticket)
|
|
1332
|
-
buy_price = self.get_tick_info(
|
|
1333
|
-
sell_price = self.get_tick_info(
|
|
1432
|
+
buy_price = self.get_tick_info(symbol).ask
|
|
1433
|
+
sell_price = self.get_tick_info(symbol).bid
|
|
1334
1434
|
deviation = self.get_deviation()
|
|
1335
1435
|
if positions is not None and len(positions) == 1:
|
|
1336
1436
|
position = positions[0]
|
|
@@ -1338,7 +1438,7 @@ class Trade(RiskManagement):
|
|
|
1338
1438
|
buy = position.type == 0
|
|
1339
1439
|
request = {
|
|
1340
1440
|
"action": Mt5.TRADE_ACTION_DEAL,
|
|
1341
|
-
"symbol":
|
|
1441
|
+
"symbol": symbol,
|
|
1342
1442
|
"volume": (position.volume * pct),
|
|
1343
1443
|
"type": Mt5.ORDER_TYPE_SELL if buy else Mt5.ORDER_TYPE_BUY,
|
|
1344
1444
|
"position": ticket,
|
|
@@ -1379,15 +1479,15 @@ class Trade(RiskManagement):
|
|
|
1379
1479
|
tickets.remove(ticket)
|
|
1380
1480
|
time.sleep(1)
|
|
1381
1481
|
if tickets is not None and len(tickets) == 0:
|
|
1382
|
-
|
|
1482
|
+
LOGGER.info(
|
|
1383
1483
|
f"ALL {order_type.upper()} {tikets_type.upper()} closed, SYMBOL={self.symbol}."
|
|
1384
1484
|
)
|
|
1385
1485
|
else:
|
|
1386
|
-
|
|
1486
|
+
LOGGER.info(
|
|
1387
1487
|
f"{len(tickets)} {order_type.upper()} {tikets_type.upper()} not closed, SYMBOL={self.symbol}"
|
|
1388
1488
|
)
|
|
1389
1489
|
else:
|
|
1390
|
-
|
|
1490
|
+
LOGGER.info(
|
|
1391
1491
|
f"No {order_type.upper()} {tikets_type.upper()} to close, SYMBOL={self.symbol}."
|
|
1392
1492
|
)
|
|
1393
1493
|
|
|
@@ -1429,7 +1529,7 @@ class Trade(RiskManagement):
|
|
|
1429
1529
|
elif order_type == "sell_stop_limits":
|
|
1430
1530
|
orders = self.get_current_sell_stop_limits(id=id)
|
|
1431
1531
|
else:
|
|
1432
|
-
|
|
1532
|
+
LOGGER.error(f"Invalid order type: {order_type}")
|
|
1433
1533
|
return
|
|
1434
1534
|
self.bulk_close(
|
|
1435
1535
|
orders, "orders", self.close_order, order_type, id=id, comment=comment
|
|
@@ -1461,7 +1561,7 @@ class Trade(RiskManagement):
|
|
|
1461
1561
|
elif position_type == "losing":
|
|
1462
1562
|
positions = self.get_current_losings(id=id)
|
|
1463
1563
|
else:
|
|
1464
|
-
|
|
1564
|
+
LOGGER.error(f"Invalid position type: {position_type}")
|
|
1465
1565
|
return
|
|
1466
1566
|
self.bulk_close(
|
|
1467
1567
|
positions,
|
|
@@ -1642,8 +1742,12 @@ def create_trade_instance(
|
|
|
1642
1742
|
Note:
|
|
1643
1743
|
`daily_risk` and `max_risk` can be used to manage the risk of each symbol
|
|
1644
1744
|
based on the importance of the symbol in the portfolio or strategy.
|
|
1745
|
+
See bbstrader.metatrader.trade.Trade for more details.
|
|
1645
1746
|
"""
|
|
1646
|
-
|
|
1747
|
+
if not isinstance(params.get("logger"), (Logger, type(log))):
|
|
1748
|
+
loggr = log
|
|
1749
|
+
else:
|
|
1750
|
+
loggr = params.get("logger")
|
|
1647
1751
|
ids = params.get("expert_id", None)
|
|
1648
1752
|
trade_instances = {}
|
|
1649
1753
|
if not symbols:
|
|
@@ -1710,15 +1814,13 @@ def create_trade_instance(
|
|
|
1710
1814
|
)
|
|
1711
1815
|
trade_instances[symbol] = Trade(**params)
|
|
1712
1816
|
except Exception as e:
|
|
1713
|
-
|
|
1817
|
+
loggr.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
|
|
1714
1818
|
|
|
1715
1819
|
if len(trade_instances) != len(symbols):
|
|
1716
1820
|
for symbol in symbols:
|
|
1717
1821
|
if symbol not in trade_instances:
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
f"Failed to create Trade instance for SYMBOL={symbol}"
|
|
1723
|
-
)
|
|
1822
|
+
loggr.error(f"Failed to create Trade instance for SYMBOL={symbol}")
|
|
1823
|
+
loggr.info(
|
|
1824
|
+
f"Trade instances created successfully for {len(trade_instances)} symbols."
|
|
1825
|
+
)
|
|
1724
1826
|
return trade_instances
|
bbstrader/metatrader/utils.py
CHANGED
|
@@ -2,8 +2,11 @@ from datetime import datetime
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import NamedTuple, Optional
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import MetaTrader5 as MT5
|
|
5
|
+
try:
|
|
6
|
+
import MetaTrader5 as MT5
|
|
7
|
+
except ImportError:
|
|
8
|
+
import bbstrader.compat # noqa: F401
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
__all__ = [
|
|
9
12
|
"TIMEFRAMES",
|