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

@@ -203,10 +203,11 @@ class BaseCSVDataHandler(DataHandler):
203
203
  self.symbol_data[s] = self.symbol_data[s].reindex(
204
204
  index=comb_index, method="pad"
205
205
  )
206
+ if "adj_close" not in new_names:
207
+ self.columns.append("adj_close")
208
+ self.symbol_data[s]["adj_close"] = self.symbol_data[s]["close"]
206
209
  self.symbol_data[s]["returns"] = (
207
- self.symbol_data[s][
208
- "adj_close" if "adj_close" in new_names else "close"
209
- ]
210
+ self.symbol_data[s]["adj_close" if "adj_close" in new_names else "close"]
210
211
  .pct_change()
211
212
  .dropna()
212
213
  )
@@ -229,7 +230,7 @@ class BaseCSVDataHandler(DataHandler):
229
230
  try:
230
231
  bars_list = self.latest_symbol_data[symbol]
231
232
  except KeyError:
232
- print("Symbol not available in the historical data set.")
233
+ print(f"{symbol} not available in the historical data set.")
233
234
  raise
234
235
  else:
235
236
  return bars_list[-1]
@@ -244,7 +245,7 @@ class BaseCSVDataHandler(DataHandler):
244
245
  try:
245
246
  bars_list = self.latest_symbol_data[symbol]
246
247
  except KeyError:
247
- print("Symbol not available in the historical data set.")
248
+ print(f"{symbol} not available in the historical data set.")
248
249
  raise
249
250
  else:
250
251
  if df:
@@ -260,7 +261,7 @@ class BaseCSVDataHandler(DataHandler):
260
261
  try:
261
262
  bars_list = self.latest_symbol_data[symbol]
262
263
  except KeyError:
263
- print("Symbol not available in the historical data set.")
264
+ print(f"{symbol} not available in the historical data set.")
264
265
  raise
265
266
  else:
266
267
  return bars_list[-1][0]
@@ -274,7 +275,7 @@ class BaseCSVDataHandler(DataHandler):
274
275
  try:
275
276
  bars_list = self.get_latest_bars(symbol, N)
276
277
  except KeyError:
277
- print("Symbol not available in the historical data set.")
278
+ print(f"{symbol} not available in the historical data set for .")
278
279
  raise
279
280
  else:
280
281
  return [b[0] for b in bars_list]
@@ -287,14 +288,14 @@ class BaseCSVDataHandler(DataHandler):
287
288
  try:
288
289
  bars_list = self.latest_symbol_data[symbol]
289
290
  except KeyError:
290
- print("Symbol not available in the historical data set.")
291
+ print(f"{symbol} not available in the historical data set.")
291
292
  raise
292
293
  else:
293
294
  try:
294
295
  return getattr(bars_list[-1][1], val_type)
295
296
  except AttributeError:
296
297
  print(
297
- f"Value type {val_type} not available in the historical data set."
298
+ f"Value type {val_type} not available in the historical data set for {symbol}."
298
299
  )
299
300
  raise
300
301
 
@@ -306,7 +307,7 @@ class BaseCSVDataHandler(DataHandler):
306
307
  try:
307
308
  bars_list = self.get_latest_bars(symbol, N, df=False)
308
309
  except KeyError:
309
- print("That symbol is not available in the historical data set.")
310
+ print(f"{symbol} not available in the historical data set.")
310
311
  raise
311
312
  else:
312
313
  try:
@@ -497,6 +498,8 @@ class YFDataHandler(BaseCSVDataHandler):
497
498
  multi_level_index=False,
498
499
  progress=False,
499
500
  )
501
+ if "Adj Close" not in data.columns:
502
+ data["Adj Close"] = data["Close"]
500
503
  if data.empty:
501
504
  raise ValueError(f"No data found for {symbol}")
502
505
  data.to_csv(filepath)
@@ -195,14 +195,12 @@ class Portfolio(object):
195
195
  try:
196
196
  price = self.bars.get_latest_bar_value(symbol, "adj_close")
197
197
  return price
198
- except AttributeError:
198
+ except (AttributeError, KeyError, ValueError):
199
199
  try:
200
200
  price = self.bars.get_latest_bar_value(symbol, "close")
201
201
  return price
202
- except AttributeError:
203
- raise AttributeError(
204
- "Bars object must have 'adj_close' or 'close' prices"
205
- )
202
+ except (AttributeError, KeyError, ValueError):
203
+ return 0.0
206
204
 
207
205
  def update_timeindex(self, event: MarketEvent):
208
206
  """
bbstrader/compat.py ADDED
@@ -0,0 +1,23 @@
1
+ import platform
2
+ import sys
3
+
4
+
5
+ def setup_mock_metatrader():
6
+ """Mock MetaTrader5 on Linux to prevent import errors."""
7
+ if platform.system() == "Linux":
8
+ from unittest.mock import MagicMock
9
+
10
+ class Mock(MagicMock):
11
+ @classmethod
12
+ def __getattr__(cls, name):
13
+ return MagicMock()
14
+
15
+ MOCK_MODULES = ["MetaTrader5"]
16
+ sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
17
+
18
+ print(
19
+ "Warning: MetaTrader5 is not available on Linux. A mock version is being used."
20
+ )
21
+
22
+
23
+ setup_mock_metatrader()
@@ -4,6 +4,7 @@ import urllib.request
4
4
  from datetime import datetime
5
5
  from typing import Any, Dict, List, Literal, Optional, Tuple, Union
6
6
 
7
+ from bbstrader import compat # noqa: F401
7
8
  import MetaTrader5 as mt5
8
9
  import pandas as pd
9
10
  from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
@@ -1,12 +1,14 @@
1
1
  from datetime import datetime
2
2
  from typing import Optional, Union
3
3
 
4
+ from bbstrader import compat # noqa: F401
4
5
  import MetaTrader5 as Mt5
5
6
  import pandas as pd
6
7
  from exchange_calendars import get_calendar, get_calendar_names
7
8
  from pandas.tseries.holiday import USFederalHolidayCalendar
8
9
  from pandas.tseries.offsets import CustomBusinessDay
9
10
 
11
+
10
12
  from bbstrader.metatrader.account import AMG_EXCHANGES, Account, check_mt5_connection
11
13
  from bbstrader.metatrader.utils import TIMEFRAMES, TimeFrame, raise_mt5_error
12
14
 
@@ -172,7 +174,7 @@ class Rates(object):
172
174
  480,
173
175
  720,
174
176
  ]:
175
- key = f"{minutes//60}h" if minutes >= 60 else f"{minutes}m"
177
+ key = f"{minutes // 60}h" if minutes >= 60 else f"{minutes}m"
176
178
  time_frame_mapping[key] = int(td * (60 / minutes) * sd)
177
179
  time_frame_mapping["D1"] = int(td)
178
180
 
@@ -2,6 +2,7 @@ import random
2
2
  from datetime import datetime
3
3
  from typing import Any, Dict, Optional, Union
4
4
 
5
+ from bbstrader import compat # noqa: F401
5
6
  import MetaTrader5 as Mt5
6
7
  from scipy.stats import norm
7
8
 
@@ -4,6 +4,7 @@ from datetime import datetime
4
4
  from logging import Logger
5
5
  from typing import Any, Callable, Dict, List, Literal, Optional, Tuple
6
6
 
7
+ from bbstrader import compat # noqa: F401
7
8
  import MetaTrader5 as Mt5
8
9
  import pandas as pd
9
10
  from tabulate import tabulate
@@ -89,7 +90,7 @@ class Trade(RiskManagement):
89
90
  symbol: str = "EURUSD",
90
91
  expert_name: str = "bbstrader",
91
92
  expert_id: int = 9818,
92
- version: str = "1.0",
93
+ version: str = "2.0",
93
94
  target: float = 5.0,
94
95
  start_time: str = "1:00",
95
96
  finishing_time: str = "23:00",
@@ -604,7 +605,7 @@ class Trade(RiskManagement):
604
605
  result = self.send_order(request)
605
606
  if result.retcode == Mt5.TRADE_RETCODE_DONE:
606
607
  break
607
- elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME: #10014
608
+ elif result.retcode == Mt5.TRADE_RETCODE_INVALID_VOLUME: # 10014
608
609
  new_volume = int(request["volume"])
609
610
  if new_volume >= 1:
610
611
  request["volume"] = new_volume
@@ -660,7 +661,7 @@ class Trade(RiskManagement):
660
661
  self.sell_positions.append(position.ticket)
661
662
  profit = round(self.get_account_info().profit, 5)
662
663
  order_info = (
663
- f"2. {order_type} Position Opened, Symbol: {self.symbol}, Price: @{round(position.price_open,5)}, "
664
+ f"2. {order_type} Position Opened, Symbol: {self.symbol}, Price: @{round(position.price_open, 5)}, "
664
665
  f"Sl: @{position.sl} Tp: @{position.tp}"
665
666
  )
666
667
  self.logger.info(order_info)
@@ -922,21 +923,35 @@ class Trade(RiskManagement):
922
923
  return current_profit >= th_profit
923
924
  return False
924
925
 
926
+ def _get_trail_after_points(self, trail_after_points: int | str) -> int:
927
+ if isinstance(trail_after_points, str):
928
+ if trail_after_points == "SL":
929
+ return self.get_stop_loss()
930
+ elif trail_after_points == "TP":
931
+ return self.get_take_profit()
932
+ elif trail_after_points == "BE":
933
+ return self.get_break_even()
934
+ # TODO: Add other combinations (e.g. "SL+TP", "SL+BE", "TP+BE", "SL*N", etc.)
935
+ return trail_after_points
936
+
925
937
  def break_even(
926
938
  self,
927
939
  mm=True,
928
940
  id: Optional[int] = None,
929
941
  trail: Optional[bool] = True,
930
942
  stop_trail: Optional[int] = None,
931
- trail_after_points: Optional[int] = None,
943
+ trail_after_points: int | str = None,
932
944
  be_plus_points: Optional[int] = None,
933
945
  ):
934
946
  """
935
- This function checks if it's time to set the break-even level for a trading position.
936
- If it is, it sets the break-even level. If the break-even level has already been set,
937
- it checks if the price has moved in a favorable direction.
938
- If it has, and the trail parameter is set to True, it updates
939
- the break-even level based on the trail_after_points and stop_trail parameters.
947
+ Manages the break-even level of a trading position.
948
+
949
+ This function checks whether it is time to set a break-even stop loss for an open position.
950
+ If the break-even level is already set, it monitors price movement and updates the stop loss
951
+ accordingly if the `trail` parameter is enabled.
952
+
953
+ When `trail` is enabled, the function dynamically adjusts the break-even level based on the
954
+ `trail_after_points` and `stop_trail` parameters.
940
955
 
941
956
  Args:
942
957
  id (int): The strategy ID or expert ID.
@@ -944,8 +959,12 @@ class Trade(RiskManagement):
944
959
  trail (bool): Whether to trail the stop loss or not.
945
960
  stop_trail (int): Number of points to trail the stop loss by.
946
961
  It represent the distance from the current price to the stop loss.
947
- trail_after_points (int): Number of points in profit
962
+ trail_after_points (int, str): Number of points in profit
948
963
  from where the strategy will start to trail the stop loss.
964
+ If set to str, it must be one of the following values:
965
+ - 'SL' to trail the stop loss after the profit reaches the stop loss level in points.
966
+ - 'TP' to trail the stop loss after the profit reaches the take profit level in points.
967
+ - 'BE' to trail the stop loss after the profit reaches the break-even level in points.
949
968
  be_plus_points (int): Number of points to add to the break-even level.
950
969
  Represents the minimum profit to secure.
951
970
  """
@@ -956,16 +975,19 @@ class Trade(RiskManagement):
956
975
  positions = self.get_positions(symbol=self.symbol)
957
976
  be = self.get_break_even()
958
977
  if trail_after_points is not None:
959
- assert trail_after_points > be, (
960
- "trail_after_points must be greater than break even" " or set to None"
961
- )
978
+ if isinstance(trail_after_points, int):
979
+ assert trail_after_points > be, (
980
+ "trail_after_points must be greater than break even or set to None"
981
+ )
982
+ trail_after_points = self._get_trail_after_points(trail_after_points)
962
983
  if positions is not None:
963
984
  for position in positions:
964
985
  if position.magic == Id:
965
- size = self.get_symbol_info(self.symbol).trade_tick_size
966
- value = self.get_symbol_info(self.symbol).trade_tick_value
967
- point = self.get_symbol_info(self.symbol).point
968
- digits = self.get_symbol_info(self.symbol).digits
986
+ symbol_info = self.get_symbol_info(self.symbol)
987
+ size = symbol_info.trade_tick_size
988
+ value = symbol_info.trade_tick_value
989
+ point = symbol_info.point
990
+ digits = symbol_info.digits
969
991
  points = position.profit * (size / value / position.volume)
970
992
  break_even = float(points / point) >= be
971
993
  if break_even:
@@ -2,6 +2,7 @@ from datetime import datetime
2
2
  from enum import Enum
3
3
  from typing import NamedTuple, Optional
4
4
 
5
+ from bbstrader import compat # noqa: F401
5
6
  import MetaTrader5 as MT5
6
7
 
7
8
  __all__ = [
@@ -621,7 +621,7 @@ class MT5ExecutionEngine:
621
621
  optimizer: str = "equal",
622
622
  trail: bool = True,
623
623
  stop_trail: Optional[int] = None,
624
- trail_after_points: Optional[int] = None,
624
+ trail_after_points: int | str = None,
625
625
  be_plus_points: Optional[int] = None,
626
626
  show_positions_orders: bool = False,
627
627
  iter_time: int | float = 5,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: bbstrader
3
- Version: 0.2.7
3
+ Version: 0.2.8
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/
@@ -19,6 +19,8 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Operating System :: POSIX :: Linux
23
+ Classifier: Operating System :: MacOS
22
24
  Classifier: License :: OSI Approved :: MIT License
23
25
  Description-Content-Type: text/markdown
24
26
  License-File: LICENSE
@@ -53,8 +55,7 @@ Requires-Dist: financetoolkit
53
55
  Requires-Dist: tables
54
56
  Requires-Dist: lightgbm
55
57
  Requires-Dist: alphalens-reloaded
56
- Provides-Extra: mt5
57
- Requires-Dist: MetaTrader5; extra == "mt5"
58
+ Requires-Dist: MetaTrader5
58
59
  Dynamic: author
59
60
  Dynamic: author-email
60
61
  Dynamic: classifier
@@ -66,7 +67,6 @@ Dynamic: keywords
66
67
  Dynamic: license
67
68
  Dynamic: maintainer
68
69
  Dynamic: project-url
69
- Dynamic: provides-extra
70
70
  Dynamic: requires-dist
71
71
  Dynamic: summary
72
72
 
@@ -1,13 +1,14 @@
1
1
  bbstrader/__ini__.py,sha256=x9sw2BKGnPi3QF4MwBqJpCpyUgui1StbvBWGNISVAhQ,548
2
+ bbstrader/compat.py,sha256=feVc7oF-TMk3uFIiJcs6qTV92cpujr_1PuPfelNRg4A,616
2
3
  bbstrader/config.py,sha256=USwdS5qaGbc1Wp5rF0ckP3R1HEZJ8tY0tkZX4CkgNoc,4204
3
4
  bbstrader/tseries.py,sha256=GYNDo03dYEnYHwcQYKpclNDGCwvZ_qAPyou0vhQndS0,69851
4
5
  bbstrader/btengine/__init__.py,sha256=FL0kC0NcsnlTH-yuTv4lu6AexY1wZKN1AQ9rv9MZagQ,3009
5
6
  bbstrader/btengine/backtest.py,sha256=ZzGhoN-_g0cF-OCyk173imze2OXEhykHTUiJ9MowDO8,14582
6
- bbstrader/btengine/data.py,sha256=qVDk-cIgtWAeBM5eW08j9MFXBzu4yeWChoFIL9WINYI,26899
7
+ bbstrader/btengine/data.py,sha256=Ycd0rXTT8h_WZBdMKJe_O3EiN5GGA1m-zECRxoU_40w,27172
7
8
  bbstrader/btengine/event.py,sha256=38mhZH9d53C4x7bZER2B0O6M18txzS4u7zveKyxeP5Y,8603
8
9
  bbstrader/btengine/execution.py,sha256=6YfErbqJx2DTy6r4cfZLU8F1YsJG-p8jEhNepdb9Sxc,10376
9
10
  bbstrader/btengine/performance.py,sha256=0meGbMFYzzI9n_09qf4RFpdyqQmCa6C_iu6PvE2POIE,10787
10
- bbstrader/btengine/portfolio.py,sha256=M97FONcdZRoSKFFF66LRsO8_KLXr128AI5-LULhMiKk,16167
11
+ bbstrader/btengine/portfolio.py,sha256=mh2_zNJDmKzb0lo55PXhbXYxXMmXRA4YLkgzwxRMuZE,16110
11
12
  bbstrader/btengine/strategy.py,sha256=EiE1P_V0wlFIsA-2U5POQ7qtRMkr1OTH1pQia4jBcGY,32937
12
13
  bbstrader/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  bbstrader/core/data.py,sha256=3_odj9jxokyKU15j1aHTlgLQDjW75tKqGpCUfkpYq2Q,452
@@ -15,11 +16,11 @@ bbstrader/core/utils.py,sha256=oB4OC0tQDJ1FIaJCuNWUlTzOccHoACSJsP_f7ELrqXQ,1448
15
16
  bbstrader/ibkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
17
  bbstrader/ibkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  bbstrader/metatrader/__init__.py,sha256=rrL_EtecsOCD2Cwhmpgta5CjSGT0K6vzSBiQoyCLe3M,283
18
- bbstrader/metatrader/account.py,sha256=Le46-lFqYm4VGQn3nPl1DUVCuiuxNi3EVf56kF8gA5Y,58221
19
- bbstrader/metatrader/rates.py,sha256=12hNcjRSKcOSjBAhDvpakqAiLaI0ndU6oBhCTIWDzr0,21089
20
- bbstrader/metatrader/risk.py,sha256=H8Iz93g3EVnb9bOdi4RSFF9JN1CY-IutYHs9-epBTpQ,27579
21
- bbstrader/metatrader/trade.py,sha256=k6IeKaC83sH_th9uNxeEIlNBzVoSzK1J-0jKIDIA7rc,73639
22
- bbstrader/metatrader/utils.py,sha256=UnwWmmfgY-Cw1V0vL14ehuWr9AhjcJMtVZK8k19b0i4,17672
19
+ bbstrader/metatrader/account.py,sha256=o-5Jbn_z3mc0xkffHz0AfOkASxyLCWz_6D714vKpLTk,58265
20
+ bbstrader/metatrader/rates.py,sha256=XyJlNhKrBp4MSMh-mM3-WZaYVMniMe1V4OT7RszbQqs,21137
21
+ bbstrader/metatrader/risk.py,sha256=9cBclFZzzXxHXIlBQlwXo9SZx_8PTDn0TDPG5kAUyZ4,27623
22
+ bbstrader/metatrader/trade.py,sha256=yUrvGg7NxjhSfLpJ2UWALfEokkObLcFe7xbBYb5DOfI,74790
23
+ bbstrader/metatrader/utils.py,sha256=sR6C9jm_JFNU1uxuSaQuYLqwbgz19c1tqKwvW0ByAtA,17716
23
24
  bbstrader/models/__init__.py,sha256=SnGBMQ-zcUIpms3oNeqg7EVDFpg-7OPjNAD8kvi_Q84,508
24
25
  bbstrader/models/factors.py,sha256=dWuXh83hLkwxUp3XwjgUl-r3_cjVcV_s0aFRlSLIfo8,13332
25
26
  bbstrader/models/ml.py,sha256=ipnrjcjigp1OgKd0XmoiNu_6s1gfzwqe8BiClymS4vs,48483
@@ -27,11 +28,11 @@ bbstrader/models/optimization.py,sha256=gp0n9a9vwbUldaNiZUYry_4RP2NW0VFZ2k5NoOkz
27
28
  bbstrader/models/portfolio.py,sha256=-Zq9cmzyOZUlGq9RWfAxClpX0KJZqYZYpc5EGNTcPGI,8302
28
29
  bbstrader/models/risk.py,sha256=IFQoHXxpBwJiifANRgwyAUOp7EgTWBAhfJFCO1sGR3g,15405
29
30
  bbstrader/trading/__init__.py,sha256=2VoxbzfP1XBLVuxJtjRhjEBCtnv9HqwQzfMV4B5mM7M,468
30
- bbstrader/trading/execution.py,sha256=fFywdEJXtVSSxKNGTpRnFHiprTI1jmDNd94Ox1F1aU4,30999
31
+ bbstrader/trading/execution.py,sha256=ap5HEm7u8vi3vcTQCca87a5Bsfr7ne1hyefAwxuE4pY,30995
31
32
  bbstrader/trading/scripts.py,sha256=pNwHr-3mW87G5fyIMd93wS43NkzOZn4npt4fLNnSUyk,1922
32
33
  bbstrader/trading/strategies.py,sha256=rMvLIhX_8MQg7_Lbo127UqdTRxBUof2m3jgRQTm55p0,37019
33
- bbstrader-0.2.7.dist-info/LICENSE,sha256=P3PBO9RuYPzl6-PkjysTNnwmwMB64ph36Bz9DBj8MS4,1115
34
- bbstrader-0.2.7.dist-info/METADATA,sha256=Ce_IeZ9fc-yb5dGN3pmq60woLrcos_WBi3T_Tras30k,11271
35
- bbstrader-0.2.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
36
- bbstrader-0.2.7.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
37
- bbstrader-0.2.7.dist-info/RECORD,,
34
+ bbstrader-0.2.8.dist-info/LICENSE,sha256=P3PBO9RuYPzl6-PkjysTNnwmwMB64ph36Bz9DBj8MS4,1115
35
+ bbstrader-0.2.8.dist-info/METADATA,sha256=NqQhVzqAn4Z7Y2qCHDm_Gb0zAOvl5cxYZt3vcBRIeVo,11296
36
+ bbstrader-0.2.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
37
+ bbstrader-0.2.8.dist-info/top_level.txt,sha256=Wwj322jZmxGZ6gD_TdaPiPLjED5ReObm5omerwlmZIg,10
38
+ bbstrader-0.2.8.dist-info/RECORD,,