bbstrader 0.2.96__py3-none-any.whl → 0.2.97__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.

bbstrader/__main__.py CHANGED
@@ -26,6 +26,7 @@ FONT = pyfiglet.figlet_format("BBSTRADER", font="big")
26
26
 
27
27
  def main():
28
28
  print(Fore.BLUE + FONT)
29
+ print(Fore.WHITE + "")
29
30
  parser = argparse.ArgumentParser(
30
31
  prog="BBSTRADER",
31
32
  usage=USAGE_TEXT,
@@ -66,21 +66,21 @@ class DataHandler(metaclass=ABCMeta):
66
66
  """
67
67
  Returns the last bar updated.
68
68
  """
69
- raise NotImplementedError("Should implement get_latest_bar()")
69
+ pass
70
70
 
71
71
  @abstractmethod
72
72
  def get_latest_bars(self, symbol, N=1, df=True) -> pd.DataFrame | List[pd.Series]:
73
73
  """
74
74
  Returns the last N bars updated.
75
75
  """
76
- raise NotImplementedError("Should implement get_latest_bars()")
76
+ pass
77
77
 
78
78
  @abstractmethod
79
79
  def get_latest_bar_datetime(self, symbol) -> datetime | pd.Timestamp:
80
80
  """
81
81
  Returns a Python datetime object for the last bar.
82
82
  """
83
- raise NotImplementedError("Should implement get_latest_bar_datetime()")
83
+ pass
84
84
 
85
85
  @abstractmethod
86
86
  def get_latest_bar_value(self, symbol, val_type) -> float:
@@ -88,7 +88,7 @@ class DataHandler(metaclass=ABCMeta):
88
88
  Returns one of the Open, High, Low, Close, Adj Close, Volume or Returns
89
89
  from the last bar.
90
90
  """
91
- raise NotImplementedError("Should implement get_latest_bar_value()")
91
+ pass
92
92
 
93
93
  @abstractmethod
94
94
  def get_latest_bars_values(self, symbol, val_type, N=1) -> np.ndarray:
@@ -96,7 +96,7 @@ class DataHandler(metaclass=ABCMeta):
96
96
  Returns the last N bar values from the
97
97
  latest_symbol list, or N-k if less available.
98
98
  """
99
- raise NotImplementedError("Should implement get_latest_bars_values()")
99
+ pass
100
100
 
101
101
  @abstractmethod
102
102
  def update_bars(self):
@@ -105,7 +105,7 @@ class DataHandler(metaclass=ABCMeta):
105
105
  in a tuple OHLCVI format: (datetime, Open, High, Low,
106
106
  Close, Adj Close, Volume, Retruns).
107
107
  """
108
- raise NotImplementedError("Should implement update_bars()")
108
+ pass
109
109
 
110
110
 
111
111
  class BaseCSVDataHandler(DataHandler):
@@ -46,7 +46,7 @@ class ExecutionHandler(metaclass=ABCMeta):
46
46
  Args:
47
47
  event (OrderEvent): Contains an Event object with order information.
48
48
  """
49
- raise NotImplementedError("Should implement execute_order()")
49
+ pass
50
50
 
51
51
 
52
52
  class SimExecutionHandler(ExecutionHandler):
@@ -12,7 +12,7 @@ from loguru import logger
12
12
  from bbstrader.btengine.data import DataHandler
13
13
  from bbstrader.btengine.event import FillEvent, SignalEvent
14
14
  from bbstrader.config import BBSTRADER_DIR
15
- from bbstrader.core.utils import TradeSignal
15
+ from bbstrader.metatrader.trade import TradeSignal
16
16
  from bbstrader.metatrader.account import (
17
17
  Account,
18
18
  AdmiralMarktsGroup,
@@ -55,7 +55,7 @@ class Strategy(metaclass=ABCMeta):
55
55
 
56
56
  @abstractmethod
57
57
  def calculate_signals(self, *args, **kwargs) -> List[TradeSignal]:
58
- raise NotImplementedError("Should implement calculate_signals()")
58
+ pass
59
59
 
60
60
  def check_pending_orders(self, *args, **kwargs): ...
61
61
  def get_update_from_portfolio(self, *args, **kwargs): ...
@@ -503,108 +503,96 @@ class MT5Strategy(Strategy):
503
503
  """
504
504
  Check for pending orders and handle them accordingly.
505
505
  """
506
- for symbol in self.symbols:
507
- dtime = self.data.get_latest_bar_datetime(symbol)
508
506
 
509
- def logmsg(order, type):
510
- return self.logger.info(
511
- f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
512
- f"PRICE @ {order.price}",
513
- custom_time=dtime,
514
- )
507
+ def logmsg(order, type, symbol, dtime):
508
+ return self.logger.info(
509
+ f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
510
+ f"PRICE @ {order.price}",
511
+ custom_time=dtime,
512
+ )
515
513
 
516
- for order in self._orders[symbol]["BLMT"].copy():
517
- if self.data.get_latest_bar_value(symbol, "close") <= order.price:
518
- self.buy_mkt(
519
- order.strategy_id, symbol, order.price, order.quantity, dtime
520
- )
521
- try:
522
- self._orders[symbol]["BLMT"].remove(order)
523
- assert order not in self._orders[symbol]["BLMT"]
524
- logmsg(order, "BUY LIMIT")
525
- except AssertionError:
526
- self._orders[symbol]["BLMT"] = [
527
- o for o in self._orders[symbol]["BLMT"] if o != order
528
- ]
529
- logmsg(order, "BUY LIMIT")
530
- for order in self._orders[symbol]["SLMT"].copy():
531
- if self.data.get_latest_bar_value(symbol, "close") >= order.price:
532
- self.sell_mkt(
533
- order.strategy_id, symbol, order.price, order.quantity, dtime
534
- )
514
+ def process_orders(order_type, condition, execute_fn, log_label, symbol, dtime):
515
+ for order in self._orders[symbol][order_type].copy():
516
+ if condition(order):
517
+ execute_fn(order)
535
518
  try:
536
- self._orders[symbol]["SLMT"].remove(order)
537
- assert order not in self._orders[symbol]["SLMT"]
538
- logmsg(order, "SELL LIMIT")
519
+ self._orders[symbol][order_type].remove(order)
520
+ assert order not in self._orders[symbol][order_type]
539
521
  except AssertionError:
540
- self._orders[symbol]["SLMT"] = [
541
- o for o in self._orders[symbol]["SLMT"] if o != order
522
+ self._orders[symbol][order_type] = [
523
+ o for o in self._orders[symbol][order_type] if o != order
542
524
  ]
543
- logmsg(order, "SELL LIMIT")
544
- for order in self._orders[symbol]["BSTP"].copy():
545
- if self.data.get_latest_bar_value(symbol, "close") >= order.price:
546
- self.buy_mkt(
547
- order.strategy_id, symbol, order.price, order.quantity, dtime
548
- )
549
- try:
550
- self._orders[symbol]["BSTP"].remove(order)
551
- assert order not in self._orders[symbol]["BSTP"]
552
- logmsg(order, "BUY STOP")
553
- except AssertionError:
554
- self._orders[symbol]["BSTP"] = [
555
- o for o in self._orders[symbol]["BSTP"] if o != order
556
- ]
557
- logmsg(order, "BUY STOP")
558
- for order in self._orders[symbol]["SSTP"].copy():
559
- if self.data.get_latest_bar_value(symbol, "close") <= order.price:
560
- self.sell_mkt(
561
- order.strategy_id, symbol, order.price, order.quantity, dtime
562
- )
563
- try:
564
- self._orders[symbol]["SSTP"].remove(order)
565
- assert order not in self._orders[symbol]["SSTP"]
566
- logmsg(order, "SELL STOP")
567
- except AssertionError:
568
- self._orders[symbol]["SSTP"] = [
569
- o for o in self._orders[symbol]["SSTP"] if o != order
570
- ]
571
- logmsg(order, "SELL STOP")
572
- for order in self._orders[symbol]["BSTPLMT"].copy():
573
- if self.data.get_latest_bar_value(symbol, "close") >= order.price:
574
- self.buy_limit(
575
- order.strategy_id,
576
- symbol,
577
- order.stoplimit,
578
- order.quantity,
579
- dtime,
580
- )
581
- try:
582
- self._orders[symbol]["BSTPLMT"].remove(order)
583
- assert order not in self._orders[symbol]["BSTPLMT"]
584
- logmsg(order, "BUY STOP LIMIT")
585
- except AssertionError:
586
- self._orders[symbol]["BSTPLMT"] = [
587
- o for o in self._orders[symbol]["BSTPLMT"] if o != order
588
- ]
589
- logmsg(order, "BUY STOP LIMIT")
590
- for order in self._orders[symbol]["SSTPLMT"].copy():
591
- if self.data.get_latest_bar_value(symbol, "close") <= order.price:
592
- self.sell_limit(
593
- order.strategy_id,
594
- symbol,
595
- order.stoplimit,
596
- order.quantity,
597
- dtime,
598
- )
599
- try:
600
- self._orders[symbol]["SSTPLMT"].remove(order)
601
- assert order not in self._orders[symbol]["SSTPLMT"]
602
- logmsg(order, "SELL STOP LIMIT")
603
- except AssertionError:
604
- self._orders[symbol]["SSTPLMT"] = [
605
- o for o in self._orders[symbol]["SSTPLMT"] if o != order
606
- ]
607
- logmsg(order, "SELL STOP LIMIT")
525
+ logmsg(order, log_label, symbol, dtime)
526
+
527
+ for symbol in self.symbols:
528
+ dtime = self.data.get_latest_bar_datetime(symbol)
529
+ latest_close = self.data.get_latest_bar_value(symbol, "close")
530
+
531
+ process_orders(
532
+ "BLMT",
533
+ lambda o: latest_close <= o.price,
534
+ lambda o: self.buy_mkt(
535
+ o.strategy_id, symbol, o.price, o.quantity, dtime
536
+ ),
537
+ "BUY LIMIT",
538
+ symbol,
539
+ dtime,
540
+ )
541
+
542
+ process_orders(
543
+ "SLMT",
544
+ lambda o: latest_close >= o.price,
545
+ lambda o: self.sell_mkt(
546
+ o.strategy_id, symbol, o.price, o.quantity, dtime
547
+ ),
548
+ "SELL LIMIT",
549
+ symbol,
550
+ dtime,
551
+ )
552
+
553
+ process_orders(
554
+ "BSTP",
555
+ lambda o: latest_close >= o.price,
556
+ lambda o: self.buy_mkt(
557
+ o.strategy_id, symbol, o.price, o.quantity, dtime
558
+ ),
559
+ "BUY STOP",
560
+ symbol,
561
+ dtime,
562
+ )
563
+
564
+ process_orders(
565
+ "SSTP",
566
+ lambda o: latest_close <= o.price,
567
+ lambda o: self.sell_mkt(
568
+ o.strategy_id, symbol, o.price, o.quantity, dtime
569
+ ),
570
+ "SELL STOP",
571
+ symbol,
572
+ dtime,
573
+ )
574
+
575
+ process_orders(
576
+ "BSTPLMT",
577
+ lambda o: latest_close >= o.price,
578
+ lambda o: self.buy_limit(
579
+ o.strategy_id, symbol, o.stoplimit, o.quantity, dtime
580
+ ),
581
+ "BUY STOP LIMIT",
582
+ symbol,
583
+ dtime,
584
+ )
585
+
586
+ process_orders(
587
+ "SSTPLMT",
588
+ lambda o: latest_close <= o.price,
589
+ lambda o: self.sell_limit(
590
+ o.strategy_id, symbol, o.stoplimit, o.quantity, dtime
591
+ ),
592
+ "SELL STOP LIMIT",
593
+ symbol,
594
+ dtime,
595
+ )
608
596
 
609
597
  @staticmethod
610
598
  def calculate_pct_change(current_price, lh_price):
bbstrader/core/utils.py CHANGED
@@ -2,8 +2,6 @@ import configparser
2
2
  import importlib
3
3
  import importlib.util
4
4
  import os
5
- from dataclasses import dataclass
6
- from enum import Enum
7
5
  from typing import Any, Dict, List
8
6
 
9
7
  __all__ = ["load_module", "load_class"]
@@ -90,59 +88,3 @@ def dict_from_ini(file_path, sections: str | List[str] = None) -> Dict[str, Any]
90
88
  except KeyError:
91
89
  raise KeyError(f"{section} not found in the {file_path} file")
92
90
  return ini_dict
93
-
94
-
95
- class TradeAction(Enum):
96
- """
97
- An enumeration class for trade actions.
98
- """
99
-
100
- BUY = "LONG"
101
- LONG = "LONG"
102
- SELL = "SHORT"
103
- EXIT = "EXIT"
104
- BMKT = "BMKT"
105
- SMKT = "SMKT"
106
- BLMT = "BLMT"
107
- SLMT = "SLMT"
108
- BSTP = "BSTP"
109
- SSTP = "SSTP"
110
- SHORT = "SHORT"
111
- BSTPLMT = "BSTPLMT"
112
- SSTPLMT = "SSTPLMT"
113
- EXIT_LONG = "EXIT_LONG"
114
- EXIT_SHORT = "EXIT_SHORT"
115
- EXIT_STOP = "EXIT_STOP"
116
- EXIT_LIMIT = "EXIT_LIMIT"
117
- EXIT_LONG_STOP = "EXIT_LONG_STOP"
118
- EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
119
- EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
120
- EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
121
- EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
122
- EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
123
- EXIT_PROFITABLES = "EXIT_PROFITABLES"
124
- EXIT_LOSINGS = "EXIT_LOSINGS"
125
- EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
126
- EXIT_ALL_ORDERS = "EXIT_ALL_ORDERS"
127
-
128
- def __str__(self):
129
- return self.value
130
-
131
-
132
- @dataclass()
133
- class TradeSignal:
134
- """
135
- A dataclass for storing trading signal.
136
- """
137
-
138
- id: int
139
- symbol: str
140
- action: TradeAction
141
- price: float = None
142
- stoplimit: float = None
143
-
144
- def __repr__(self):
145
- return (
146
- f"TradeSignal(id={self.id}, symbol='{self.symbol}', "
147
- f"action='{self.action.value}', price={self.price}, stoplimit={self.stoplimit})"
148
- )
@@ -607,6 +607,7 @@ class TradeCopier(object):
607
607
  continue
608
608
  self.copy_orders(destination)
609
609
  self.copy_positions(destination)
610
+ time.sleep(0.1)
610
611
  except Exception as e:
611
612
  self.log_error(e)
612
613
  except KeyboardInterrupt:
@@ -1,19 +1,22 @@
1
1
  import os
2
2
  import time
3
- from datetime import datetime
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timedelta
5
+ from enum import Enum
4
6
  from logging import Logger
5
7
  from pathlib import Path
6
8
  from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
7
9
 
8
10
  import pandas as pd
11
+ import quantstats as qs
9
12
  from loguru import logger as log
10
13
  from tabulate import tabulate
11
14
 
12
- from bbstrader.btengine.performance import create_sharpe_ratio
13
15
  from bbstrader.config import BBSTRADER_DIR, config_logger
14
16
  from bbstrader.metatrader.account import INIT_MSG, check_mt5_connection
15
17
  from bbstrader.metatrader.risk import RiskManagement
16
18
  from bbstrader.metatrader.utils import (
19
+ TradeDeal,
17
20
  TradePosition,
18
21
  raise_mt5_error,
19
22
  trade_retcode_message,
@@ -47,6 +50,93 @@ global LOGGER
47
50
  LOGGER = log
48
51
 
49
52
 
53
+ class TradeAction(Enum):
54
+ """
55
+ An enumeration class for trade actions.
56
+ """
57
+
58
+ BUY = "LONG"
59
+ SELL = "SHORT"
60
+ LONG = "LONG"
61
+ SHORT = "SHORT"
62
+ BMKT = "BMKT"
63
+ SMKT = "SMKT"
64
+ BLMT = "BLMT"
65
+ SLMT = "SLMT"
66
+ BSTP = "BSTP"
67
+ SSTP = "SSTP"
68
+ BSTPLMT = "BSTPLMT"
69
+ SSTPLMT = "SSTPLMT"
70
+ EXIT = "EXIT"
71
+ EXIT_LONG = "EXIT_LONG"
72
+ EXIT_SHORT = "EXIT_SHORT"
73
+ EXIT_STOP = "EXIT_STOP"
74
+ EXIT_LIMIT = "EXIT_LIMIT"
75
+ EXIT_LONG_STOP = "EXIT_LONG_STOP"
76
+ EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
77
+ EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
78
+ EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
79
+ EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
80
+ EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
81
+ EXIT_PROFITABLES = "EXIT_PROFITABLES"
82
+ EXIT_LOSINGS = "EXIT_LOSINGS"
83
+ EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
84
+ EXIT_ALL_ORDERS = "EXIT_ALL_ORDERS"
85
+
86
+ def __str__(self):
87
+ return self.value
88
+
89
+
90
+ @dataclass()
91
+ class TradeSignal:
92
+ """
93
+ Represents a trading signal generated by a trading system or strategy.
94
+
95
+ Attributes:
96
+ id (int):
97
+ A unique identifier for the trade signal or the strategy.
98
+
99
+ symbol (str):
100
+ The trading symbol (e.g., stock ticker, forex pair, crypto asset)
101
+ related to the signal.
102
+
103
+ action (TradeAction):
104
+ The trading action to perform.
105
+ Must be an instance of the `TradeAction` enum (e.g., BUY, SELL).
106
+
107
+ price (float, optional):
108
+ The price at which the trade should be executed.
109
+
110
+ stoplimit (float, optional):
111
+ A stop-limit price for the trade.
112
+ Must not be set without specifying a price.
113
+
114
+ comment (str, optional):
115
+ An optional comment or description related to the trade signal.
116
+ """
117
+
118
+ id: int
119
+ symbol: str
120
+ action: TradeAction
121
+ price: float = None
122
+ stoplimit: float = None
123
+ comment: str = None
124
+
125
+ def __post_init__(self):
126
+ if not isinstance(self.action, TradeAction):
127
+ raise TypeError(
128
+ f"action must be of type TradeAction, not {type(self.action)}"
129
+ )
130
+ if self.stoplimit is not None and self.price is None:
131
+ raise ValueError("stoplimit cannot be set without price")
132
+
133
+ def __repr__(self):
134
+ return (
135
+ f"TradeSignal(id={self.id}, symbol='{self.symbol}', action='{self.action.value}', "
136
+ f"price={self.price}, stoplimit={self.stoplimit}), comment='{self.comment}'"
137
+ )
138
+
139
+
50
140
  class Trade(RiskManagement):
51
141
  """
52
142
  Extends the `RiskManagement` class to include specific trading operations,
@@ -437,7 +527,8 @@ class Trade(RiskManagement):
437
527
  action (str): `BMKT` for Market orders or `BLMT`,
438
528
  `BSTP`,`BSTPLMT` for pending orders
439
529
  price (float): The price at which to open an order
440
- stoplimit (float): A price a pending Limit order is set at when the price reaches the 'price' value (this condition is mandatory).
530
+ stoplimit (float): A price a pending Limit order is set at when the price reaches
531
+ the 'price' value (this condition is mandatory).
441
532
  The pending order is not passed to the trading system until that moment
442
533
  id (int): The strategy id or expert Id
443
534
  mm (bool): Weither to put stop loss and tp or not
@@ -527,7 +618,8 @@ class Trade(RiskManagement):
527
618
  action (str): `SMKT` for Market orders
528
619
  or ``SLMT``, ``SSTP``,``SSTPLMT`` for pending orders
529
620
  price (float): The price at which to open an order
530
- stoplimit (float): A price a pending Limit order is set at when the price reaches the 'price' value (this condition is mandatory).
621
+ stoplimit (float): A price a pending Limit order is set at when the price reaches
622
+ the 'price' value (this condition is mandatory).
531
623
  The pending order is not passed to the trading system until that moment
532
624
  id (int): The strategy id or expert Id
533
625
  mm (bool): Weither to put stop loss and tp or not
@@ -594,6 +686,8 @@ class Trade(RiskManagement):
594
686
  Args:
595
687
  comment (str): The comment for the closing position
596
688
  """
689
+ if self.copy_mode:
690
+ return True
597
691
  if self.days_end():
598
692
  return False
599
693
  elif not self.trading_time():
@@ -603,6 +697,9 @@ class Trade(RiskManagement):
603
697
  LOGGER.error(f"Account Risk not allowed, SYMBOL={self.symbol}")
604
698
  self._check(comment)
605
699
  return False
700
+ elif self.is_max_trades_reached():
701
+ LOGGER.error(f"Maximum trades reached for Today, SYMBOL={self.symbol}")
702
+ return False
606
703
  elif self.profit_target():
607
704
  self._check(f"Profit target Reached !!! SYMBOL={self.symbol}")
608
705
  return True
@@ -1582,6 +1679,50 @@ class Trade(RiskManagement):
1582
1679
  comment=comment,
1583
1680
  )
1584
1681
 
1682
+ def get_today_deals(self, group=None) -> List[TradeDeal]:
1683
+ """
1684
+ Get all today deals for a specific symbol or group of symbols
1685
+
1686
+ Args:
1687
+ group (str): Symbol or group or symbol
1688
+ Returns:
1689
+ List[TradeDeal]: List of today deals
1690
+ """
1691
+ date_from = datetime.now() - timedelta(days=2)
1692
+ history = self.get_trades_history(date_from=date_from, group=group, to_df=False)
1693
+ positions_ids = set(
1694
+ [
1695
+ deal.position_id
1696
+ for deal in history
1697
+ if history is not None and deal.magic == self.expert_id
1698
+ ]
1699
+ )
1700
+ today_deals = []
1701
+ for position in positions_ids:
1702
+ deal = self.get_trades_history(
1703
+ date_from=date_from, position=position, to_df=False
1704
+ )
1705
+ if deal is not None and len(deal) == 2:
1706
+ deal_time = datetime.fromtimestamp(deal[1].time)
1707
+ if deal_time.date() == datetime.now().date():
1708
+ today_deals.append(deal[1])
1709
+ return today_deals
1710
+
1711
+ def is_max_trades_reached(self) -> bool:
1712
+ """
1713
+ Check if the maximum number of trades for the day has been reached.
1714
+
1715
+ :return: bool
1716
+ """
1717
+ negative_deals = 0
1718
+ today_deals = self.get_today_deals(group=self.symbol)
1719
+ for deal in today_deals:
1720
+ if deal.profit < 0:
1721
+ negative_deals += 1
1722
+ if negative_deals >= self.max_trades:
1723
+ return True
1724
+ return False
1725
+
1585
1726
  def get_stats(self) -> Tuple[Dict[str, Any]]:
1586
1727
  """
1587
1728
  get some stats about the trading day and trading history
@@ -1594,11 +1735,14 @@ class Trade(RiskManagement):
1594
1735
  loss_trades = 0
1595
1736
  win_trades = 0
1596
1737
  balance = self.get_account_info().balance
1597
- deals = len(self.opened_positions)
1738
+ today_deals = self.get_today_deals(group=self.symbol)
1739
+ deals = len(today_deals)
1598
1740
  if deals != 0:
1599
- for position in self.opened_positions:
1741
+ for position in today_deals:
1600
1742
  time.sleep(0.1)
1601
- history = self.get_trades_history(position=position, to_df=False)
1743
+ history = self.get_trades_history(
1744
+ position=position.position_id, to_df=False
1745
+ )
1602
1746
  if history is not None and len(history) == 2:
1603
1747
  result = history[1].profit
1604
1748
  comm = history[0].commission
@@ -1641,13 +1785,12 @@ class Trade(RiskManagement):
1641
1785
  _fees = df2["fee"].sum()
1642
1786
  _swap = df2["swap"].sum()
1643
1787
  total_profit = commisions + _fees + _swap + profit
1644
- account_info = self.get_account_info()
1645
- balance = account_info.balance
1788
+ balance = self.get_account_info().balance
1646
1789
  initial_balance = balance - total_profit
1647
1790
  profittable = "Yes" if balance > initial_balance else "No"
1648
1791
  stats2 = {"total_profit": total_profit, "profitability": profittable}
1649
1792
  else:
1650
- stats2 = {"total_profit": 0, "profitability": 0}
1793
+ stats2 = {"total_profit": 0, "profitability": "No"}
1651
1794
  return (stats1, stats2)
1652
1795
 
1653
1796
  def sharpe(self):
@@ -1658,6 +1801,9 @@ class Trade(RiskManagement):
1658
1801
  those compared to a benchmark.
1659
1802
  """
1660
1803
  # Get total history
1804
+ import warnings
1805
+
1806
+ warnings.filterwarnings("ignore")
1661
1807
  df2 = self.get_trades_history()
1662
1808
  if df2 is None:
1663
1809
  return 0.0
@@ -1665,7 +1811,7 @@ class Trade(RiskManagement):
1665
1811
  profit = df[["profit", "commission", "fee", "swap"]].sum(axis=1)
1666
1812
  returns = profit.pct_change(fill_method=None)
1667
1813
  periods = self.max_trade() * 252
1668
- sharpe = create_sharpe_ratio(returns, periods=periods)
1814
+ sharpe = qs.stats.sharpe(returns, periods=periods)
1669
1815
 
1670
1816
  return round(sharpe, 3)
1671
1817
 
bbstrader/models/risk.py CHANGED
@@ -68,7 +68,7 @@ class RiskModel(metaclass=ABCMeta):
68
68
  such as historical returns or volatility, used to
69
69
  assess market conditions.
70
70
  """
71
- raise NotImplementedError("Should implement which_trade_allowed()")
71
+ pass
72
72
 
73
73
  @abstractmethod
74
74
  def which_quantity_allowed(self):
@@ -76,7 +76,7 @@ class RiskModel(metaclass=ABCMeta):
76
76
  Defines the strategy for asset allocation within
77
77
  the portfolio to optimize risk-reward ratio.
78
78
  """
79
- raise NotImplementedError("Should implement which_quantity_allowed()")
79
+ pass
80
80
 
81
81
 
82
82
  class HMMRiskManager(RiskModel):
@@ -8,9 +8,8 @@ from loguru import logger as log
8
8
 
9
9
  from bbstrader.btengine.strategy import MT5Strategy, Strategy
10
10
  from bbstrader.config import BBSTRADER_DIR
11
- from bbstrader.core.utils import TradeAction
12
11
  from bbstrader.metatrader.account import Account, check_mt5_connection
13
- from bbstrader.metatrader.trade import Trade
12
+ from bbstrader.metatrader.trade import Trade, TradeAction
14
13
  from bbstrader.trading.utils import send_message
15
14
 
16
15
  try:
@@ -114,6 +113,7 @@ def _mt5_execution(
114
113
  comment,
115
114
  **kwargs,
116
115
  ):
116
+ global logger
117
117
  logger = kwargs.get("logger", log)
118
118
 
119
119
  def _print_exc(dm, msg):
@@ -168,6 +168,7 @@ def _mt5_execution(
168
168
  if symbol not in weights:
169
169
  continue
170
170
  trade = trades_instances[symbol]
171
+ assert daily_risk is not None
171
172
  dailydd = round(weights[symbol] * daily_risk, 5)
172
173
  trade.dailydd = dailydd
173
174
 
@@ -212,7 +213,9 @@ def _mt5_execution(
212
213
  f"Running {STRATEGY} Strategy in {time_frame} Interval ..., ACCOUNT={ACCOUNT}"
213
214
  )
214
215
 
215
- def run_trade_algorithm(signal, symbol, id, trade: Trade, price, stoplimit):
216
+ def run_trade_algorithm(
217
+ signal, symbol, id, trade: Trade, price, stoplimit, comment
218
+ ):
216
219
  signal = "BMKT" if signal == "LONG" or signal == "BUY" else signal
217
220
  signal = "SMKT" if signal == "SHORT" or signal == "SELL" else signal
218
221
  info = f"SIGNAL = {signal}, SYMBOL={symbol}, STRATEGY={STRATEGY}, TIMEFRAME={time_frame} , ACCOUNT={ACCOUNT}"
@@ -242,9 +245,13 @@ def _mt5_execution(
242
245
  if notify:
243
246
  _send_notification(sigmsg, symbol)
244
247
  if position_type in POSITIONS_TYPES:
245
- trade.close_positions(position_type=order_type, id=id)
248
+ trade.close_positions(
249
+ position_type=order_type, id=id, comment=comment
250
+ )
246
251
  else:
247
- trade.close_orders(order_type=order_type, id=id)
252
+ trade.close_orders(
253
+ order_type=order_type, id=id, comment=comment
254
+ )
248
255
  elif signal in BUYS and not long_market[symbol]:
249
256
  if use_trade_time:
250
257
  if time_intervals % trade_time == 0 or buys[symbol] is None:
@@ -412,6 +419,7 @@ def _mt5_execution(
412
419
  trade,
413
420
  signal.price,
414
421
  signal.stoplimit,
422
+ signal.comment or comment,
415
423
  )
416
424
  else:
417
425
  if len(symbols) >= 10:
@@ -822,7 +830,7 @@ def mt5_engine(account_id: str, **kwargs):
822
830
 
823
831
  def RunMt5Engines(accounts: Dict[str, Dict], start_delay: float = 1.0):
824
832
  """Runs multiple MT5 execution engines in parallel using multiprocessing.
825
-
833
+
826
834
  Args:
827
835
  accounts: Dictionary of accounts to run the execution engines on.
828
836
  Keys are the account names or IDs and values are the parameters for the execution engine.
@@ -1,5 +1,11 @@
1
1
  """
2
2
  Strategies module for trading strategies backtesting and execution.
3
+
4
+ DISCLAIMER:
5
+ This module is for educational purposes only and should not be
6
+ considered as financial advice. Always consult with a qualified financial advisor before making any investment decisions.
7
+ The authors and contributors of this module are not responsible for any financial losses or damages incurred as a result of using
8
+ this module or the information contained herein.
3
9
  """
4
10
 
5
11
  from datetime import datetime
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bbstrader
3
- Version: 0.2.96
3
+ Version: 0.2.97
4
4
  Summary: Simplified Investment & Trading Toolkit
5
5
  Home-page: https://github.com/bbalouki/bbstrader
6
6
  Download-URL: https://pypi.org/project/bbstrader/
@@ -63,6 +63,7 @@ Requires-Dist: tweepy
63
63
  Requires-Dist: beautifulsoup4
64
64
  Requires-Dist: dash
65
65
  Requires-Dist: nltk
66
+ Requires-Dist: spacy
66
67
  Requires-Dist: textblob
67
68
  Requires-Dist: vaderSentiment
68
69
  Provides-Extra: mt5
@@ -1,29 +1,29 @@
1
1
  bbstrader/__ini__.py,sha256=v6zyJHj5FMRL-_P7AwnTGbCF-riMqhqlTvDgfulj7go,621
2
- bbstrader/__main__.py,sha256=9KDkv2eXb01wuGW5AB-K6bAecSTXG4eCAErmWxTUx1E,1498
2
+ bbstrader/__main__.py,sha256=EtfD-TrAphboYxwfe7-WeD-1d4bJceeBputFrV-W-qQ,1525
3
3
  bbstrader/compat.py,sha256=djbHMvTvy0HYm1zyZ6Ttp_LMwP2PqTSVw1r7pqbz7So,487
4
4
  bbstrader/config.py,sha256=c2nCUw-bYWf5kkyFls5Nqld8HdMczexSilTni7rYUBw,3973
5
5
  bbstrader/tseries.py,sha256=H4D_A966HdN8YjBfuCcF8QBQdhjOrTcidR98wP2KN_I,68339
6
6
  bbstrader/btengine/__init__.py,sha256=y1btjaEfhWsH8vuE7mBRpP9Tu-Azt9REhuVYsPCAfBU,2955
7
7
  bbstrader/btengine/backtest.py,sha256=ZzGhoN-_g0cF-OCyk173imze2OXEhykHTUiJ9MowDO8,14582
8
- bbstrader/btengine/data.py,sha256=UpHW27o7cb9ibQJ6JYovfG_zyxA9SCwR0-Zv7zUQxSM,27296
8
+ bbstrader/btengine/data.py,sha256=Tuc6M8itbGpPjsfRpZBB8v0FJpPt7-hUkP6I5meP0Sg,26927
9
9
  bbstrader/btengine/event.py,sha256=38mhZH9d53C4x7bZER2B0O6M18txzS4u7zveKyxeP5Y,8603
10
- bbstrader/btengine/execution.py,sha256=Ij5dLc9mGgtWp2dKAH5oURplA3RS_ZtwTwSrp9IYfpk,10644
10
+ bbstrader/btengine/execution.py,sha256=mXY0tyFv6G1XF48R5Kx2-pDnwu1mUyOc0ZeeysQG62A,10587
11
11
  bbstrader/btengine/performance.py,sha256=1ecWrTzHBQbk4ORvbTEKxwCzlL1brcXOEUwgbnjAwx4,12470
12
12
  bbstrader/btengine/portfolio.py,sha256=mh2_zNJDmKzb0lo55PXhbXYxXMmXRA4YLkgzwxRMuZE,16110
13
13
  bbstrader/btengine/scripts.py,sha256=8o66dq4Ex4DsH4s8xvJqUOFjLzZJSnbBvvNBzohtzoE,4837
14
- bbstrader/btengine/strategy.py,sha256=c-wvotJdhHu5FWAlPkv33LfjoW-7ID2G0Di_hc7CYMM,33217
14
+ bbstrader/btengine/strategy.py,sha256=PFIb9gUWahaXpzCFL-RchpBumlEJsz5Elbz_A2qZ8Jc,31285
15
15
  bbstrader/core/__init__.py,sha256=GIFzFSStPfE0XM2j7mDeZZQeMTh_AwPsDOQXwMVJLgw,97
16
16
  bbstrader/core/data.py,sha256=VPuynoT0uFYduh7la8gZSnEv_Gq8Xu2vJZJ7TfQMll8,18797
17
- bbstrader/core/utils.py,sha256=lmL-hpaVHxuhX-V5wgBslnA4Ob89iY1omoSvL3FFOro,4120
17
+ bbstrader/core/utils.py,sha256=WjuabzBjhY65ku2KL-f7CMalE2x-wrX-6mCA_qhhFPE,2728
18
18
  bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  bbstrader/metatrader/__init__.py,sha256=A5Ye9tpc2sp9Xk5qjKw-EfYsoRcZtAt8nqvC3tCtZs8,333
21
21
  bbstrader/metatrader/account.py,sha256=qAFqCPmoUjiqjY5M9sjbSvh-STjQUGdFCHOEcB7uoDI,56493
22
- bbstrader/metatrader/copier.py,sha256=AOuoNWafNiQiZ4IIjWBG5b7UhdyYMXK6-myEPDl1O9g,31761
22
+ bbstrader/metatrader/copier.py,sha256=-cFroX4M1q9rlupU4xLP0N6lR8ZbP_izUOQ2sBe_lFc,31797
23
23
  bbstrader/metatrader/rates.py,sha256=0bKyjGnafZ19DwC0-IxXUBJzOdAo-cNmhabwhKLxcos,20585
24
24
  bbstrader/metatrader/risk.py,sha256=pwG4q1_uPGgPlokDGVNtd04O6p28yIbgT-evvHuo-Qc,27029
25
25
  bbstrader/metatrader/scripts.py,sha256=Yjp7Un-wDTInptHS_rPFpXNKWbVM871VEkaHxsO2MPQ,2115
26
- bbstrader/metatrader/trade.py,sha256=6FbFywRPu0_TlB31lTMb3VjACw1GPQsi7RjQNtJnh6w,76780
26
+ bbstrader/metatrader/trade.py,sha256=Q1cArcpMnovRTED0kwMybcpwPB28rQKXLcJV4jldF0w,81202
27
27
  bbstrader/metatrader/utils.py,sha256=lYIvAeiL_ceACmkVNo4-QRopias08KyBUI7THfdj3c0,17103
28
28
  bbstrader/models/__init__.py,sha256=s2mJrtKePXQaw_PvcrtPCD2mPCdVXP4Myzg0MlLVipo,547
29
29
  bbstrader/models/factors.py,sha256=5kSAOS1MHvTZ-Ti03TtjOxl_EvC-V_9e389xeR_82ak,13020
@@ -31,15 +31,15 @@ bbstrader/models/ml.py,sha256=tCr7YyODl0CDoOUpYqJ1q12ls86Sc-_Fu3b2Y0Z7TJ8,47551
31
31
  bbstrader/models/nlp.py,sha256=P7SYaTIqEBldjwYfS6IrO66Y6-ioDXUrCSf3bZxQrDE,28073
32
32
  bbstrader/models/optimization.py,sha256=vnks6uxFZdqXgxaZJNxq8z0IH45KZ8yaXo38JhIVKGc,6399
33
33
  bbstrader/models/portfolio.py,sha256=r-47Zrn2r7iKCHm5YVtwkbBJXAZGM3QYy-rXCWY9-Bg,8079
34
- bbstrader/models/risk.py,sha256=Efr7dKcr37n75TXv5rcgSYNDPu2Plzcn65AOOIh9x_8,15007
34
+ bbstrader/models/risk.py,sha256=JQOXPshMOru6Eb0AMU6oKCNzg6mlGfL6_tN90lWcVBE,14878
35
35
  bbstrader/trading/__init__.py,sha256=ycLyuuxN5SujqtzR9X0Q74UQfK93q2va-GGAXdr-KS8,457
36
- bbstrader/trading/execution.py,sha256=cZ--FDEUaFVyLXG3Iym39nwW42xAs5XZIh8BbhdLwhE,34760
36
+ bbstrader/trading/execution.py,sha256=4-XyRll1x8gfF68cTZXjtRF79csDx_oKNI0F71cv1Bw,35028
37
37
  bbstrader/trading/scripts.py,sha256=wR5TUrHn-Cd2kzfURXn14VTCEZ-QA8ydwYHayMPK0oI,5720
38
- bbstrader/trading/strategies.py,sha256=yibrXPa8yw8KCNkJEmNaygjfleCNgA_T58vizGS584I,36159
38
+ bbstrader/trading/strategies.py,sha256=D_M91UwRiFwZKvKFKmz427TPd-dNyWWbhehaP8RI17c,36539
39
39
  bbstrader/trading/utils.py,sha256=57dKF9dcRu04oU2VRqydRrzW39dCW2wlDWhVt-sZdRw,1857
40
- bbstrader-0.2.96.dist-info/licenses/LICENSE,sha256=ZwC_RqqGmOPBUiMDKqLyJZ5HBeHq53LpL7TMRzrJY8c,1094
41
- bbstrader-0.2.96.dist-info/METADATA,sha256=4R54TJMj-pORXTs5xbcH4BvmAkwnacbqatjhJExwZzc,11569
42
- bbstrader-0.2.96.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
43
- bbstrader-0.2.96.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
44
- bbstrader-0.2.96.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
45
- bbstrader-0.2.96.dist-info/RECORD,,
40
+ bbstrader-0.2.97.dist-info/licenses/LICENSE,sha256=ZwC_RqqGmOPBUiMDKqLyJZ5HBeHq53LpL7TMRzrJY8c,1094
41
+ bbstrader-0.2.97.dist-info/METADATA,sha256=mBnvqbUXVjE-dl5A5tkdYRuGd_fh2oKU55K-Ryfm9po,11590
42
+ bbstrader-0.2.97.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
43
+ bbstrader-0.2.97.dist-info/entry_points.txt,sha256=0yDCbhbgHswOzJnY5wRSM_FjjyMHGvY7lJpSSVh0xtI,54
44
+ bbstrader-0.2.97.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
45
+ bbstrader-0.2.97.dist-info/RECORD,,