bbstrader 0.2.0__py3-none-any.whl → 0.2.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.

@@ -1,36 +1,22 @@
1
1
  import os
2
- import csv
3
2
  import time
4
- import numpy as np
5
- import pandas as pd
6
3
  from datetime import datetime
7
- import MetaTrader5 as Mt5
8
4
  from logging import Logger
5
+ from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
6
+
7
+ import MetaTrader5 as Mt5
8
+ import pandas as pd
9
9
  from tabulate import tabulate
10
- from typing import (
11
- List,
12
- Tuple,
13
- Dict,
14
- Any,
15
- Optional,
16
- Literal,
17
- Callable
18
- )
10
+
19
11
  from bbstrader.btengine.performance import create_sharpe_ratio
12
+ from bbstrader.config import BBSTRADER_DIR, config_logger
13
+ from bbstrader.metatrader.account import INIT_MSG, check_mt5_connection
20
14
  from bbstrader.metatrader.risk import RiskManagement
21
- from bbstrader.metatrader.account import(
22
- check_mt5_connection,
23
- INIT_MSG
24
- )
25
15
  from bbstrader.metatrader.utils import (
26
- TimeFrame,
27
- TradePosition,
28
- TickInfo,
29
- raise_mt5_error,
30
- trade_retcode_message
16
+ TradePosition,
17
+ raise_mt5_error,
18
+ trade_retcode_message,
31
19
  )
32
- from bbstrader.config import config_logger, BBSTRADER_DIR
33
-
34
20
 
35
21
  __all__ = [
36
22
  'Trade',
@@ -168,13 +154,14 @@ class Trade(RiskManagement):
168
154
  ":")
169
155
  self.ending_time_hour, self.ending_time_minutes = self.end.split(":")
170
156
 
171
- self.buy_positions = []
172
- self.sell_positions = []
173
- self.opened_positions = []
174
- self.opened_orders = []
175
- self.break_even_status = []
176
- self.break_even_points = {}
157
+ self.buy_positions = []
158
+ self.sell_positions = []
159
+ self.opened_positions = []
160
+ self.opened_orders = []
161
+ self.break_even_status = []
162
+ self.break_even_points = {}
177
163
  self.trail_after_points = []
164
+ self._retcodes = []
178
165
 
179
166
  self.initialize(**kwargs)
180
167
  self.select_symbol(**kwargs)
@@ -188,14 +175,19 @@ class Trade(RiskManagement):
188
175
  print(
189
176
  f">>> Everything is OK, @{self.expert_name} is Running ...>>>\n")
190
177
 
191
- def _get_logger(self, logger: str | Logger, consol_log: bool) -> Logger:
178
+ @property
179
+ def retcodes(self) -> List[int]:
180
+ """Return all the retcodes"""
181
+ return self._retcodes
182
+
183
+ def _get_logger(self, logger: str | Logger, consol_log: bool) -> Logger:
192
184
  """Get the logger object"""
193
185
  if isinstance(logger, str):
194
186
  log_path = BBSTRADER_DIR / 'logs'
195
187
  log_path.mkdir(exist_ok=True)
196
188
  return config_logger(f'{log_path}/{logger}', consol_log)
197
189
  return logger
198
-
190
+
199
191
  def initialize(self, **kwargs):
200
192
  """
201
193
  Initializes the MetaTrader 5 (MT5) terminal for trading operations.
@@ -266,12 +258,16 @@ class Trade(RiskManagement):
266
258
  ["Expert | Strategy ID", self.expert_id],
267
259
  ["Trading Symbol", self.symbol],
268
260
  ["Trading Time Frame", self.tf],
269
- ["Start Trading Time", f"{self.start_time_hour}:{self.start_time_minutes}"],
270
- ["Finishing Trading Time", f"{self.finishing_time_hour}:{self.finishing_time_minutes}"],
271
- ["Closing Position After", f"{self.ending_time_hour}:{self.ending_time_minutes}"],
261
+ ["Start Trading Time",
262
+ f"{self.start_time_hour}:{self.start_time_minutes}"],
263
+ ["Finishing Trading Time",
264
+ f"{self.finishing_time_hour}:{self.finishing_time_minutes}"],
265
+ ["Closing Position After",
266
+ f"{self.ending_time_hour}:{self.ending_time_minutes}"],
272
267
  ]
273
268
  # Custom table format
274
- summary_table = tabulate(summary_data, headers=["Summary", "Values"], tablefmt="outline")
269
+ summary_table = tabulate(summary_data, headers=[
270
+ "Summary", "Values"], tablefmt="outline")
275
271
 
276
272
  # Print the table
277
273
  print("\n[======= Trade Account Summary =======]")
@@ -299,10 +295,12 @@ class Trade(RiskManagement):
299
295
  ["Account Margin", f"{round(account_info.margin, 2)} {currency}"],
300
296
  ["Account Free Margin", f"{account_info.margin_free} {currency}"],
301
297
  ["Maximum Drawdown", f"{self.max_risk}%"],
302
- ["Risk Allowed", f"{round((self.max_risk - self.risk_level()), 2)}%"],
298
+ ["Risk Allowed",
299
+ f"{round((self.max_risk - self.risk_level()), 2)}%"],
303
300
  ["Volume", f"{self.volume()} {marging_currency}"],
304
301
  ["Risk Per trade", f"{-self.get_currency_risk()} {currency}"],
305
- ["Profit Expected Per trade", f"{self.expected_profit()} {currency}"],
302
+ ["Profit Expected Per trade",
303
+ f"{self.expected_profit()} {currency}"],
306
304
  ["Lot Size", f"{self.get_lot()} Lots"],
307
305
  ["Stop Loss", f"{self.get_stop_loss()} Points"],
308
306
  ["Loss Value Per Tick", f"{round(loss, 5)} {currency}"],
@@ -316,7 +314,8 @@ class Trade(RiskManagement):
316
314
  ]
317
315
  # Custom table format
318
316
  print("\n[======= Account Risk Management Overview =======]")
319
- table = tabulate(account_data, headers=["Risk Metrics", "Values"], tablefmt="outline")
317
+ table = tabulate(account_data, headers=[
318
+ "Risk Metrics", "Values"], tablefmt="outline")
320
319
 
321
320
  # Print the table
322
321
  print(table)
@@ -338,7 +337,6 @@ class Trade(RiskManagement):
338
337
  currency = self.get_account_info().currency
339
338
  net_profit = round((profit + total_fees), 2)
340
339
  trade_risk = round(self.get_currency_risk() * -1, 2)
341
- expected_profit = round((trade_risk * self.rr * -1), 2)
342
340
 
343
341
  # Formatting the statistics output
344
342
  session_data = [
@@ -350,7 +348,8 @@ class Trade(RiskManagement):
350
348
  ["Average Fees", f"{average_fee} {currency}"],
351
349
  ["Net Profit", f"{net_profit} {currency}"],
352
350
  ["Risk per Trade", f"{trade_risk} {currency}"],
353
- ["Expected Profit per Trade", f"{self.expected_profit()} {currency}"],
351
+ ["Expected Profit per Trade",
352
+ f"{self.expected_profit()} {currency}"],
354
353
  ["Risk Reward Ratio", self.rr],
355
354
  ["Win Rate", f"{win_rate}%"],
356
355
  ["Sharpe Ratio", self.sharpe()],
@@ -384,6 +383,7 @@ class Trade(RiskManagement):
384
383
  self.logger.info(f"Session statistics saved to {filepath}")
385
384
 
386
385
  Buys = Literal['BMKT', 'BLMT', 'BSTP', 'BSTPLMT']
386
+
387
387
  def open_buy_position(
388
388
  self,
389
389
  action: Buys = 'BMKT',
@@ -416,7 +416,6 @@ class Trade(RiskManagement):
416
416
  "You need to set a price for pending orders")
417
417
  else:
418
418
  _price = self.get_tick_info(self.symbol).ask
419
- digits = self.get_symbol_info(self.symbol).digits
420
419
 
421
420
  lot = self.get_lot()
422
421
  stop_loss = self.get_stop_loss()
@@ -468,6 +467,7 @@ class Trade(RiskManagement):
468
467
  return type
469
468
 
470
469
  Sells = Literal['SMKT', 'SLMT', 'SSTP', 'SSTPLMT']
470
+
471
471
  def open_sell_position(
472
472
  self,
473
473
  action: Sells = 'SMKT',
@@ -500,7 +500,6 @@ class Trade(RiskManagement):
500
500
  "You need to set a price for pending orders")
501
501
  else:
502
502
  _price = self.get_tick_info(self.symbol).bid
503
- digits = self.get_symbol_info(self.symbol).digits
504
503
 
505
504
  lot = self.get_lot()
506
505
  stop_loss = self.get_stop_loss()
@@ -553,7 +552,8 @@ class Trade(RiskManagement):
553
552
  self.logger.info(f"Not Trading time, SYMBOL={self.symbol}")
554
553
  return False
555
554
  elif not self.is_risk_ok():
556
- self.logger.error(f"Risk not allowed, SYMBOL={self.symbol}")
555
+ self.logger.error(
556
+ f"Account Risk not allowed, SYMBOL={self.symbol}")
557
557
  self._check(comment)
558
558
  return False
559
559
  elif self.profit_target():
@@ -561,9 +561,9 @@ class Trade(RiskManagement):
561
561
  return True
562
562
 
563
563
  def _check(self, txt: str = ""):
564
- if (self.positive_profit(id=self.expert_id)
565
- or self.get_current_positions() is None
566
- ):
564
+ if (self.positive_profit(id=self.expert_id)
565
+ or self.get_current_positions() is None
566
+ ):
567
567
  self.close_positions(position_type='all')
568
568
  self.logger.info(txt)
569
569
  time.sleep(5)
@@ -582,7 +582,7 @@ class Trade(RiskManagement):
582
582
  price (float): Price for opening the position
583
583
  request (Dict[str, Any]): A trade request to sent to Mt5.order_sent()
584
584
  all detail in request can be found here https://www.mql5.com/en/docs/python_metatrader5/mt5ordersend_py
585
-
585
+
586
586
  type (str): The type of the order `(BMKT, SMKT, BLMT, SLMT, BSTP, SSTP, BSTPLMT, SSTPLMT)`
587
587
  """
588
588
  # Send a trading request
@@ -590,23 +590,25 @@ class Trade(RiskManagement):
590
590
  pos = self._order_type()[type][1]
591
591
  addtionnal = f", SYMBOL={self.symbol}"
592
592
  try:
593
- check = self.check_order(request)
593
+ self.check_order(request)
594
594
  result = self.send_order(request)
595
595
  except Exception as e:
596
596
  print(f"{self.current_datetime()} -", end=' ')
597
597
  trade_retcode_message(
598
598
  result.retcode, display=True, add_msg=f"{e}{addtionnal}")
599
599
  if result.retcode != Mt5.TRADE_RETCODE_DONE:
600
- msg = trade_retcode_message(result.retcode)
601
- self.logger.error(
602
- f"Trade Order Request, RETCODE={result.retcode}: {msg}{addtionnal}")
600
+ if result.retcode not in self._retcodes:
601
+ self._retcodes.append(result.retcode)
602
+ msg = trade_retcode_message(result.retcode)
603
+ self.logger.error(
604
+ f"Trade Order Request, RETCODE={result.retcode}: {msg}{addtionnal}")
603
605
  if result.retcode in [
604
606
  Mt5.TRADE_RETCODE_CONNECTION, Mt5.TRADE_RETCODE_TIMEOUT]:
605
607
  tries = 0
606
608
  while result.retcode != Mt5.TRADE_RETCODE_DONE and tries < 5:
607
609
  time.sleep(1)
608
610
  try:
609
- check = self.check_order(request)
611
+ self.check_order(request)
610
612
  result = self.send_order(request)
611
613
  except Exception as e:
612
614
  print(f"{self.current_datetime()} -", end=' ')
@@ -650,7 +652,7 @@ class Trade(RiskManagement):
650
652
  f"{self.get_account_info().currency}]\n"
651
653
  )
652
654
  self.logger.info(pos_info)
653
-
655
+
654
656
  def open_position(
655
657
  self,
656
658
  action: Buys | Sells,
@@ -682,7 +684,8 @@ class Trade(RiskManagement):
682
684
  self.open_sell_position(
683
685
  action=action, price=price, stoplimit=stoplimit, id=id, mm=mm, comment=comment)
684
686
  else:
685
- raise ValueError(f"Invalid action type '{action}', must be {', '.join(BUYS + SELLS)}")
687
+ raise ValueError(
688
+ f"Invalid action type '{action}', must be {', '.join(BUYS + SELLS)}")
686
689
 
687
690
  @property
688
691
  def orders(self):
@@ -766,7 +769,7 @@ class Trade(RiskManagement):
766
769
  continue
767
770
  if filter_type == 'sells' and item.type != 1:
768
771
  continue
769
- if filter_type == 'losings' and item.profit > 0:
772
+ if filter_type == 'losings' and item.profit > 0:
770
773
  continue
771
774
  if filter_type == 'profitables' and not self.win_trade(item, th=th):
772
775
  continue
@@ -791,19 +794,19 @@ class Trade(RiskManagement):
791
794
 
792
795
  def get_current_buy_stops(self, id: Optional[int] = None) -> List[int] | None:
793
796
  return self.get_filtered_tickets(id=id, filter_type='buy_stops')
794
-
797
+
795
798
  def get_current_sell_stops(self, id: Optional[int] = None) -> List[int] | None:
796
799
  return self.get_filtered_tickets(id=id, filter_type='sell_stops')
797
-
800
+
798
801
  def get_current_buy_limits(self, id: Optional[int] = None) -> List[int] | None:
799
802
  return self.get_filtered_tickets(id=id, filter_type='buy_limits')
800
-
803
+
801
804
  def get_current_sell_limits(self, id: Optional[int] = None) -> List[int] | None:
802
805
  return self.get_filtered_tickets(id=id, filter_type='sell_limits')
803
-
806
+
804
807
  def get_current_buy_stop_limits(self, id: Optional[int] = None) -> List[int] | None:
805
808
  return self.get_filtered_tickets(id=id, filter_type='buy_stop_limits')
806
-
809
+
807
810
  def get_current_sell_stop_limits(self, id: Optional[int] = None) -> List[int] | None:
808
811
  return self.get_filtered_tickets(id=id, filter_type='sell_stop_limits')
809
812
 
@@ -842,7 +845,8 @@ class Trade(RiskManagement):
842
845
  # All open positions for a specific strategy or expert no matter the symbol
843
846
  positions = self.get_positions()
844
847
  if positions is not None:
845
- positions = [position for position in positions if position.magic == id]
848
+ positions = [
849
+ position for position in positions if position.magic == id]
846
850
  elif not account and id is None:
847
851
  # All open positions for the current symbol no matter the strategy or expert
848
852
  positions = self.get_positions(symbol=self.symbol)
@@ -850,8 +854,9 @@ class Trade(RiskManagement):
850
854
  # All open positions for the current symbol and a specific strategy or expert
851
855
  positions = self.get_positions(symbol=self.symbol)
852
856
  if positions is not None:
853
- positions = [position for position in positions if position.magic == id]
854
-
857
+ positions = [
858
+ position for position in positions if position.magic == id]
859
+
855
860
  if positions is not None:
856
861
  profit = 0.0
857
862
  balance = self.get_account_info().balance
@@ -864,7 +869,7 @@ class Trade(RiskManagement):
864
869
  return current_profit >= th_profit
865
870
  return False
866
871
 
867
- def break_even(self, mm=True,
872
+ def break_even(self, mm=True,
868
873
  id: Optional[int] = None,
869
874
  trail: Optional[bool] = True,
870
875
  stop_trail: Optional[int] = None,
@@ -913,7 +918,8 @@ class Trade(RiskManagement):
913
918
  if position.ticket not in self.break_even_status:
914
919
  price = None
915
920
  if be_plus_points is not None:
916
- price = position.price_open + (be_plus_points * point)
921
+ price = position.price_open + \
922
+ (be_plus_points * point)
917
923
  self.set_break_even(position, be, price=price)
918
924
  self.break_even_status.append(position.ticket)
919
925
  self.break_even_points[position.ticket] = be
@@ -922,32 +928,40 @@ class Trade(RiskManagement):
922
928
  if not trail:
923
929
  continue
924
930
  # Check if the price has moved favorably
925
- new_be = round(be * 0.10) if be_plus_points is None else be_plus_points
931
+ new_be = round(
932
+ be * 0.10) if be_plus_points is None else be_plus_points
926
933
  if trail_after_points is not None:
927
934
  if position.ticket not in self.trail_after_points:
928
935
  # This ensures that the position rich the minimum points required
929
936
  # before the trail can be set
930
937
  new_be = trail_after_points - be
931
- self.trail_after_points.append(position.ticket)
938
+ self.trail_after_points.append(
939
+ position.ticket)
932
940
  new_be_points = self.break_even_points[position.ticket] + new_be
933
- favorable_move = float(points/point) >= new_be_points
941
+ favorable_move = float(
942
+ points/point) >= new_be_points
934
943
  if favorable_move:
935
944
  # This allows the position to go to take profit in case of a swing trade
936
945
  # If is a scalping position, we can set the stop_trail close to the current price.
937
- trail_points = round(be * 0.50) if stop_trail is None else stop_trail
946
+ trail_points = round(
947
+ be * 0.50) if stop_trail is None else stop_trail
938
948
  # Calculate the new break-even level and price
939
949
  if position.type == 0:
940
950
  # This level validate the favorable move of the price
941
- new_level = round(position.price_open + (new_be_points * point), digits)
942
- # This price is set away from the current price by the trail_points
943
- new_price = round(position.price_current - (trail_points * point), digits)
951
+ new_level = round(
952
+ position.price_open + (new_be_points * point), digits)
953
+ # This price is set away from the current price by the trail_points
954
+ new_price = round(
955
+ position.price_current - (trail_points * point), digits)
944
956
  if new_price < position.sl:
945
- new_price = position.sl
957
+ new_price = position.sl
946
958
  elif position.type == 1:
947
- new_level = round(position.price_open - (new_be_points * point), digits)
948
- new_price = round(position.price_current + (trail_points * point), digits)
959
+ new_level = round(
960
+ position.price_open - (new_be_points * point), digits)
961
+ new_price = round(
962
+ position.price_current + (trail_points * point), digits)
949
963
  if new_price > position.sl:
950
- new_price = position.sl
964
+ new_price = position.sl
951
965
  self.set_break_even(
952
966
  position, be, price=new_price, level=new_level
953
967
  )
@@ -1027,7 +1041,7 @@ class Trade(RiskManagement):
1027
1041
  addtionnal = f", SYMBOL={self.symbol}"
1028
1042
  time.sleep(0.1)
1029
1043
  try:
1030
- check_result = self.check_order(request)
1044
+ self.check_order(request)
1031
1045
  result = self.send_order(request)
1032
1046
  except Exception as e:
1033
1047
  print(f"{self.current_datetime()} -", end=' ')
@@ -1045,7 +1059,7 @@ class Trade(RiskManagement):
1045
1059
  else:
1046
1060
  time.sleep(1)
1047
1061
  try:
1048
- check_result = self.check_order(request)
1062
+ self.check_order(request)
1049
1063
  result = self.send_order(request)
1050
1064
  except Exception as e:
1051
1065
  print(f"{self.current_datetime()} -", end=' ')
@@ -1078,7 +1092,6 @@ class Trade(RiskManagement):
1078
1092
  value = self.get_symbol_info(self.symbol).trade_tick_value
1079
1093
  points = position.profit * (size / value / position.volume)
1080
1094
 
1081
- spread = self.get_symbol_info(self.symbol).spread
1082
1095
  point = self.get_symbol_info(self.symbol).point
1083
1096
  fees = self.get_stats()[0]["average_fee"] * -1
1084
1097
  risk = self.currency_risk()["trade_profit"]
@@ -1088,7 +1101,7 @@ class Trade(RiskManagement):
1088
1101
  win_be = th
1089
1102
  else:
1090
1103
  win_be = max(min_be, round((0.1 * be)))
1091
- win_trade = float(points/point) >= be
1104
+ win_trade = float(points/point) >= win_be
1092
1105
  # Check if the positon is in profit
1093
1106
  if win_trade:
1094
1107
  # Check if break-even has already been set for this position
@@ -1133,7 +1146,7 @@ class Trade(RiskManagement):
1133
1146
  ticket = request[type]
1134
1147
  addtionnal = f", SYMBOL={self.symbol}"
1135
1148
  try:
1136
- check_result = self.check_order(request)
1149
+ self.check_order(request)
1137
1150
  result = self.send_order(request)
1138
1151
  except Exception as e:
1139
1152
  print(f"{self.current_datetime()} -", end=' ')
@@ -1147,7 +1160,7 @@ class Trade(RiskManagement):
1147
1160
  while result.retcode != Mt5.TRADE_RETCODE_DONE and tries < 5:
1148
1161
  time.sleep(1)
1149
1162
  try:
1150
- check_result = self.check_order(request)
1163
+ self.check_order(request)
1151
1164
  result = self.send_order(request)
1152
1165
  except Exception as e:
1153
1166
  print(f"{self.current_datetime()} -", end=' ')
@@ -1166,10 +1179,10 @@ class Trade(RiskManagement):
1166
1179
  return True
1167
1180
  else:
1168
1181
  return False
1169
-
1170
- def close_order(self,
1171
- ticket: int,
1172
- id: Optional[int] = None,
1182
+
1183
+ def close_order(self,
1184
+ ticket: int,
1185
+ id: Optional[int] = None,
1173
1186
  comment: Optional[str] = None):
1174
1187
  """
1175
1188
  Close an open order by it ticket
@@ -1183,14 +1196,14 @@ class Trade(RiskManagement):
1183
1196
  - True if order closed, False otherwise
1184
1197
  """
1185
1198
  request = {
1186
- "action": Mt5.TRADE_ACTION_REMOVE,
1187
- "symbol": self.symbol,
1188
- "order": ticket,
1189
- "magic": id if id is not None else self.expert_id,
1190
- "comment": f"@{self.expert_name}" if comment is None else comment,
1191
- }
1199
+ "action": Mt5.TRADE_ACTION_REMOVE,
1200
+ "symbol": self.symbol,
1201
+ "order": ticket,
1202
+ "magic": id if id is not None else self.expert_id,
1203
+ "comment": f"@{self.expert_name}" if comment is None else comment,
1204
+ }
1192
1205
  return self.close_request(request, type="order")
1193
-
1206
+
1194
1207
  def close_position(self,
1195
1208
  ticket: int,
1196
1209
  id: Optional[int] = None,
@@ -1215,15 +1228,13 @@ class Trade(RiskManagement):
1215
1228
  positions = self.get_positions(ticket=ticket)
1216
1229
  buy_price = self.get_tick_info(self.symbol).ask
1217
1230
  sell_price = self.get_tick_info(self.symbol).bid
1218
- digits = self.get_symbol_info(self.symbol).digits
1219
1231
  deviation = self.get_deviation()
1220
1232
  if positions is not None and len(positions) == 1:
1221
1233
  position = positions[0]
1222
1234
  if (position.ticket == ticket
1223
- and position.magic == Id
1224
- ):
1235
+ and position.magic == Id
1236
+ ):
1225
1237
  buy = position.type == 0
1226
- sell = position.type == 1
1227
1238
  request = {
1228
1239
  "action": Mt5.TRADE_ACTION_DEAL,
1229
1240
  "symbol": self.symbol,
@@ -1238,15 +1249,14 @@ class Trade(RiskManagement):
1238
1249
  "type_filling": Mt5.ORDER_FILLING_FOK,
1239
1250
  }
1240
1251
  return self.close_request(request, type="position")
1241
-
1242
- def bulk_close(self,
1243
- tickets: List,
1252
+
1253
+ def bulk_close(self,
1254
+ tickets: List,
1244
1255
  tikets_type: Literal["positions", "orders"],
1245
- close_func: Callable,
1246
- order_type: str,
1256
+ close_func: Callable,
1257
+ order_type: str,
1247
1258
  id: Optional[int] = None,
1248
1259
  comment: Optional[str] = None):
1249
-
1250
1260
  """
1251
1261
  Close multiple orders or positions at once.
1252
1262
 
@@ -1260,13 +1270,12 @@ class Trade(RiskManagement):
1260
1270
  """
1261
1271
  if order_type == 'all':
1262
1272
  order_type = 'open'
1263
- if len(tickets) > 0:
1273
+ if tickets is not None and len(tickets) > 0:
1264
1274
  for ticket in tickets.copy():
1265
1275
  if close_func(ticket, id=id, comment=comment):
1266
1276
  tickets.remove(ticket)
1267
1277
  time.sleep(1)
1268
-
1269
- if len(tickets) == 0:
1278
+ if tickets is not None and len(tickets) == 0:
1270
1279
  self.logger.info(
1271
1280
  f"ALL {order_type.upper()} {tikets_type.upper()} closed, SYMBOL={self.symbol}.")
1272
1281
  else:
@@ -1276,11 +1285,12 @@ class Trade(RiskManagement):
1276
1285
  self.logger.info(
1277
1286
  f"No {order_type.upper()} {tikets_type.upper()} to close, SYMBOL={self.symbol}.")
1278
1287
 
1279
- Orders = Literal["all", "buy_stops", "sell_stops", "buy_limits",
1288
+ Orders = Literal["all", "buy_stops", "sell_stops", "buy_limits",
1280
1289
  "sell_limits", "buy_stop_limits", "sell_stop_limits"]
1281
- def close_orders(self,
1282
- order_type: Orders,
1283
- id: Optional[int] = None,
1290
+
1291
+ def close_orders(self,
1292
+ order_type: Orders,
1293
+ id: Optional[int] = None,
1284
1294
  comment: Optional[str] = None):
1285
1295
  """
1286
1296
  Args:
@@ -1310,6 +1320,7 @@ class Trade(RiskManagement):
1310
1320
  orders, "orders", self.close_order, order_type, id=id, comment=comment)
1311
1321
 
1312
1322
  Positions = Literal["all", "buy", "sell", "profitable", "losing"]
1323
+
1313
1324
  def close_positions(
1314
1325
  self,
1315
1326
  position_type: Positions,
@@ -1350,7 +1361,6 @@ class Trade(RiskManagement):
1350
1361
  loss_trades = 0
1351
1362
  win_trades = 0
1352
1363
  balance = self.get_account_info().balance
1353
- target = round((balance * self.target)/100)
1354
1364
  deals = len(self.opened_positions)
1355
1365
  if deals != 0:
1356
1366
  for position in self.opened_positions:
@@ -1477,27 +1487,28 @@ class Trade(RiskManagement):
1477
1487
  else:
1478
1488
  # claculate number of minute from the end to the start
1479
1489
  start = datetime.strptime(self.start, '%H:%M')
1480
- end = datetime.strptime(self.current_time(), '%H:%M')
1490
+ end = datetime.strptime(self.current_time(), '%H:%M')
1481
1491
  minutes = (end - start).total_seconds() // 60
1482
1492
  sleep_time = (24*60) - minutes
1483
1493
  return sleep_time
1484
1494
 
1485
1495
  def current_datetime(self):
1486
1496
  return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1487
-
1497
+
1488
1498
  def current_time(self, seconds=False):
1489
1499
  if seconds:
1490
1500
  return datetime.now().strftime("%H:%M:%S")
1491
1501
  return datetime.now().strftime("%H:%M")
1492
1502
 
1503
+
1493
1504
  def create_trade_instance(
1494
- symbols: List[str],
1495
- params: Dict[str, Any],
1496
- daily_risk: Optional[Dict[str, float]] = None,
1497
- max_risk: Optional[Dict[str, float]] = None,
1498
- pchange_sl: Optional[Dict[str, float] | float] = None,
1499
- **kwargs
1500
- ) -> Dict[str, Trade]:
1505
+ symbols: List[str],
1506
+ params: Dict[str, Any],
1507
+ daily_risk: Optional[Dict[str, float]] = None,
1508
+ max_risk: Optional[Dict[str, float]] = None,
1509
+ pchange_sl: Optional[Dict[str, float] | float] = None,
1510
+ **kwargs
1511
+ ) -> Dict[str, Trade]:
1501
1512
  """
1502
1513
  Creates Trade instances for each symbol provided.
1503
1514
 
@@ -1512,7 +1523,7 @@ def create_trade_instance(
1512
1523
 
1513
1524
  Raises:
1514
1525
  ValueError: If the 'symbols' list is empty or the 'params' dictionary is missing required keys.
1515
-
1526
+
1516
1527
  Note:
1517
1528
  `daily_risk` and `max_risk` can be used to manage the risk of each symbol
1518
1529
  based on the importance of the symbol in the portfolio or strategy.
@@ -1524,57 +1535,62 @@ def create_trade_instance(
1524
1535
  raise ValueError("The 'symbols' list cannot be empty.")
1525
1536
  if not params:
1526
1537
  raise ValueError("The 'params' dictionary cannot be empty.")
1527
-
1538
+
1528
1539
  if daily_risk is not None:
1529
1540
  for symbol in symbols:
1530
1541
  if symbol not in daily_risk:
1531
- raise ValueError(f"Missing daily risk weight for symbol '{symbol}'.")
1542
+ raise ValueError(
1543
+ f"Missing daily risk weight for symbol '{symbol}'.")
1532
1544
  if max_risk is not None:
1533
1545
  for symbol in symbols:
1534
1546
  if symbol not in max_risk:
1535
- raise ValueError(f"Missing maximum risk percentage for symbol '{symbol}'.")
1547
+ raise ValueError(
1548
+ f"Missing maximum risk percentage for symbol '{symbol}'.")
1536
1549
  if pchange_sl is not None:
1537
1550
  if isinstance(pchange_sl, dict):
1538
1551
  for symbol in symbols:
1539
1552
  if symbol not in pchange_sl:
1540
- raise ValueError(f"Missing percentage change for symbol '{symbol}'.")
1553
+ raise ValueError(
1554
+ f"Missing percentage change for symbol '{symbol}'.")
1541
1555
  if isinstance(ids, dict):
1542
1556
  for symbol in symbols:
1543
1557
  if symbol not in ids:
1544
1558
  raise ValueError(f"Missing expert ID for symbol '{symbol}'.")
1545
-
1559
+
1546
1560
  for symbol in symbols:
1547
1561
  try:
1548
1562
  params['symbol'] = symbol
1549
1563
  params['expert_id'] = (
1550
- ids[symbol] if ids is not None and isinstance(ids, dict) else
1551
- ids if ids is not None and isinstance(ids, (int, float)) else
1564
+ ids[symbol] if ids is not None and isinstance(ids, dict) else
1565
+ ids if ids is not None and isinstance(ids, (int, float)) else
1552
1566
  params['expert_id'] if 'expert_id' in params else None
1553
1567
  )
1554
1568
  params['pchange_sl'] = (
1555
1569
  pchange_sl[symbol] if pchange_sl is not None
1556
- and isinstance(pchange_sl, dict) else
1557
- pchange_sl if pchange_sl is not None
1558
- and isinstance(pchange_sl, (int, float)) else
1570
+ and isinstance(pchange_sl, dict) else
1571
+ pchange_sl if pchange_sl is not None
1572
+ and isinstance(pchange_sl, (int, float)) else
1559
1573
  params['pchange_sl'] if 'pchange_sl' in params else None
1560
1574
  )
1561
1575
  params['daily_risk'] = (
1562
- daily_risk[symbol] if daily_risk is not None else
1576
+ daily_risk[symbol] if daily_risk is not None else
1563
1577
  params['daily_risk'] if 'daily_risk' in params else None
1564
1578
  )
1565
1579
  params['max_risk'] = (
1566
- max_risk[symbol] if max_risk is not None else
1580
+ max_risk[symbol] if max_risk is not None else
1567
1581
  params['max_risk'] if 'max_risk' in params else 10.0
1568
1582
  )
1569
1583
  trade_instances[symbol] = Trade(**params)
1570
1584
  except Exception as e:
1571
1585
  logger.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
1572
-
1586
+
1573
1587
  if len(trade_instances) != len(symbols):
1574
1588
  for symbol in symbols:
1575
1589
  if symbol not in trade_instances:
1576
1590
  if logger is not None and isinstance(logger, Logger):
1577
- logger.error(f"Failed to create Trade instance for SYMBOL={symbol}")
1591
+ logger.error(
1592
+ f"Failed to create Trade instance for SYMBOL={symbol}")
1578
1593
  else:
1579
- raise ValueError(f"Failed to create Trade instance for SYMBOL={symbol}")
1580
- return trade_instances
1594
+ raise ValueError(
1595
+ f"Failed to create Trade instance for SYMBOL={symbol}")
1596
+ return trade_instances