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
@@ -0,0 +1,402 @@
1
+ from enum import Enum
2
+ from typing import NamedTuple, Optional
3
+
4
+ import numpy as np
5
+
6
+ try:
7
+ import MetaTrader5 as MT5
8
+ except ImportError:
9
+ import bbstrader.compat # noqa: F401
10
+
11
+
12
+ __all__ = [
13
+ "TIMEFRAMES",
14
+ "RateInfo",
15
+ "RateDtype",
16
+ "TimeFrame",
17
+ "SymbolType",
18
+ "InvalidBroker",
19
+ "GenericFail",
20
+ "InvalidParams",
21
+ "HistoryNotFound",
22
+ "InvalidVersion",
23
+ "AuthFailed",
24
+ "UnsupportedMethod",
25
+ "AutoTradingDisabled",
26
+ "InternalFailSend",
27
+ "InternalFailReceive",
28
+ "InternalFailInit",
29
+ "InternalFailConnect",
30
+ "InternalFailTimeout",
31
+ "trade_retcode_message",
32
+ "raise_mt5_error",
33
+ ]
34
+
35
+ # TIMEFRAME is an enumeration with possible chart period values
36
+ # See https://www.mql5.com/en/docs/python_metatrader5/mt5copyratesfrom_py#timeframe
37
+ TIMEFRAMES = {
38
+ "1m": MT5.TIMEFRAME_M1,
39
+ "2m": MT5.TIMEFRAME_M2,
40
+ "3m": MT5.TIMEFRAME_M3,
41
+ "4m": MT5.TIMEFRAME_M4,
42
+ "5m": MT5.TIMEFRAME_M5,
43
+ "6m": MT5.TIMEFRAME_M6,
44
+ "10m": MT5.TIMEFRAME_M10,
45
+ "12m": MT5.TIMEFRAME_M12,
46
+ "15m": MT5.TIMEFRAME_M15,
47
+ "20m": MT5.TIMEFRAME_M20,
48
+ "30m": MT5.TIMEFRAME_M30,
49
+ "1h": MT5.TIMEFRAME_H1,
50
+ "2h": MT5.TIMEFRAME_H2,
51
+ "3h": MT5.TIMEFRAME_H3,
52
+ "4h": MT5.TIMEFRAME_H4,
53
+ "6h": MT5.TIMEFRAME_H6,
54
+ "8h": MT5.TIMEFRAME_H8,
55
+ "12h": MT5.TIMEFRAME_H12,
56
+ "D1": MT5.TIMEFRAME_D1,
57
+ "W1": MT5.TIMEFRAME_W1,
58
+ "MN1": MT5.TIMEFRAME_MN1,
59
+ }
60
+
61
+
62
+ class TimeFrame(Enum):
63
+ """
64
+ Rrepresent a time frame object
65
+ """
66
+
67
+ M1 = TIMEFRAMES["1m"]
68
+ M2 = TIMEFRAMES["2m"]
69
+ M3 = TIMEFRAMES["3m"]
70
+ M4 = TIMEFRAMES["4m"]
71
+ M5 = TIMEFRAMES["5m"]
72
+ M6 = TIMEFRAMES["6m"]
73
+ M10 = TIMEFRAMES["10m"]
74
+ M12 = TIMEFRAMES["12m"]
75
+ M15 = TIMEFRAMES["15m"]
76
+ M20 = TIMEFRAMES["20m"]
77
+ M30 = TIMEFRAMES["30m"]
78
+ H1 = TIMEFRAMES["1h"]
79
+ H2 = TIMEFRAMES["2h"]
80
+ H3 = TIMEFRAMES["3h"]
81
+ H4 = TIMEFRAMES["4h"]
82
+ H6 = TIMEFRAMES["6h"]
83
+ H8 = TIMEFRAMES["8h"]
84
+ H12 = TIMEFRAMES["12h"]
85
+ D1 = TIMEFRAMES["D1"]
86
+ W1 = TIMEFRAMES["W1"]
87
+ MN1 = TIMEFRAMES["MN1"]
88
+
89
+ def __str__(self):
90
+ """Return the string representation of the time frame."""
91
+ return self.name
92
+
93
+
94
+ class SymbolType(Enum):
95
+ """
96
+ Represents the type of a symbol.
97
+ """
98
+
99
+ FOREX = "FOREX" # Forex currency pairs
100
+ FUTURES = "FUTURES" # Futures contracts
101
+ STOCKS = "STOCKS" # Stocks and shares
102
+ BONDS = "BONDS" # Bonds
103
+ CRYPTO = "CRYPTO" # Cryptocurrencies
104
+ ETFs = "ETFs" # Exchange-Traded Funds
105
+ INDICES = "INDICES" # Market indices
106
+ COMMODITIES = "COMMODITIES" # Commodities
107
+ OPTIONS = "OPTIONS" # Options contracts
108
+ unknown = "UNKNOWN" # Unknown or unsupported type
109
+
110
+
111
+ RateDtype = np.dtype(
112
+ [
113
+ ("time", "<i8"),
114
+ ("open", "<f8"),
115
+ ("high", "<f8"),
116
+ ("low", "<f8"),
117
+ ("close", "<f8"),
118
+ ("tick_volume", "<u8"),
119
+ ("spread", "<i4"),
120
+ ("real_volume", "<u8"),
121
+ ]
122
+ )
123
+
124
+
125
+ class RateInfo(NamedTuple):
126
+ """
127
+ Reprents a candle (bar) for a specified period.
128
+ * time: Time in seconds since 1970.01.01 00:00
129
+ * open: Open price
130
+ * high: High price
131
+ * low: Low price
132
+ * close: Close price
133
+ * tick_volume: Tick volume
134
+ * spread: Spread value
135
+ * real_volume: Real volume
136
+
137
+ """
138
+
139
+ time: int
140
+ open: float
141
+ high: float
142
+ low: float
143
+ close: float
144
+ tick_volume: float
145
+ spread: int
146
+ real_volume: float
147
+
148
+
149
+ class InvalidBroker(Exception):
150
+ """Exception raised for invalid broker errors."""
151
+
152
+ def __init__(self, message="Invalid broker."):
153
+ super().__init__(message)
154
+
155
+
156
+ class MT5TerminalError(Exception):
157
+ """Base exception class for trading-related errors."""
158
+
159
+ def __init__(self, code, message):
160
+ super().__init__(message)
161
+ self.code = code
162
+ self.message = message
163
+
164
+ def __repr__(self) -> str:
165
+ msg_str = str(self.message) if self.message is not None else ""
166
+ return f"{self.code} - {self.__class__.__name__}: {msg_str}"
167
+
168
+
169
+ class GenericFail(MT5TerminalError):
170
+ """Exception raised for generic failure."""
171
+
172
+ def __init__(self, message="Generic fail"):
173
+ super().__init__(MT5.RES_E_FAIL, message)
174
+
175
+
176
+ class InvalidParams(MT5TerminalError):
177
+ """Exception raised for invalid arguments or parameters."""
178
+
179
+ def __init__(self, message="Invalid arguments or parameters."):
180
+ super().__init__(MT5.RES_E_INVALID_PARAMS, message)
181
+
182
+
183
+ class HistoryNotFound(MT5TerminalError):
184
+ """Exception raised when no history is found."""
185
+
186
+ def __init__(self, message="No history found."):
187
+ super().__init__(MT5.RES_E_NOT_FOUND, message)
188
+
189
+
190
+ class InvalidVersion(MT5TerminalError):
191
+ """Exception raised for an invalid version."""
192
+
193
+ def __init__(self, message="Invalid version."):
194
+ super().__init__(MT5.RES_E_INVALID_VERSION, message)
195
+
196
+
197
+ class AuthFailed(MT5TerminalError):
198
+ """Exception raised for authorization failure."""
199
+
200
+ def __init__(self, message="Authorization failed."):
201
+ super().__init__(MT5.RES_E_AUTH_FAILED, message)
202
+
203
+
204
+ class UnsupportedMethod(MT5TerminalError):
205
+ """Exception raised for an unsupported method."""
206
+
207
+ def __init__(self, message="Unsupported method."):
208
+ super().__init__(MT5.RES_E_UNSUPPORTED, message)
209
+
210
+
211
+ class AutoTradingDisabled(MT5TerminalError):
212
+ """Exception raised when auto-trading is disabled."""
213
+
214
+ def __init__(self, message="Auto-trading is disabled."):
215
+ super().__init__(MT5.RES_E_AUTO_TRADING_DISABLED, message)
216
+
217
+
218
+ class InternalFailError(MT5TerminalError):
219
+ """Base exception class for internal IPC errors."""
220
+
221
+ def __init__(self, code, message):
222
+ super().__init__(code, message)
223
+
224
+
225
+ class InternalFailSend(InternalFailError):
226
+ """Exception raised for internal IPC send failure."""
227
+
228
+ def __init__(self, message="Internal IPC send failed."):
229
+ super().__init__(MT5.RES_E_INTERNAL_FAIL_SEND, message)
230
+
231
+
232
+ class InternalFailReceive(InternalFailError):
233
+ """Exception raised for internal IPC receive failure."""
234
+
235
+ def __init__(self, message="Internal IPC receive failed."):
236
+ super().__init__(MT5.RES_E_INTERNAL_FAIL_RECEIVE, message)
237
+
238
+
239
+ class InternalFailInit(InternalFailError):
240
+ """Exception raised for internal IPC initialization failure."""
241
+
242
+ def __init__(self, message="Internal IPC initialization failed."):
243
+ super().__init__(MT5.RES_E_INTERNAL_FAIL_INIT, message)
244
+
245
+
246
+ class InternalFailConnect(InternalFailError):
247
+ """Exception raised for no IPC connection."""
248
+
249
+ def __init__(self, message="No IPC connection."):
250
+ super().__init__(MT5.RES_E_INTERNAL_FAIL_CONNECT, message)
251
+
252
+
253
+ class InternalFailTimeout(InternalFailError):
254
+ """Exception raised for an internal timeout."""
255
+
256
+ def __init__(self, message="Internal timeout."):
257
+ super().__init__(MT5.RES_E_INTERNAL_FAIL_TIMEOUT, message)
258
+
259
+
260
+ RES_E_FAIL = 1 # Generic error
261
+ RES_E_INVALID_PARAMS = 2 # Invalid parameters
262
+ RES_E_NOT_FOUND = 3 # Not found
263
+ RES_E_INVALID_VERSION = 4 # Invalid version
264
+ RES_E_AUTH_FAILED = 5 # Authorization failed
265
+ RES_E_UNSUPPORTED = 6 # Unsupported method
266
+ RES_E_AUTO_TRADING_DISABLED = 7 # Autotrading disabled
267
+
268
+ # Actual internal error codes from MetaTrader5
269
+ RES_E_INTERNAL_FAIL_CONNECT = -10000
270
+ RES_E_INTERNAL_FAIL_INIT = -10001
271
+ RES_E_INTERNAL_FAIL_SEND = -10006
272
+ RES_E_INTERNAL_FAIL_RECEIVE = -10007
273
+ RES_E_INTERNAL_FAIL_TIMEOUT = -10008
274
+
275
+ # Dictionary to map error codes to exception classes
276
+ _ERROR_CODE_TO_EXCEPTION_ = {
277
+ MT5.RES_E_FAIL: GenericFail,
278
+ MT5.RES_E_INVALID_PARAMS: InvalidParams,
279
+ MT5.RES_E_NOT_FOUND: HistoryNotFound,
280
+ MT5.RES_E_INVALID_VERSION: InvalidVersion,
281
+ MT5.RES_E_AUTH_FAILED: AuthFailed,
282
+ MT5.RES_E_UNSUPPORTED: UnsupportedMethod,
283
+ MT5.RES_E_AUTO_TRADING_DISABLED: AutoTradingDisabled,
284
+ MT5.RES_E_INTERNAL_FAIL_SEND: InternalFailSend,
285
+ MT5.RES_E_INTERNAL_FAIL_RECEIVE: InternalFailReceive,
286
+ MT5.RES_E_INTERNAL_FAIL_INIT: InternalFailInit,
287
+ MT5.RES_E_INTERNAL_FAIL_CONNECT: InternalFailConnect,
288
+ MT5.RES_E_INTERNAL_FAIL_TIMEOUT: InternalFailTimeout,
289
+ RES_E_FAIL: GenericFail,
290
+ RES_E_INVALID_PARAMS: InvalidParams,
291
+ RES_E_NOT_FOUND: HistoryNotFound,
292
+ RES_E_INVALID_VERSION: InvalidVersion,
293
+ RES_E_AUTH_FAILED: AuthFailed,
294
+ RES_E_UNSUPPORTED: UnsupportedMethod,
295
+ RES_E_AUTO_TRADING_DISABLED: AutoTradingDisabled,
296
+ RES_E_INTERNAL_FAIL_SEND: InternalFailSend,
297
+ RES_E_INTERNAL_FAIL_RECEIVE: InternalFailReceive,
298
+ RES_E_INTERNAL_FAIL_INIT: InternalFailInit,
299
+ RES_E_INTERNAL_FAIL_CONNECT: InternalFailConnect,
300
+ RES_E_INTERNAL_FAIL_TIMEOUT: InternalFailTimeout,
301
+ }
302
+
303
+
304
+ def raise_mt5_error(message: Optional[str] = None):
305
+ """Raises an exception based on the given error code.
306
+
307
+ Args:
308
+ message: An optional custom error message.
309
+
310
+ Raises:
311
+ MT5TerminalError: A specific exception based on the error code.
312
+ """
313
+ if message and isinstance(message, Exception):
314
+ message = str(message)
315
+ exception = _ERROR_CODE_TO_EXCEPTION_.get(MT5.last_error()[0])
316
+ if exception is not None:
317
+ raise exception(f"{message or MT5.last_error()[1]}")
318
+ else:
319
+ raise Exception(f"{message or MT5.last_error()[1]}")
320
+
321
+
322
+ _ORDER_FILLING_TYPE_ = "https://www.mql5.com/en/docs/constants/tradingconstants/orderproperties#enum_order_type_filling"
323
+ _ORDER_TYPE_ = "https://www.mql5.com/en/docs/constants/tradingconstants/orderproperties#enum_order_type"
324
+ _POSITION_IDENTIFIER_ = "https://www.mql5.com/en/docs/constants/tradingconstants/positionproperties#enum_position_property_integer"
325
+ _FIFO_RULE_ = "https://www.mql5.com/en/docs/constants/environment_state/accountinformation#enum_account_info_integer"
326
+
327
+ _TRADE_RETCODE_MESSAGES_ = {
328
+ 10004: "Requote: The price has changed, please try again",
329
+ 10006: "Request rejected",
330
+ 10007: "Request canceled by trader",
331
+ 10008: "Order placed",
332
+ 10009: "Request completed",
333
+ 10010: "Only part of the request was completed",
334
+ 10011: "Request processing error",
335
+ 10012: "Request canceled by timeout",
336
+ 10013: "Invalid request",
337
+ 10014: "Invalid volume in the request",
338
+ 10015: "Invalid price in the request",
339
+ 10016: "Invalid stops in the request",
340
+ 10017: "Trade is disabled",
341
+ 10018: "Market is closed",
342
+ 10019: "Insufficient funds to complete the request",
343
+ 10020: "Prices changed",
344
+ 10021: "No quotes to process the request",
345
+ 10022: "Invalid order expiration date in the request",
346
+ 10023: "Order state changed",
347
+ 10024: "Too many requests, please try again later",
348
+ 10025: "No changes in request",
349
+ 10026: "Autotrading disabled by server",
350
+ 10027: "Autotrading disabled by client terminal",
351
+ 10028: "Request locked for processing",
352
+ 10029: "Order or position frozen",
353
+ 10030: "Invalid order filling type: see" + " " + _ORDER_FILLING_TYPE_,
354
+ 10031: "No connection with the trade server",
355
+ 10032: "Operation allowed only for live accounts",
356
+ 10033: "The number of pending orders has reached the limit",
357
+ 10034: "Order/position volume limit for the symbol reached",
358
+ 10035: "Incorrect or prohibited order type: see" + " " + _ORDER_TYPE_,
359
+ 10036: "Position with the specified ID has already been closed: see"
360
+ + " "
361
+ + _POSITION_IDENTIFIER_,
362
+ 10038: "Close volume exceeds the current position volume",
363
+ 10039: "A close order already exists for this position",
364
+ 10040: "Maximum number of open positions reached",
365
+ 10041: "Pending order activation rejected, order canceled",
366
+ 10042: "Only long positions are allowed",
367
+ 10043: "Only short positions are allowed",
368
+ 10044: "Only position closing is allowed",
369
+ 10045: "Position closing allowed only by FIFO rule: see" + " " + _FIFO_RULE_,
370
+ 10046: "Opposite positions on this symbol are disabled",
371
+ }
372
+
373
+
374
+ def trade_retcode_message(code, display=False, add_msg=""):
375
+ """
376
+ Retrieves a user-friendly message corresponding to a given trade return code.
377
+
378
+ Args:
379
+ code (int): The trade return code to look up.
380
+ display (bool, optional): Whether to print the message to the console. Defaults to False.
381
+
382
+ Returns:
383
+ str: The message associated with the provided trade return code. If the code is not found,
384
+ it returns "Unknown trade error.".
385
+ """
386
+ message = _TRADE_RETCODE_MESSAGES_.get(code, "Unknown trade error")
387
+ if display:
388
+ print(message + add_msg)
389
+ return message
390
+
391
+
392
+ _ADMIRAL_MARKETS_URL_ = "https://one.justmarkets.link/a/tufvj0xugm/registration/trader"
393
+ _JUST_MARKETS_URL_ = "https://one.justmarkets.link/a/tufvj0xugm/registration/trader"
394
+ _FTMO_URL_ = "https://trader.ftmo.com/?affiliates=JGmeuQqepAZLMcdOEQRp"
395
+
396
+ INIT_MSG = (
397
+ f"\n* Check your internet connection\n"
398
+ f"* Make sure MT5 is installed and active\n"
399
+ f"* Looking for a boker? See [{_ADMIRAL_MARKETS_URL_}] "
400
+ f"or [{_JUST_MARKETS_URL_}]\n"
401
+ f"* Looking for a prop firm? See [{_FTMO_URL_}]\n"
402
+ )
@@ -0,0 +1,39 @@
1
+ """
2
+ Overview
3
+ ========
4
+
5
+ The Models Module provides a collection of quantitative models for financial analysis and decision-making.
6
+ It includes tools for portfolio optimization and natural language processing (NLP) to extract insights
7
+ from financial text data. This module is designed to support quantitative trading strategies by
8
+ providing a robust framework for financial modeling.
9
+
10
+ Features
11
+ ========
12
+
13
+ - **Portfolio Optimization**: Implements techniques to optimize portfolio allocation, helping to maximize returns and manage risk.
14
+ - **Natural Language Processing (NLP)**: Provides tools for analyzing financial news and other text-based data to gauge market sentiment.
15
+ - **Extensible Design**: Structured to allow for the easy addition of new quantitative models and algorithms.
16
+
17
+ Components
18
+ ==========
19
+
20
+ - **Optimization**: Contains portfolio optimization models and related utilities.
21
+ - **NLP**: Includes tools and models for natural language processing tailored for financial applications.
22
+
23
+ Examples
24
+ ========
25
+
26
+ >>> from bbstrader.models import optimized_weights
27
+ >>> # Assuming 'returns' is a DataFrame of asset returns
28
+ >>> optimal_weights = optimized_weights(returns=returns)
29
+ >>> print(optimal_weights)
30
+
31
+ Notes
32
+ =====
33
+
34
+ This module is focused on providing the analytical tools for quantitative analysis. The models
35
+ can be integrated into trading strategies to provide data-driven signals.
36
+ """
37
+
38
+ from bbstrader.models.optimization import * # noqa: F403
39
+ from bbstrader.models.nlp import * # noqa: F403