bbstrader 0.2.0__py3-none-any.whl → 0.2.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.

@@ -1,26 +1,23 @@
1
+ import string
1
2
  from abc import ABCMeta, abstractmethod
2
- import pytz
3
- import pandas as pd
4
- import numpy as np
5
- from queue import Queue
6
3
  from datetime import datetime
7
- from bbstrader.config import config_logger
8
- from bbstrader.btengine.event import SignalEvent
9
- from bbstrader.btengine.event import FillEvent
4
+ from queue import Queue
5
+ from typing import Dict, List, Literal, Union
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+ import pytz
10
+
10
11
  from bbstrader.btengine.data import DataHandler
11
- from bbstrader.metatrader.account import Account
12
+ from bbstrader.btengine.event import FillEvent, SignalEvent
13
+ from bbstrader.metatrader.account import Account, AdmiralMarktsGroup
12
14
  from bbstrader.metatrader.rates import Rates
13
- from typing import (
14
- Dict,
15
- Union,
16
- Any,
17
- List,
18
- Literal
19
- )
20
15
  from bbstrader.models.optimization import optimized_weights
16
+ from bbstrader.core.utils import TradeSignal
21
17
 
22
18
  __all__ = ['Strategy', 'MT5Strategy']
23
19
 
20
+
24
21
  class Strategy(metaclass=ABCMeta):
25
22
  """
26
23
  A `Strategy()` object encapsulates all calculation on market data
@@ -44,13 +41,15 @@ class Strategy(metaclass=ABCMeta):
44
41
  """
45
42
 
46
43
  @abstractmethod
47
- def calculate_signals(self, *args, **kwargs) -> Any:
44
+ def calculate_signals(self, *args, **kwargs) -> List[TradeSignal]:
48
45
  raise NotImplementedError(
49
46
  "Should implement calculate_signals()"
50
47
  )
48
+
51
49
  def check_pending_orders(self, *args, **kwargs): ...
52
50
  def get_update_from_portfolio(self, *args, **kwargs): ...
53
51
  def update_trades_from_fill(self, *args, **kwargs): ...
52
+ def perform_period_end_checks(self, *args, **kwargs): ...
54
53
 
55
54
 
56
55
  class MT5Strategy(Strategy):
@@ -61,8 +60,8 @@ class MT5Strategy(Strategy):
61
60
  for live trading and `MT5BacktestEngine` objects for backtesting.
62
61
  """
63
62
 
64
- def __init__(self, events: Queue=None, symbol_list: List[str]=None,
65
- bars: DataHandler=None, mode: str=None, **kwargs):
63
+ def __init__(self, events: Queue = None, symbol_list: List[str] = None,
64
+ bars: DataHandler = None, mode: str = None, **kwargs):
66
65
  """
67
66
  Initialize the `MT5Strategy` object.
68
67
 
@@ -82,16 +81,18 @@ class MT5Strategy(Strategy):
82
81
  self.mode = mode
83
82
  self._porfolio_value = None
84
83
  self.risk_budget = self._check_risk_budget(**kwargs)
85
- self.max_trades = kwargs.get("max_trades", {s: 1 for s in self.symbols})
84
+ self.max_trades = kwargs.get(
85
+ "max_trades", {s: 1 for s in self.symbols})
86
86
  self.tf = kwargs.get("time_frame", 'D1')
87
87
  self.logger = kwargs.get("logger")
88
- self._initialize_portfolio()
88
+ if self.mode == 'backtest':
89
+ self._initialize_portfolio()
89
90
  self.kwargs = kwargs
90
91
 
91
92
  @property
92
93
  def cash(self) -> float:
93
94
  return self._porfolio_value
94
-
95
+
95
96
  @cash.setter
96
97
  def cash(self, value):
97
98
  self._porfolio_value = value
@@ -99,28 +100,30 @@ class MT5Strategy(Strategy):
99
100
  @property
100
101
  def orders(self) -> Dict[str, Dict[str, List[SignalEvent]]]:
101
102
  return self._orders
102
-
103
+
103
104
  @property
104
105
  def trades(self) -> Dict[str, Dict[str, int]]:
105
106
  return self._trades
106
107
 
107
108
  @property
108
- def positions(self) -> Dict[str, Dict[str, int|float]]:
109
+ def positions(self) -> Dict[str, Dict[str, int | float]]:
109
110
  return self._positions
110
-
111
+
111
112
  @property
112
113
  def holdings(self) -> Dict[str, float]:
113
114
  return self._holdings
114
-
115
+
115
116
  def _check_risk_budget(self, **kwargs):
116
117
  weights = kwargs.get('risk_weights')
117
118
  if weights is not None and isinstance(weights, dict):
118
119
  for asset in self.symbols:
119
120
  if asset not in weights:
120
- raise ValueError(f"Risk budget for asset {asset} is missing.")
121
+ raise ValueError(
122
+ f"Risk budget for asset {asset} is missing.")
121
123
  total_risk = sum(weights.values())
122
124
  if not np.isclose(total_risk, 1.0):
123
- raise ValueError(f'Risk budget weights must sum to 1. got {total_risk}')
125
+ raise ValueError(
126
+ f'Risk budget weights must sum to 1. got {total_risk}')
124
127
  return weights
125
128
  elif isinstance(weights, str):
126
129
  return weights
@@ -141,7 +144,7 @@ class MT5Strategy(Strategy):
141
144
  for order in orders:
142
145
  self._orders[symbol][order] = []
143
146
  self._holdings = {s: 0.0 for s in self.symbols}
144
-
147
+
145
148
  def get_update_from_portfolio(self, positions, holdings):
146
149
  """
147
150
  Update the positions and holdings for the strategy from the portfolio.
@@ -164,7 +167,7 @@ class MT5Strategy(Strategy):
164
167
  self._positions[symbol]['SHORT'] = 0
165
168
  if symbol in holdings:
166
169
  self._holdings[symbol] = holdings[symbol]
167
-
170
+
168
171
  def update_trades_from_fill(self, event: FillEvent):
169
172
  """
170
173
  This method updates the trades for the strategy based on the fill event.
@@ -178,34 +181,25 @@ class MT5Strategy(Strategy):
178
181
  elif event.order == 'EXIT' and event.direction == 'SELL':
179
182
  self._trades[event.symbol]['LONG'] = 0
180
183
 
181
- def calculate_signals(self, *args, **kwargs
182
- ) -> Dict[str, Union[str, dict, None]] | None:
184
+ def calculate_signals(self, *args, **kwargs) -> List[TradeSignal]:
183
185
  """
184
186
  Provides the mechanisms to calculate signals for the strategy.
185
- This methods should return a dictionary of symbols and their respective signals.
186
- The returned signals should be either string or dictionary objects.
187
-
188
- If a string is used, it should be:
189
- - ``LONG`` , ``BMKT``, ``BLMT``, ``BSTP``, ``BSTPLMT`` for a long signal (market, limit, stop, stop-limit).
190
- - ``SHORT``, ``SMKT``, ``SLMT``, ``SSTP``, ``SSTPLMT`` for a short signal (market, limit, stop, stop-limit).
191
- - ``EXIT``, ``EXIT_LONG``, ``EXIT_LONG_STOP``, ``EXIT_LONG_LIMIT``, ``EXIT_LONG_STOP_LIMIT`` for an exit signal (long).
192
- - ``EXIT_SHORT``, ``EXIT_SHORT_STOP``, ``EXIT_SHORT_LIMIT``, ``EXIT_SHORT_STOP_LIMIT`` for an exit signal (short).
193
- - ``EXIT_ALL_ORDERS`` for cancelling all orders.
194
- - ``EXIT_ALL_POSITIONS`` for exiting all positions.
195
- - ``EXIT_PROFITABLES`` for exiting all profitable positions.
196
- - ``EXIT_LOSINGS`` for exiting all losing positions.
197
-
198
- The signals could also be ``EXIT_STOP``, ``EXIT_LIMIT``, ``EXIT_STOP_LIMIT`` for exiting a position.
199
-
200
- If a dictionary is used, it should be:
201
- for each symbol, a dictionary with the following keys
202
- - ``action``: The action to take for the symbol (LONG, SHORT, EXIT, etc.)
203
- - ``price``: The price at which to execute the action.
204
- - ``stoplimit``: The stop-limit price for STOP-LIMIT orders.
187
+ This methods should return a list of signals for the strategy.
188
+
189
+ Each signal must be a ``TradeSignal`` object with the following attributes:
190
+ - ``action``: The order to execute on the symbol (LONG, SHORT, EXIT, etc.), see `bbstrader.core.utils.TradeAction`.
191
+ - ``price``: The price at which to execute the action, used for pending orders.
192
+ - ``stoplimit``: The stop-limit price for STOP-LIMIT orders, used for pending stop limit orders.
205
193
  - ``id``: The unique identifier for the strategy or order.
194
+ """
195
+ pass
196
+
197
+ def perform_period_end_checks(self, *args, **kwargs):
198
+ """
199
+ Some strategies may require additional checks at the end of the period,
200
+ such as closing all positions or orders or tracking the performance of the strategy etc.
206
201
 
207
- The dictionary can be use for pending orders (limit, stop, stop-limit) where the price is required
208
- or for executing orders where the each order has a unique identifier.
202
+ This method is called at the end of the period to perform such checks.
209
203
  """
210
204
  pass
211
205
 
@@ -217,13 +211,14 @@ class MT5Strategy(Strategy):
217
211
  return None
218
212
  symbols = symbols or self.symbols
219
213
  prices = self.get_asset_values(
220
- symbol_list=symbols, bars=self.data, mode=self.mode,
214
+ symbol_list=symbols, bars=self.data, mode=self.mode,
221
215
  window=freq, value_type='close', array=False, tf=self.tf
222
216
  )
223
217
  prices = pd.DataFrame(prices)
224
218
  prices = prices.dropna(axis=0, how='any')
225
219
  try:
226
- weights = optimized_weights(prices=prices, freq=freq, method=optimer)
220
+ weights = optimized_weights(
221
+ prices=prices, freq=freq, method=optimer)
227
222
  return {symbol: weight for symbol, weight in weights.items()}
228
223
  except Exception:
229
224
  return {symbol: 0.0 for symbol in symbols}
@@ -241,17 +236,17 @@ class MT5Strategy(Strategy):
241
236
  qty : The quantity to buy or sell for the symbol.
242
237
  """
243
238
  if (self._porfolio_value is None or weight == 0 or
244
- self._porfolio_value == 0 or np.isnan(self._porfolio_value)):
239
+ self._porfolio_value == 0 or np.isnan(self._porfolio_value)):
245
240
  return 0
246
241
  if volume is None:
247
242
  volume = round(self._porfolio_value * weight)
248
243
  if price is None:
249
244
  price = self.data.get_latest_bar_value(symbol, 'close')
250
245
  if (price is None or not isinstance(price, (int, float, np.number))
251
- or volume is None or not isinstance(volume, (int, float, np.number))
246
+ or volume is None or not isinstance(volume, (int, float, np.number))
252
247
  or np.isnan(float(price))
253
248
  or np.isnan(float(volume))
254
- ):
249
+ ):
255
250
  if weight != 0:
256
251
  return 1
257
252
  return 0
@@ -260,7 +255,7 @@ class MT5Strategy(Strategy):
260
255
  if maxqty is not None:
261
256
  qty = min(qty, maxqty)
262
257
  return max(round(qty, 2), 0)
263
-
258
+
264
259
  def get_quantities(self, quantities: Union[None, dict, int]) -> dict:
265
260
  """
266
261
  Get the quantities to buy or sell for the symbols in the strategy.
@@ -275,7 +270,7 @@ class MT5Strategy(Strategy):
275
270
  return quantities
276
271
  elif isinstance(quantities, int):
277
272
  return {symbol: quantities for symbol in self.symbols}
278
-
273
+
279
274
  def _send_order(self, id, symbol: str, signal: str, strength: float, price: float,
280
275
  quantity: int, dtime: datetime | pd.Timestamp):
281
276
 
@@ -294,8 +289,8 @@ class MT5Strategy(Strategy):
294
289
  self.logger.info(
295
290
  f"{signal} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={quantity}, PRICE @{price}", custom_time=dtime)
296
291
 
297
- def buy_mkt(self, id: int, symbol: str, price: float, quantity: int,
298
- strength: float=1.0, dtime: datetime | pd.Timestamp=None):
292
+ def buy_mkt(self, id: int, symbol: str, price: float, quantity: int,
293
+ strength: float = 1.0, dtime: datetime | pd.Timestamp = None):
299
294
  """
300
295
  Open a long position
301
296
 
@@ -309,7 +304,8 @@ class MT5Strategy(Strategy):
309
304
 
310
305
  See `bbstrader.btengine.event.SignalEvent` for more details on arguments.
311
306
  """
312
- self._send_order(id, symbol, 'SHORT', strength, price, quantity, dtime)
307
+ self._send_order(id, symbol, 'SHORT', strength,
308
+ price, quantity, dtime)
313
309
 
314
310
  def close_positions(self, id, symbol, price, quantity, strength=1.0, dtime=None):
315
311
  """
@@ -375,8 +371,8 @@ class MT5Strategy(Strategy):
375
371
  quantity=quantity, strength=strength, price=price)
376
372
  self._orders[symbol]['SLMT'].append(order)
377
373
 
378
- def buy_stop_limit(self, id: int, symbol: str, price: float, stoplimit: float,
379
- quantity: int, strength: float=1.0, dtime: datetime | pd.Timestamp = None):
374
+ def buy_stop_limit(self, id: int, symbol: str, price: float, stoplimit: float,
375
+ quantity: int, strength: float = 1.0, dtime: datetime | pd.Timestamp = None):
380
376
  """
381
377
  Open a pending order to buy at a stop-limit price
382
378
 
@@ -416,52 +412,57 @@ class MT5Strategy(Strategy):
416
412
  """
417
413
  for symbol in self.symbols:
418
414
  dtime = self.data.get_latest_bar_datetime(symbol)
419
- logmsg = lambda order, type: self.logger.info(
415
+
416
+ def logmsg(order, type): return self.logger.info(
420
417
  f"{type} ORDER EXECUTED: SYMBOL={symbol}, QUANTITY={order.quantity}, "
421
418
  f"PRICE @ {order.price}", custom_time=dtime)
422
419
  for order in self._orders[symbol]['BLMT'].copy():
423
420
  if self.data.get_latest_bar_value(symbol, 'close') <= order.price:
424
421
  self.buy_mkt(order.strategy_id, symbol,
425
- order.price, order.quantity, dtime)
422
+ order.price, order.quantity, dtime)
426
423
  try:
427
424
  self._orders[symbol]['BLMT'].remove(order)
428
425
  assert order not in self._orders[symbol]['BLMT']
429
426
  logmsg(order, 'BUY LIMIT')
430
427
  except AssertionError:
431
- self._orders[symbol]['BLMT'] = [o for o in self._orders[symbol]['BLMT'] if o != order]
428
+ self._orders[symbol]['BLMT'] = [
429
+ o for o in self._orders[symbol]['BLMT'] if o != order]
432
430
  logmsg(order, 'BUY LIMIT')
433
431
  for order in self._orders[symbol]['SLMT'].copy():
434
432
  if self.data.get_latest_bar_value(symbol, 'close') >= order.price:
435
433
  self.sell_mkt(order.strategy_id, symbol,
436
- order.price, order.quantity, dtime)
434
+ order.price, order.quantity, dtime)
437
435
  try:
438
436
  self._orders[symbol]['SLMT'].remove(order)
439
437
  assert order not in self._orders[symbol]['SLMT']
440
438
  logmsg(order, 'SELL LIMIT')
441
439
  except AssertionError:
442
- self._orders[symbol]['SLMT'] = [o for o in self._orders[symbol]['SLMT'] if o != order]
440
+ self._orders[symbol]['SLMT'] = [
441
+ o for o in self._orders[symbol]['SLMT'] if o != order]
443
442
  logmsg(order, 'SELL LIMIT')
444
443
  for order in self._orders[symbol]['BSTP'].copy():
445
444
  if self.data.get_latest_bar_value(symbol, 'close') >= order.price:
446
445
  self.buy_mkt(order.strategy_id, symbol,
447
- order.price, order.quantity, dtime)
446
+ order.price, order.quantity, dtime)
448
447
  try:
449
448
  self._orders[symbol]['BSTP'].remove(order)
450
449
  assert order not in self._orders[symbol]['BSTP']
451
450
  logmsg(order, 'BUY STOP')
452
451
  except AssertionError:
453
- self._orders[symbol]['BSTP'] = [o for o in self._orders[symbol]['BSTP'] if o != order]
452
+ self._orders[symbol]['BSTP'] = [
453
+ o for o in self._orders[symbol]['BSTP'] if o != order]
454
454
  logmsg(order, 'BUY STOP')
455
455
  for order in self._orders[symbol]['SSTP'].copy():
456
456
  if self.data.get_latest_bar_value(symbol, 'close') <= order.price:
457
457
  self.sell_mkt(order.strategy_id, symbol,
458
- order.price, order.quantity, dtime)
458
+ order.price, order.quantity, dtime)
459
459
  try:
460
460
  self._orders[symbol]['SSTP'].remove(order)
461
461
  assert order not in self._orders[symbol]['SSTP']
462
462
  logmsg(order, 'SELL STOP')
463
463
  except AssertionError:
464
- self._orders[symbol]['SSTP'] = [o for o in self._orders[symbol]['SSTP'] if o != order]
464
+ self._orders[symbol]['SSTP'] = [
465
+ o for o in self._orders[symbol]['SSTP'] if o != order]
465
466
  logmsg(order, 'SELL STOP')
466
467
  for order in self._orders[symbol]['BSTPLMT'].copy():
467
468
  if self.data.get_latest_bar_value(symbol, 'close') >= order.price:
@@ -472,7 +473,8 @@ class MT5Strategy(Strategy):
472
473
  assert order not in self._orders[symbol]['BSTPLMT']
473
474
  logmsg(order, 'BUY STOP LIMIT')
474
475
  except AssertionError:
475
- self._orders[symbol]['BSTPLMT'] = [o for o in self._orders[symbol]['BSTPLMT'] if o != order]
476
+ self._orders[symbol]['BSTPLMT'] = [
477
+ o for o in self._orders[symbol]['BSTPLMT'] if o != order]
476
478
  logmsg(order, 'BUY STOP LIMIT')
477
479
  for order in self._orders[symbol]['SSTPLMT'].copy():
478
480
  if self.data.get_latest_bar_value(symbol, 'close') <= order.price:
@@ -483,21 +485,23 @@ class MT5Strategy(Strategy):
483
485
  assert order not in self._orders[symbol]['SSTPLMT']
484
486
  logmsg(order, 'SELL STOP LIMIT')
485
487
  except AssertionError:
486
- self._orders[symbol]['SSTPLMT'] = [o for o in self._orders[symbol]['SSTPLMT'] if o != order]
488
+ self._orders[symbol]['SSTPLMT'] = [
489
+ o for o in self._orders[symbol]['SSTPLMT'] if o != order]
487
490
  logmsg(order, 'SELL STOP LIMIT')
488
491
 
489
- def calculate_pct_change(self, current_price, lh_price):
492
+ @staticmethod
493
+ def calculate_pct_change(current_price, lh_price):
490
494
  return ((current_price - lh_price) / lh_price) * 100
491
-
495
+
492
496
  def get_asset_values(self,
493
- symbol_list: List[str],
494
- window: int,
495
- value_type: str = 'returns',
496
- array: bool = True,
497
- bars: DataHandler = None,
498
- mode: Literal['backtest', 'live'] = 'backtest',
499
- tf: str = 'D1'
500
- ) -> Dict[str, np.ndarray | pd.Series] | None:
497
+ symbol_list: List[str],
498
+ window: int,
499
+ value_type: str = 'returns',
500
+ array: bool = True,
501
+ bars: DataHandler = None,
502
+ mode: Literal['backtest', 'live'] = 'backtest',
503
+ tf: str = 'D1'
504
+ ) -> Dict[str, np.ndarray | pd.Series] | None:
501
505
  """
502
506
  Get the historical OHLCV value or returns or custum value
503
507
  based on the DataHandker of the assets in the symbol list.
@@ -513,7 +517,7 @@ class MT5Strategy(Strategy):
513
517
 
514
518
  Returns:
515
519
  asset_values : Historical values of the assets in the symbol list.
516
-
520
+
517
521
  Note:
518
522
  In Live mode, the `bbstrader.metatrader.rates.Rates` class is used to get the historical data
519
523
  so the value_type must be 'returns', 'open', 'high', 'low', 'close', 'adjclose', 'volume'.
@@ -531,10 +535,11 @@ class MT5Strategy(Strategy):
531
535
  asset_values[asset] = values[~np.isnan(values)]
532
536
  else:
533
537
  values = bars.get_latest_bars(asset, N=window)
534
- asset_values[asset] = getattr(values, value_type)
538
+ asset_values[asset] = getattr(values, value_type)
535
539
  elif mode == 'live':
536
540
  for asset in symbol_list:
537
- rates = Rates(asset, timeframe=tf, count=window + 1, **self.kwargs)
541
+ rates = Rates(asset, timeframe=tf,
542
+ count=window + 1, **self.kwargs)
538
543
  if array:
539
544
  values = getattr(rates, value_type).values
540
545
  asset_values[asset] = values[~np.isnan(values)]
@@ -546,7 +551,8 @@ class MT5Strategy(Strategy):
546
551
  else:
547
552
  return None
548
553
 
549
- def is_signal_time(self, period_count, signal_inverval) -> bool:
554
+ @staticmethod
555
+ def is_signal_time(period_count, signal_inverval) -> bool:
550
556
  """
551
557
  Check if we can generate a signal based on the current period count.
552
558
  We use the signal interval as a form of periodicity or rebalancing period.
@@ -614,10 +620,12 @@ class MT5Strategy(Strategy):
614
620
  return prices
615
621
  return np.array([])
616
622
 
617
- def get_current_dt(self, time_zone: str = 'US/Eastern') -> datetime:
623
+ @staticmethod
624
+ def get_current_dt(time_zone: str = 'US/Eastern') -> datetime:
618
625
  return datetime.now(pytz.timezone(time_zone))
619
626
 
620
- def convert_time_zone(self, dt: datetime | int | pd.Timestamp,
627
+ @staticmethod
628
+ def convert_time_zone(dt: datetime | int | pd.Timestamp,
621
629
  from_tz: str = 'UTC',
622
630
  to_tz: str = 'US/Eastern'
623
631
  ) -> pd.Timestamp:
@@ -645,6 +653,19 @@ class MT5Strategy(Strategy):
645
653
  dt_to = dt.tz_convert(pytz.timezone(to_tz))
646
654
  return dt_to
647
655
 
656
+ @staticmethod
657
+ def get_mt5_equivalent(symbols, type='STK', path: str = None) -> List[str]:
658
+ account = Account(path=path)
659
+ mt5_symbols = account.get_symbols(symbol_type=type)
660
+ mt5_equivalent = []
661
+ if account.broker == AdmiralMarktsGroup():
662
+ for s in mt5_symbols:
663
+ _s = s[1:] if s[0] in string.punctuation else s
664
+ for symbol in symbols:
665
+ if _s.split('.')[0] == symbol or _s.split('_')[0] == symbol:
666
+ mt5_equivalent.append(s)
667
+ return mt5_equivalent
668
+
648
669
 
649
670
  class TWSStrategy(Strategy):
650
671
  ...
bbstrader/config.py CHANGED
@@ -1,13 +1,12 @@
1
1
  import logging
2
- from typing import List
3
2
  from pathlib import Path
4
- from datetime import datetime
5
-
3
+ from typing import List
6
4
 
7
5
  ADMIRAL_PATH = "C:\\Program Files\\Admirals Group MT5 Terminal\\terminal64.exe"
8
- FTMO_PATH = "C:\\Program Files\\FTMO MetaTrader 5\\terminal64.exe"
6
+ FTMO_PATH = "C:\\Program Files\\FTMO MetaTrader 5\\terminal64.exe"
9
7
 
10
- def get_config_dir(name: str=".bbstrader") -> Path:
8
+
9
+ def get_config_dir(name: str = ".bbstrader") -> Path:
11
10
  """
12
11
  Get the path to the configuration directory.
13
12
 
@@ -22,6 +21,7 @@ def get_config_dir(name: str=".bbstrader") -> Path:
22
21
  home_dir.mkdir()
23
22
  return home_dir
24
23
 
24
+
25
25
  BBSTRADER_DIR = get_config_dir()
26
26
 
27
27
 
@@ -47,7 +47,7 @@ class LogLevelFilter(logging.Filter):
47
47
  True if the record's level is in the allowed levels, False otherwise.
48
48
  """
49
49
  return record.levelno in self.levels
50
-
50
+
51
51
 
52
52
  class CustomFormatter(logging.Formatter):
53
53
  def formatTime(self, record, datefmt=None):
@@ -61,7 +61,7 @@ class CustomLogger(logging.Logger):
61
61
  def __init__(self, name, level=logging.NOTSET):
62
62
  super().__init__(name, level)
63
63
 
64
- def _log(self, level, msg, args, exc_info=None,
64
+ def _log(self, level, msg, args, exc_info=None,
65
65
  extra=None, stack_info=False, stacklevel=1, custom_time=None):
66
66
  if extra is None:
67
67
  extra = {}
@@ -77,13 +77,15 @@ class CustomLogger(logging.Logger):
77
77
  self._log(logging.DEBUG, msg, args, custom_time=custom_time, **kwargs)
78
78
 
79
79
  def warning(self, msg, *args, custom_time=None, **kwargs):
80
- self._log(logging.WARNING, msg, args, custom_time=custom_time, **kwargs)
80
+ self._log(logging.WARNING, msg, args,
81
+ custom_time=custom_time, **kwargs)
81
82
 
82
83
  def error(self, msg, *args, custom_time=None, **kwargs):
83
84
  self._log(logging.ERROR, msg, args, custom_time=custom_time, **kwargs)
84
85
 
85
86
  def critical(self, msg, *args, custom_time=None, **kwargs):
86
- self._log(logging.CRITICAL, msg, args, custom_time=custom_time, **kwargs)
87
+ self._log(logging.CRITICAL, msg, args,
88
+ custom_time=custom_time, **kwargs)
87
89
 
88
90
 
89
91
  def config_logger(log_file: str, console_log=True):
@@ -112,4 +114,4 @@ def config_logger(log_file: str, console_log=True):
112
114
  console_handler.setFormatter(formatter)
113
115
  logger.addHandler(console_handler)
114
116
 
115
- return logger
117
+ return logger
bbstrader/core/data.py CHANGED
@@ -1,12 +1,10 @@
1
- import pandas as pd
2
- import numpy as np
3
1
  from financetoolkit import Toolkit
4
2
 
5
-
6
3
  __all__ = [
7
4
  'FMP',
8
5
  ]
9
6
 
7
+
10
8
  class FMP(Toolkit):
11
9
  """
12
10
  FMPData class for fetching data from Financial Modeling Prep API
@@ -15,9 +13,10 @@ class FMP(Toolkit):
15
13
  See `financetoolkit` for more details.
16
14
 
17
15
  """
18
- def __init__(self, api_key: str ='', symbols: str | list = 'AAPL'):
16
+
17
+ def __init__(self, api_key: str = '', symbols: str | list = 'AAPL'):
19
18
  super().__init__(tickers=symbols, api_key=api_key)
20
19
 
21
20
 
22
21
  class DataBendo:
23
- ...
22
+ ...
bbstrader/core/utils.py CHANGED
@@ -0,0 +1,57 @@
1
+ from enum import Enum
2
+ from dataclasses import dataclass
3
+
4
+
5
+ class TradeAction(Enum):
6
+ """
7
+ An enumeration class for trade actions.
8
+ """
9
+ BUY = "LONG"
10
+ LONG = "LONG"
11
+ SELL = "SHORT"
12
+ EXIT = "EXIT"
13
+ BMKT = "BMKT"
14
+ SMKT = "SMKT"
15
+ BLMT = "BLMT"
16
+ SLMT = "SLMT"
17
+ BSTP = "BSTP"
18
+ SSTP = "SSTP"
19
+ SHORT = "SHORT"
20
+ BSTPLMT = "BSTPLMT"
21
+ SSTPLMT = "SSTPLMT"
22
+ EXIT_LONG = "EXIT_LONG"
23
+ EXIT_SHORT = "EXIT_SHORT"
24
+ EXIT_STOP = "EXIT_STOP"
25
+ EXIT_LIMIT = "EXIT_LIMIT"
26
+ EXIT_LONG_STOP = "EXIT_LONG_STOP"
27
+ EXIT_LONG_LIMIT = "EXIT_LONG_LIMIT"
28
+ EXIT_SHORT_STOP = "EXIT_SHORT_STOP"
29
+ EXIT_SHORT_LIMIT = "EXIT_SHORT_LIMIT"
30
+ EXIT_LONG_STOP_LIMIT = "EXIT_LONG_STOP_LIMIT"
31
+ EXIT_SHORT_STOP_LIMIT = "EXIT_SHORT_STOP_LIMIT"
32
+ EXIT_PROFITABLES = "EXIT_PROFITABLES"
33
+ EXIT_LOSINGS = "EXIT_LOSINGS"
34
+ EXIT_ALL_POSITIONS = "EXIT_ALL_POSITIONS"
35
+ EXIT_ALL_LONGS = "EXIT_ALL_LONGS"
36
+
37
+ def __str__(self):
38
+ return self.value
39
+
40
+
41
+ @dataclass()
42
+ class TradeSignal:
43
+ """
44
+ A dataclass for storing trading signal.
45
+ """
46
+
47
+ id: int
48
+ symbol: str
49
+ action: TradeAction
50
+ price: float = None
51
+ stoplimit: float = None
52
+
53
+ def __repr__(self):
54
+ return (
55
+ f"TradeSignal(id={self.id}, symbol='{self.symbol}', "
56
+ f"action='{self.action.value}', price={self.price}, stoplimit={self.stoplimit})"
57
+ )
File without changes
@@ -1,6 +1,6 @@
1
1
 
2
- from bbstrader.metatrader.account import *
3
- from bbstrader.metatrader.rates import *
4
- from bbstrader.metatrader.risk import *
5
- from bbstrader.metatrader.trade import *
6
- from bbstrader.metatrader.utils import *
2
+ from bbstrader.metatrader.account import * # noqa: F403
3
+ from bbstrader.metatrader.rates import * # noqa: F403
4
+ from bbstrader.metatrader.risk import * # noqa: F403
5
+ from bbstrader.metatrader.trade import * # noqa: F403
6
+ from bbstrader.metatrader.utils import * # noqa: F403