bbstrader 0.1.5__py3-none-any.whl → 0.1.7__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,10 +1,10 @@
1
1
  import os
2
2
  import csv
3
3
  import time
4
- import logging
5
4
  import numpy as np
6
5
  from datetime import datetime
7
6
  import MetaTrader5 as Mt5
7
+ from logging import Logger
8
8
  from typing import List, Tuple, Dict, Any, Optional, Literal
9
9
  from bbstrader.metatrader.risk import RiskManagement
10
10
  from bbstrader.metatrader.account import INIT_MSG
@@ -13,9 +13,6 @@ from bbstrader.metatrader.utils import (
13
13
  raise_mt5_error, trade_retcode_message, config_logger
14
14
  )
15
15
 
16
- # Configure the logger
17
- logger = config_logger('trade.log', console_log=True)
18
-
19
16
  class Trade(RiskManagement):
20
17
  """
21
18
  Extends the `RiskManagement` class to include specific trading operations,
@@ -81,6 +78,8 @@ class Trade(RiskManagement):
81
78
  finishing_time: str = "23:00",
82
79
  ending_time: str = "23:30",
83
80
  verbose: Optional[bool] = None,
81
+ console_log: Optional[bool] = False,
82
+ logger: Logger | str = 'bbstrader.log',
84
83
  **kwargs,
85
84
  ):
86
85
  """
@@ -98,6 +97,8 @@ class Trade(RiskManagement):
98
97
  ending_time (str): The time after which any open position will be closed.
99
98
  verbose (bool | None): If set to None (default), account summary and risk managment
100
99
  parameters are printed in the terminal.
100
+ console_log (bool): If set to True, log messages are displayed in the console.
101
+ logger (Logger | str): The logger object to use for logging messages could be a string or a logger object.
101
102
 
102
103
  Inherits:
103
104
  - max_risk
@@ -131,6 +132,8 @@ class Trade(RiskManagement):
131
132
  self.start = start_time
132
133
  self.end = ending_time
133
134
  self.finishing = finishing_time
135
+ self.console_log = console_log
136
+ self.logger = self._get_logger(logger, console_log)
134
137
  self.tf = kwargs.get("time_frame", 'D1')
135
138
 
136
139
  self.lot = self.get_lot()
@@ -160,8 +163,14 @@ class Trade(RiskManagement):
160
163
  print()
161
164
  self.risk_managment()
162
165
  print(
163
- f">>> Everything is OK, @{self.expert_name} is Running ....>>>\n")
166
+ f">>> Everything is OK, @{self.expert_name} is Running ...>>>\n")
164
167
 
168
+ def _get_logger(self, logger: str | Logger, consol_log: bool) -> Logger:
169
+ """Get the logger object"""
170
+ if isinstance(logger, str):
171
+ return config_logger(logger, consol_log)
172
+ return logger
173
+
165
174
  def initialize(self):
166
175
  """
167
176
  Initializes the MetaTrader 5 (MT5) terminal for trading operations.
@@ -183,7 +192,7 @@ class Trade(RiskManagement):
183
192
  f" Version @{self.version}, on {self.symbol}."
184
193
  )
185
194
  except Exception as e:
186
- logger.error(f"During initialization: {e}")
195
+ self.logger.error(f"During initialization: {e}")
187
196
 
188
197
  def select_symbol(self):
189
198
  """
@@ -200,7 +209,7 @@ class Trade(RiskManagement):
200
209
  if not Mt5.symbol_select(self.symbol, True):
201
210
  raise_mt5_error(message=INIT_MSG)
202
211
  except Exception as e:
203
- logger.error(f"Selecting symbol '{self.symbol}': {e}")
212
+ self.logger.error(f"Selecting symbol '{self.symbol}': {e}")
204
213
 
205
214
  def prepare_symbol(self):
206
215
  """
@@ -222,7 +231,7 @@ class Trade(RiskManagement):
222
231
  if self.verbose:
223
232
  print("Initialization successfully completed.")
224
233
  except Exception as e:
225
- logger.error(f"Preparing symbol '{self.symbol}': {e}")
234
+ self.logger.error(f"Preparing symbol '{self.symbol}': {e}")
226
235
 
227
236
  def summary(self):
228
237
  """Show a brief description about the trading program"""
@@ -327,7 +336,7 @@ class Trade(RiskManagement):
327
336
 
328
337
  # Save to CSV if specified
329
338
  if save:
330
- today_date = datetime.now().strftime("%Y-%m-%d")
339
+ today_date = datetime.now().strftime('%Y%m%d%H%M%S')
331
340
  # Create a dictionary with the statistics
332
341
  statistics_dict = {
333
342
  "Total Trades": deals,
@@ -351,7 +360,7 @@ class Trade(RiskManagement):
351
360
  else:
352
361
  symbol = self.symbol
353
362
 
354
- filename = f"{symbol}_{today_date}_session.csv"
363
+ filename = f"{symbol}_{today_date}@{self.expert_id}_session.csv"
355
364
  filepath = os.path.join(dir, filename)
356
365
 
357
366
  # Updated code to write to CSV
@@ -362,7 +371,7 @@ class Trade(RiskManagement):
362
371
  writer.writerow(["Statistic", "Value"])
363
372
  for stat, value in statistics_dict.items():
364
373
  writer.writerow([stat, value])
365
- logger.info(f"Session statistics saved to {filepath}")
374
+ self.logger.info(f"Session statistics saved to {filepath}")
366
375
 
367
376
  def open_buy_position(
368
377
  self,
@@ -416,7 +425,7 @@ class Trade(RiskManagement):
416
425
  request["action"] = Mt5.TRADE_ACTION_PENDING
417
426
  request["type"] = self._order_type()[action][0]
418
427
 
419
- self.break_even(comment)
428
+ self.break_even(mm=mm)
420
429
  if self.check(comment):
421
430
  self.request_result(_price, request, action),
422
431
 
@@ -508,14 +517,14 @@ class Trade(RiskManagement):
508
517
  if self.days_end():
509
518
  return False
510
519
  elif not self.trading_time():
511
- logger.info(f"Not Trading time, SYMBOL={self.symbol}")
520
+ self.logger.info(f"Not Trading time, SYMBOL={self.symbol}")
512
521
  return False
513
522
  elif not self.is_risk_ok():
514
- logger.error(f"Risk not allowed, SYMBOL={self.symbol}")
523
+ self.logger.error(f"Risk not allowed, SYMBOL={self.symbol}")
515
524
  self._check(comment)
516
525
  return False
517
526
  elif not self._risk_free():
518
- logger.error(f"Maximum trades Reached, SYMBOL={self.symbol}")
527
+ self.logger.error(f"Maximum trades Reached, SYMBOL={self.symbol}")
519
528
  self._check(comment)
520
529
  return False
521
530
  elif self.profit_target():
@@ -525,7 +534,7 @@ class Trade(RiskManagement):
525
534
  def _check(self, txt: str = ""):
526
535
  if self.positive_profit() or self.get_current_open_positions() is None:
527
536
  self.close_positions(position_type='all')
528
- logger.info(txt)
537
+ self.logger.info(txt)
529
538
  time.sleep(5)
530
539
  self.statistics(save=True)
531
540
 
@@ -560,7 +569,7 @@ class Trade(RiskManagement):
560
569
  result.retcode, display=True, add_msg=f"{e}{addtionnal}")
561
570
  if result.retcode != Mt5.TRADE_RETCODE_DONE:
562
571
  msg = trade_retcode_message(result.retcode)
563
- logger.error(
572
+ self.logger.error(
564
573
  f"Trade Order Request, RETCODE={result.retcode}: {msg}{addtionnal}")
565
574
  if result.retcode in [
566
575
  Mt5.TRADE_RETCODE_CONNECTION, Mt5.TRADE_RETCODE_TIMEOUT]:
@@ -580,7 +589,7 @@ class Trade(RiskManagement):
580
589
  # Print the result
581
590
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
582
591
  msg = trade_retcode_message(result.retcode)
583
- logger.info(f"Trade Order {msg}{addtionnal}")
592
+ self.logger.info(f"Trade Order {msg}{addtionnal}")
584
593
  if type != "BMKT" or type != "SMKT":
585
594
  self.opened_orders.append(result.order)
586
595
  long_msg = (
@@ -588,7 +597,7 @@ class Trade(RiskManagement):
588
597
  f"Lot(s): {result.volume}, Sl: {self.get_stop_loss()}, "
589
598
  f"Tp: {self.get_take_profit()}"
590
599
  )
591
- logger.info(long_msg)
600
+ self.logger.info(long_msg)
592
601
  time.sleep(0.1)
593
602
  if type == "BMKT" or type == "SMKT":
594
603
  self.opened_positions.append(result.order)
@@ -606,12 +615,12 @@ class Trade(RiskManagement):
606
615
  f"2. {order_type} Position Opened, Symbol: {self.symbol}, Price: @{round(position.price_open,5)}, "
607
616
  f"Sl: @{position.sl} Tp: @{position.tp}"
608
617
  )
609
- logger.info(order_info)
618
+ self.logger.info(order_info)
610
619
  pos_info = (
611
620
  f"3. [OPEN POSITIONS ON {self.symbol} = {len(positions)}, ACCOUNT OPEN PnL = {profit} "
612
621
  f"{self.get_account_info().currency}]\n"
613
622
  )
614
- logger.info(pos_info)
623
+ self.logger.info(pos_info)
615
624
 
616
625
  def open_position(
617
626
  self,
@@ -767,7 +776,7 @@ class Trade(RiskManagement):
767
776
  return True
768
777
  return False
769
778
 
770
- def break_even(self, id: Optional[int] = None):
779
+ def break_even(self, mm=True, id: Optional[int] = None):
771
780
  """
772
781
  Checks if it's time to put the break even,
773
782
  if so , it will sets the break even ,and if the break even was already set,
@@ -776,8 +785,11 @@ class Trade(RiskManagement):
776
785
 
777
786
  Args:
778
787
  id (int): The strategy Id or Expert Id
788
+ mm (bool): Weither to manage the position or not
779
789
  """
780
790
  time.sleep(0.1)
791
+ if not mm:
792
+ return
781
793
  Id = id if id is not None else self.expert_id
782
794
  positions = self.get_positions(symbol=self.symbol)
783
795
  be = self.get_break_even()
@@ -905,7 +917,7 @@ class Trade(RiskManagement):
905
917
  result.retcode, display=True, add_msg=f"{e}{addtionnal}")
906
918
  if result.retcode != Mt5.TRADE_RETCODE_DONE:
907
919
  msg = trade_retcode_message(result.retcode)
908
- logger.error(
920
+ self.logger.error(
909
921
  f"Break-Even Order Request, Position: #{tiket}, RETCODE={result.retcode}: {msg}{addtionnal}")
910
922
  tries = 0
911
923
  while result.retcode != Mt5.TRADE_RETCODE_DONE and tries < 10:
@@ -925,11 +937,11 @@ class Trade(RiskManagement):
925
937
  tries += 1
926
938
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
927
939
  msg = trade_retcode_message(result.retcode)
928
- logger.info(f"Break-Even Order {msg}{addtionnal}")
940
+ self.logger.info(f"Break-Even Order {msg}{addtionnal}")
929
941
  info = (
930
942
  f"Stop loss set to Break-even, Position: #{tiket}, Symbol: {self.symbol}, Price: @{price}"
931
943
  )
932
- logger.info(info)
944
+ self.logger.info(info)
933
945
  self.break_even_status.append(tiket)
934
946
 
935
947
  def win_trade(self,
@@ -1046,7 +1058,7 @@ class Trade(RiskManagement):
1046
1058
  result.retcode, display=True, add_msg=f"{e}{addtionnal}")
1047
1059
  if result.retcode != Mt5.TRADE_RETCODE_DONE:
1048
1060
  msg = trade_retcode_message(result.retcode)
1049
- logger.error(
1061
+ self.logger.error(
1050
1062
  f"Closing Order Request, Position: #{ticket}, RETCODE={result.retcode}: {msg}{addtionnal}")
1051
1063
  tries = 0
1052
1064
  while result.retcode != Mt5.TRADE_RETCODE_DONE and tries < 5:
@@ -1063,11 +1075,11 @@ class Trade(RiskManagement):
1063
1075
  tries += 1
1064
1076
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
1065
1077
  msg = trade_retcode_message(result.retcode)
1066
- logger.info(
1078
+ self.logger.info(
1067
1079
  f"Closing Order {msg}{addtionnal}")
1068
1080
  info = (
1069
1081
  f"Position #{ticket} closed, Symbol: {self.symbol}, Price: @{request['price']}")
1070
- logger.info(info)
1082
+ self.logger.info(info)
1071
1083
  return True
1072
1084
  else:
1073
1085
  return False
@@ -1095,13 +1107,16 @@ class Trade(RiskManagement):
1095
1107
 
1096
1108
  if positions is not None:
1097
1109
  if position_type == 'all':
1098
- pos_type = ""
1099
1110
  tickets = [position.ticket for position in positions]
1100
1111
  else:
1101
1112
  tickets = positions
1102
- pos_type = position_type
1103
1113
  else:
1104
1114
  tickets = []
1115
+
1116
+ if position_type == 'all':
1117
+ pos_type = ''
1118
+ else:
1119
+ pos_type = position_type
1105
1120
 
1106
1121
  if len(tickets) != 0:
1107
1122
  for ticket in tickets.copy():
@@ -1110,14 +1125,14 @@ class Trade(RiskManagement):
1110
1125
  time.sleep(1)
1111
1126
 
1112
1127
  if len(tickets) == 0:
1113
- logger.info(
1114
- f"ALL {position_type.upper()} Positions closed, SYMBOL={self.symbol}.")
1128
+ self.logger.info(
1129
+ f"ALL {pos_type.upper()} Positions closed, SYMBOL={self.symbol}.")
1115
1130
  else:
1116
- logger.info(
1117
- f"{len(tickets)} {position_type.upper()} Positions not closed, SYMBOL={self.symbol}")
1131
+ self.logger.info(
1132
+ f"{len(tickets)} {pos_type.upper()} Positions not closed, SYMBOL={self.symbol}")
1118
1133
  else:
1119
- logger.info(
1120
- f"No {position_type.upper()} Positions to close, SYMBOL={self.symbol}.")
1134
+ self.logger.info(
1135
+ f"No {pos_type.upper()} Positions to close, SYMBOL={self.symbol}.")
1121
1136
 
1122
1137
  def get_stats(self) -> Tuple[Dict[str, Any]]:
1123
1138
  """
@@ -1274,7 +1289,8 @@ class Trade(RiskManagement):
1274
1289
 
1275
1290
  def create_trade_instance(
1276
1291
  symbols: List[str],
1277
- params: Dict[str, Any]) -> Dict[str, Trade]:
1292
+ params: Dict[str, Any],
1293
+ logger: Logger = ...) -> Dict[str, Trade]:
1278
1294
  """
1279
1295
  Creates Trade instances for each symbol provided.
1280
1296
 
@@ -1289,7 +1305,6 @@ def create_trade_instance(
1289
1305
  ValueError: If the 'symbols' list is empty or the 'params' dictionary is missing required keys.
1290
1306
  """
1291
1307
  instances = {}
1292
-
1293
1308
  if not symbols:
1294
1309
  raise ValueError("The 'symbols' list cannot be empty.")
1295
1310
  for symbol in symbols:
@@ -1297,4 +1312,5 @@ def create_trade_instance(
1297
1312
  instances[symbol] = Trade(**params, symbol=symbol)
1298
1313
  except Exception as e:
1299
1314
  logger.error(f"Creating Trade instance, SYMBOL={symbol} {e}")
1315
+ assert len(instances) == len(symbols), "Failed to create Trade instances for all symbols."
1300
1316
  return instances
@@ -1,3 +1,4 @@
1
+ from datetime import datetime
1
2
  import MetaTrader5 as MT5
2
3
  import logging
3
4
  from typing import List, NamedTuple, Optional
@@ -216,7 +217,7 @@ class SymbolInfo(NamedTuple):
216
217
  volume: int
217
218
  volumehigh: int
218
219
  volumelow: int
219
- time: int
220
+ time: datetime
220
221
  digits: int
221
222
  spread: int
222
223
  spread_float: bool
@@ -316,7 +317,7 @@ class TickInfo(NamedTuple):
316
317
  * flags: Tick flags
317
318
  * volume_real: Volume for the current Last price with greater accuracy
318
319
  """
319
- time: int
320
+ time: datetime
320
321
  bid: float
321
322
  ask: float
322
323
  last: float
bbstrader/models/risk.py CHANGED
@@ -7,12 +7,14 @@ from hmmlearn.hmm import GaussianHMM
7
7
  from abc import ABCMeta, abstractmethod
8
8
  from matplotlib import cm, pyplot as plt
9
9
  from matplotlib.dates import YearLocator, MonthLocator
10
- from typing import Optional
10
+ from typing import Optional, Dict
11
+ from bbstrader.metatrader.rates import Rates
11
12
  sns.set_theme()
12
13
 
13
14
  __all__ = [
14
15
  "RiskModel",
15
- "HMMRiskManager"
16
+ "HMMRiskManager",
17
+ "build_hmm_models"
16
18
  ]
17
19
 
18
20
 
@@ -347,3 +349,38 @@ class HMMRiskManager(RiskModel):
347
349
  ax.xaxis.set_minor_locator(MonthLocator())
348
350
  ax.grid(True)
349
351
  plt.show()
352
+
353
+ def build_hmm_models(symbol_list=None, **kwargs
354
+ ) -> Dict[str, HMMRiskManager]:
355
+ mt5_data = kwargs.get("use_mt5_data", False)
356
+ data = kwargs.get("hmm_data")
357
+ tf = kwargs.get("time_frame", 'D1')
358
+ hmm_end = kwargs.get("hmm_end", 0)
359
+ sd = kwargs.get("session_duration", 23.0)
360
+ hmm_tickers = kwargs.get("hmm_tickers")
361
+ if hmm_tickers is not None:
362
+ symbols = hmm_tickers
363
+ else:
364
+ symbols = symbol_list
365
+ hmm_models = {symbol: None for symbol in symbols}
366
+ if data is not None:
367
+ if isinstance(data, pd.DataFrame):
368
+ hmm_data = data
369
+ hmm = HMMRiskManager(
370
+ data=hmm_data, verbose=True, iterations=1000, **kwargs)
371
+ for symbol in symbols:
372
+ hmm_models[symbol] = hmm
373
+ elif isinstance(data, dict):
374
+ for symbol, data in data.items():
375
+ hmm = HMMRiskManager(
376
+ data=data, verbose=True, iterations=1000, **kwargs)
377
+ hmm_models[symbol] = hmm
378
+ if mt5_data:
379
+ for symbol in symbols:
380
+ rates = Rates(symbol, tf, start_pos=hmm_end, session_duration=sd)
381
+ data = rates.get_rates_from_pos()
382
+ assert data is not None, f"No data for {symbol}"
383
+ hmm = HMMRiskManager(
384
+ data=data, verbose=True, iterations=1000, **kwargs)
385
+ hmm_models[symbol] = hmm
386
+ return hmm_models
@@ -1,4 +1,11 @@
1
1
  """
2
2
  Trading strategies execution module
3
+
4
+ This module is responsible for executing trading strategies.
5
+ It provides a framework for executing trading strategies and managing the trading process.
6
+ The module is designed to be flexible and extensible, allowing users to define their own trading
7
+ strategies and customize the trading process.
8
+
3
9
  """
4
- from bbstrader.trading.run import *
10
+ from bbstrader.trading.execution import ExecutionEngine
11
+ from bbstrader.trading.strategies import *