bbstrader 0.2.91__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.

@@ -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 = "1:00",
96
- finishing_time: str = "23:00",
97
- ending_time: str = "23:30",
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
- def _get_logger(self, logger: str | Logger, consol_log: bool) -> Logger:
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
- if isinstance(logger, str):
193
- return config_logger(logger, consol_log)
194
- return logger
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
- self.logger.error(f"During initialization: {e}")
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
- self.logger.error(f"Selecting symbol '{self.symbol}': {e}")
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
- self.logger.error(f"Preparing symbol '{self.symbol}': {e}")
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
- self.logger.info(f"Session statistics saved to {filepath}")
415
+ LOGGER.info(f"Session statistics saved to {filepath}")
392
416
 
393
417
  Buys = Literal["BMKT", "BLMT", "BSTP", "BSTPLMT"]
394
418
 
@@ -398,8 +422,13 @@ class Trade(RiskManagement):
398
422
  price: Optional[float] = None,
399
423
  stoplimit: Optional[float] = None,
400
424
  mm: bool = True,
425
+ trail: bool = True,
401
426
  id: Optional[int] = None,
402
427
  comment: Optional[str] = None,
428
+ symbol: Optional[str] = None,
429
+ volume: Optional[float] = None,
430
+ sl: Optional[float] = None,
431
+ tp: Optional[float] = None,
403
432
  ):
404
433
  """
405
434
  Open a Buy positin
@@ -412,6 +441,7 @@ class Trade(RiskManagement):
412
441
  The pending order is not passed to the trading system until that moment
413
442
  id (int): The strategy id or expert Id
414
443
  mm (bool): Weither to put stop loss and tp or not
444
+ trail (bool): Weither to trail the stop loss or not
415
445
  comment (str): The comment for the opening position
416
446
  """
417
447
  Id = id if id is not None else self.expert_id
@@ -424,19 +454,19 @@ class Trade(RiskManagement):
424
454
  else:
425
455
  _price = self.get_tick_info(self.symbol).ask
426
456
 
427
- lot = self.get_lot()
457
+ lot = volume or self.get_lot()
428
458
  stop_loss = self.get_stop_loss()
429
459
  take_profit = self.get_take_profit()
430
460
  deviation = self.get_deviation()
431
461
  request = {
432
462
  "action": Mt5.TRADE_ACTION_DEAL,
433
- "symbol": self.symbol,
463
+ "symbol": symbol or self.symbol,
434
464
  "volume": float(lot),
435
465
  "type": Mt5.ORDER_TYPE_BUY,
436
466
  "price": _price,
437
467
  "deviation": deviation,
438
468
  "magic": Id,
439
- "comment": f"@{self.expert_name}" if comment is None else comment,
469
+ "comment": comment or f"@{self.expert_name}",
440
470
  "type_time": Mt5.ORDER_TIME_GTC,
441
471
  "type_filling": Mt5.ORDER_FILLING_FOK,
442
472
  }
@@ -454,11 +484,12 @@ class Trade(RiskManagement):
454
484
  request["stoplimit"] = stoplimit
455
485
  mm_price = stoplimit
456
486
  if mm:
457
- request["sl"] = mm_price - stop_loss * point
458
- request["tp"] = mm_price + take_profit * point
459
- self.break_even(mm=mm, id=Id)
487
+ request["sl"] = sl or mm_price - stop_loss * point
488
+ request["tp"] = tp or mm_price + take_profit * point
489
+ self.break_even(mm=mm, id=Id, trail=trail)
460
490
  if self.check(comment):
461
- self.request_result(_price, request, action)
491
+ return self.request_result(_price, request, action)
492
+ return False
462
493
 
463
494
  def _order_type(self):
464
495
  type = {
@@ -481,8 +512,13 @@ class Trade(RiskManagement):
481
512
  price: Optional[float] = None,
482
513
  stoplimit: Optional[float] = None,
483
514
  mm: bool = True,
515
+ trail: bool = True,
484
516
  id: Optional[int] = None,
485
517
  comment: Optional[str] = None,
518
+ symbol: Optional[str] = None,
519
+ volume: Optional[float] = None,
520
+ sl: Optional[float] = None,
521
+ tp: Optional[float] = None,
486
522
  ):
487
523
  """
488
524
  Open a sell positin
@@ -495,7 +531,12 @@ class Trade(RiskManagement):
495
531
  The pending order is not passed to the trading system until that moment
496
532
  id (int): The strategy id or expert Id
497
533
  mm (bool): Weither to put stop loss and tp or not
534
+ trail (bool): Weither to trail the stop loss or not
498
535
  comment (str): The comment for the closing position
536
+ symbol (str): The symbol to trade
537
+ volume (float): The volume (lot) to trade
538
+ sl (float): The stop loss in points
539
+ tp (float): The take profit in points
499
540
  """
500
541
  Id = id if id is not None else self.expert_id
501
542
  point = self.get_symbol_info(self.symbol).point
@@ -507,19 +548,19 @@ class Trade(RiskManagement):
507
548
  else:
508
549
  _price = self.get_tick_info(self.symbol).bid
509
550
 
510
- lot = self.get_lot()
551
+ lot = volume or self.get_lot()
511
552
  stop_loss = self.get_stop_loss()
512
553
  take_profit = self.get_take_profit()
513
554
  deviation = self.get_deviation()
514
555
  request = {
515
556
  "action": Mt5.TRADE_ACTION_DEAL,
516
- "symbol": self.symbol,
557
+ "symbol": symbol or self.symbol,
517
558
  "volume": float(lot),
518
559
  "type": Mt5.ORDER_TYPE_SELL,
519
560
  "price": _price,
520
561
  "deviation": deviation,
521
562
  "magic": Id,
522
- "comment": f"@{self.expert_name}" if comment is None else comment,
563
+ "comment": comment or f"@{self.expert_name}",
523
564
  "type_time": Mt5.ORDER_TIME_GTC,
524
565
  "type_filling": Mt5.ORDER_FILLING_FOK,
525
566
  }
@@ -537,11 +578,12 @@ class Trade(RiskManagement):
537
578
  request["stoplimit"] = stoplimit
538
579
  mm_price = stoplimit
539
580
  if mm:
540
- request["sl"] = mm_price + stop_loss * point
541
- request["tp"] = mm_price - take_profit * point
542
- self.break_even(mm=mm, id=Id)
581
+ request["sl"] = sl or mm_price + stop_loss * point
582
+ request["tp"] = tp or mm_price - take_profit * point
583
+ self.break_even(mm=mm, id=Id, trail=trail)
543
584
  if self.check(comment):
544
- self.request_result(_price, request, action)
585
+ return self.request_result(_price, request, action)
586
+ return False
545
587
 
546
588
  def check(self, comment):
547
589
  """
@@ -555,10 +597,10 @@ class Trade(RiskManagement):
555
597
  if self.days_end():
556
598
  return False
557
599
  elif not self.trading_time():
558
- self.logger.info(f"Not Trading time, SYMBOL={self.symbol}")
600
+ LOGGER.info(f"Not Trading time, SYMBOL={self.symbol}")
559
601
  return False
560
602
  elif not self.is_risk_ok():
561
- self.logger.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
603
+ LOGGER.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
562
604
  self._check(comment)
563
605
  return False
564
606
  elif self.profit_target():
@@ -571,7 +613,7 @@ class Trade(RiskManagement):
571
613
  or self.get_current_positions() is None
572
614
  ):
573
615
  self.close_positions(position_type="all")
574
- self.logger.info(txt)
616
+ LOGGER.info(txt)
575
617
  time.sleep(5)
576
618
  self.statistics(save=True)
577
619
 
@@ -613,7 +655,7 @@ class Trade(RiskManagement):
613
655
  elif result.retcode not in self._retcodes:
614
656
  self._retcodes.append(result.retcode)
615
657
  msg = trade_retcode_message(result.retcode)
616
- self.logger.error(
658
+ LOGGER.error(
617
659
  f"Trade Order Request, RETCODE={result.retcode}: {msg}{addtionnal}"
618
660
  )
619
661
  elif result.retcode in [
@@ -637,7 +679,7 @@ class Trade(RiskManagement):
637
679
  # Print the result
638
680
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
639
681
  msg = trade_retcode_message(result.retcode)
640
- self.logger.info(f"Trade Order {msg}{addtionnal}")
682
+ LOGGER.info(f"Trade Order {msg}{addtionnal}")
641
683
  if type != "BMKT" or type != "SMKT":
642
684
  self.opened_orders.append(result.order)
643
685
  long_msg = (
@@ -645,7 +687,7 @@ class Trade(RiskManagement):
645
687
  f"Lot(s): {result.volume}, Sl: {self.get_stop_loss()}, "
646
688
  f"Tp: {self.get_take_profit()}"
647
689
  )
648
- self.logger.info(long_msg)
690
+ LOGGER.info(long_msg)
649
691
  time.sleep(0.1)
650
692
  if type == "BMKT" or type == "SMKT":
651
693
  self.opened_positions.append(result.order)
@@ -664,17 +706,19 @@ class Trade(RiskManagement):
664
706
  f"2. {order_type} Position Opened, Symbol: {self.symbol}, Price: @{round(position.price_open, 5)}, "
665
707
  f"Sl: @{position.sl} Tp: @{position.tp}"
666
708
  )
667
- self.logger.info(order_info)
709
+ LOGGER.info(order_info)
668
710
  pos_info = (
669
711
  f"3. [OPEN POSITIONS ON {self.symbol} = {len(positions)}, ACCOUNT OPEN PnL = {profit} "
670
712
  f"{self.get_account_info().currency}]\n"
671
713
  )
672
- self.logger.info(pos_info)
714
+ LOGGER.info(pos_info)
715
+ return True
673
716
  else:
674
717
  msg = trade_retcode_message(result.retcode)
675
- self.logger.error(
718
+ LOGGER.error(
676
719
  f"Unable to Open Position, RETCODE={result.retcode}: {msg}{addtionnal}"
677
720
  )
721
+ return False
678
722
 
679
723
  def open_position(
680
724
  self,
@@ -683,7 +727,12 @@ class Trade(RiskManagement):
683
727
  stoplimit: Optional[float] = None,
684
728
  id: Optional[int] = None,
685
729
  mm: bool = True,
730
+ trail: bool = True,
686
731
  comment: Optional[str] = None,
732
+ symbol: Optional[str] = None,
733
+ volume: Optional[float] = None,
734
+ sl: Optional[float] = None,
735
+ tp: Optional[float] = None,
687
736
  ):
688
737
  """
689
738
  Open a buy or sell position.
@@ -696,27 +745,42 @@ class Trade(RiskManagement):
696
745
  The pending order is not passed to the trading system until that moment
697
746
  id (int): The strategy id or expert Id
698
747
  mm (bool): Weither to put stop loss and tp or not
748
+ trail (bool): Weither to trail the stop loss or not
699
749
  comment (str): The comment for the closing position
750
+ symbol (str): The symbol to trade
751
+ volume (float): The volume (lot) to trade
752
+ sl (float): The stop loss in points
753
+ tp (float): The take profit in points
700
754
  """
701
755
  BUYS = ["BMKT", "BLMT", "BSTP", "BSTPLMT"]
702
756
  SELLS = ["SMKT", "SLMT", "SSTP", "SSTPLMT"]
703
757
  if action in BUYS:
704
- self.open_buy_position(
758
+ return self.open_buy_position(
705
759
  action=action,
706
760
  price=price,
707
761
  stoplimit=stoplimit,
708
762
  id=id,
709
763
  mm=mm,
764
+ trail=trail,
710
765
  comment=comment,
766
+ symbol=symbol,
767
+ volume=volume,
768
+ sl=sl,
769
+ tp=tp,
711
770
  )
712
771
  elif action in SELLS:
713
- self.open_sell_position(
772
+ return self.open_sell_position(
714
773
  action=action,
715
774
  price=price,
716
775
  stoplimit=stoplimit,
717
776
  id=id,
718
777
  mm=mm,
778
+ trail=trail,
719
779
  comment=comment,
780
+ symbol=symbol,
781
+ volume=volume,
782
+ sl=sl,
783
+ tp=tp,
720
784
  )
721
785
  else:
722
786
  raise ValueError(
@@ -939,7 +1003,7 @@ class Trade(RiskManagement):
939
1003
  mm=True,
940
1004
  id: Optional[int] = None,
941
1005
  trail: Optional[bool] = True,
942
- stop_trail: Optional[int] = None,
1006
+ stop_trail: int | str = None,
943
1007
  trail_after_points: int | str = None,
944
1008
  be_plus_points: Optional[int] = None,
945
1009
  ):
@@ -1096,7 +1160,6 @@ class Trade(RiskManagement):
1096
1160
  # Set the stop loss to break even
1097
1161
  request = {
1098
1162
  "action": Mt5.TRADE_ACTION_SLTP,
1099
- "type": Mt5.ORDER_TYPE_SELL_STOP,
1100
1163
  "position": position.ticket,
1101
1164
  "sl": round(_price, digits),
1102
1165
  "tp": position.tp,
@@ -1115,7 +1178,6 @@ class Trade(RiskManagement):
1115
1178
  # Set the stop loss to break even
1116
1179
  request = {
1117
1180
  "action": Mt5.TRADE_ACTION_SLTP,
1118
- "type": Mt5.ORDER_TYPE_BUY_STOP,
1119
1181
  "position": position.ticket,
1120
1182
  "sl": round(_price, digits),
1121
1183
  "tp": position.tp,
@@ -1144,7 +1206,7 @@ class Trade(RiskManagement):
1144
1206
  if result.retcode != Mt5.TRADE_RETCODE_DONE:
1145
1207
  msg = trade_retcode_message(result.retcode)
1146
1208
  if result.retcode != Mt5.TRADE_RETCODE_NO_CHANGES:
1147
- self.logger.error(
1209
+ LOGGER.error(
1148
1210
  f"Break-Even Order Request, Position: #{tiket}, RETCODE={result.retcode}: {msg}{addtionnal}"
1149
1211
  )
1150
1212
  tries = 0
@@ -1166,9 +1228,9 @@ class Trade(RiskManagement):
1166
1228
  tries += 1
1167
1229
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
1168
1230
  msg = trade_retcode_message(result.retcode)
1169
- self.logger.info(f"Break-Even Order {msg}{addtionnal}")
1231
+ LOGGER.info(f"Break-Even Order {msg}{addtionnal}")
1170
1232
  info = f"Stop loss set to Break-even, Position: #{tiket}, Symbol: {self.symbol}, Price: @{price}"
1171
- self.logger.info(info)
1233
+ LOGGER.info(info)
1172
1234
  self.break_even_status.append(tiket)
1173
1235
 
1174
1236
  def win_trade(self, position: TradePosition, th: Optional[int] = None) -> bool:
@@ -1190,7 +1252,7 @@ class Trade(RiskManagement):
1190
1252
  try:
1191
1253
  min_be = round((fees / risk)) + 2
1192
1254
  except ZeroDivisionError:
1193
- min_be = self.symbol_info(self.symbol).spread
1255
+ min_be = self.symbol_info.spread
1194
1256
  be = self.get_break_even()
1195
1257
  if th is not None:
1196
1258
  win_be = th
@@ -1256,7 +1318,7 @@ class Trade(RiskManagement):
1256
1318
  elif result.retcode not in self._retcodes:
1257
1319
  self._retcodes.append(result.retcode)
1258
1320
  msg = trade_retcode_message(result.retcode)
1259
- self.logger.error(
1321
+ LOGGER.error(
1260
1322
  f"Closing Order Request, {type.capitalize()}: #{ticket}, RETCODE={result.retcode}: {msg}{addtionnal}"
1261
1323
  )
1262
1324
  else:
@@ -1276,13 +1338,59 @@ class Trade(RiskManagement):
1276
1338
  tries += 1
1277
1339
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
1278
1340
  msg = trade_retcode_message(result.retcode)
1279
- self.logger.info(f"Closing Order {msg}{addtionnal}")
1341
+ LOGGER.info(f"Closing Order {msg}{addtionnal}")
1280
1342
  info = f"{type.capitalize()} #{ticket} closed, Symbol: {self.symbol}, Price: @{request.get('price', 0.0)}"
1281
- self.logger.info(info)
1343
+ LOGGER.info(info)
1282
1344
  return True
1283
1345
  else:
1284
1346
  return False
1285
1347
 
1348
+ def modify_order(
1349
+ self,
1350
+ ticket: int,
1351
+ price: Optional[float] = None,
1352
+ stoplimit: Optional[float] = None,
1353
+ sl: Optional[float] = None,
1354
+ tp: Optional[float] = None,
1355
+ ):
1356
+ """
1357
+ Modify an open order by it ticket
1358
+
1359
+ Args:
1360
+ ticket (int): Order ticket to modify (e.g TradeOrder.ticket)
1361
+ price (float): The price at which to modify the order
1362
+ stoplimit (float): A price a pending Limit order is set at when the price reaches the 'price' value (this condition is mandatory).
1363
+ The pending order is not passed to the trading system until that moment
1364
+ sl (float): The stop loss in points
1365
+ tp (float): The take profit in points
1366
+ """
1367
+ orders = self.get_orders(ticket=ticket) or []
1368
+ if len(orders) == 0:
1369
+ LOGGER.error(
1370
+ f"Order #{ticket} not found, SYMBOL={self.symbol}, PRICE={price}"
1371
+ )
1372
+ return
1373
+ order = orders[0]
1374
+ request = {
1375
+ "action": Mt5.TRADE_ACTION_MODIFY,
1376
+ "order": ticket,
1377
+ "price": price or order.price_open,
1378
+ "sl": sl or order.sl,
1379
+ "tp": tp or order.tp,
1380
+ "stoplimit": stoplimit or order.price_stoplimit,
1381
+ }
1382
+ self.check_order(request)
1383
+ result = self.send_order(request)
1384
+ if result.retcode == Mt5.TRADE_RETCODE_DONE:
1385
+ LOGGER.info(
1386
+ f"Order #{ticket} modified, SYMBOL={self.symbol}, PRICE={price}, SL={sl}, TP={tp}, STOP_LIMIT={stoplimit}"
1387
+ )
1388
+ else:
1389
+ msg = trade_retcode_message(result.retcode)
1390
+ LOGGER.error(
1391
+ f"Unable to modify Order #{ticket}, RETCODE={result.retcode}: {msg}, SYMBOL={self.symbol}"
1392
+ )
1393
+
1286
1394
  def close_order(
1287
1395
  self, ticket: int, id: Optional[int] = None, comment: Optional[str] = None
1288
1396
  ):
@@ -1312,6 +1420,7 @@ class Trade(RiskManagement):
1312
1420
  id: Optional[int] = None,
1313
1421
  pct: Optional[float] = 1.0,
1314
1422
  comment: Optional[str] = None,
1423
+ symbol: Optional[str] = None,
1315
1424
  ) -> bool:
1316
1425
  """
1317
1426
  Close an open position by it ticket
@@ -1327,10 +1436,11 @@ class Trade(RiskManagement):
1327
1436
  """
1328
1437
  # get all Actives positions
1329
1438
  time.sleep(0.1)
1439
+ symbol = symbol or self.symbol
1330
1440
  Id = id if id is not None else self.expert_id
1331
1441
  positions = self.get_positions(ticket=ticket)
1332
- buy_price = self.get_tick_info(self.symbol).ask
1333
- sell_price = self.get_tick_info(self.symbol).bid
1442
+ buy_price = self.get_tick_info(symbol).ask
1443
+ sell_price = self.get_tick_info(symbol).bid
1334
1444
  deviation = self.get_deviation()
1335
1445
  if positions is not None and len(positions) == 1:
1336
1446
  position = positions[0]
@@ -1338,7 +1448,7 @@ class Trade(RiskManagement):
1338
1448
  buy = position.type == 0
1339
1449
  request = {
1340
1450
  "action": Mt5.TRADE_ACTION_DEAL,
1341
- "symbol": self.symbol,
1451
+ "symbol": symbol,
1342
1452
  "volume": (position.volume * pct),
1343
1453
  "type": Mt5.ORDER_TYPE_SELL if buy else Mt5.ORDER_TYPE_BUY,
1344
1454
  "position": ticket,
@@ -1379,15 +1489,15 @@ class Trade(RiskManagement):
1379
1489
  tickets.remove(ticket)
1380
1490
  time.sleep(1)
1381
1491
  if tickets is not None and len(tickets) == 0:
1382
- self.logger.info(
1492
+ LOGGER.info(
1383
1493
  f"ALL {order_type.upper()} {tikets_type.upper()} closed, SYMBOL={self.symbol}."
1384
1494
  )
1385
1495
  else:
1386
- self.logger.info(
1496
+ LOGGER.info(
1387
1497
  f"{len(tickets)} {order_type.upper()} {tikets_type.upper()} not closed, SYMBOL={self.symbol}"
1388
1498
  )
1389
1499
  else:
1390
- self.logger.info(
1500
+ LOGGER.info(
1391
1501
  f"No {order_type.upper()} {tikets_type.upper()} to close, SYMBOL={self.symbol}."
1392
1502
  )
1393
1503
 
@@ -1429,7 +1539,7 @@ class Trade(RiskManagement):
1429
1539
  elif order_type == "sell_stop_limits":
1430
1540
  orders = self.get_current_sell_stop_limits(id=id)
1431
1541
  else:
1432
- self.logger.error(f"Invalid order type: {order_type}")
1542
+ LOGGER.error(f"Invalid order type: {order_type}")
1433
1543
  return
1434
1544
  self.bulk_close(
1435
1545
  orders, "orders", self.close_order, order_type, id=id, comment=comment
@@ -1461,7 +1571,7 @@ class Trade(RiskManagement):
1461
1571
  elif position_type == "losing":
1462
1572
  positions = self.get_current_losings(id=id)
1463
1573
  else:
1464
- self.logger.error(f"Invalid position type: {position_type}")
1574
+ LOGGER.error(f"Invalid position type: {position_type}")
1465
1575
  return
1466
1576
  self.bulk_close(
1467
1577
  positions,
@@ -1642,8 +1752,12 @@ def create_trade_instance(
1642
1752
  Note:
1643
1753
  `daily_risk` and `max_risk` can be used to manage the risk of each symbol
1644
1754
  based on the importance of the symbol in the portfolio or strategy.
1755
+ See bbstrader.metatrader.trade.Trade for more details.
1645
1756
  """
1646
- logger = params.get("logger", None)
1757
+ if not isinstance(params.get("logger"), (Logger, type(log))):
1758
+ loggr = log
1759
+ else:
1760
+ loggr = params.get("logger")
1647
1761
  ids = params.get("expert_id", None)
1648
1762
  trade_instances = {}
1649
1763
  if not symbols:
@@ -1710,15 +1824,13 @@ def create_trade_instance(
1710
1824
  )
1711
1825
  trade_instances[symbol] = Trade(**params)
1712
1826
  except Exception as e:
1713
- logger.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
1827
+ loggr.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
1714
1828
 
1715
1829
  if len(trade_instances) != len(symbols):
1716
1830
  for symbol in symbols:
1717
1831
  if symbol not in trade_instances:
1718
- if logger is not None and isinstance(logger, Logger):
1719
- logger.error(f"Failed to create Trade instance for SYMBOL={symbol}")
1720
- else:
1721
- raise ValueError(
1722
- f"Failed to create Trade instance for SYMBOL={symbol}"
1723
- )
1832
+ loggr.error(f"Failed to create Trade instance for SYMBOL={symbol}")
1833
+ loggr.info(
1834
+ f"Trade instances created successfully for {len(trade_instances)} symbols."
1835
+ )
1724
1836
  return trade_instances
@@ -2,8 +2,11 @@ from datetime import datetime
2
2
  from enum import Enum
3
3
  from typing import NamedTuple, Optional
4
4
 
5
- from bbstrader import compat # noqa: F401
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",