bbstrader 0.0.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.

@@ -0,0 +1,626 @@
1
+ import random
2
+ import numpy as np
3
+ from scipy.stats import norm
4
+ from datetime import datetime
5
+ import MetaTrader5 as Mt5
6
+ from bbstrader.metatrader.account import Account
7
+ from bbstrader.metatrader.rates import Rates
8
+ from bbstrader.metatrader.utils import (
9
+ TIMEFRAMES, raise_mt5_error, TimeFrame
10
+ )
11
+ from typing import List, Dict, Optional, Literal, Union, Any
12
+
13
+
14
+ _COMMD_SUPPORTED_ = [
15
+ "GOLD", "XAUEUR", "SILVER", "BRENT", "CRUDOIL", "WTI", "UKOIL",
16
+ 'XAGEUR', 'XAGUSD', 'XAUAUD', 'XAUEUR', 'XAUUSD', 'XAUGBP', 'USOIL'
17
+ ]
18
+
19
+
20
+ _ADMIRAL_MARKETS_FUTURES_ = [
21
+ '#USTNote_', '#Bund_', '#USDX_', '_AUS200_', '_Canada60_', '_SouthAfrica40_',
22
+ '_STXE600_', '_EURO50_', '_GER40_', '_GermanyTech30_', '_MidCapGER50_',
23
+ '_SWISS20_', '_UK100_', '_USNASDAQ100_', '_YM_', '_ES_', '_CrudeOilUS_',
24
+ '_DUTCH25_', '_FRANCE40_', '_NORWAY25_', '_SPAIN35_', '_CrudeOilUK_',
25
+ '_XAU_', '_HK50_', '_HSCEI50_'
26
+ ]
27
+
28
+
29
+ class RiskManagement(Account):
30
+ """
31
+ The RiskManagement class provides foundational
32
+ risk management functionalities for trading activities.
33
+ It calculates risk levels, determines stop loss and take profit levels,
34
+ and ensures trading activities align with predefined risk parameters.
35
+
36
+ Exemple:
37
+ >>> risk_manager = RiskManagement(
38
+ ... symbol="EURUSD",
39
+ ... max_risk=5.0,
40
+ ... daily_risk=2.0,
41
+ ... max_trades=10,
42
+ ... std_stop=True,
43
+ ... account_leverage=True,
44
+ ... start_time="09:00",
45
+ ... finishing_time="17:00",
46
+ ... time_frame="1h"
47
+ ... )
48
+ >>> # Calculate risk level
49
+ >>> risk_level = risk_manager.risk_level()
50
+
51
+ >>> # Get appropriate lot size for a trade
52
+ >>> lot_size = risk_manager.get_lot()
53
+
54
+ >>> # Determine stop loss and take profit levels
55
+ >>> stop_loss = risk_manager.get_stop_loss()
56
+ >>> take_profit = risk_manager.get_take_profit()
57
+
58
+ >>> # Check if current risk is acceptable
59
+ >>> is_risk_acceptable = risk_manager.is_risk_ok()
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ symbol: str,
65
+ max_risk: float = 10.0,
66
+ daily_risk: Optional[float] = None,
67
+ max_trades: Optional[int] = None,
68
+ std_stop: bool = False,
69
+ pchange_sl: Optional[float] = None,
70
+ account_leverage: bool = True,
71
+ time_frame: TimeFrame = 'D1',
72
+ start_time: str = "1:00",
73
+ finishing_time: str = "23:00",
74
+ sl: Optional[int] = None,
75
+ tp: Optional[int] = None,
76
+ be: Optional[int] = None,
77
+ rr: float = 1.5,
78
+ **kwargs
79
+ ):
80
+ """
81
+ Initialize the RiskManagement class to manage risk in trading activities.
82
+
83
+ Args:
84
+ symbol (str): The symbol of the financial instrument to trade.
85
+ max_risk (float): The `maximum risk allowed` on the trading account.
86
+ daily_risk (float, optional): `Daily Max risk allowed`.
87
+ If Set to None it will be determine based on Maximum risk.
88
+ max_trades (int, optional): Maximum number of trades in a trading session.
89
+ If set to None it will be determine based on the timeframe of trading.
90
+ std_stop (bool, optional): If set to True, the Stop loss is calculated based
91
+ On `historical volatility` of the trading instrument. Defaults to False.
92
+ pchange_sl (float, optional): If set, the Stop loss is calculated based
93
+ On `percentage change` of the trading instrument.
94
+ account_leverage (bool, optional): If set to True the account leverage will be used
95
+ In risk management setting. Defaults to False.
96
+ time_frame (str, optional): The time frame on which the program is working
97
+ `(1m, 3m, 5m, 10m, 15m, 30m, 1h, 2h, 4h, D1)`. Defaults to 'D1'.
98
+ start_time (str, optional): The starting time for the trading strategy
99
+ `(HH:MM, H an M do not star with 0)`. Defaults to "6:30".
100
+ finishing_time (str, optional): The finishing time for the trading strategy
101
+ `(HH:MM, H an M do not star with 0)`. Defaults to "19:30".
102
+ sl (int, optional): Stop Loss in points, Must be a positive number.
103
+ tp (int, optional): Take Profit in points, Must be a positive number.
104
+ be (int, optional): Break Even in points, Must be a positive number.
105
+ rr (float, optional): Risk reward ratio, Must be a positive number. Defaults to 1.5.
106
+ """
107
+ super().__init__()
108
+
109
+ # Validation
110
+ if daily_risk is not None and daily_risk < 0:
111
+ raise ValueError("daily_risk must be a positive number")
112
+ if max_risk <= 0:
113
+ raise ValueError("max_risk must be a positive number")
114
+ if sl is not None and not isinstance(sl, int):
115
+ raise ValueError("sl must be an integer number")
116
+ if tp is not None and not isinstance(tp, int):
117
+ raise ValueError("tp must be an integer number")
118
+ if be is not None and (not isinstance(be, int) or be <= 0):
119
+ raise ValueError("be must be a positive integer number")
120
+ if time_frame not in TIMEFRAMES:
121
+ raise ValueError("Unsupported time frame")
122
+
123
+ self.symbol = symbol
124
+ self.start_time = start_time
125
+ self.finishing_time = finishing_time
126
+ self.max_trades = max_trades
127
+ self.std = std_stop
128
+ self.pchange = pchange_sl
129
+ self.daily_dd = daily_risk
130
+ self.max_risk = max_risk
131
+ self.rr = rr
132
+ self.sl = sl
133
+ self.tp = tp
134
+ self.be = be
135
+
136
+ self.account_leverage = account_leverage
137
+ self.symbol_info = super().get_symbol_info(self.symbol)
138
+
139
+ self.TF = self.get_minutes(
140
+ ) if time_frame == 'D1' else TIMEFRAMES[time_frame]
141
+
142
+ def risk_level(self) -> float:
143
+ """
144
+ Calculates the risk level of a trade
145
+
146
+ Returns:
147
+ - Risk level in the form of a float percentage.
148
+ """
149
+ account_info = self.get_account_info()
150
+ balance = account_info.balance
151
+ equity = account_info.equity
152
+ df = self.get_trades_history()
153
+ if df is None:
154
+ profit = 0
155
+ else:
156
+ profit_df = df.iloc[1:]
157
+ profit = profit_df['profit'].sum()
158
+ commisions = df['commission'].sum()
159
+ fees = df['fee'].sum()
160
+ swap = df['swap'].sum()
161
+ total_profit = commisions + fees + swap + profit
162
+ initial_balance = balance - total_profit
163
+ if balance != 0:
164
+ risk_alowed = (((equity-initial_balance)/equity)*100) * -1
165
+ return round(risk_alowed, 2)
166
+ return 0.0
167
+
168
+ def get_lot(self) -> float:
169
+ """"Get the approprite lot size for a trade"""
170
+ s_info = self.symbol_info
171
+ volume_step = s_info.volume_step
172
+ lot = self.currency_risk()['lot']
173
+ steps = self._volume_step(volume_step)
174
+ if steps >= 2:
175
+ return round(lot, steps)
176
+ else:
177
+ return round(lot)
178
+
179
+ def _volume_step(self, value):
180
+ """Get the number of decimal places in a number"""
181
+
182
+ value_str = str(value)
183
+
184
+ if '.' in value_str:
185
+ decimal_index = value_str.index('.')
186
+ num_digits = len(value_str) - decimal_index - 1
187
+
188
+ return num_digits
189
+ elif value_str == '1':
190
+ return 1
191
+ else:
192
+ return 0
193
+
194
+ def max_trade(self) -> int:
195
+ """calculates the maximum number of trades allowed"""
196
+ minutes = self.get_minutes()
197
+ if self.max_trades is not None:
198
+ max_trades = self.max_trades
199
+ else:
200
+ max_trades = round(minutes / self.TF)
201
+ return max(max_trades, 1)
202
+
203
+ def get_minutes(self) -> int:
204
+ """calculates the number of minutes between two times"""
205
+
206
+ start = datetime.strptime(self.start_time, '%H:%M')
207
+ end = datetime.strptime(self.finishing_time, '%H:%M')
208
+ return (end - start).total_seconds() // 60
209
+
210
+ def get_hours(self) -> int:
211
+ """Calculates the number of hours between two times"""
212
+
213
+ start = datetime.strptime(self.start_time, '%H:%M')
214
+ end = datetime.strptime(self.finishing_time, '%H:%M')
215
+ # Calculate the difference in hours
216
+ hours = (end - start).total_seconds() // 3600 # 1 hour = 3600 seconds
217
+
218
+ return hours
219
+
220
+ def get_std_stop(self, tf: TimeFrame = 'D1', interval: int = 252):
221
+ """
222
+ Calculate the standard deviation-based stop loss level
223
+ for a given financial instrument.
224
+
225
+ Args:
226
+ tf (str): Timeframe for data, default is 'D1' (Daily).
227
+ interval (int): Number of historical data points to consider
228
+ for calculating standard deviation, default is 252.
229
+
230
+ Returns:
231
+ - Standard deviation-based stop loss level, rounded to the nearest point.
232
+ - 0 if the calculated stop loss is less than or equal to 0.
233
+ """
234
+ rate = Rates(self.symbol, tf, 0, interval)
235
+ data = rate.get_rates_from_pos()
236
+ returns = np.diff(data['Close'])
237
+ std = np.std(returns)
238
+ point = self.get_symbol_info(self.symbol).point
239
+ av_price = (self.symbol_info.bid + self.symbol_info.ask)/2
240
+ price_interval = av_price * ((100-std))/100
241
+ sl_point = float((av_price - price_interval) / point)
242
+ sl = round(sl_point)
243
+ min_sl = self.symbol_info.trade_stops_level * 2 \
244
+ + self.get_deviation()
245
+
246
+ return max(sl, min_sl)
247
+
248
+ def get_pchange_stop(self, pchange: Optional[float]):
249
+ """
250
+ Calculate the percentage change-based stop loss level
251
+ for a given financial instrument.
252
+
253
+ Args:
254
+ pchange (float): Percentage change in price to use for calculating stop loss level.
255
+ If pchange is set to None, the stop loss is calculate using std.
256
+
257
+ Returns:
258
+ - Percentage change-based stop loss level, rounded to the nearest point.
259
+ - 0 if the calculated stop loss is <= 0.
260
+ """
261
+ if pchange is not None:
262
+ av_price = (self.symbol_info.bid + self.symbol_info.ask)/2
263
+ price_interval = av_price*((100-pchange))/100
264
+ point = self.get_symbol_info(self.symbol).point
265
+ sl_point = float((av_price - price_interval) / point)
266
+ sl = round(sl_point)
267
+ min_sl = self.symbol_info.trade_stops_level * 2 \
268
+ + self.get_deviation()
269
+ return max(sl, min_sl)
270
+ else:
271
+ # Use std as default pchange
272
+ return self.get_std_stop()
273
+
274
+ def calculate_var(self, tf: TimeFrame = 'D1', interval=252, c=0.95):
275
+ """
276
+ Calculate Value at Risk (VaR) for a given portfolio.
277
+
278
+ Args:
279
+ tf (str): Time frame to use to calculate volatility.
280
+ interval (int): How many periods to use based on time frame.
281
+ c (float): Confidence level for VaR calculation (default is 95%).
282
+
283
+ Returns:
284
+ - VaR value
285
+ """
286
+ rate = Rates(self.symbol, tf, 0, interval)
287
+ prices = rate.get_rates_from_pos()
288
+ prices['return'] = prices['Close'].pct_change()
289
+ prices.dropna(inplace=True)
290
+ P = self.get_account_info().margin_free
291
+ mu = np.mean(prices['return'])
292
+ sigma = np.std(prices['return'])
293
+ var = self.var_cov_var(P, c, mu, sigma)
294
+ return var
295
+
296
+ def var_cov_var(self, P: float, c: float, mu: float, sigma: float):
297
+ """
298
+ Variance-Covariance calculation of daily Value-at-Risk.
299
+
300
+ Args:
301
+ P (float): Portfolio value in USD.
302
+ c (float): Confidence level for Value-at-Risk,e.g., 0.99 for 99% confidence interval.
303
+ mu (float): Mean of the returns of the portfolio.
304
+ sigma (float): Standard deviation of the returns of the portfolio.
305
+
306
+ Returns:
307
+ - float: Value-at-Risk for the given portfolio.
308
+ """
309
+ alpha = norm.ppf(1 - c, mu, sigma)
310
+ return P - P * (alpha + 1)
311
+
312
+ def var_loss_value(self):
313
+ """
314
+ Calculate the stop-loss level based on VaR.
315
+ """
316
+ P = self.get_account_info().margin_free
317
+ trade_risk = self.get_trade_risk()
318
+ loss_allowed = P * trade_risk
319
+ var = self.calculate_var()
320
+ return min(var, loss_allowed)
321
+
322
+ def get_take_profit(self) -> int:
323
+ """calculates the take profit of a trade in points"""
324
+ deviation = self.get_deviation()
325
+ if self.tp is not None:
326
+ return self.tp + deviation
327
+ else:
328
+ return round(self.get_stop_loss()*self.rr)
329
+
330
+ def get_stop_loss(self) -> int:
331
+ """calculates the stop loss of a trade in points"""
332
+ min_sl = self.symbol_info.trade_stops_level * 2 \
333
+ + self.get_deviation()
334
+ if self.sl is not None:
335
+ return max(self.sl, min_sl)
336
+ elif self.sl is None and self.std:
337
+ sl = self.get_std_stop()
338
+ return max(sl, min_sl)
339
+ elif self.sl is None and not self.std:
340
+ risk = self.currency_risk()
341
+ if risk['trade_loss'] != 0:
342
+ sl = round((risk['currency_risk'] / risk['trade_loss']))
343
+ return max(sl, min_sl)
344
+ return min_sl
345
+
346
+ def get_currency_risk(self) -> float:
347
+ """calculates the currency risk of a trade"""
348
+ return round(self.currency_risk()['currency_risk'], 2)
349
+
350
+ def expected_profit(self):
351
+ """Calculate the expected profit per trade"""
352
+ risk = self.get_currency_risk()
353
+ return round(risk*self.rr, 2)
354
+
355
+ def volume(self):
356
+ """Volume per trade"""
357
+
358
+ return self.currency_risk()['volume']
359
+
360
+ def currency_risk(self) -> Dict[str, Union[int, float, Any]]:
361
+ """
362
+ Calculates the currency risk of a trade.
363
+
364
+ Returns:
365
+ Dict[str, Union[int, float, Any]]: A dictionary containing the following keys:
366
+
367
+ - `'currency_risk'`: Dollar amount risk on a single trade.
368
+ - `'trade_loss'`: Loss value per tick in dollars.
369
+ - `'trade_profit'`: Profit value per tick in dollars.
370
+ - `'volume'`: Contract size multiplied by the average price.
371
+ - `'lot'`: Lot size per trade.
372
+ """
373
+
374
+ account_info = self.get_account_info()
375
+ s_info = self.symbol_info
376
+
377
+ laverage = self.get_leverage(self.account_leverage)
378
+ contract_size = s_info.trade_contract_size
379
+
380
+ av_price = (s_info.bid + s_info.ask)/2
381
+ trade_risk = self.get_trade_risk()
382
+ FX = self.get_symbol_type(self.symbol) == 'FX'
383
+ COMD = self.get_symbol_type(self.symbol) == 'COMD'
384
+ FUT = self.get_symbol_type(self.symbol) == 'FUT'
385
+ CRYPTO = self.get_symbol_type(self.symbol) == 'CRYPTO'
386
+ if COMD:
387
+ supported = _COMMD_SUPPORTED_
388
+ if self.symbol.split('.')[0] not in supported:
389
+ raise ValueError(
390
+ f"Currency risk calculation for '{self.symbol}' is not a currently supported. \n"
391
+ f"Supported commodity symbols are: {', '.join(supported)}"
392
+ )
393
+ if FUT:
394
+ supported = _ADMIRAL_MARKETS_FUTURES_
395
+ if self.symbol[:-2] not in supported:
396
+ raise ValueError(
397
+ f"Currency risk calculation for '{self.symbol}' is not a currently supported. \n"
398
+ f"Supported future symbols are: {', '.join(supported)}"
399
+ )
400
+ if trade_risk > 0:
401
+ currency_risk = round(self.var_loss_value(), 5)
402
+ volume = round(currency_risk*laverage)
403
+ _lot = round((volume / (contract_size * av_price)), 2)
404
+ lot = self._check_lot(_lot)
405
+ if COMD and contract_size > 1:
406
+ # lot = volume / av_price / contract_size
407
+ lot = volume / av_price / contract_size
408
+ lot = self._check_lot(_lot)
409
+ if FX:
410
+ __lot = round((volume / contract_size), 2)
411
+ lot = self._check_lot(__lot)
412
+
413
+ tick_value = s_info.trade_tick_value
414
+ tick_value_loss = s_info.trade_tick_value_loss
415
+ tick_value_profit = s_info.trade_tick_value_profit
416
+
417
+ if COMD or FUT or CRYPTO and contract_size > 1:
418
+ tick_value_loss = tick_value_loss / contract_size
419
+ tick_value_profit = tick_value_profit / contract_size
420
+ if (tick_value == 0
421
+ or tick_value_loss == 0
422
+ or tick_value_profit == 0
423
+ ):
424
+ raise ValueError(
425
+ f"""The Tick Values for {self.symbol} is 0.0
426
+ We can not procced with currency risk calculation
427
+ Please check your Broker trade conditions
428
+ and symbol specifications for {self.symbol}"""
429
+ )
430
+ point = float(s_info.point)
431
+
432
+ # Case where the stop loss is given
433
+ if self.sl is not None:
434
+ trade_loss = currency_risk/self.sl
435
+ if self.tp is not None:
436
+ trade_profit = (currency_risk*(self.tp//self.sl))/self.tp
437
+ else:
438
+ trade_profit = (currency_risk*self.rr)/(self.sl*self.rr)
439
+ lot_ = round(trade_loss / (contract_size*tick_value_loss), 2)
440
+ lot = self._check_lot(lot_)
441
+ volume = round(lot * contract_size * av_price)
442
+
443
+ if COMD or CRYPTO and contract_size > 1:
444
+ # trade_risk = points * tick_value_loss * lot
445
+ lot = currency_risk / \
446
+ (self.sl * tick_value_loss*contract_size)
447
+ lot = self._check_lot(lot)
448
+ trade_loss = lot * contract_size * tick_value_loss
449
+
450
+ if FX:
451
+ volume = round(
452
+ (trade_loss * contract_size) / tick_value_loss)
453
+ __lot = round((volume / contract_size), 2)
454
+ lot = self._check_lot(__lot)
455
+
456
+ # Case where the stantard deviation is used
457
+ elif self.std and self.pchange is None and self.sl is None:
458
+ sl = self.get_std_stop()
459
+ infos = self._std_pchange_stop(
460
+ currency_risk, sl, contract_size, tick_value_loss)
461
+ trade_loss, trade_profit, lot, volume = infos
462
+
463
+ # Case where the stop loss is based on a percentage change
464
+ elif self.pchange is not None and not self.std and self.sl is None:
465
+ sl = self.get_pchange_stop(self.pchange)
466
+ infos = self._std_pchange_stop(
467
+ currency_risk, sl, contract_size, tick_value_loss)
468
+ trade_loss, trade_profit, lot, volume = infos
469
+
470
+ # Default cases
471
+ else:
472
+ # Handle FX
473
+ if FX:
474
+ trade_loss = tick_value_loss * (volume / contract_size)
475
+ trade_profit = tick_value_profit * (volume / contract_size)
476
+ else:
477
+ trade_loss = (lot * contract_size) * tick_value_loss
478
+ trade_profit = (lot * contract_size) * tick_value_profit
479
+
480
+ if self.get_symbol_type(self.symbol) == 'IDX':
481
+ rates = self.get_currency_rates(self.symbol)
482
+ if rates['mc'] == rates['pc'] == 'JPY':
483
+ if self.std:
484
+ raise ValueError(
485
+ f"""Please Set std=False or use pchange_sl=True
486
+ or set sl=value or use the default method calculation for {self.symbol}
487
+ Currency risk"""
488
+ )
489
+ lot = lot * contract_size
490
+ volume = round(lot * av_price * contract_size)
491
+ if contract_size == 1:
492
+ volume = round(lot * av_price)
493
+
494
+ return {
495
+ 'currency_risk': currency_risk,
496
+ 'trade_loss': trade_loss,
497
+ 'trade_profit': trade_profit,
498
+ 'volume': round(volume),
499
+ 'lot': lot
500
+ }
501
+ else:
502
+ return {
503
+ 'currency_risk': 0.0,
504
+ 'trade_loss': 0.0,
505
+ 'trade_profit': 0.0,
506
+ 'volume': 0,
507
+ 'lot': 0.01
508
+ }
509
+
510
+ def _std_pchange_stop(self, currency_risk, sl, size, loss):
511
+ """
512
+ Calculate the stop loss level based on standard deviation or percentage change.
513
+
514
+ Args:
515
+ currency_risk (float): The amount of risk in dollars.
516
+ sl (int): Stop loss level in points.
517
+ size (int): Contract size.
518
+ loss (float): Loss value per tick in dollars.
519
+
520
+ """
521
+ trade_loss = currency_risk/sl
522
+ trade_profit = (currency_risk*self.rr)/(sl*self.rr)
523
+ laverage = self.get_account_info().leverage
524
+ av_price = (self.symbol_info.bid + self.symbol_info.ask)/2
525
+ _lot = round(trade_loss/(size*loss), 2)
526
+ lot = self._check_lot(_lot)
527
+
528
+ volume = round(lot * size * av_price)
529
+ if self.get_symbol_type(self.symbol) == 'FX':
530
+ volume = round((trade_loss * size) / loss)
531
+ __lot = round((volume / size), 2)
532
+ lot = self._check_lot(__lot)
533
+
534
+ if (
535
+ self.get_symbol_type(self.symbol) == 'COMD'
536
+ or self.get_symbol_type(self.symbol) == 'CRYPTO'
537
+ and size > 1
538
+ ):
539
+ lot = currency_risk / (sl * loss * size)
540
+ lot = self._check_lot(lot)
541
+ trade_loss = lot * size * loss
542
+ volume = round(lot * size * av_price)
543
+
544
+ return trade_loss, trade_profit, lot, volume
545
+
546
+ def _check_lot(self, lot: float) -> float:
547
+ if lot > self.symbol_info.volume_max:
548
+ return self.symbol_info.volume_max / 2
549
+ elif lot < self.symbol_info.volume_min:
550
+ return self.symbol_info.volume_min
551
+ return lot
552
+
553
+ def get_trade_risk(self):
554
+ """Calculate risk per trade as percentage"""
555
+ total_risk = self.risk_level()
556
+ max_trades = self.max_trade()
557
+ if total_risk < self.max_risk:
558
+ if self.daily_dd is not None:
559
+ trade_risk = (self.daily_dd / max_trades)
560
+ else:
561
+ trade_risk = ((self.max_risk - total_risk) / max_trades)
562
+ return trade_risk
563
+ else:
564
+ return 0
565
+
566
+ def get_leverage(self, account: bool) -> int:
567
+ """
568
+ get the Laverage for each symbol
569
+
570
+ Args:
571
+ account (bool): If set to True, the account leverage will be used
572
+ in risk managment setting
573
+ Notes:
574
+ For FX symbols, account leverage is used by default.
575
+ For Other instruments the account leverage is used if any error
576
+ occurs in leverage calculation.
577
+ """
578
+ AL = self.get_account_info().leverage
579
+ if account:
580
+ return AL
581
+
582
+ if self.get_symbol_type(self.symbol) == 'FX':
583
+ return AL
584
+ else:
585
+ s_info = self.symbol_info
586
+ volume_min = s_info.volume_min
587
+ contract_size = s_info.trade_contract_size
588
+ av_price = (s_info.bid + s_info.ask)/2
589
+ action = random.choice(
590
+ [Mt5.ORDER_TYPE_BUY, Mt5.ORDER_TYPE_SELL]
591
+ )
592
+ margin = Mt5.order_calc_margin(
593
+ action, self.symbol, volume_min, av_price
594
+ )
595
+ if margin == None:
596
+ return AL
597
+ try:
598
+ leverage = (
599
+ volume_min * contract_size * av_price
600
+ ) / margin
601
+ return round(leverage)
602
+ except ZeroDivisionError:
603
+ return AL
604
+
605
+ def get_deviation(self) -> int:
606
+ return self.symbol_info.spread
607
+
608
+ def get_break_even(self) -> int:
609
+ if self.be is not None:
610
+ return self.be
611
+ else:
612
+ stop = self.get_stop_loss()
613
+ spread = self.get_symbol_info(self.symbol).spread
614
+ if stop <= 100:
615
+ be = round((stop + spread) * 0.5)
616
+ elif stop > 100 and stop <= 150:
617
+ be = round((stop + spread) * 0.35)
618
+ elif stop > 150:
619
+ be = round((stop + spread) * 0.25)
620
+ return be
621
+
622
+ def is_risk_ok(self) -> bool:
623
+ if self.risk_level() <= self.max_risk:
624
+ return True
625
+ else:
626
+ return False