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.

@@ -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
 
@@ -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}" if comment is None else comment,
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}" if comment is None else comment,
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
- self.logger.info(f"Not Trading time, SYMBOL={self.symbol}")
596
+ LOGGER.info(f"Not Trading time, SYMBOL={self.symbol}")
559
597
  return False
560
598
  elif not self.is_risk_ok():
561
- self.logger.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
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
- self.logger.info(txt)
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
- self.logger.error(
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
- self.logger.info(f"Trade Order {msg}{addtionnal}")
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
- self.logger.info(long_msg)
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
- self.logger.info(order_info)
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
- self.logger.info(pos_info)
710
+ LOGGER.info(pos_info)
711
+ return True
673
712
  else:
674
713
  msg = trade_retcode_message(result.retcode)
675
- self.logger.error(
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: Optional[int] = None,
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
- self.logger.error(
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
- self.logger.info(f"Break-Even Order {msg}{addtionnal}")
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
- self.logger.info(info)
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
- self.logger.error(
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
- self.logger.info(f"Closing Order {msg}{addtionnal}")
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
- self.logger.info(info)
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(self.symbol).ask
1333
- sell_price = self.get_tick_info(self.symbol).bid
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": self.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
- self.logger.info(
1482
+ LOGGER.info(
1383
1483
  f"ALL {order_type.upper()} {tikets_type.upper()} closed, SYMBOL={self.symbol}."
1384
1484
  )
1385
1485
  else:
1386
- self.logger.info(
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
- self.logger.info(
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
- self.logger.error(f"Invalid order type: {order_type}")
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
- self.logger.error(f"Invalid position type: {position_type}")
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
- logger = params.get("logger", None)
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
- logger.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
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
- 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
- )
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
@@ -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",
bbstrader/models/ml.py CHANGED
@@ -243,6 +243,7 @@ class LightGBModel(object):
243
243
  end=end,
244
244
  progress=False,
245
245
  multi_level_index=False,
246
+ auto_adjust=True,
246
247
  )
247
248
  prices["symbol"] = ticker
248
249
  data.append(prices)