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

@@ -50,4 +50,5 @@ from bbstrader.btengine.event import *
50
50
  from bbstrader.btengine.execution import *
51
51
  from bbstrader.btengine.performance import *
52
52
  from bbstrader.btengine.backtest import *
53
+ from bbstrader.btengine.strategy import Strategy
53
54
  from bbstrader.btengine.portfolio import Portfolio
@@ -99,6 +99,7 @@ class Backtest(object):
99
99
  self.fills = 0
100
100
 
101
101
  self._generate_trading_instances()
102
+ self.show_equity = kwargs.get("show_equity", False)
102
103
 
103
104
  def _generate_trading_instances(self):
104
105
  """
@@ -186,15 +187,16 @@ class Backtest(object):
186
187
  "\n"
187
188
  )
188
189
 
189
- print("\nCreating equity curve...")
190
- print("\n[======= EQUITY CURVE =======]")
191
- print(
192
- tabulate(
193
- self.portfolio.equity_curve.tail(10),
194
- headers="keys",
195
- tablefmt="outline"),
196
- "\n"
197
- )
190
+ if self.show_equity:
191
+ print("\nCreating equity curve...")
192
+ print("\n[======= EQUITY CURVE =======]")
193
+ print(
194
+ tabulate(
195
+ self.portfolio.equity_curve.tail(10),
196
+ headers="keys",
197
+ tablefmt="outline"),
198
+ "\n"
199
+ )
198
200
 
199
201
  def simulate_trading(self):
200
202
  """
@@ -24,7 +24,7 @@ __all__ = [
24
24
  ]
25
25
 
26
26
 
27
- def create_sharpe_ratio(returns, periods=252):
27
+ def create_sharpe_ratio(returns, periods=252) -> float:
28
28
  """
29
29
  Create the Sharpe ratio for the strategy, based on a
30
30
  benchmark of zero (i.e. no risk-free rate information).
@@ -36,15 +36,10 @@ def create_sharpe_ratio(returns, periods=252):
36
36
  Returns:
37
37
  S (float): Sharpe ratio
38
38
  """
39
- if np.std(returns) != 0:
40
- return np.sqrt(periods) * (np.mean(returns)) / np.std(returns)
41
- else:
42
- return 0.0
39
+ return qs.stats.sharpe(returns, periods=periods)
43
40
 
44
41
  # Define a function to calculate the Sortino Ratio
45
-
46
-
47
- def create_sortino_ratio(returns, periods=252):
42
+ def create_sortino_ratio(returns, periods=252) -> float:
48
43
  """
49
44
  Create the Sortino ratio for the strategy, based on a
50
45
  benchmark of zero (i.e. no risk-free rate information).
@@ -56,17 +51,7 @@ def create_sortino_ratio(returns, periods=252):
56
51
  Returns:
57
52
  S (float): Sortino ratio
58
53
  """
59
- # Calculate the annualized return
60
- annualized_return = np.power(1 + np.mean(returns), periods) - 1
61
- # Calculate the downside deviation
62
- downside_returns = returns.copy()
63
- downside_returns[returns > 0] = 0
64
- annualized_downside_std = np.std(downside_returns) * np.sqrt(periods)
65
- if annualized_downside_std != 0:
66
- return annualized_return / annualized_downside_std
67
- else:
68
- return 0.0
69
-
54
+ return qs.stats.sortino(returns, periods=periods)
70
55
 
71
56
  def create_drawdowns(pnl):
72
57
  """
@@ -311,19 +296,19 @@ def plot_monthly_yearly_returns(df, title):
311
296
  # Show the plot
312
297
  plt.show()
313
298
 
314
- def show_qs_stats(equity_curve, benchmark, strategy_name):
299
+ def show_qs_stats(returns, benchmark, strategy_name, save_dir=None):
315
300
  """
316
301
  Generate the full quantstats report for the strategy.
317
302
 
318
303
  Args:
319
- equity_curve (pd.DataFrame):
304
+ returns (pd.Serie):
320
305
  The DataFrame containing the strategy returns and drawdowns.
321
306
  benchmark (str):
322
307
  The ticker symbol of the benchmark to compare the strategy to.
323
308
  strategy_name (str): The name of the strategy.
324
309
  """
325
310
  # Load the returns data
326
- returns = equity_curve.copy()
311
+ returns = returns.copy()
327
312
 
328
313
  # Drop duplicate index entries
329
314
  returns = returns[~returns.index.duplicated(keep='first')]
@@ -332,11 +317,5 @@ def show_qs_stats(equity_curve, benchmark, strategy_name):
332
317
  qs.extend_pandas()
333
318
 
334
319
  # Generate the full report with a benchmark
335
- file = strategy_name.replace(' ', '_')
336
- qs.reports.full(
337
- returns['Returns'], mode='full', benchmark=benchmark)
338
- qs.reports.html(
339
- returns['Returns'],
340
- benchmark='SPY',
341
- output=f"{file}_report.html",
342
- title=strategy_name)
320
+ qs.reports.full(returns, mode='full', benchmark=benchmark)
321
+ qs.reports.html(returns, benchmark=benchmark, output=save_dir, title=strategy_name)
@@ -1,13 +1,15 @@
1
1
  import pandas as pd
2
2
  from queue import Queue
3
+ from pathlib import Path
3
4
  from datetime import datetime
4
5
  from bbstrader.btengine.event import (
5
6
  OrderEvent, FillEvent, MarketEvent, SignalEvent
6
7
  )
7
8
  from bbstrader.btengine.data import DataHandler
8
9
  from bbstrader.btengine.performance import (
9
- create_drawdowns, plot_performance, show_qs_stats,
10
- plot_returns_and_dd, plot_monthly_yearly_returns
10
+ create_drawdowns, create_sharpe_ratio, create_sortino_ratio,
11
+ plot_performance, show_qs_stats,plot_returns_and_dd,
12
+ plot_monthly_yearly_returns
11
13
  )
12
14
  import quantstats as qs
13
15
 
@@ -306,8 +308,8 @@ class Portfolio(object):
306
308
  returns = self.equity_curve['Returns']
307
309
  pnl = self.equity_curve['Equity Curve']
308
310
 
309
- sharpe_ratio = qs.stats.sharpe(returns, periods=self.tf)
310
- sortino_ratio = qs.stats.sortino(returns, periods=self.tf)
311
+ sharpe_ratio = create_sharpe_ratio(returns, periods=self.tf)
312
+ sortino_ratio = create_sortino_ratio(returns, periods=self.tf)
311
313
  drawdown, _, _ = create_drawdowns(pnl)
312
314
  max_dd = qs.stats.max_drawdown(returns)
313
315
  dd_details = qs.stats.drawdown_details(drawdown)
@@ -323,14 +325,20 @@ class Portfolio(object):
323
325
  ]
324
326
  now = datetime.now().strftime('%Y%m%d%H%M%S')
325
327
  strategy_name = self.strategy_name.replace(' ', '_')
326
- file = f"{strategy_name}_{now}_equity.csv"
327
- self.equity_curve.to_csv(file)
328
+ results_dir = Path('.backtests') / strategy_name
329
+ results_dir.mkdir(parents=True, exist_ok=True)
330
+
331
+ csv_file = f"{strategy_name}_{now}_equity.csv"
332
+ png_file = f'{strategy_name}_{now}_returns_heatmap.png'
333
+ html_file = f"{strategy_name}_{now}_report.html"
334
+ self.equity_curve.to_csv(results_dir / csv_file)
335
+
328
336
  plot_performance(self.equity_curve, self.strategy_name)
329
- plot_returns_and_dd(self.equity_curve,
330
- self.benchmark, self.strategy_name)
331
- qs.plots.monthly_heatmap(returns, savefig='monthly_heatmap.png')
337
+ plot_returns_and_dd(self.equity_curve, self.benchmark, self.strategy_name)
338
+ qs.plots.monthly_heatmap(returns, savefig=f"{results_dir}/{png_file}")
332
339
  plot_monthly_yearly_returns(self.equity_curve, self.strategy_name)
333
- show_qs_stats(self.equity_curve, self.benchmark, self.strategy_name)
340
+ show_qs_stats(returns, self.benchmark, self.strategy_name,
341
+ save_dir=f"{results_dir}/{html_file}")
334
342
 
335
343
  return stats
336
344
 
@@ -6,10 +6,9 @@ from datetime import datetime
6
6
  import MetaTrader5 as mt5
7
7
  from currency_converter import SINGLE_DAY_ECB_URL, CurrencyConverter
8
8
  from bbstrader.metatrader.utils import (
9
- raise_mt5_error, AccountInfo, TerminalInfo,
9
+ raise_mt5_error, AccountInfo, TerminalInfo, InvalidBroker,
10
10
  SymbolInfo, TickInfo, TradeRequest, OrderCheckResult,
11
- OrderSentResult, TradePosition, TradeOrder, TradeDeal,
12
- )
11
+ OrderSentResult, TradePosition, TradeOrder, TradeDeal,)
13
12
  from typing import Tuple, Union, List, Dict, Any, Optional, Literal
14
13
 
15
14
 
@@ -18,6 +17,13 @@ __BROKERS__ = {
18
17
  'JGM': "Just Global Markets Ltd.",
19
18
  'FTMO': "FTMO S.R.O."
20
19
  }
20
+
21
+ BROKERS_TIMEZONES = {
22
+ 'AMG': 'Europe/Helsinki',
23
+ 'JGM': 'Europe/Helsinki',
24
+ 'FTMO': 'Europe/Helsinki'
25
+ }
26
+
21
27
  _ADMIRAL_MARKETS_URL_ = "https://cabinet.a-partnership.com/visit/?bta=35537&brand=admiralmarkets"
22
28
  _ADMIRAL_MARKETS_PRODUCTS_ = ["Stocks", "ETFs",
23
29
  "Indices", "Commodities", "Futures", "Forex"]
@@ -48,6 +54,14 @@ _SYMBOLS_TYPE_ = {
48
54
  "CRYPTO": r'\b(Cryptos?)\b'
49
55
  }
50
56
 
57
+ def check_mt5_connection():
58
+ try:
59
+ init = mt5.initialize()
60
+ if not init:
61
+ raise_mt5_error(INIT_MSG)
62
+ except Exception:
63
+ raise_mt5_error(INIT_MSG)
64
+
51
65
  class Account(object):
52
66
  """
53
67
  The `Account` class is utilized to retrieve information about
@@ -85,21 +99,21 @@ class Account(object):
85
99
  """
86
100
 
87
101
  def __init__(self):
88
- if not mt5.initialize():
89
- raise_mt5_error(message=INIT_MSG)
102
+ check_mt5_connection()
90
103
  self._check_brokers()
91
104
 
92
105
  def _check_brokers(self):
93
106
  supported = __BROKERS__.copy()
94
107
  broker = self.get_terminal_info().company
95
108
  if broker not in supported.values():
96
- raise ValueError(
109
+ msg = (
97
110
  f"{broker} is not currently supported broker for the Account() class\n"
98
111
  f"Currently Supported brokers are: {', '.join(supported.values())}\n"
99
112
  f"For {supported['AMG']}, See [{amg_url}]\n"
100
113
  f"For {supported['JGM']}, See [{jgm_url}]\n"
101
114
  f"For {supported['FTMO']}, See [{ftmo_url}]\n"
102
115
  )
116
+ raise InvalidBroker(message=msg)
103
117
 
104
118
  def get_account_info(
105
119
  self,
@@ -3,9 +3,8 @@ import MetaTrader5 as Mt5
3
3
  from datetime import datetime
4
4
  from typing import Union, Optional
5
5
  from bbstrader.metatrader.utils import (
6
- raise_mt5_error, TimeFrame, TIMEFRAMES
7
- )
8
- from bbstrader.metatrader.account import INIT_MSG
6
+ raise_mt5_error, TimeFrame, TIMEFRAMES)
7
+ from bbstrader.metatrader.account import check_mt5_connection
9
8
  from pandas.tseries.offsets import CustomBusinessDay
10
9
  from pandas.tseries.holiday import USFederalHolidayCalendar
11
10
 
@@ -28,7 +27,7 @@ class Rates(object):
28
27
  In your MT5 terminal, go to `Tools` -> `Options` -> `Charts` -> `Max bars in chart`.
29
28
 
30
29
  2. The `get_open, get_high, get_low, get_close, get_adj_close, get_returns,
31
- get_volume` properties return data in Broker's timezone.
30
+ get_volume` properties returns data in Broker's timezone.
32
31
 
33
32
  Example:
34
33
  >>> rates = Rates("EURUSD", "1h")
@@ -75,9 +74,7 @@ class Rates(object):
75
74
 
76
75
 
77
76
  def _mt5_initialized(self):
78
- """Ensures the MetaTrader 5 Terminal is initialized."""
79
- if not Mt5.initialize():
80
- raise_mt5_error(message=INIT_MSG)
77
+ check_mt5_connection()
81
78
 
82
79
  def _get_start_pos(self, index, time_frame):
83
80
  if isinstance(index, int):
@@ -226,7 +223,7 @@ class Rates(object):
226
223
 
227
224
  @property
228
225
  def get_volume(self):
229
- return self.data['Volume']
226
+ return self.__data['Volume']
230
227
 
231
228
  def get_historical_data(
232
229
  self,
@@ -252,7 +249,7 @@ class Rates(object):
252
249
  ValueError: If the starting date is greater than the ending date.
253
250
 
254
251
  Notes:
255
- The Datetime for this method is in UTC timezone.
252
+ The Datetime for this method is in Local timezone.
256
253
  """
257
254
  df = self._fetch_data(date_from, date_to)
258
255
  if save_csv and df is not None:
@@ -7,8 +7,7 @@ import MetaTrader5 as Mt5
7
7
  from bbstrader.metatrader.account import Account
8
8
  from bbstrader.metatrader.rates import Rates
9
9
  from bbstrader.metatrader.utils import (
10
- TIMEFRAMES, raise_mt5_error, TimeFrame
11
- )
10
+ TIMEFRAMES, raise_mt5_error, TimeFrame)
12
11
  from typing import List, Dict, Optional, Literal, Union, Any
13
12
 
14
13
 
@@ -342,7 +341,7 @@ class RiskManagement(Account):
342
341
  """
343
342
  P = self.get_account_info().margin_free
344
343
  trade_risk = self.get_trade_risk()
345
- loss_allowed = P * trade_risk
344
+ loss_allowed = P * trade_risk / 100
346
345
  var = self.calculate_var(c=self.var_level, tf=self.var_tf)
347
346
  return min(var, loss_allowed)
348
347