bbstrader 2.0.3__cp312-cp312-macosx_11_0_arm64.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.
Files changed (45) hide show
  1. bbstrader/__init__.py +27 -0
  2. bbstrader/__main__.py +92 -0
  3. bbstrader/api/__init__.py +96 -0
  4. bbstrader/api/handlers.py +245 -0
  5. bbstrader/api/metatrader_client.cpython-312-darwin.so +0 -0
  6. bbstrader/api/metatrader_client.pyi +624 -0
  7. bbstrader/assets/bbs_.png +0 -0
  8. bbstrader/assets/bbstrader.ico +0 -0
  9. bbstrader/assets/bbstrader.png +0 -0
  10. bbstrader/assets/qs_metrics_1.png +0 -0
  11. bbstrader/btengine/__init__.py +54 -0
  12. bbstrader/btengine/backtest.py +358 -0
  13. bbstrader/btengine/data.py +737 -0
  14. bbstrader/btengine/event.py +229 -0
  15. bbstrader/btengine/execution.py +287 -0
  16. bbstrader/btengine/performance.py +408 -0
  17. bbstrader/btengine/portfolio.py +393 -0
  18. bbstrader/btengine/strategy.py +588 -0
  19. bbstrader/compat.py +28 -0
  20. bbstrader/config.py +100 -0
  21. bbstrader/core/__init__.py +27 -0
  22. bbstrader/core/data.py +628 -0
  23. bbstrader/core/strategy.py +466 -0
  24. bbstrader/metatrader/__init__.py +48 -0
  25. bbstrader/metatrader/_copier.py +720 -0
  26. bbstrader/metatrader/account.py +865 -0
  27. bbstrader/metatrader/broker.py +418 -0
  28. bbstrader/metatrader/copier.py +1487 -0
  29. bbstrader/metatrader/rates.py +495 -0
  30. bbstrader/metatrader/risk.py +667 -0
  31. bbstrader/metatrader/trade.py +1692 -0
  32. bbstrader/metatrader/utils.py +402 -0
  33. bbstrader/models/__init__.py +39 -0
  34. bbstrader/models/nlp.py +932 -0
  35. bbstrader/models/optimization.py +182 -0
  36. bbstrader/scripts.py +665 -0
  37. bbstrader/trading/__init__.py +33 -0
  38. bbstrader/trading/execution.py +1159 -0
  39. bbstrader/trading/strategy.py +362 -0
  40. bbstrader/trading/utils.py +69 -0
  41. bbstrader-2.0.3.dist-info/METADATA +396 -0
  42. bbstrader-2.0.3.dist-info/RECORD +45 -0
  43. bbstrader-2.0.3.dist-info/WHEEL +5 -0
  44. bbstrader-2.0.3.dist-info/entry_points.txt +3 -0
  45. bbstrader-2.0.3.dist-info/licenses/LICENSE +21 -0
bbstrader/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ Simplified Investment & Trading Toolkit with Python & C++
3
+
4
+ """
5
+
6
+ __author__ = "Bertin Balouki SIMYELI"
7
+ __copyright__ = "2023-2026 Bertin Balouki SIMYELI"
8
+ __email__ = "bertin@bbs-trading.com"
9
+ __license__ = "MIT"
10
+
11
+ from importlib.metadata import version, PackageNotFoundError
12
+
13
+ try:
14
+ __version__ = version("bbstrader")
15
+ except PackageNotFoundError:
16
+ __version__ = "unknown"
17
+
18
+
19
+ from bbstrader import compat # noqa: F401
20
+ from bbstrader import core # noqa: F401
21
+ from bbstrader import btengine # noqa: F401
22
+ from bbstrader import metatrader # noqa: F401
23
+ from bbstrader import models # noqa: F401
24
+ from bbstrader import trading # noqa: F401
25
+ from bbstrader.config import config_logger # noqa: F401
26
+
27
+ version = __version__
bbstrader/__main__.py ADDED
@@ -0,0 +1,92 @@
1
+ import argparse
2
+ import multiprocessing
3
+ import sys
4
+ from enum import Enum
5
+
6
+ import pyfiglet
7
+ from colorama import Fore
8
+ from loguru import logger
9
+
10
+ from bbstrader.scripts import backtest, copy_trades, execute_strategy, send_news_feed
11
+
12
+ from . import __author__, __version__
13
+
14
+ logger.remove()
15
+
16
+ custom_format = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | <white>{message}</white>"
17
+
18
+ logger.add(sys.stdout, level="DEBUG", format=custom_format, colorize=True)
19
+
20
+
21
+ class _Module(Enum):
22
+ COPIER = "copier"
23
+ BACKTEST = "backtest"
24
+ EXECUTION = "execution"
25
+ NEWS_FEED = "news_feed"
26
+
27
+
28
+ FONT = pyfiglet.figlet_format("BBSTRADER", font="big")
29
+
30
+
31
+ def main() -> None:
32
+ DESCRIPTION = "BBSTRADER"
33
+ USAGE_TEXT = """
34
+ Usage:
35
+ python -m bbstrader --run <module> [options]
36
+
37
+ Modules:
38
+ copier: Copy trades from one MetaTrader account to another or multiple accounts
39
+ backtest: Backtest a strategy, see bbstrader.btengine.backtest.run_backtest
40
+ execution: Execute a strategy, see bbstrader.trading.execution.Mt5ExecutionEngine
41
+ news_feed: Send news feed from Coindesk to Telegram channel
42
+
43
+ python -m bbstrader --run <module> --help for more information on the module
44
+ """
45
+ print(Fore.BLUE + FONT)
46
+ print(Fore.WHITE + "")
47
+ parser = argparse.ArgumentParser(
48
+ prog="BBSTRADER",
49
+ usage=USAGE_TEXT,
50
+ description=DESCRIPTION,
51
+ formatter_class=argparse.RawTextHelpFormatter,
52
+ add_help=False,
53
+ )
54
+ parser.add_argument("--run", type=str, nargs="?", default=None, help="Run a module")
55
+ args, unknown = parser.parse_known_args()
56
+ if ("-h" in sys.argv or "--help" in sys.argv) and args.run is None:
57
+ print(Fore.WHITE + USAGE_TEXT)
58
+ sys.exit(0)
59
+ if "-v" in sys.argv or "--version" in sys.argv:
60
+ print(Fore.GREEN + f"bbstrader version {__version__}")
61
+ print(Fore.WHITE + f"bbstrader maintained and supported by {__author__}")
62
+ sys.exit(0)
63
+ try:
64
+ match args.run:
65
+ case _Module.COPIER.value:
66
+ copy_trades(unknown)
67
+ case _Module.BACKTEST.value:
68
+ backtest(unknown)
69
+ case _Module.EXECUTION.value:
70
+ execute_strategy(unknown)
71
+ case _Module.NEWS_FEED.value:
72
+ send_news_feed(unknown)
73
+ case _:
74
+ print(Fore.RED + f"Unknown module: {args.run}")
75
+ sys.exit(1)
76
+ except KeyboardInterrupt:
77
+ sys.exit(0)
78
+ except Exception as e:
79
+ print(Fore.RED + f"Error: {e}")
80
+ sys.exit(1)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ multiprocessing.freeze_support()
85
+ try:
86
+ main()
87
+ except KeyboardInterrupt:
88
+ print(Fore.RED + "\nExecution interrupted by user")
89
+ sys.exit(0)
90
+ except Exception as e:
91
+ print(Fore.RED + f"Error: {e}")
92
+ sys.exit(1)
@@ -0,0 +1,96 @@
1
+ """
2
+ Overview
3
+ ========
4
+
5
+ The API Module provides a high-level client for interacting with the MetaTrader 5 platform.
6
+ It serves as the primary interface for connecting to, retrieving data from, and sending commands
7
+ to a MetaTrader 5 terminal from a Python application. The module is designed to simplify
8
+ interactions and provide convenient data handling.
9
+
10
+ Features
11
+ ========
12
+
13
+ - **High-Level MT5 Client**: A simplified client (`Mt5client`) for easy access to MetaTrader 5 functionalities.
14
+ - **Dynamic Object Representation**: Automatically patches MetaTrader 5 data objects for better string representation, making debugging easier.
15
+ - **DataFrame Conversion**: Includes a utility function (`trade_object_to_df`) to quickly convert lists of MT5 trade objects (like deals, orders, positions) into pandas DataFrames for analysis.
16
+ - **Handler-Based Architecture**: Uses a handler class (`Mt5Handlers`) to manage the underlying MetaTrader 5 API calls, promoting modularity.
17
+
18
+ Components
19
+ ==========
20
+
21
+ - **Mt5client**: The main client instance used to interact with the MetaTrader 5 terminal.
22
+ - **Mt5Handlers**: A class that encapsulates the direct calls to the MetaTrader 5 API.
23
+ - **Helper Functions**:
24
+ - `trade_object_to_df`: Converts lists of trade-related objects to pandas DataFrames.
25
+ - Dynamic patching of `__str__` and `__repr__` for improved object inspection.
26
+
27
+ Notes
28
+ =====
29
+
30
+ This module requires a running MetaTrader 5 terminal and the `MetaTrader5` Python package to be installed.
31
+ The connection is managed by the `Mt5client`.
32
+ """
33
+
34
+ from operator import attrgetter
35
+
36
+ import pandas as pd
37
+
38
+ from bbstrader.api.handlers import Mt5Handlers
39
+ from bbstrader.api.metatrader_client import * # type: ignore # noqa: F403
40
+
41
+ # ruff: noqa: F405
42
+ classes_to_patch = [
43
+ AccountInfo,
44
+ BookInfo,
45
+ OrderCheckResult,
46
+ OrderSentResult,
47
+ RateInfo,
48
+ SymbolInfo,
49
+ TerminalInfo,
50
+ TickInfo,
51
+ TradeDeal,
52
+ TradeOrder,
53
+ TradePosition,
54
+ TradeRequest,
55
+ ]
56
+
57
+
58
+ def dynamic_str(self):
59
+ fields = set()
60
+ for name in dir(self):
61
+ if name.startswith("_"):
62
+ continue
63
+ try:
64
+ value = getattr(self, name)
65
+ if not callable(value):
66
+ fields.add(f"{name}={value!r}")
67
+ except Exception:
68
+ pass
69
+ return f"{type(self).__name__}({', '.join(fields)})"
70
+
71
+
72
+ for cls in classes_to_patch:
73
+ cls.__str__ = dynamic_str
74
+ cls.__repr__ = dynamic_str
75
+
76
+
77
+ def trade_object_to_df(obj_list):
78
+ """
79
+ Fast conversion of a list of C++ bound objects to a pandas DataFrame.
80
+ """
81
+ if not obj_list:
82
+ return pd.DataFrame()
83
+
84
+ first_obj = obj_list[0]
85
+ columns = [
86
+ name
87
+ for name in dir(first_obj)
88
+ if not name.startswith("_") and not callable(getattr(first_obj, name))
89
+ ]
90
+ fetcher = attrgetter(*columns)
91
+ data = [fetcher(obj) for obj in obj_list]
92
+ df = pd.DataFrame(data, columns=columns)
93
+ return df
94
+
95
+
96
+ Mt5client = MetaTraderClient(Mt5Handlers)
@@ -0,0 +1,245 @@
1
+ from datetime import datetime
2
+
3
+ import pytz
4
+
5
+ from bbstrader.api.metatrader_client import ( # type: ignore
6
+ AccountInfo,
7
+ BookInfo,
8
+ MetaTraderHandlers,
9
+ OrderCheckResult,
10
+ OrderSentResult,
11
+ SymbolInfo,
12
+ TerminalInfo,
13
+ TickInfo,
14
+ TradeDeal,
15
+ TradeOrder,
16
+ TradePosition,
17
+ TradeRequest,
18
+ )
19
+
20
+ try:
21
+ import MetaTrader5 as mt5
22
+ except ImportError:
23
+ import bbstrader.compat # noqa: F401
24
+
25
+
26
+ def _convert_obj(obj, obj_type):
27
+ """Converts a single MT5 object to the target C++ class instance."""
28
+ if obj is None:
29
+ return None
30
+
31
+ if hasattr(obj, "_asdict"):
32
+ field_data: dict = obj._asdict()
33
+ instance = obj_type()
34
+
35
+ for k, v in field_data.items():
36
+ if not hasattr(instance, k):
37
+ continue
38
+ if (
39
+ k == "request"
40
+ and hasattr(v, "_asdict")
41
+ and obj_type in (OrderCheckResult, OrderSentResult)
42
+ ):
43
+ setattr(instance, k, v._asdict())
44
+ else:
45
+ setattr(instance, k, v)
46
+
47
+ return instance
48
+ else:
49
+ raise TypeError(f"Expected MT5 namedtuple, got {type(obj)}")
50
+
51
+
52
+ def _convert_list(obj_list, obj_type):
53
+ if obj_list is None:
54
+ return None
55
+ return [_convert_obj(obj, obj_type) for obj in obj_list]
56
+
57
+
58
+ def _build_request(req: TradeRequest | dict) -> dict:
59
+ if isinstance(req, dict):
60
+ return req
61
+
62
+ request = {}
63
+ attrs = [
64
+ "action",
65
+ "magic",
66
+ "order",
67
+ "symbol",
68
+ "volume",
69
+ "price",
70
+ "stoplimit",
71
+ "sl",
72
+ "tp",
73
+ "deviation",
74
+ "type",
75
+ "type_filling",
76
+ "type_time",
77
+ "expiration",
78
+ "comment",
79
+ "position",
80
+ "position_by",
81
+ ]
82
+ enum_types = {"action", "type", "type_time", "type_filling"}
83
+ for attr in attrs:
84
+ val = getattr(req, attr)
85
+ if attr in enum_types or (val != 0 and val != ""):
86
+ request[attr] = val
87
+ return request
88
+
89
+
90
+ def check_order(request: TradeRequest) -> OrderCheckResult:
91
+ return mt5.order_check(_build_request(request))
92
+
93
+
94
+ def send_order(request: TradeRequest) -> OrderSentResult:
95
+ return mt5.order_send(_build_request(request))
96
+
97
+
98
+ def get_time(ts):
99
+ timezone = pytz.timezone("UTC")
100
+ if isinstance(ts, datetime):
101
+ return ts
102
+ elif isinstance(ts, int):
103
+ return datetime.fromtimestamp(ts, tz=timezone)
104
+ else:
105
+ raise ValueError(f"Invalide Time format {type(ts)} must be and int or datetime")
106
+
107
+
108
+ def get_mt5_handlers():
109
+ """
110
+ Exhaustively maps all MetaTrader 5 Python functions to the C++ Handlers struct,
111
+ converting return values to the appropriate types.
112
+ """
113
+ h = MetaTraderHandlers()
114
+
115
+ # 1. System & Session Management (Functions returning structs are wrapped)
116
+ h.init_auto = lambda: mt5.initialize()
117
+ h.init_path = lambda path: mt5.initialize(path)
118
+ h.init_full = (
119
+ lambda path, login, password, server, timeout, portable: mt5.initialize(
120
+ path=path,
121
+ login=login,
122
+ password=password,
123
+ server=server,
124
+ timeout=timeout,
125
+ portable=portable,
126
+ )
127
+ )
128
+ h.login = lambda login, password, server, timeout: mt5.login(
129
+ login=login, password=password, server=server, timeout=timeout
130
+ )
131
+ h.shutdown = mt5.shutdown
132
+ h.get_version = mt5.version
133
+ h.get_last_error = mt5.last_error
134
+ h.get_terminal_info = lambda: _convert_obj(mt5.terminal_info(), TerminalInfo)
135
+ h.get_account_info = lambda: _convert_obj(mt5.account_info(), AccountInfo)
136
+
137
+ # 2. Symbols & Market Depth (Level 2)
138
+ h.get_total_symbols = mt5.symbols_total
139
+ h.get_symbols_all = lambda: _convert_list(mt5.symbols_get(), SymbolInfo)
140
+ h.get_symbol_info = lambda symbol: _convert_obj(mt5.symbol_info(symbol), SymbolInfo)
141
+ h.select_symbol = mt5.symbol_select
142
+ h.get_symbols_by_group = lambda group: _convert_list(
143
+ mt5.symbols_get(group), SymbolInfo
144
+ )
145
+ h.subscribe_book = mt5.market_book_add
146
+ h.unsubscribe_book = mt5.market_book_release
147
+ h.get_book_info = lambda symbol: _convert_list(
148
+ mt5.market_book_get(symbol), BookInfo
149
+ )
150
+
151
+ # 3. Market Data (Rates & Ticks)
152
+ h.get_rates_by_date = (
153
+ lambda symbol, timeframe, date_from, count: mt5.copy_rates_from(
154
+ symbol, timeframe, get_time(date_from), count
155
+ )
156
+ )
157
+ h.get_rates_by_pos = (
158
+ lambda symbol, timeframe, start_pos, count: mt5.copy_rates_from_pos(
159
+ symbol, timeframe, start_pos, count
160
+ )
161
+ )
162
+ h.get_rates_by_range = (
163
+ lambda symbol, timeframe, date_from, date_to: mt5.copy_rates_range(
164
+ symbol, timeframe, get_time(date_from), get_time(date_to)
165
+ )
166
+ )
167
+ h.get_ticks_by_date = lambda symbol, date_from, count, flags: mt5.copy_ticks_from(
168
+ symbol, get_time(date_from), count, flags
169
+ )
170
+ h.get_ticks_by_range = (
171
+ lambda symbol, date_from, date_to, flags: mt5.copy_ticks_range(
172
+ symbol, get_time(date_from), get_time(date_to), flags
173
+ )
174
+ )
175
+
176
+ h.get_tick_info = lambda symbol: _convert_obj(
177
+ mt5.symbol_info_tick(symbol), TickInfo
178
+ )
179
+
180
+ # 4. Trading Operations
181
+ h.check_order = lambda request: _convert_obj(check_order(request), OrderCheckResult)
182
+ h.send_order = lambda request: _convert_obj(send_order(request), OrderSentResult)
183
+ h.calc_margin = mt5.order_calc_margin
184
+ h.calc_profit = mt5.order_calc_profit
185
+
186
+ # 5. Active Orders & Positions
187
+ h.get_orders_all = lambda: _convert_list(mt5.orders_get(), TradeOrder)
188
+ h.get_orders_by_symbol = lambda symbol: _convert_list(
189
+ mt5.orders_get(symbol=symbol), TradeOrder
190
+ )
191
+ h.get_orders_by_group = lambda group: _convert_list(
192
+ mt5.orders_get(group=group), TradeOrder
193
+ )
194
+ h.get_order_by_ticket = lambda ticket: _convert_obj(
195
+ (mt5.orders_get(ticket=ticket) or [None])[0], TradeOrder
196
+ )
197
+ h.get_total_orders = mt5.orders_total
198
+ h.get_positions_all = lambda: _convert_list(mt5.positions_get(), TradePosition)
199
+ h.get_positions_symbol = lambda symbol: _convert_list(
200
+ mt5.positions_get(symbol=symbol), TradePosition
201
+ )
202
+ h.get_positions_group = lambda group: _convert_list(
203
+ mt5.positions_get(group=group), TradePosition
204
+ )
205
+ h.get_position_ticket = lambda ticket: _convert_obj(
206
+ (mt5.positions_get(ticket=ticket) or [None])[0], TradePosition
207
+ )
208
+ h.get_total_positions = mt5.positions_total
209
+
210
+ # 6. Trade History (Orders & Deals)
211
+ h.get_hist_orders_group = lambda date_from, date_to, group: _convert_list(
212
+ mt5.history_orders_get(get_time(date_from), get_time(date_to), group=group),
213
+ TradeOrder,
214
+ )
215
+ h.get_hist_orders_range = lambda date_from, date_to: _convert_list(
216
+ mt5.history_orders_get(get_time(date_from), get_time(date_to)),
217
+ TradeOrder,
218
+ )
219
+ h.get_hist_order_ticket = lambda ticket: _convert_obj(
220
+ mt5.history_orders_get(ticket=ticket), TradeOrder
221
+ )
222
+ h.get_hist_orders_pos = lambda position: _convert_list(
223
+ mt5.history_orders_get(position=position), TradeOrder
224
+ )
225
+ h.get_hist_orders_total = mt5.history_orders_total
226
+ h.get_hist_deals_group = lambda date_from, date_to, group: _convert_list(
227
+ mt5.history_deals_get(get_time(date_from), get_time(date_to), group=group),
228
+ TradeDeal,
229
+ )
230
+ h.get_hist_deals_range = lambda date_from, date_to: _convert_list(
231
+ mt5.history_deals_get(get_time(date_from), get_time(date_to)), TradeDeal
232
+ )
233
+
234
+ h.get_hist_deals_ticket = lambda ticket: _convert_obj(
235
+ mt5.history_deals_get(ticket=ticket), TradeDeal
236
+ )
237
+ h.get_hist_deals_pos = lambda position: _convert_list(
238
+ mt5.history_deals_get(position=position), TradeDeal
239
+ )
240
+ h.get_hist_deals_total = mt5.history_deals_total
241
+
242
+ return h
243
+
244
+
245
+ Mt5Handlers = get_mt5_handlers()