bbstrader 0.1.94__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.

Files changed (38) hide show
  1. bbstrader/__ini__.py +9 -9
  2. bbstrader/btengine/__init__.py +7 -7
  3. bbstrader/btengine/backtest.py +30 -26
  4. bbstrader/btengine/data.py +100 -79
  5. bbstrader/btengine/event.py +2 -1
  6. bbstrader/btengine/execution.py +18 -16
  7. bbstrader/btengine/performance.py +11 -7
  8. bbstrader/btengine/portfolio.py +35 -36
  9. bbstrader/btengine/strategy.py +119 -94
  10. bbstrader/config.py +14 -8
  11. bbstrader/core/__init__.py +0 -0
  12. bbstrader/core/data.py +22 -0
  13. bbstrader/core/utils.py +57 -0
  14. bbstrader/ibkr/__init__.py +0 -0
  15. bbstrader/ibkr/utils.py +0 -0
  16. bbstrader/metatrader/__init__.py +5 -5
  17. bbstrader/metatrader/account.py +117 -121
  18. bbstrader/metatrader/rates.py +83 -80
  19. bbstrader/metatrader/risk.py +23 -37
  20. bbstrader/metatrader/trade.py +169 -140
  21. bbstrader/metatrader/utils.py +3 -3
  22. bbstrader/models/__init__.py +5 -5
  23. bbstrader/models/factors.py +280 -0
  24. bbstrader/models/ml.py +1092 -0
  25. bbstrader/models/optimization.py +31 -28
  26. bbstrader/models/{portfolios.py → portfolio.py} +64 -46
  27. bbstrader/models/risk.py +15 -9
  28. bbstrader/trading/__init__.py +2 -2
  29. bbstrader/trading/execution.py +252 -164
  30. bbstrader/trading/scripts.py +8 -4
  31. bbstrader/trading/strategies.py +79 -66
  32. bbstrader/tseries.py +482 -107
  33. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
  34. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/METADATA +6 -1
  35. bbstrader-0.2.1.dist-info/RECORD +37 -0
  36. bbstrader-0.1.94.dist-info/RECORD +0 -32
  37. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
  38. {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
- from datetime import datetime
2
- from queue import Queue
3
1
  from abc import ABCMeta, abstractmethod
4
- from bbstrader.btengine.event import FillEvent, OrderEvent
2
+ from queue import Queue
3
+
5
4
  from bbstrader.btengine.data import DataHandler
5
+ from bbstrader.btengine.event import FillEvent, OrderEvent
6
6
  from bbstrader.metatrader.account import Account
7
7
 
8
8
  __all__ = [
@@ -11,6 +11,7 @@ __all__ = [
11
11
  "MT5ExecutionHandler"
12
12
  ]
13
13
 
14
+
14
15
  class ExecutionHandler(metaclass=ABCMeta):
15
16
  """
16
17
  The ExecutionHandler abstract class handles the interaction
@@ -84,7 +85,7 @@ class SimExecutionHandler(ExecutionHandler):
84
85
  self.events.put(fill_event)
85
86
  self.logger.info(
86
87
  f"{event.direction} ORDER FILLED: SYMBOL={event.symbol}, "
87
- f"QUANTITY={event.quantity}, PRICE @{event.price} EXCHANGE={fill_event.exchange}",
88
+ f"QUANTITY={event.quantity}, PRICE @{event.price} EXCHANGE={fill_event.exchange}",
88
89
  custom_time=fill_event.timeindex
89
90
  )
90
91
 
@@ -93,16 +94,16 @@ class MT5ExecutionHandler(ExecutionHandler):
93
94
  """
94
95
  The main role of `MT5ExecutionHandler` class is to estimate the execution fees
95
96
  for different asset classes on the MT5 terminal.
96
-
97
+
97
98
  Generally we have four types of fees when we execute trades using the MT5 terminal
98
99
  (commissions, swap, spread and other fees). But most of these fees depend on the specifications
99
100
  of each instrument and the duration of the transaction for the swap for example.
100
-
101
+
101
102
  Calculating the exact fees for each instrument would be a bit complex because our Backtest engine
102
103
  and the Portfolio class do not take into account the duration of each trade to apply the appropriate
103
104
  rate for the swap for example. So we have to use only the model of calculating the commissions
104
105
  for each asset class and each instrument.
105
-
106
+
106
107
  The second thing that must be taken into account on MT5 is the type of account offered by the broker.
107
108
  Brokers have different account categories each with its specifications for each asset class and each instrument.
108
109
  Again considering all these conditions would make our class very complex. So we took the `Raw Spread`
@@ -113,6 +114,7 @@ class MT5ExecutionHandler(ExecutionHandler):
113
114
  NOTE:
114
115
  This class only works with `bbstrader.metatrader.data.MT5DataHandler` class.
115
116
  """
117
+
116
118
  def __init__(self, events: Queue, data: DataHandler, **kwargs):
117
119
  """
118
120
  Initialises the handler, setting the event queues up internally.
@@ -134,8 +136,8 @@ class MT5ExecutionHandler(ExecutionHandler):
134
136
  if contract_size == 1:
135
137
  lot = quantity
136
138
  if symbol_type in [
137
- 'COMD', 'FUT', 'CRYPTO'] and contract_size > 1:
138
- lot = quantity / contract_size
139
+ 'COMD', 'FUT', 'CRYPTO'] and contract_size > 1:
140
+ lot = quantity / contract_size
139
141
  if symbol_type == 'FX':
140
142
  lot = (quantity*price / contract_size)
141
143
  return self._check_lot(symbol, lot)
@@ -147,7 +149,7 @@ class MT5ExecutionHandler(ExecutionHandler):
147
149
  elif lot > symbol_info.volume_max:
148
150
  return symbol_info.volume_max
149
151
  return round(lot, 2)
150
-
152
+
151
153
  def _estimate_total_fees(self, symbol, lot, qty, price):
152
154
  symbol_type = self.__account.get_symbol_type(symbol)
153
155
  if symbol_type in ['STK', 'ETF']:
@@ -164,16 +166,16 @@ class MT5ExecutionHandler(ExecutionHandler):
164
166
  return self._estimate_crypto_commission()
165
167
  else:
166
168
  return 0.0
167
-
169
+
168
170
  def _estimate_stock_commission(self, symbol, qty, price):
169
171
  # https://admiralmarkets.com/start-trading/contract-specifications?regulator=jsc
170
172
  min_com = 1.0
171
173
  min_aud = 8.0
172
174
  min_dkk = 30.0
173
175
  min_nok = min_sek = 10.0
174
- us_com = 0.02 # per chare
175
- ger_fr_uk_cm = 0.001 # percent
176
- eu_asia_cm = 0.0015 # percent
176
+ us_com = 0.02 # per chare
177
+ ger_fr_uk_cm = 0.001 # percent
178
+ eu_asia_cm = 0.0015 # percent
177
179
  if (
178
180
  symbol in self.__account.get_stocks_from_country('USA')
179
181
  or self.__account.get_symbol_type(symbol) == 'ETF'
@@ -214,7 +216,7 @@ class MT5ExecutionHandler(ExecutionHandler):
214
216
 
215
217
  def _estimate_crypto_commission(self):
216
218
  return 0.0
217
-
219
+
218
220
  def execute_order(self, event: OrderEvent):
219
221
  """
220
222
  Executes an Order event by converting it into a Fill event.
@@ -243,4 +245,4 @@ class MT5ExecutionHandler(ExecutionHandler):
243
245
 
244
246
 
245
247
  class IBExecutionHandler(ExecutionHandler):
246
- ...
248
+ ...
@@ -1,12 +1,12 @@
1
+ import warnings
2
+
3
+ import matplotlib.pyplot as plt
1
4
  import numpy as np
2
5
  import pandas as pd
6
+ import quantstats as qs
3
7
  import seaborn as sns
4
8
  import yfinance as yf
5
- from scipy.stats import mstats
6
- import matplotlib.pyplot as plt
7
- from matplotlib.ticker import MaxNLocator
8
- import quantstats as qs
9
- import warnings
9
+
10
10
  warnings.filterwarnings("ignore")
11
11
 
12
12
  sns.set_theme()
@@ -37,6 +37,8 @@ def create_sharpe_ratio(returns, periods=252) -> float:
37
37
  return qs.stats.sharpe(returns, periods=periods)
38
38
 
39
39
  # Define a function to calculate the Sortino Ratio
40
+
41
+
40
42
  def create_sortino_ratio(returns, periods=252) -> float:
41
43
  """
42
44
  Create the Sortino ratio for the strategy, based on a
@@ -215,7 +217,7 @@ def plot_returns_and_dd(df: pd.DataFrame, benchmark: str, title):
215
217
  plt.show()
216
218
 
217
219
 
218
- def plot_monthly_yearly_returns(df:pd.DataFrame, title):
220
+ def plot_monthly_yearly_returns(df: pd.DataFrame, title):
219
221
  """
220
222
  Plot the monthly and yearly returns of the strategy.
221
223
 
@@ -295,6 +297,7 @@ def plot_monthly_yearly_returns(df:pd.DataFrame, title):
295
297
  # Show the plot
296
298
  plt.show()
297
299
 
300
+
298
301
  def show_qs_stats(returns, benchmark, strategy_name, save_dir=None):
299
302
  """
300
303
  Generate the full quantstats report for the strategy.
@@ -317,4 +320,5 @@ def show_qs_stats(returns, benchmark, strategy_name, save_dir=None):
317
320
 
318
321
  # Generate the full report with a benchmark
319
322
  qs.reports.full(returns, mode='full', benchmark=benchmark)
320
- qs.reports.html(returns, benchmark=benchmark, output=save_dir, title=strategy_name)
323
+ qs.reports.html(returns, benchmark=benchmark,
324
+ output=save_dir, title=strategy_name)
@@ -1,26 +1,21 @@
1
- import pandas as pd
2
- from queue import Queue
3
- from pathlib import Path
4
1
  from datetime import datetime
5
- from bbstrader.btengine.event import (
6
- OrderEvent,
7
- FillEvent,
8
- MarketEvent,
9
- SignalEvent
10
- )
2
+ from pathlib import Path
3
+ from queue import Queue
4
+
5
+ import pandas as pd
6
+ import quantstats as qs
7
+
11
8
  from bbstrader.btengine.data import DataHandler
9
+ from bbstrader.btengine.event import FillEvent, MarketEvent, OrderEvent, SignalEvent
12
10
  from bbstrader.btengine.performance import (
13
- create_drawdowns,
14
- create_sharpe_ratio,
15
- create_sortino_ratio,
16
- plot_performance,
17
- show_qs_stats,
18
- plot_returns_and_dd,
19
- plot_monthly_yearly_returns
11
+ create_drawdowns,
12
+ create_sharpe_ratio,
13
+ create_sortino_ratio,
14
+ plot_monthly_yearly_returns,
15
+ plot_performance,
16
+ plot_returns_and_dd,
17
+ show_qs_stats,
20
18
  )
21
- from bbstrader.config import BBSTRADER_DIR
22
- import quantstats as qs
23
-
24
19
 
25
20
  __all__ = [
26
21
  'Portfolio',
@@ -175,7 +170,7 @@ class Portfolio(object):
175
170
  d['Commission'] = 0.0
176
171
  d['Total'] = self.initial_capital
177
172
  return d
178
-
173
+
179
174
  def _get_price(self, symbol: str) -> float:
180
175
  try:
181
176
  price = self.bars.get_latest_bar_value(
@@ -190,7 +185,7 @@ class Portfolio(object):
190
185
  return price
191
186
  except AttributeError:
192
187
  raise AttributeError(
193
- f"Bars object must have 'adj_close' or 'close' prices"
188
+ "Bars object must have 'adj_close' or 'close' prices"
194
189
  )
195
190
 
196
191
  def update_timeindex(self, event: MarketEvent):
@@ -285,7 +280,7 @@ class Portfolio(object):
285
280
 
286
281
  Args:
287
282
  signal (SignalEvent): The tuple containing Signal information.
288
-
283
+
289
284
  Returns:
290
285
  OrderEvent: The OrderEvent to be executed.
291
286
  """
@@ -305,19 +300,22 @@ class Portfolio(object):
305
300
  else:
306
301
  order_type = direction
307
302
 
308
- if direction == 'LONG' and new_quantity > 0:
309
- order = OrderEvent(symbol, order_type, new_quantity, 'BUY', price, direction)
303
+ if direction == 'LONG' and new_quantity > 0:
304
+ order = OrderEvent(symbol, order_type,
305
+ new_quantity, 'BUY', price, direction)
310
306
  if direction == 'SHORT' and new_quantity > 0:
311
- order = OrderEvent(symbol, order_type, new_quantity, 'SELL', price, direction)
307
+ order = OrderEvent(symbol, order_type,
308
+ new_quantity, 'SELL', price, direction)
312
309
 
313
310
  if direction == 'EXIT' and cur_quantity > 0:
314
- order = OrderEvent(symbol, order_type, abs(cur_quantity), 'SELL', price, direction)
311
+ order = OrderEvent(symbol, order_type, abs(
312
+ cur_quantity), 'SELL', price, direction)
315
313
  if direction == 'EXIT' and cur_quantity < 0:
316
- order = OrderEvent(symbol, order_type, abs(cur_quantity), 'BUY', price, direction)
314
+ order = OrderEvent(symbol, order_type, abs(
315
+ cur_quantity), 'BUY', price, direction)
317
316
 
318
317
  return order
319
318
 
320
-
321
319
  def update_signal(self, event: SignalEvent):
322
320
  """
323
321
  Acts on a SignalEvent to generate new orders
@@ -369,19 +367,20 @@ class Portfolio(object):
369
367
  else:
370
368
  results_dir = Path('.backtests') / strategy_name
371
369
  results_dir.mkdir(parents=True, exist_ok=True)
372
-
373
- csv_file = f"{strategy_name}_{now}_equities.csv"
370
+
371
+ csv_file = f"{strategy_name}_{now}_equities.csv"
374
372
  png_file = f'{strategy_name}_{now}_returns_heatmap.png'
375
373
  html_file = f"{strategy_name}_{now}_report.html"
376
374
  self.equity_curve.to_csv(results_dir / csv_file)
377
-
375
+
378
376
  if self.print_stats:
379
377
  plot_performance(self.equity_curve, self.strategy_name)
380
- plot_returns_and_dd(self.equity_curve, self.benchmark, self.strategy_name)
381
- qs.plots.monthly_heatmap(returns, savefig=f"{results_dir}/{png_file}")
378
+ plot_returns_and_dd(self.equity_curve,
379
+ self.benchmark, self.strategy_name)
380
+ qs.plots.monthly_heatmap(
381
+ returns, savefig=f"{results_dir}/{png_file}")
382
382
  plot_monthly_yearly_returns(self.equity_curve, self.strategy_name)
383
- show_qs_stats(returns, self.benchmark, self.strategy_name,
384
- save_dir=f"{results_dir}/{html_file}")
383
+ show_qs_stats(returns, self.benchmark, self.strategy_name,
384
+ save_dir=f"{results_dir}/{html_file}")
385
385
 
386
386
  return stats
387
-