bbstrader 0.1.92__py3-none-any.whl → 0.1.94__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,6 +1,5 @@
1
1
  from abc import ABCMeta, abstractmethod
2
2
  import pytz
3
- import math
4
3
  import pandas as pd
5
4
  import numpy as np
6
5
  from queue import Queue
@@ -18,6 +17,7 @@ from typing import (
18
17
  List,
19
18
  Literal
20
19
  )
20
+ from bbstrader.models.optimization import optimized_weights
21
21
 
22
22
  __all__ = ['Strategy', 'MT5Strategy']
23
23
 
@@ -72,17 +72,29 @@ class MT5Strategy(Strategy):
72
72
  bars : The data handler object.
73
73
  mode : The mode of operation for the strategy (backtest or live).
74
74
  **kwargs : Additional keyword arguments for other classes (e.g, Portfolio, ExecutionHandler).
75
+ - max_trades : The maximum number of trades allowed per symbol.
76
+ - time_frame : The time frame for the strategy.
77
+ - logger : The logger object for the strategy.
75
78
  """
76
79
  self.events = events
77
80
  self.data = bars
78
81
  self.symbols = symbol_list
79
82
  self.mode = mode
80
- self.volume = kwargs.get("volume")
81
- self.max_trades = kwargs.get("max_trades",
82
- {symbol: 1 for symbol in self.symbols})
83
+ self._porfolio_value = None
84
+ self.risk_budget = self._check_risk_budget(**kwargs)
85
+ self.max_trades = kwargs.get("max_trades", {s: 1 for s in self.symbols})
86
+ self.tf = kwargs.get("time_frame", 'D1')
83
87
  self.logger = kwargs.get("logger")
84
88
  self._initialize_portfolio()
89
+
90
+ @property
91
+ def cash(self) -> float:
92
+ return self._porfolio_value
85
93
 
94
+ @cash.setter
95
+ def cash(self, value):
96
+ self._porfolio_value = value
97
+
86
98
  @property
87
99
  def orders(self) -> Dict[str, Dict[str, List[SignalEvent]]]:
88
100
  return self._orders
@@ -98,11 +110,24 @@ class MT5Strategy(Strategy):
98
110
  @property
99
111
  def holdings(self) -> Dict[str, float]:
100
112
  return self._holdings
113
+
114
+ def _check_risk_budget(self, **kwargs):
115
+ weights = kwargs.get('risk_weights')
116
+ if weights is not None and isinstance(weights, dict):
117
+ for asset in self.symbols:
118
+ if asset not in weights:
119
+ raise ValueError(f"Risk budget for asset {asset} is missing.")
120
+ total_risk = sum(weights.values())
121
+ if not np.isclose(total_risk, 1.0):
122
+ raise ValueError(f'Risk budget weights must sum to 1. got {total_risk}')
123
+ return weights
124
+ elif isinstance(weights, str):
125
+ return weights
101
126
 
102
127
  def _initialize_portfolio(self):
103
128
  positions = ['LONG', 'SHORT']
104
129
  orders = ['BLMT', 'BSTP', 'BSTPLMT', 'SLMT', 'SSTP', 'SSTPLMT']
105
- self._positions: Dict[str, Dict[str, int]] = {}
130
+ self._positions: Dict[str, Dict[str, int | float]] = {}
106
131
  self._trades: Dict[str, Dict[str, int]] = {}
107
132
  self._orders: Dict[str, Dict[str, List[SignalEvent]]] = {}
108
133
  for symbol in self.symbols:
@@ -111,7 +136,7 @@ class MT5Strategy(Strategy):
111
136
  self._trades[symbol] = {}
112
137
  for position in positions:
113
138
  self._trades[symbol][position] = 0
114
- self._positions[symbol][position] = 0
139
+ self._positions[symbol][position] = 0.0
115
140
  for order in orders:
116
141
  self._orders[symbol][order] = []
117
142
  self._holdings = {s: 0.0 for s in self.symbols}
@@ -133,6 +158,9 @@ class MT5Strategy(Strategy):
133
158
  self._positions[symbol]['LONG'] = positions[symbol]
134
159
  elif positions[symbol] < 0:
135
160
  self._positions[symbol]['SHORT'] = positions[symbol]
161
+ else:
162
+ self._positions[symbol]['LONG'] = 0
163
+ self._positions[symbol]['SHORT'] = 0
136
164
  if symbol in holdings:
137
165
  self._holdings[symbol] = holdings[symbol]
138
166
 
@@ -142,7 +170,12 @@ class MT5Strategy(Strategy):
142
170
  It is used to keep track of the number of trades executed for each order.
143
171
  """
144
172
  if event.type == 'FILL':
145
- self._trades[event.symbol][event.order] += 1
173
+ if event.order != 'EXIT':
174
+ self._trades[event.symbol][event.order] += 1
175
+ elif event.order == 'EXIT' and event.direction == 'BUY':
176
+ self._trades[event.symbol]['SHORT'] = 0
177
+ elif event.order == 'EXIT' and event.direction == 'SELL':
178
+ self._trades[event.symbol]['LONG'] = 0
146
179
 
147
180
  def calculate_signals(self, *args, **kwargs
148
181
  ) -> Dict[str, Union[str, dict, None]] | None:
@@ -173,7 +206,25 @@ class MT5Strategy(Strategy):
173
206
  """
174
207
  pass
175
208
 
176
- def get_quantity(self, symbol, volume=None) -> int:
209
+ def apply_risk_management(self, optimer, freq=252) -> Dict[str, float] | None:
210
+ """
211
+ Apply risk management rules to the strategy.
212
+ """
213
+ if optimer is None:
214
+ return None
215
+ prices = self.get_asset_values(
216
+ symbol_list=self.symbols, bars=self.data, mode=self.mode,
217
+ window=freq, value_type='close', array=False, tf=self.tf
218
+ )
219
+ prices = pd.DataFrame(prices)
220
+ prices = prices.dropna(axis=0, how='any')
221
+ try:
222
+ weights = optimized_weights(prices=prices, freq=freq, method=optimer)
223
+ return {symbol: weight for symbol, weight in weights.items()}
224
+ except Exception:
225
+ return {symbol: 0.0 for symbol in self.symbols}
226
+
227
+ def get_quantity(self, symbol, weight, price=None, volume=None, maxqty=None) -> int:
177
228
  """
178
229
  Calculate the quantity to buy or sell for a given symbol based on the dollar value provided.
179
230
  The quantity calculated can be used to evalute a strategy's performance for each symbol
@@ -185,15 +236,26 @@ class MT5Strategy(Strategy):
185
236
  Returns:
186
237
  qty : The quantity to buy or sell for the symbol.
187
238
  """
188
- if self.volume is None and volume is None:
189
- raise ValueError("Volume must be provided for the method.")
190
- current_price = self.data.get_latest_bar_value(symbol, 'close')
191
- try:
192
- qty = math.ceil(self.volume or volume / current_price)
193
- qty = max(qty, 1) / self.max_trades[symbol]
194
- return max(math.ceil(qty), 1)
195
- except Exception:
196
- return 1
239
+ if (self._porfolio_value is None or weight == 0 or
240
+ self._porfolio_value == 0 or np.isnan(self._porfolio_value)):
241
+ return 0
242
+ if volume is None:
243
+ volume = round(self._porfolio_value * weight)
244
+ if price is None:
245
+ price = self.data.get_latest_bar_value(symbol, 'close')
246
+ if (price is None or not isinstance(price, (int, float, np.number))
247
+ or volume is None or not isinstance(volume, (int, float, np.number))
248
+ or np.isnan(float(price))
249
+ or np.isnan(float(volume))
250
+ ):
251
+ if weight != 0:
252
+ return 1
253
+ return 0
254
+ qty = round(volume / price, 2)
255
+ qty = max(qty, 0) / self.max_trades[symbol]
256
+ if maxqty is not None:
257
+ qty = min(qty, maxqty)
258
+ return max(round(qty, 2), 0)
197
259
 
198
260
  def get_quantities(self, quantities: Union[None, dict, int]) -> dict:
199
261
  """
@@ -215,9 +277,18 @@ class MT5Strategy(Strategy):
215
277
 
216
278
  position = SignalEvent(id, symbol, dtime, signal,
217
279
  quantity=quantity, strength=strength, price=price)
218
- self.events.put(position)
219
- self.logger.info(
220
- f"{signal} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={quantity}, PRICE @{price}", custom_time=dtime)
280
+ log = False
281
+ if signal in ['LONG', 'SHORT']:
282
+ if self._trades[symbol][signal] < self.max_trades[symbol] and quantity > 0:
283
+ self.events.put(position)
284
+ log = True
285
+ elif signal == 'EXIT':
286
+ if self._positions[symbol]['LONG'] > 0 or self._positions[symbol]['SHORT'] < 0:
287
+ self.events.put(position)
288
+ log = True
289
+ if log:
290
+ self.logger.info(
291
+ f"{signal} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={quantity}, PRICE @{price}", custom_time=dtime)
221
292
 
222
293
  def buy_mkt(self, id: int, symbol: str, price: float, quantity: int,
223
294
  strength: float=1.0, dtime: datetime | pd.Timestamp=None):
@@ -341,55 +412,79 @@ class MT5Strategy(Strategy):
341
412
  """
342
413
  for symbol in self.symbols:
343
414
  dtime = self.data.get_latest_bar_datetime(symbol)
415
+ logmsg = lambda order, type: self.logger.info(
416
+ f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
417
+ f"PRICE @ {order.price}", custom_time=dtime)
344
418
  for order in self._orders[symbol]['BLMT'].copy():
345
419
  if self.data.get_latest_bar_value(symbol, 'close') <= order.price:
346
420
  self.buy_mkt(order.strategy_id, symbol,
347
421
  order.price, order.quantity, dtime)
348
- self.logger.info(
349
- f"BUY LIMIT ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
350
- f"PRICE @ {order.price}", custom_time=dtime)
351
- self._orders[symbol]['BLMT'].remove(order)
422
+ try:
423
+ self._orders[symbol]['BLMT'].remove(order)
424
+ assert order not in self._orders[symbol]['BLMT']
425
+ logmsg(order, 'BUY LIMIT')
426
+ except AssertionError:
427
+ self._orders[symbol]['BLMT'] = [o for o in self._orders[symbol]['BLMT'] if o != order]
428
+ logmsg(order, 'BUY LIMIT')
352
429
  for order in self._orders[symbol]['SLMT'].copy():
353
430
  if self.data.get_latest_bar_value(symbol, 'close') >= order.price:
354
431
  self.sell_mkt(order.strategy_id, symbol,
355
432
  order.price, order.quantity, dtime)
356
- self.logger.info(
357
- f"SELL LIMIT ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
358
- f"PRICE @ {order.price}", custom_time=dtime)
359
- self._orders[symbol]['SLMT'].remove(order)
433
+ try:
434
+ self._orders[symbol]['SLMT'].remove(order)
435
+ assert order not in self._orders[symbol]['SLMT']
436
+ logmsg(order, 'SELL LIMIT')
437
+ except AssertionError:
438
+ self._orders[symbol]['SLMT'] = [o for o in self._orders[symbol]['SLMT'] if o != order]
439
+ logmsg(order, 'SELL LIMIT')
360
440
  for order in self._orders[symbol]['BSTP'].copy():
361
441
  if self.data.get_latest_bar_value(symbol, 'close') >= order.price:
362
442
  self.buy_mkt(order.strategy_id, symbol,
363
443
  order.price, order.quantity, dtime)
364
- self.logger.info(
365
- f"BUY STOP ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
366
- f"PRICE @ {order.price}", custom_time=dtime)
367
- self._orders[symbol]['BSTP'].remove(order)
444
+ try:
445
+ self._orders[symbol]['BSTP'].remove(order)
446
+ assert order not in self._orders[symbol]['BSTP']
447
+ logmsg(order, 'BUY STOP')
448
+ except AssertionError:
449
+ self._orders[symbol]['BSTP'] = [o for o in self._orders[symbol]['BSTP'] if o != order]
450
+ logmsg(order, 'BUY STOP')
368
451
  for order in self._orders[symbol]['SSTP'].copy():
369
452
  if self.data.get_latest_bar_value(symbol, 'close') <= order.price:
370
453
  self.sell_mkt(order.strategy_id, symbol,
371
454
  order.price, order.quantity, dtime)
372
- self.logger.info(
373
- f"SELL STOP ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
374
- f"PRICE @ {order.price}", custom_time=dtime)
375
- self._orders[symbol]['SSTP'].remove(order)
455
+ try:
456
+ self._orders[symbol]['SSTP'].remove(order)
457
+ assert order not in self._orders[symbol]['SSTP']
458
+ logmsg(order, 'SELL STOP')
459
+ except AssertionError:
460
+ self._orders[symbol]['SSTP'] = [o for o in self._orders[symbol]['SSTP'] if o != order]
461
+ logmsg(order, 'SELL STOP')
376
462
  for order in self._orders[symbol]['BSTPLMT'].copy():
377
463
  if self.data.get_latest_bar_value(symbol, 'close') >= order.price:
378
464
  self.buy_limit(order.strategy_id, symbol,
379
465
  order.stoplimit, order.quantity, dtime)
380
- self.logger.info(
381
- f"BUY STOP LIMIT ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
382
- f"PRICE @ {order.price}", custom_time=dtime)
383
- self._orders[symbol]['BSTPLMT'].remove(order)
466
+ try:
467
+ self._orders[symbol]['BSTPLMT'].remove(order)
468
+ assert order not in self._orders[symbol]['BSTPLMT']
469
+ logmsg(order, 'BUY STOP LIMIT')
470
+ except AssertionError:
471
+ self._orders[symbol]['BSTPLMT'] = [o for o in self._orders[symbol]['BSTPLMT'] if o != order]
472
+ logmsg(order, 'BUY STOP LIMIT')
384
473
  for order in self._orders[symbol]['SSTPLMT'].copy():
385
474
  if self.data.get_latest_bar_value(symbol, 'close') <= order.price:
386
475
  self.sell_limit(order.strategy_id, symbol,
387
476
  order.stoplimit, order.quantity, dtime)
388
- self.logger.info(
389
- f"SELL STOP LIMIT ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
390
- f"PRICE @ {order.price}", custom_time=dtime)
391
- self._orders[symbol]['SSTPLMT'].remove(order)
392
-
477
+ try:
478
+ self._orders[symbol]['SSTPLMT'].remove(order)
479
+ assert order not in self._orders[symbol]['SSTPLMT']
480
+ logmsg(order, 'SELL STOP LIMIT')
481
+ except AssertionError:
482
+ self._orders[symbol]['SSTPLMT'] = [o for o in self._orders[symbol]['SSTPLMT'] if o != order]
483
+ logmsg(order, 'SELL STOP LIMIT')
484
+
485
+ def calculate_pct_change(self, current_price, lh_price):
486
+ return ((current_price - lh_price) / lh_price) * 100
487
+
393
488
  def get_asset_values(self,
394
489
  symbol_list: List[str],
395
490
  window: int,
@@ -125,9 +125,57 @@ AMG_EXCHANGES = {
125
125
  'XSWX': r"Switzerland.*\(SWX\)"
126
126
  }
127
127
 
128
- def check_mt5_connection():
128
+ def check_mt5_connection(**kwargs):
129
+ """
130
+ Initialize the connection to the MetaTrader 5 terminal.
131
+
132
+ Args:
133
+ path (str, optional): The path to the MetaTrader 5 terminal executable file.
134
+ Defaults to None (e.g., "C:\\Program Files\\MetaTrader 5\\terminal64.exe").
135
+ login (int, optional): The login ID of the trading account. Defaults to None.
136
+ password (str, optional): The password of the trading account. Defaults to None.
137
+ server (str, optional): The name of the trade server to which the client terminal is connected.
138
+ Defaults to None.
139
+ timeout (int, optional): Connection timeout in milliseconds. Defaults to 60_000.
140
+ portable (bool, optional): If True, the portable mode of the terminal is used.
141
+ Defaults to False (See https://www.metatrader5.com/en/terminal/help/start_advanced/start#portable).
142
+
143
+ Notes:
144
+ If you want to lunch multiple terminal instances:
145
+ - Follow these instructions to lunch each terminal in portable mode first:
146
+ https://www.metatrader5.com/en/terminal/help/start_advanced/start#configuration_file
147
+ """
148
+ path = kwargs.get('path', None)
149
+ login = kwargs.get('login', None)
150
+ password = kwargs.get('password', None)
151
+ server = kwargs.get('server', None)
152
+ timeout = kwargs.get('timeout', 60_000)
153
+ portable = kwargs.get('portable', False)
154
+
155
+ if path is None and (login or password or server):
156
+ raise ValueError(
157
+ f"You must provide a path to the terminal executable file"
158
+ f"when providing login, password or server"
159
+ )
129
160
  try:
130
- init = mt5.initialize()
161
+ if path is not None:
162
+ if (
163
+ login is not None and
164
+ password is not None and
165
+ server is not None
166
+ ):
167
+ init = mt5.initialize(
168
+ path=path,
169
+ login=login,
170
+ password=password,
171
+ server=server,
172
+ timeout=timeout,
173
+ portable=portable
174
+ )
175
+ else:
176
+ init = mt5.initialize(path=path)
177
+ else:
178
+ init = mt5.initialize()
131
179
  if not init:
132
180
  raise_mt5_error(INIT_MSG)
133
181
  except Exception:
@@ -135,9 +183,9 @@ def check_mt5_connection():
135
183
 
136
184
 
137
185
  class Broker(object):
138
- def __init__(self, name: str=None):
186
+ def __init__(self, name: str=None, **kwargs):
139
187
  if name is None:
140
- check_mt5_connection()
188
+ check_mt5_connection(**kwargs)
141
189
  self._name = mt5.account_info().company
142
190
  else:
143
191
  self._name = name
@@ -157,8 +205,8 @@ class Broker(object):
157
205
 
158
206
 
159
207
  class AdmiralMarktsGroup(Broker):
160
- def __init__(self):
161
- super().__init__("Admirals Group AS")
208
+ def __init__(self, **kwargs):
209
+ super().__init__("Admirals Group AS", **kwargs)
162
210
 
163
211
  @property
164
212
  def timezone(self) -> str:
@@ -166,8 +214,8 @@ class AdmiralMarktsGroup(Broker):
166
214
 
167
215
 
168
216
  class JustGlobalMarkets(Broker):
169
- def __init__(self):
170
- super().__init__("Just Global Markets Ltd.")
217
+ def __init__(self, **kwargs):
218
+ super().__init__("Just Global Markets Ltd.", **kwargs)
171
219
 
172
220
  @property
173
221
  def timezone(self) -> str:
@@ -175,8 +223,8 @@ class JustGlobalMarkets(Broker):
175
223
 
176
224
 
177
225
  class FTMO(Broker):
178
- def __init__(self):
179
- super().__init__("FTMO S.R.O.")
226
+ def __init__(self, **kwargs):
227
+ super().__init__("FTMO S.R.O.", **kwargs)
180
228
 
181
229
  @property
182
230
  def timezone(self) -> str:
@@ -230,8 +278,14 @@ class Account(object):
230
278
  >>> trade_history = account.get_trade_history(from_date, to_date)
231
279
  """
232
280
 
233
- def __init__(self):
234
- check_mt5_connection()
281
+ def __init__(self, **kwargs):
282
+ """
283
+ Initialize the Account class.
284
+
285
+ See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
286
+
287
+ """
288
+ check_mt5_connection(**kwargs)
235
289
  self._check_brokers()
236
290
 
237
291
  def _check_brokers(self):
@@ -688,7 +742,7 @@ class Account(object):
688
742
  "minors": r"\b(Minors?)\b",
689
743
  "exotics": r"\b(Exotics?)\b",
690
744
  }
691
- return self._get_symbols_by_category('forex', category, fx_categories)
745
+ return self._get_symbols_by_category('FX', category, fx_categories)
692
746
 
693
747
  def get_stocks_from_country(self, country_code: str = 'USA', etf=True) -> List[str]:
694
748
  """
@@ -82,6 +82,8 @@ class Rates(object):
82
82
  2. The `open, high, low, close, adjclose, returns,
83
83
  volume` properties returns data in Broker's timezone by default.
84
84
 
85
+ See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
86
+
85
87
  Example:
86
88
  >>> rates = Rates("EURUSD", "1h")
87
89
  >>> df = rates.get_historical_data(
@@ -94,17 +96,18 @@ class Rates(object):
94
96
  def __init__(
95
97
  self,
96
98
  symbol: str,
97
- time_frame: TimeFrame = 'D1',
99
+ timeframe: TimeFrame = 'D1',
98
100
  start_pos: Union[int , str] = 0,
99
101
  count: Optional[int] = MAX_BARS,
100
- session_duration: Optional[float] = None
102
+ session_duration: Optional[float] = None,
103
+ **kwargs
101
104
  ):
102
105
  """
103
106
  Initializes a new Rates instance.
104
107
 
105
108
  Args:
106
109
  symbol (str): Financial instrument symbol (e.g., "EURUSD").
107
- time_frame (str): Timeframe string (e.g., "D1", "1h", "5m").
110
+ timeframe (str): Timeframe string (e.g., "D1", "1h", "5m").
108
111
  start_pos (int, | str): Starting index (int) or date (str) for data retrieval.
109
112
  count (int, optional): Number of bars to retrieve default is
110
113
  the maximum bars availble in the MT5 terminal.
@@ -118,16 +121,17 @@ class Rates(object):
118
121
  For `session_duration` check your broker symbols details
119
122
  """
120
123
  self.symbol = symbol
121
- self.time_frame = self._validate_time_frame(time_frame)
124
+ tf = kwargs.get('time_frame')
125
+ self.time_frame = self._validate_time_frame(timeframe)
122
126
  self.sd = session_duration
123
- self.start_pos = self._get_start_pos(start_pos, time_frame)
127
+ self.start_pos = self._get_start_pos(start_pos, timeframe)
124
128
  self.count = count
125
- self._mt5_initialized()
126
- self.__account = Account()
129
+ self._mt5_initialized(**kwargs)
130
+ self.__account = Account(**kwargs)
127
131
  self.__data = self.get_rates_from_pos()
128
132
 
129
- def _mt5_initialized(self):
130
- check_mt5_connection()
133
+ def _mt5_initialized(self, **kwargs):
134
+ check_mt5_connection(**kwargs)
131
135
 
132
136
  def _get_start_pos(self, index, time_frame):
133
137
  if isinstance(index, int):
@@ -471,13 +475,13 @@ class Rates(object):
471
475
  df.to_csv(f"{self.symbol}.csv")
472
476
  return df
473
477
 
474
- def download_historical_data(symbol, time_frame, date_from,
478
+ def download_historical_data(symbol, timeframe, date_from,
475
479
  date_to=pd.Timestamp.now(),lower_colnames=True,
476
- utc=False, filter=False, fill_na=False, save_csv=False):
480
+ utc=False, filter=False, fill_na=False, save_csv=False, **kwargs):
477
481
  """Download historical data from MetaTrader 5 terminal.
478
482
  See `Rates.get_historical_data` for more details.
479
483
  """
480
- rates = Rates(symbol, time_frame)
484
+ rates = Rates(symbol, timeframe, **kwargs)
481
485
  data = rates.get_historical_data(
482
486
  date_from=date_from,
483
487
  date_to=date_to,
@@ -488,23 +492,23 @@ def download_historical_data(symbol, time_frame, date_from,
488
492
  )
489
493
  return data
490
494
 
491
- def get_data_from_pos(symbol, time_frame, start_pos=0, fill_na=False,
495
+ def get_data_from_pos(symbol, timeframe, start_pos=0, fill_na=False,
492
496
  count=MAX_BARS, lower_colnames=False, utc=False, filter=False,
493
- session_duration=23.0):
497
+ session_duration=23.0, **kwargs):
494
498
  """Get historical data from a specific position.
495
499
  See `Rates.get_rates_from_pos` for more details.
496
500
  """
497
- rates = Rates(symbol, time_frame, start_pos, count, session_duration)
501
+ rates = Rates(symbol, timeframe, start_pos, count, session_duration, **kwargs)
498
502
  data = rates.get_rates_from_pos(filter=filter, fill_na=fill_na,
499
503
  lower_colnames=lower_colnames, utc=utc)
500
504
  return data
501
505
 
502
- def get_data_from_date(symbol, time_frame, date_from, count=MAX_BARS, fill_na=False,
503
- lower_colnames=False, utc=False, filter=False):
506
+ def get_data_from_date(symbol, timeframe, date_from, count=MAX_BARS, fill_na=False,
507
+ lower_colnames=False, utc=False, filter=False, **kwargs):
504
508
  """Get historical data from a specific date.
505
509
  See `Rates.get_rates_from` for more details.
506
510
  """
507
- rates = Rates(symbol, time_frame)
511
+ rates = Rates(symbol, timeframe, **kwargs)
508
512
  data = rates.get_rates_from(date_from, count, filter=filter, fill_na=fill_na,
509
513
  lower_colnames=lower_colnames, utc=utc)
510
514
  return data
@@ -118,8 +118,10 @@ class RiskManagement(Account):
118
118
  tp (int, optional): Take Profit in points, Must be a positive number.
119
119
  be (int, optional): Break Even in points, Must be a positive number.
120
120
  rr (float, optional): Risk reward ratio, Must be a positive number. Defaults to 1.5.
121
+
122
+ See `bbstrader.metatrader.account.check_mt5_connection()` for more details on how to connect to MT5 terminal.
121
123
  """
122
- super().__init__()
124
+ super().__init__(**kwargs)
123
125
 
124
126
  # Validation
125
127
  if daily_risk is not None and daily_risk < 0:
@@ -137,6 +139,7 @@ class RiskManagement(Account):
137
139
  if var_time_frame not in TIMEFRAMES:
138
140
  raise ValueError("Unsupported time frame {}".format(var_time_frame))
139
141
 
142
+ self.kwargs = kwargs
140
143
  self.symbol = symbol
141
144
  self.start_time = start_time
142
145
  self.finishing_time = finishing_time
@@ -156,6 +159,22 @@ class RiskManagement(Account):
156
159
  self.symbol_info = super().get_symbol_info(self.symbol)
157
160
 
158
161
  self._tf = time_frame
162
+
163
+ @property
164
+ def dailydd(self) -> float:
165
+ return self.daily_dd
166
+
167
+ @dailydd.setter
168
+ def dailydd(self, value: float):
169
+ self.daily_dd = value
170
+
171
+ @property
172
+ def maxrisk(self) -> float:
173
+ return self.max_risk
174
+
175
+ @maxrisk.setter
176
+ def maxrisk(self, value: float):
177
+ self.max_risk = value
159
178
 
160
179
  def _convert_time_frame(self, tf: str) -> int:
161
180
  """Convert time frame to minutes"""
@@ -263,7 +282,7 @@ class RiskManagement(Account):
263
282
  tf_int = self._convert_time_frame(self._tf)
264
283
  interval = round((minutes / tf_int) * 252)
265
284
 
266
- rate = Rates(self.symbol, self._tf, 0, interval)
285
+ rate = Rates(self.symbol, self._tf, 0, interval, **self.kwargs)
267
286
  returns = rate.returns*100
268
287
  std = returns.std()
269
288
  point = self.get_symbol_info(self.symbol).point
@@ -317,7 +336,7 @@ class RiskManagement(Account):
317
336
  tf_int = self._convert_time_frame(tf)
318
337
  interval = round((minutes / tf_int) * 252)
319
338
 
320
- rate = Rates(self.symbol, tf, 0, interval)
339
+ rate = Rates(self.symbol, tf, 0, interval, **self.kwargs)
321
340
  returns = rate.returns*100
322
341
  p = self.get_account_info().margin_free
323
342
  mu = returns.mean()
@@ -437,14 +456,23 @@ class RiskManagement(Account):
437
456
  if trade_risk > 0:
438
457
  currency_risk = round(self.var_loss_value(), 5)
439
458
  volume = round(currency_risk*laverage)
440
- _lot = round((volume / (contract_size * av_price)), 2)
459
+ try:
460
+ _lot = round((volume / (contract_size * av_price)), 2)
461
+ except ZeroDivisionError:
462
+ _lot = 0.0
441
463
  lot = self._check_lot(_lot)
442
464
  if COMD and contract_size > 1:
443
465
  # lot = volume / av_price / contract_size
444
- lot = volume / av_price / contract_size
466
+ try:
467
+ lot = volume / av_price / contract_size
468
+ except ZeroDivisionError:
469
+ lot = 0.0
445
470
  lot = self._check_lot(_lot)
446
471
  if FX:
447
- __lot = round((volume / contract_size), 2)
472
+ try:
473
+ __lot = round((volume / contract_size), 2)
474
+ except ZeroDivisionError:
475
+ __lot = 0.0
448
476
  lot = self._check_lot(__lot)
449
477
 
450
478
  tick_value = s_info.trade_tick_value