bbstrader 0.1.7__py3-none-any.whl → 0.1.8__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.
- bbstrader/btengine/__init__.py +1 -0
- bbstrader/btengine/backtest.py +11 -9
- bbstrader/btengine/performance.py +9 -30
- bbstrader/btengine/portfolio.py +18 -10
- bbstrader/metatrader/account.py +11 -4
- bbstrader/metatrader/rates.py +4 -5
- bbstrader/metatrader/risk.py +1 -2
- bbstrader/metatrader/trade.py +269 -214
- bbstrader/metatrader/utils.py +37 -29
- bbstrader/trading/execution.py +55 -18
- bbstrader/trading/strategies.py +5 -3
- bbstrader/tseries.py +500 -494
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/METADATA +2 -1
- bbstrader-0.1.8.dist-info/RECORD +26 -0
- bbstrader-0.1.7.dist-info/RECORD +0 -26
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/LICENSE +0 -0
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/WHEEL +0 -0
- {bbstrader-0.1.7.dist-info → bbstrader-0.1.8.dist-info}/top_level.txt +0 -0
bbstrader/btengine/__init__.py
CHANGED
|
@@ -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
|
bbstrader/btengine/backtest.py
CHANGED
|
@@ -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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
336
|
-
qs.reports.
|
|
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)
|
bbstrader/btengine/portfolio.py
CHANGED
|
@@ -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,
|
|
10
|
-
plot_returns_and_dd,
|
|
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 =
|
|
310
|
-
sortino_ratio =
|
|
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
|
-
|
|
327
|
-
|
|
328
|
+
results_dir = Path('Backtest_Results') / 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
|
-
|
|
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(
|
|
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
|
|
bbstrader/metatrader/account.py
CHANGED
|
@@ -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"]
|
|
@@ -93,13 +99,14 @@ class Account(object):
|
|
|
93
99
|
supported = __BROKERS__.copy()
|
|
94
100
|
broker = self.get_terminal_info().company
|
|
95
101
|
if broker not in supported.values():
|
|
96
|
-
|
|
102
|
+
msg = (
|
|
97
103
|
f"{broker} is not currently supported broker for the Account() class\n"
|
|
98
104
|
f"Currently Supported brokers are: {', '.join(supported.values())}\n"
|
|
99
105
|
f"For {supported['AMG']}, See [{amg_url}]\n"
|
|
100
106
|
f"For {supported['JGM']}, See [{jgm_url}]\n"
|
|
101
107
|
f"For {supported['FTMO']}, See [{ftmo_url}]\n"
|
|
102
108
|
)
|
|
109
|
+
raise InvalidBroker(message=msg)
|
|
103
110
|
|
|
104
111
|
def get_account_info(
|
|
105
112
|
self,
|
bbstrader/metatrader/rates.py
CHANGED
|
@@ -3,8 +3,7 @@ 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
|
-
)
|
|
6
|
+
raise_mt5_error, TimeFrame, TIMEFRAMES)
|
|
8
7
|
from bbstrader.metatrader.account import INIT_MSG
|
|
9
8
|
from pandas.tseries.offsets import CustomBusinessDay
|
|
10
9
|
from pandas.tseries.holiday import USFederalHolidayCalendar
|
|
@@ -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
|
|
30
|
+
get_volume` properties returns data in Broker's timezone.
|
|
32
31
|
|
|
33
32
|
Example:
|
|
34
33
|
>>> rates = Rates("EURUSD", "1h")
|
|
@@ -226,7 +225,7 @@ class Rates(object):
|
|
|
226
225
|
|
|
227
226
|
@property
|
|
228
227
|
def get_volume(self):
|
|
229
|
-
return self.
|
|
228
|
+
return self.__data['Volume']
|
|
230
229
|
|
|
231
230
|
def get_historical_data(
|
|
232
231
|
self,
|
|
@@ -252,7 +251,7 @@ class Rates(object):
|
|
|
252
251
|
ValueError: If the starting date is greater than the ending date.
|
|
253
252
|
|
|
254
253
|
Notes:
|
|
255
|
-
The Datetime for this method is in
|
|
254
|
+
The Datetime for this method is in Local timezone.
|
|
256
255
|
"""
|
|
257
256
|
df = self._fetch_data(date_from, date_to)
|
|
258
257
|
if save_csv and df is not None:
|
bbstrader/metatrader/risk.py
CHANGED
|
@@ -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
|
|